catacomb/pwsafe.py, pwsafe: Replace `PW''s MODE parameter with WRITEP flag.
[catacomb-python] / pwsafe
CommitLineData
24b3d57b 1#! /usr/bin/python
d1c45f5c
MW
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.
d7ab1bab 29
85f15f07
MW
30from __future__ import with_statement
31
43c09851 32from os import environ
d7ab1bab 33from sys import argv, exit, stdin, stdout, stderr
34from getopt import getopt, GetoptError
d7ab1bab 35from fnmatch import fnmatch
80dc9c94 36import re
2e6a3fda 37
d1c45f5c
MW
38import catacomb as C
39from catacomb.pwsafe import *
40
41###--------------------------------------------------------------------------
42### Utilities.
43
44## The program name.
2e6a3fda 45prog = re.sub(r'^.*[/\\]', '', argv[0])
d1c45f5c 46
2e6a3fda 47def moan(msg):
d1c45f5c 48 """Issue a warning message MSG."""
2e6a3fda 49 print >>stderr, '%s: %s' % (prog, msg)
d1c45f5c 50
2e6a3fda 51def die(msg):
d1c45f5c 52 """Report MSG as a fatal error, and exit."""
2e6a3fda 53 moan(msg)
54 exit(1)
d7ab1bab 55
d1c45f5c
MW
56def 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
d1c45f5c
MW
62###--------------------------------------------------------------------------
63### Subcommand implementations.
d7ab1bab 64
d7ab1bab 65def cmd_create(av):
d1c45f5c
MW
66
67 ## Default crypto-primitive selections.
d7ab1bab 68 cipher = 'blowfish-cbc'
69 hash = 'rmd160'
70 mac = None
d1c45f5c
MW
71
72 ## Parse the options.
d7ab1bab 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'
d1c45f5c 92
09b8041d
MW
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)
d7ab1bab 96
97def cmd_changepp(av):
98 if len(av) != 0:
99 return 1
4a35c9a7 100 pw = PW(file, writep = True)
d7ab1bab 101 pw.changepp()
102
103def cmd_find(av):
104 if len(av) != 1:
105 return 1
106 pw = PW(file)
2e6a3fda 107 try:
108 print pw[av[0]]
109 except KeyError, exc:
110 die('Password `%s\' not found.' % exc.args[0])
d7ab1bab 111
112def 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]
4a35c9a7 125 pw = PW(file, writep = True)
43c09851 126 pw[av[0]] = chomp(pp)
d7ab1bab 127
128def 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
141def 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
153def cmd_topixie(av):
43c09851 154 if len(av) > 2:
d7ab1bab 155 return 1
156 pw = PW(file)
43c09851 157 pix = C.Pixie()
158 if len(av) == 0:
159 for tag in pw:
160 pix.set(tag, pw[tag])
d7ab1bab 161 else:
43c09851 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])
d7ab1bab 168
3aa33042 169def cmd_del(av):
170 if len(av) != 1:
171 return 1
4a35c9a7 172 pw = PW(file, writep = True)
3aa33042 173 tag = av[0]
2e6a3fda 174 try:
175 del pw[tag]
176 except KeyError, exc:
177 die('Password `%s\' not found.' % exc.args[0])
3aa33042 178
d7ab1bab 179commands = { 'create': [cmd_create,
d1c45f5c
MW
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]]'],
85f15f07 187 'delete' : [cmd_del, 'TAG']}
d1c45f5c
MW
188
189###--------------------------------------------------------------------------
190### Command-line handling and dispatch.
d7ab1bab 191
192def version():
2e6a3fda 193 print '%s 1.0.0' % prog
d1c45f5c 194
d7ab1bab 195def usage(fp):
2e6a3fda 196 print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
d1c45f5c 197
d7ab1bab 198def help():
199 version()
200 print
b2687a0a 201 usage(stdout)
d7ab1bab 202 print '''
203Maintains passwords or other short secrets securely.
204
205Options:
206
207-h, --help Show this help text.
208-v, --version Show program version number.
209-u, --usage Show short usage message.
210
2e6a3fda 211-f, --file=FILE Where to find the password-safe file.
212
d7ab1bab 213Commands provided:
214'''
05a82542 215 for c in sorted(commands):
d7ab1bab 216 print '%s %s' % (c, commands[c][1])
217
d1c45f5c
MW
218## Choose a default database file.
219if 'PWSAFE' in environ:
220 file = environ['PWSAFE']
221else:
222 file = '%s/.pwsafe' % environ['HOME']
223
224## Parse the command-line options.
d7ab1bab 225try:
d1c45f5c
MW
226 opts, argv = getopt(argv[1:], 'hvuf:',
227 ['help', 'version', 'usage', 'file='])
d7ab1bab 228except GetoptError:
229 usage(stderr)
230 exit(1)
231for 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!'
245if len(argv) < 1:
246 usage(stderr)
247 exit(1)
248
d1c45f5c 249## Dispatch to a command handler.
d7ab1bab 250if argv[0] in commands:
251 c = argv[0]
252 argv = argv[1:]
253else:
254 c = 'find'
40b16f0c
MW
255try:
256 if commands[c][0](argv):
257 print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
258 exit(1)
259except DecryptError:
260 die("decryption failure")
d1c45f5c
MW
261
262###----- That's all, folks --------------------------------------------------