chiark / gitweb /
catacomb/pwsafe.py, pwsafe: Replace `PW''s MODE parameter with WRITEP flag.
[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, writep = True)
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, writep = True)
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, writep = True)
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 --------------------------------------------------