pwsafe: Abolish the `chomp' function, and only chomp when reading stdin.
[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 ###--------------------------------------------------------------------------
57 ### Subcommand implementations.
58
59 def cmd_create(av):
60
61 ## Default crypto-primitive selections.
62 cipher = 'blowfish-cbc'
63 hash = 'rmd160'
64 mac = None
65
66 ## Parse the options.
67 try:
68 opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
69 except GetoptError:
70 return 1
71 for o, a in opts:
72 if o in ('-c', '--cipher'): cipher = a
73 elif o in ('-m', '--mac'): mac = a
74 elif o in ('-h', '--hash'): hash = a
75 else: raise 'Barf!'
76 if len(args) > 2: return 1
77 if len(args) >= 1: tag = args[0]
78 else: tag = 'pwsafe'
79
80 ## Set up the database.
81 if mac is None: mac = hash + '-hmac'
82 PW.create(file, C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac], tag)
83
84 def cmd_changepp(av):
85 if len(av) != 0: return 1
86 with PW(file, writep = True) as pw: pw.changepp()
87
88 def cmd_find(av):
89 if len(av) != 1: return 1
90 with PW(file) as pw:
91 try: print pw[av[0]]
92 except KeyError, exc: die("Password `%s' not found" % exc.args[0])
93
94 def cmd_store(av):
95 if len(av) < 1 or len(av) > 2: return 1
96 tag = av[0]
97 with PW(file, writep = True) as pw:
98 if len(av) < 2:
99 pp = C.getpass("Enter passphrase `%s': " % tag)
100 vpp = C.getpass("Confirm passphrase `%s': " % tag)
101 if pp != vpp: die("passphrases don't match")
102 elif av[1] == '-':
103 pp = stdin.readline().rstrip('\n')
104 else:
105 pp = av[1]
106 pw[av[0]] = pp
107
108 def cmd_copy(av):
109 if len(av) < 1 or len(av) > 2: return 1
110 with PW(file) as pw_in:
111 with PW(av[0], writep = True) as pw_out:
112 if len(av) >= 3: pat = av[1]
113 else: pat = None
114 for k in pw_in:
115 if pat is None or fnmatch(k, pat): pw_out[k] = pw_in[k]
116
117 def cmd_list(av):
118 if len(av) > 1: return 1
119 with PW(file) as pw:
120 if len(av) >= 1: pat = av[0]
121 else: pat = None
122 for k in pw:
123 if pat is None or fnmatch(k, pat): print k
124
125 def cmd_topixie(av):
126 if len(av) > 2: return 1
127 with PW(file) as pw:
128 pix = C.Pixie()
129 if len(av) == 0:
130 for tag in pw: pix.set(tag, pw[tag])
131 else:
132 tag = av[0]
133 if len(av) >= 2: pptag = av[1]
134 else: pptag = av[0]
135 pix.set(pptag, pw[tag])
136
137 def cmd_del(av):
138 if len(av) != 1: return 1
139 with PW(file, writep = True) as pw:
140 tag = av[0]
141 try: del pw[tag]
142 except KeyError, exc: die("Password `%s' not found" % exc.args[0])
143
144 commands = { 'create': [cmd_create,
145 '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
146 'find' : [cmd_find, 'LABEL'],
147 'store' : [cmd_store, 'LABEL [VALUE]'],
148 'list' : [cmd_list, '[GLOB-PATTERN]'],
149 'changepp' : [cmd_changepp, ''],
150 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
151 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
152 'delete' : [cmd_del, 'TAG']}
153
154 ###--------------------------------------------------------------------------
155 ### Command-line handling and dispatch.
156
157 def version():
158 print '%s 1.0.0' % prog
159
160 def usage(fp):
161 print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
162
163 def help():
164 version()
165 print
166 usage(stdout)
167 print '''
168 Maintains passwords or other short secrets securely.
169
170 Options:
171
172 -h, --help Show this help text.
173 -v, --version Show program version number.
174 -u, --usage Show short usage message.
175
176 -f, --file=FILE Where to find the password-safe file.
177
178 Commands provided:
179 '''
180 for c in sorted(commands):
181 print '%s %s' % (c, commands[c][1])
182
183 ## Choose a default database file.
184 if 'PWSAFE' in environ:
185 file = environ['PWSAFE']
186 else:
187 file = '%s/.pwsafe' % environ['HOME']
188
189 ## Parse the command-line options.
190 try:
191 opts, argv = getopt(argv[1:], 'hvuf:',
192 ['help', 'version', 'usage', 'file='])
193 except GetoptError:
194 usage(stderr)
195 exit(1)
196 for o, a in opts:
197 if o in ('-h', '--help'):
198 help()
199 exit(0)
200 elif o in ('-v', '--version'):
201 version()
202 exit(0)
203 elif o in ('-u', '--usage'):
204 usage(stdout)
205 exit(0)
206 elif o in ('-f', '--file'):
207 file = a
208 else:
209 raise 'Barf!'
210 if len(argv) < 1:
211 usage(stderr)
212 exit(1)
213
214 ## Dispatch to a command handler.
215 if argv[0] in commands:
216 c = argv[0]
217 argv = argv[1:]
218 else:
219 c = 'find'
220 try:
221 if commands[c][0](argv):
222 print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
223 exit(1)
224 except DecryptError:
225 die("decryption failure")
226
227 ###----- That's all, folks --------------------------------------------------