chiark / gitweb /
pwsafe: Present the list of commands in alphabetical order.
[catacomb-python] / pwsafe
... / ...
CommitLineData
1#! /usr/bin/python
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.
29
30import gdbm as G
31from os import environ
32from sys import argv, exit, stdin, stdout, stderr
33from getopt import getopt, GetoptError
34from fnmatch import fnmatch
35import re
36
37import catacomb as C
38from catacomb.pwsafe import *
39
40###--------------------------------------------------------------------------
41### Utilities.
42
43## The program name.
44prog = re.sub(r'^.*[/\\]', '', argv[0])
45
46def moan(msg):
47 """Issue a warning message MSG."""
48 print >>stderr, '%s: %s' % (prog, msg)
49
50def die(msg):
51 """Report MSG as a fatal error, and exit."""
52 moan(msg)
53 exit(1)
54
55def chomp(pp):
56 """Return the string PP, without its trailing newline if it has one."""
57 if len(pp) > 0 and pp[-1] == '\n':
58 pp = pp[:-1]
59 return pp
60
61def asciip(s):
62 """Answer whether all of the characters of S are plain ASCII."""
63 for ch in s:
64 if ch < ' ' or ch > '~': return False
65 return True
66
67def present(s):
68 """
69 Return a presentation form of the string S.
70
71 If S is plain ASCII, then return S unchanged; otherwise return it as one of
72 Catacomb's ByteString objects.
73 """
74 if asciip(s): return s
75 return C.ByteString(s)
76
77###--------------------------------------------------------------------------
78### Subcommand implementations.
79
80def cmd_create(av):
81
82 ## Default crypto-primitive selections.
83 cipher = 'blowfish-cbc'
84 hash = 'rmd160'
85 mac = None
86
87 ## Parse the options.
88 try:
89 opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
90 except GetoptError:
91 return 1
92 for o, a in opts:
93 if o in ('-c', '--cipher'):
94 cipher = a
95 elif o in ('-m', '--mac'):
96 mac = a
97 elif o in ('-h', '--hash'):
98 hash = a
99 else:
100 raise 'Barf!'
101 if len(args) > 2:
102 return 1
103 if len(args) >= 1:
104 tag = args[0]
105 else:
106 tag = 'pwsafe'
107
108 ## Set up the database.
109 if mac is None: mac = hash + '-hmac'
110 PW.create(file, C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac], tag)
111
112def cmd_changepp(av):
113 if len(av) != 0:
114 return 1
115 pw = PW(file, 'w')
116 pw.changepp()
117
118def cmd_find(av):
119 if len(av) != 1:
120 return 1
121 pw = PW(file)
122 try:
123 print pw[av[0]]
124 except KeyError, exc:
125 die('Password `%s\' not found.' % exc.args[0])
126
127def cmd_store(av):
128 if len(av) < 1 or len(av) > 2:
129 return 1
130 tag = av[0]
131 if len(av) < 2:
132 pp = C.getpass("Enter passphrase `%s': " % tag)
133 vpp = C.getpass("Confirm passphrase `%s': " % tag)
134 if pp != vpp:
135 raise ValueError, "passphrases don't match"
136 elif av[1] == '-':
137 pp = stdin.readline()
138 else:
139 pp = av[1]
140 pw = PW(file, 'w')
141 pw[av[0]] = chomp(pp)
142
143def cmd_copy(av):
144 if len(av) < 1 or len(av) > 2:
145 return 1
146 pw_in = PW(file)
147 pw_out = PW(av[0], 'w')
148 if len(av) >= 3:
149 pat = av[1]
150 else:
151 pat = None
152 for k in pw_in:
153 if pat is None or fnmatch(k, pat):
154 pw_out[k] = pw_in[k]
155
156def cmd_list(av):
157 if len(av) > 1:
158 return 1
159 pw = PW(file)
160 if len(av) >= 1:
161 pat = av[0]
162 else:
163 pat = None
164 for k in pw:
165 if pat is None or fnmatch(k, pat):
166 print k
167
168def cmd_topixie(av):
169 if len(av) > 2:
170 return 1
171 pw = PW(file)
172 pix = C.Pixie()
173 if len(av) == 0:
174 for tag in pw:
175 pix.set(tag, pw[tag])
176 else:
177 tag = av[0]
178 if len(av) >= 2:
179 pptag = av[1]
180 else:
181 pptag = av[0]
182 pix.set(pptag, pw[tag])
183
184def cmd_del(av):
185 if len(av) != 1:
186 return 1
187 pw = PW(file, 'w')
188 tag = av[0]
189 try:
190 del pw[tag]
191 except KeyError, exc:
192 die('Password `%s\' not found.' % exc.args[0])
193
194def cmd_dump(av):
195 db = gdbm.open(file, 'r')
196 k = db.firstkey()
197 while True:
198 if k is None: break
199 print '%r: %r' % (present(k), present(db[k]))
200 k = db.nextkey(k)
201
202commands = { 'create': [cmd_create,
203 '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
204 'find' : [cmd_find, 'LABEL'],
205 'store' : [cmd_store, 'LABEL [VALUE]'],
206 'list' : [cmd_list, '[GLOB-PATTERN]'],
207 'changepp' : [cmd_changepp, ''],
208 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
209 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
210 'delete' : [cmd_del, 'TAG'],
211 'dump' : [cmd_dump, '']}
212
213###--------------------------------------------------------------------------
214### Command-line handling and dispatch.
215
216def version():
217 print '%s 1.0.0' % prog
218
219def usage(fp):
220 print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
221
222def help():
223 version()
224 print
225 usage(stdout)
226 print '''
227Maintains passwords or other short secrets securely.
228
229Options:
230
231-h, --help Show this help text.
232-v, --version Show program version number.
233-u, --usage Show short usage message.
234
235-f, --file=FILE Where to find the password-safe file.
236
237Commands provided:
238'''
239 for c in sorted(commands):
240 print '%s %s' % (c, commands[c][1])
241
242## Choose a default database file.
243if 'PWSAFE' in environ:
244 file = environ['PWSAFE']
245else:
246 file = '%s/.pwsafe' % environ['HOME']
247
248## Parse the command-line options.
249try:
250 opts, argv = getopt(argv[1:], 'hvuf:',
251 ['help', 'version', 'usage', 'file='])
252except GetoptError:
253 usage(stderr)
254 exit(1)
255for o, a in opts:
256 if o in ('-h', '--help'):
257 help()
258 exit(0)
259 elif o in ('-v', '--version'):
260 version()
261 exit(0)
262 elif o in ('-u', '--usage'):
263 usage(stdout)
264 exit(0)
265 elif o in ('-f', '--file'):
266 file = a
267 else:
268 raise 'Barf!'
269if len(argv) < 1:
270 usage(stderr)
271 exit(1)
272
273## Dispatch to a command handler.
274if argv[0] in commands:
275 c = argv[0]
276 argv = argv[1:]
277else:
278 c = 'find'
279try:
280 if commands[c][0](argv):
281 print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
282 exit(1)
283except DecryptError:
284 die("decryption failure")
285
286###----- That's all, folks --------------------------------------------------