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