Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 319 lines (294 sloc) 11.636 kb
208c6ab @gpmidi - Moved files to src directory
authored
1 #!/usr/bin/python
274accd @gpmidi - Added license
authored
2 #===============================================================================
3 # This file is part of PyPWSafe.
4 #
5 # PyPWSafe is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # PyPWSafe is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with PyPWSafe. If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
17 #===============================================================================
208c6ab @gpmidi - Moved files to src directory
authored
18 ''' A CLI interface for PyPWSafe.
19
20 Allow users to view Password Safe files.
21
22 Created on Jul 22, 2011
23
24 @author: paulson mcintyre <paul@gpmidi.net>
ed26855 @gpmidi - Filled out README with full install instructions
authored
25 @author: steve <rader@hep.wisc.edu>
208c6ab @gpmidi - Moved files to src directory
authored
26 '''
27 import logging, logging.config
28 logger = logging.getLogger("psafebin.dump")
29 log = logger.log
30 logging.basicConfig(
31 level = logging.INFO,
32 format = '%(asctime)s %(levelname)s %(message)s',
33 )
34 log(10, 'initing')
35 import os, sys, os.path
36 # FIXME: Add in tests to make sure we can import all
37 # required libraries and generate non-coder errors
38 # as to what is missing.
39 #try:
40 import pypwsafe as pws
41 #except ImportError:
42 # log(50, "Can't find the pypwsafe library")
43 # sys.exit(1)
44
45 from optparse import OptionParser
46
47 # TODO: Find a simpler way of doing this
48 def show_entry(entry, opts):
49 """ Return true if an entry should be displayed """
50 m = (
51 ('Group', opts.filter_group),
52 ('UUID', opts.filter_uuid),
53 ('Title', opts.filter_title),
54 ('Username', opts.filter_username),
55 )
56 def match(e_name, lst):
57 """ Return true if the given entry properity,
58 e_name, is in lst or lst is empty.
59 """
60 if len(lst) > 0:
61 if entry[e_name] in lst:
62 return True
63 else:
64 return False
65 else:
66 return True
67
68 for e_name, lst in m:
69 if not match(e_name, lst):
70 return False
71
72 return True
73
74 def display_xml(entries, opts):
75 raise NotImplementedError
76
274accd @gpmidi - Added license
authored
77 from StringIO import StringIO
78 from csv import DictWriter
79 import datetime
208c6ab @gpmidi - Moved files to src directory
authored
80 def display_csv(entries, opts):
274accd @gpmidi - Added license
authored
81 csvFields = [
82 'Group',
83 'Title',
84 'Username',
85 'Password',
86 'UUID',
87 'Note',
88 'Created',
89 'Modified',
90 'Last Access',
91 'Expires',
92 'URL',
93 'AutoType',
94 'History',
95 ]
ed26855 @gpmidi - Filled out README with full install instructions
authored
96 by_groups = {}
97 for entry in entries:
98 group = '.'.join(entry['Group'])
99 if not by_groups.has_key(group):
100 by_groups[group] = []
101 by_groups[group].append(entry)
102 groups = by_groups.keys()
103 groups.sort()
274accd @gpmidi - Added license
authored
104 csvString = StringIO()
105 c = DictWriter(csvString, csvFields)
106 c.writerow(dict(zip(csvFields, csvFields)))
ed26855 @gpmidi - Filled out README with full install instructions
authored
107 for group in groups:
108 by_groups[group].sort(lambda a, b: cmp(a['Title'] + a['Username'], b['Title'] + b['Username']))
109 for entry in by_groups[group]:
274accd @gpmidi - Added license
authored
110 policy = entry['PasswordPolicy']
111 history = []
112 # May want to add in dt at some point
113 for dt, pwd in entry['PasswordHistory']['history'].items():
114 history.append(pwd)
115 c.writerow({
116 'Group':'.'.join(entry['Group']),
117 'Title':entry['Title'],
118 'Username':entry['Username'],
119 'Password':entry['Password'],
120 'UUID':entry['UUID'],
121 'Note':entry['Notes'],
122 'Created':datetime.datetime(*entry['ctime'][:6]).isoformat(),
123 'Modified':datetime.datetime(*entry['mtime'][:6]).isoformat(),
124 'Last Access':datetime.datetime(*entry['LastAccess'][:6]).isoformat(),
125 'Expires':datetime.datetime(*entry['PasswordExpiry'][:6]).isoformat(),
126 'URL':entry['URL'],
127 'AutoType':entry['Autotype'],
128 # TODO: Add in support for creating new rows for each history entry
129 'History':';'.join(history),
130 })
131 return csvString.getvalue()
208c6ab @gpmidi - Moved files to src directory
authored
132
133 def display_display(entries, opts):
134 ret = ''
274accd @gpmidi - Added license
authored
135 # Group by group
208c6ab @gpmidi - Moved files to src directory
authored
136 by_groups = {}
137 for entry in entries:
809b4f2 @gpmidi - Fixed a minor error
authored
138 group = '.'.join(entry['Group'])
139 if not by_groups.has_key(group):
140 by_groups[group] = []
141 by_groups[group].append(entry)
208c6ab @gpmidi - Moved files to src directory
authored
142 groups = by_groups.keys()
143 # In place!
144 groups.sort()
145 for group in groups:
146 ret += "= %r =\n" % group
147 by_groups[group].sort(lambda a, b: cmp(a['Title'] + a['Username'], b['Title'] + b['Username']))
148 for entry in by_groups[group]:
149 ret += """ == %(Title)r ==
150 Username: %(Username)r
151 Password: %(Password)r
274accd @gpmidi - Added license
authored
152 UUID: %(UUID)r
153 URL: %(URL)r
154 AutoType: %(Autotype)r
208c6ab @gpmidi - Moved files to src directory
authored
155 """ % entry
156 return ret
157
158
159 if __name__ == "__main__":
160 # Setup option parser to parse out args
161 parser = OptionParser(
162 usage = "%prog",
163 version = "%prog v0.1",
164 prog = "psafedump",
165 description = """
166 A CLI interface for viewing Password Safe files.
167 """,
168 )
169 # TODO: Support multiple files
170 parser.add_option(
171 "-f",
172 "--file",
173 action = "store",
174 type = "string",
175 dest = "filename",
176 default = None,
177 help = "Password Safe file to create, edit, or view. ",
178 )
179 parser.add_option(
180 "-r",
181 "--readonly",
182 action = "store_true",
183 dest = "readonly",
184 default = False,
185 help = "Open the Password Safe in read-only mode. [Default: %default]",
186 )
187 parser.add_option(
188 "-p",
189 "--password",
190 action = "append",
191 dest = "password",
192 default = [],
193 help = "The password to use when opening the file. If given multiple times each password given will be tried. If not given, a password will be prompted for via STDIN and STDOUT. Note: Specifying the password via an argument is INSECURE. ",
194 )
195 parser.add_option(
196 "-d",
197 "--debug",
198 action = "store_true",
199 dest = "debug",
200 default = False,
201 help = "Run in debug mode. Running in this mode WILL EXPOSE YOUR PASSWORDS and other sensitive information. [Default: %default]",
202 )
203 # Output format
204 parser.add_option(
205 "--xml",
206 action = "store_true",
207 default = False,
208 dest = "xml",
209 help = "Display results in XML",
210 )
211 parser.add_option(
212 "--csv",
213 action = "store_true",
214 default = False,
215 dest = "csv",
216 help = "Display results in CSV",
217 )
218 parser.add_option(
219 "--display",
220 action = "store_true",
221 default = False,
222 dest = "display",
223 help = "Display results in a human readable format",
224 )
225
226 # Display Filters
227 parser.add_option(
228 "--uuid",
229 action = "append",
230 dest = "filter_uuid",
231 default = [],
232 help = "Limit display to entries with this UUID. If given multiple times then displayed entries must match at least one of the UUIDs. ",
233 )
234 parser.add_option(
235 "--title",
236 action = "append",
237 dest = "filter_title",
238 default = [],
239 help = "Limit display to entries with this title. If given multiple times then displayed entries must match at least one of the titles. ",
240 )
241 parser.add_option(
242 "--group",
243 action = "append",
244 dest = "filter_group",
245 default = [],
246 help = "Limit display to entries with this group. If given multiple times then displayed entries must match at least one of the groups. ",
247 )
248 parser.add_option(
249 "--username",
250 action = "append",
251 dest = "filter_username",
252 default = [],
253 help = "Limit display to entries with this username. If given multiple times then displayed entries must match at least one of the usernames. ",
254 )
255 log(10, "Parsing args")
256 (opts, args) = parser.parse_args()
257
258 if opts.debug:
259 logger.setLevel(logging.DEBUG)
260
261 # Handle filename as first arg
262 if not opts.filename:
263 if len(args) == 1:
264 opts.filename = args[0]
265 else:
266 log(50, "No filename was given")
267 parser.error("No filename specified. ")
268 sys.exit(2)
269
270 # FIXME: Add tests to make sure that
271 # - File exists (reading)
272 # - File doesn't exist but can create it (creating)
273 # - Can write the file if not readonly
274
275 if not (opts.xml or opts.csv or opts.display):
276 opts.display = True
277 log(10, "No output format given. Defaulting to display.")
278
279 if len(opts.password) == 0:
280 # FIXME: Add in support for prompting for a password.
281 raise NotImplementedError
282
283 if opts.readonly:
284 mode = "RO"
285 else:
286 mode = "RW"
287 log(10, "Set mode to %r", mode)
288
289 psafe = None
290 for passwd in opts.password:
291 try:
292 log(10, "Trying password %r", passwd)
293 psafe = pws.PWSafe3(
294 filename = opts.filename,
295 password = passwd,
296 mode = mode,
297 )
298 log(10, "Got psafe object %r", psafe)
299 break
300 except pws.PasswordError:
301 log(10, "Password error - Trying next password if any")
302 continue
303
304 if not psafe:
305 log(50, "No valid password was found. ")
306
307 to_display = []
308 for entry in psafe.records:
309 if show_entry(entry, opts):
310 to_display.append(entry)
311
312 if opts.xml:
313 print display_xml(to_display, opts)
314 elif opts.csv:
315 print display_csv(to_display, opts)
316 elif opts.display:
317 print display_display(to_display, opts)
318
Something went wrong with that request. Please try again.