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