chiark / gitweb /
General: Update for Python 2.5.
[catacomb-python] / pwsafe
1 #! /usr/bin/python
2 # -*-python-*-
3
4 import catacomb as C
5 from catacomb.pwsafe import *
6 import gdbm as G
7 from os import environ
8 from sys import argv, exit, stdin, stdout, stderr
9 from getopt import getopt, GetoptError
10 from fnmatch import fnmatch
11 import sre as re
12
13 prog = re.sub(r'^.*[/\\]', '', argv[0])
14 def moan(msg):
15   print >>stderr, '%s: %s' % (prog, msg)
16 def die(msg):
17   moan(msg)
18   exit(1)
19
20 if 'PWSAFE' in environ:
21   file = environ['PWSAFE']
22 else:
23   file = '%s/.pwsafe' % environ['HOME']
24
25 def cmd_create(av):
26   cipher = 'blowfish-cbc'
27   hash = 'rmd160'
28   mac = None
29   try:
30     opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
31   except GetoptError:
32     return 1
33   for o, a in opts:
34     if o in ('-c', '--cipher'):
35       cipher = a
36     elif o in ('-m', '--mac'):
37       mac = a
38     elif o in ('-h', '--hash'):
39       hash = a
40     else:
41       raise 'Barf!'
42   if len(args) > 2:
43     return 1
44   if len(args) >= 1:
45     tag = args[0]
46   else:
47     tag = 'pwsafe'
48   db = G.open(file, 'n', 0600)
49   pp = C.ppread(tag, C.PMODE_VERIFY)
50   if not mac: mac = hash + '-hmac'
51   c = C.gcciphers[cipher]
52   h = C.gchashes[hash]
53   m = C.gcmacs[mac]
54   ppk = PW.PPK(pp, c, h, m)
55   ck = C.rand.block(c.keysz.default)
56   mk = C.rand.block(m.keysz.default)
57   k = Crypto(c, h, m, ck, mk)
58   db['tag'] = tag
59   db['salt'] = ppk.salt
60   db['cipher'] = cipher
61   db['hash'] = hash
62   db['mac'] = mac
63   db['key'] = ppk.encrypt(wrapstr(ck) + wrapstr(mk))
64   db['magic'] = k.encrypt(C.rand.block(h.hashsz))
65
66 def chomp(pp):
67   if len(pp) > 0 and pp[-1] == '\n':
68     pp = pp[:-1]
69   return pp
70
71 def cmd_changepp(av):
72   if len(av) != 0:
73     return 1
74   pw = PW(file, 'w')
75   pw.changepp()
76
77 def cmd_find(av):
78   if len(av) != 1:
79     return 1
80   pw = PW(file)
81   try:
82     print pw[av[0]]
83   except KeyError, exc:
84     die('Password `%s\' not found.' % exc.args[0])
85
86 def cmd_store(av):
87   if len(av) < 1 or len(av) > 2:
88     return 1
89   tag = av[0]
90   if len(av) < 2:
91     pp = C.getpass("Enter passphrase `%s': " % tag)
92     vpp = C.getpass("Confirm passphrase `%s': " % tag)
93     if pp != vpp:
94       raise ValueError, "passphrases don't match"
95   elif av[1] == '-':
96     pp = stdin.readline()
97   else:
98     pp = av[1]
99   pw = PW(file, 'w')
100   pw[av[0]] = chomp(pp)
101
102 def cmd_copy(av):
103   if len(av) < 1 or len(av) > 2:
104     return 1
105   pw_in = PW(file)
106   pw_out = PW(av[0], 'w')
107   if len(av) >= 3:
108     pat = av[1]
109   else:
110     pat = None
111   for k in pw_in:
112     if pat is None or fnmatch(k, pat):
113       pw_out[k] = pw_in[k]
114
115 def cmd_list(av):
116   if len(av) > 1:
117     return 1
118   pw = PW(file)
119   if len(av) >= 1:
120     pat = av[0]
121   else:
122     pat = None
123   for k in pw:
124     if pat is None or fnmatch(k, pat):
125       print k
126
127 def cmd_topixie(av):
128   if len(av) > 2:
129     return 1
130   pw = PW(file)
131   pix = C.Pixie()
132   if len(av) == 0:
133     for tag in pw:
134       pix.set(tag, pw[tag])
135   else:
136     tag = av[0]
137     if len(av) >= 2:
138       pptag = av[1]
139     else:
140       pptag = av[0]
141     pix.set(pptag, pw[tag])
142
143 def cmd_del(av):
144   if len(av) != 1:
145     return 1
146   pw = PW(file, 'w')
147   tag = av[0]
148   try:
149     del pw[tag]
150   except KeyError, exc:
151     die('Password `%s\' not found.' % exc.args[0])
152
153 def asciip(s):
154   for ch in s:
155     if ch < ' ' or ch > '~': return False
156   return True
157 def present(s):
158   if asciip(s): return s
159   return C.ByteString(s)
160 def cmd_dump(av):
161   db = gdbm.open(file, 'r')
162   k = db.firstkey()
163   while True:
164     if k is None: break
165     print '%r: %r' % (present(k), present(db[k]))
166     k = db.nextkey(k)
167
168 commands = { 'create': [cmd_create,
169                         '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
170              'find' : [cmd_find, 'LABEL'],
171              'store' : [cmd_store, 'LABEL [VALUE]'],
172              'list' : [cmd_list, '[GLOB-PATTERN]'],
173              'changepp' : [cmd_changepp, ''],
174              'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
175              'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
176              'delete' : [cmd_del, 'TAG'],
177              'dump' : [cmd_dump, '']}
178
179 def version():
180   print '%s 1.0.0' % prog
181 def usage(fp):
182   print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
183 def help():
184   version()
185   print
186   usage(stdout)  
187   print '''
188 Maintains passwords or other short secrets securely.
189
190 Options:
191
192 -h, --help              Show this help text.
193 -v, --version           Show program version number.
194 -u, --usage             Show short usage message.
195
196 -f, --file=FILE         Where to find the password-safe file.
197
198 Commands provided:
199 '''
200   for c in commands:
201     print '%s %s' % (c, commands[c][1])
202
203 try:
204   opts, argv = getopt(argv[1:],
205                       'hvuf:',
206                       ['help', 'version', 'usage', 'file='])
207 except GetoptError:
208   usage(stderr)
209   exit(1)
210 for o, a in opts:
211   if o in ('-h', '--help'):
212     help()
213     exit(0)
214   elif o in ('-v', '--version'):
215     version()
216     exit(0)
217   elif o in ('-u', '--usage'):
218     usage(stdout)
219     exit(0)
220   elif o in ('-f', '--file'):
221     file = a
222   else:
223     raise 'Barf!'
224 if len(argv) < 1:
225   usage(stderr)
226   exit(1)
227
228 if argv[0] in commands:
229   c = argv[0]
230   argv = argv[1:]
231 else:
232   c = 'find'
233 if commands[c][0](argv):
234   print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
235   exit(1)