Commit | Line | Data |
---|---|---|
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 |
30 | from __future__ import with_statement |
31 | ||
43c09851 | 32 | from os import environ |
d7ab1bab | 33 | from sys import argv, exit, stdin, stdout, stderr |
34 | from getopt import getopt, GetoptError | |
d7ab1bab | 35 | from fnmatch import fnmatch |
80dc9c94 | 36 | import re |
2e6a3fda | 37 | |
d1c45f5c MW |
38 | import catacomb as C |
39 | from catacomb.pwsafe import * | |
40 | ||
41 | ###-------------------------------------------------------------------------- | |
42 | ### Utilities. | |
43 | ||
44 | ## The program name. | |
2e6a3fda | 45 | prog = re.sub(r'^.*[/\\]', '', argv[0]) |
d1c45f5c | 46 | |
2e6a3fda | 47 | def moan(msg): |
d1c45f5c | 48 | """Issue a warning message MSG.""" |
2e6a3fda | 49 | print >>stderr, '%s: %s' % (prog, msg) |
d1c45f5c | 50 | |
2e6a3fda | 51 | def die(msg): |
d1c45f5c | 52 | """Report MSG as a fatal error, and exit.""" |
2e6a3fda | 53 | moan(msg) |
54 | exit(1) | |
d7ab1bab | 55 | |
d1c45f5c MW |
56 | ###-------------------------------------------------------------------------- |
57 | ### Subcommand implementations. | |
d7ab1bab | 58 | |
d7ab1bab | 59 | def cmd_create(av): |
d1c45f5c MW |
60 | |
61 | ## Default crypto-primitive selections. | |
d7ab1bab | 62 | cipher = 'blowfish-cbc' |
63 | hash = 'rmd160' | |
64 | mac = None | |
d1c45f5c MW |
65 | |
66 | ## Parse the options. | |
d7ab1bab | 67 | try: |
68 | opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash=']) | |
69 | except GetoptError: | |
70 | return 1 | |
71 | for o, a in opts: | |
9d0d0a7b MW |
72 | if o in ('-c', '--cipher'): cipher = a |
73 | elif o in ('-m', '--mac'): mac = a | |
74 | elif o in ('-h', '--hash'): hash = a | |
75 | else: raise 'Barf!' | |
76 | if len(args) > 2: return 1 | |
77 | if len(args) >= 1: tag = args[0] | |
78 | else: tag = 'pwsafe' | |
d1c45f5c | 79 | |
09b8041d MW |
80 | ## Set up the database. |
81 | if mac is None: mac = hash + '-hmac' | |
82 | PW.create(file, C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac], tag) | |
d7ab1bab | 83 | |
84 | def cmd_changepp(av): | |
9d0d0a7b | 85 | if len(av) != 0: return 1 |
5bf6e9f5 | 86 | with PW(file, writep = True) as pw: pw.changepp() |
d7ab1bab | 87 | |
88 | def cmd_find(av): | |
9d0d0a7b | 89 | if len(av) != 1: return 1 |
5bf6e9f5 MW |
90 | with PW(file) as pw: |
91 | try: print pw[av[0]] | |
92 | except KeyError, exc: die("Password `%s' not found" % exc.args[0]) | |
d7ab1bab | 93 | |
94 | def cmd_store(av): | |
5bf6e9f5 | 95 | if len(av) < 1 or len(av) > 2: return 1 |
d7ab1bab | 96 | tag = av[0] |
5bf6e9f5 MW |
97 | with PW(file, writep = True) as pw: |
98 | if len(av) < 2: | |
99 | pp = C.getpass("Enter passphrase `%s': " % tag) | |
100 | vpp = C.getpass("Confirm passphrase `%s': " % tag) | |
101 | if pp != vpp: die("passphrases don't match") | |
102 | elif av[1] == '-': | |
63ba6dfa | 103 | pp = stdin.readline().rstrip('\n') |
5bf6e9f5 MW |
104 | else: |
105 | pp = av[1] | |
63ba6dfa | 106 | pw[av[0]] = pp |
d7ab1bab | 107 | |
108 | def cmd_copy(av): | |
9d0d0a7b | 109 | if len(av) < 1 or len(av) > 2: return 1 |
5bf6e9f5 MW |
110 | with PW(file) as pw_in: |
111 | with PW(av[0], writep = True) as pw_out: | |
112 | if len(av) >= 3: pat = av[1] | |
113 | else: pat = None | |
114 | for k in pw_in: | |
115 | if pat is None or fnmatch(k, pat): pw_out[k] = pw_in[k] | |
d7ab1bab | 116 | |
117 | def cmd_list(av): | |
9d0d0a7b | 118 | if len(av) > 1: return 1 |
5bf6e9f5 MW |
119 | with PW(file) as pw: |
120 | if len(av) >= 1: pat = av[0] | |
121 | else: pat = None | |
122 | for k in pw: | |
123 | if pat is None or fnmatch(k, pat): print k | |
d7ab1bab | 124 | |
125 | def cmd_topixie(av): | |
9d0d0a7b | 126 | if len(av) > 2: return 1 |
5bf6e9f5 MW |
127 | with PW(file) as pw: |
128 | pix = C.Pixie() | |
129 | if len(av) == 0: | |
130 | for tag in pw: pix.set(tag, pw[tag]) | |
131 | else: | |
132 | tag = av[0] | |
133 | if len(av) >= 2: pptag = av[1] | |
134 | else: pptag = av[0] | |
135 | pix.set(pptag, pw[tag]) | |
d7ab1bab | 136 | |
3aa33042 | 137 | def cmd_del(av): |
9d0d0a7b | 138 | if len(av) != 1: return 1 |
5bf6e9f5 MW |
139 | with PW(file, writep = True) as pw: |
140 | tag = av[0] | |
141 | try: del pw[tag] | |
142 | except KeyError, exc: die("Password `%s' not found" % exc.args[0]) | |
3aa33042 | 143 | |
d7ab1bab | 144 | commands = { 'create': [cmd_create, |
d1c45f5c MW |
145 | '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'], |
146 | 'find' : [cmd_find, 'LABEL'], | |
147 | 'store' : [cmd_store, 'LABEL [VALUE]'], | |
148 | 'list' : [cmd_list, '[GLOB-PATTERN]'], | |
149 | 'changepp' : [cmd_changepp, ''], | |
150 | 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'], | |
151 | 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'], | |
85f15f07 | 152 | 'delete' : [cmd_del, 'TAG']} |
d1c45f5c MW |
153 | |
154 | ###-------------------------------------------------------------------------- | |
155 | ### Command-line handling and dispatch. | |
d7ab1bab | 156 | |
157 | def version(): | |
2e6a3fda | 158 | print '%s 1.0.0' % prog |
d1c45f5c | 159 | |
d7ab1bab | 160 | def usage(fp): |
2e6a3fda | 161 | print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog |
d1c45f5c | 162 | |
d7ab1bab | 163 | def help(): |
164 | version() | |
165 | ||
b2687a0a | 166 | usage(stdout) |
d7ab1bab | 167 | print ''' |
168 | Maintains passwords or other short secrets securely. | |
169 | ||
170 | Options: | |
171 | ||
172 | -h, --help Show this help text. | |
173 | -v, --version Show program version number. | |
174 | -u, --usage Show short usage message. | |
175 | ||
2e6a3fda | 176 | -f, --file=FILE Where to find the password-safe file. |
177 | ||
d7ab1bab | 178 | Commands provided: |
179 | ''' | |
05a82542 | 180 | for c in sorted(commands): |
d7ab1bab | 181 | print '%s %s' % (c, commands[c][1]) |
182 | ||
d1c45f5c MW |
183 | ## Choose a default database file. |
184 | if 'PWSAFE' in environ: | |
185 | file = environ['PWSAFE'] | |
186 | else: | |
187 | file = '%s/.pwsafe' % environ['HOME'] | |
188 | ||
189 | ## Parse the command-line options. | |
d7ab1bab | 190 | try: |
d1c45f5c MW |
191 | opts, argv = getopt(argv[1:], 'hvuf:', |
192 | ['help', 'version', 'usage', 'file=']) | |
d7ab1bab | 193 | except GetoptError: |
194 | usage(stderr) | |
195 | exit(1) | |
196 | for o, a in opts: | |
197 | if o in ('-h', '--help'): | |
198 | help() | |
199 | exit(0) | |
200 | elif o in ('-v', '--version'): | |
201 | version() | |
202 | exit(0) | |
203 | elif o in ('-u', '--usage'): | |
204 | usage(stdout) | |
205 | exit(0) | |
206 | elif o in ('-f', '--file'): | |
207 | file = a | |
208 | else: | |
209 | raise 'Barf!' | |
210 | if len(argv) < 1: | |
211 | usage(stderr) | |
212 | exit(1) | |
213 | ||
d1c45f5c | 214 | ## Dispatch to a command handler. |
d7ab1bab | 215 | if argv[0] in commands: |
216 | c = argv[0] | |
217 | argv = argv[1:] | |
218 | else: | |
219 | c = 'find' | |
40b16f0c MW |
220 | try: |
221 | if commands[c][0](argv): | |
222 | print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1]) | |
223 | exit(1) | |
224 | except DecryptError: | |
225 | die("decryption failure") | |
d1c45f5c MW |
226 | |
227 | ###----- That's all, folks -------------------------------------------------- |