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