Erik Grinaker is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this againRevelation / src / lib / util.py
- commit
- dceea539b127
- parent
- 4896555f19bf
- branch
- default
fixed crash when saving file after certain imports (fixes bug #203)
1 |
4b0901db17b8
|
# |
2 |
78fb3436ec03
|
# Revelation - a password manager for GNOME 2 |
3 |
4b0901db17b8
|
# http://oss.codepoet.no/revelation/ |
4 |
4b0901db17b8
|
# $Id$ |
5 |
4b0901db17b8
|
# |
6 |
4b0901db17b8
|
# Module with various utility functions |
7 |
4b0901db17b8
|
# |
8 |
4b0901db17b8
|
# |
9 |
d230b54e5239
|
# Copyright (c) 2003-2006 Erik Grinaker |
10 |
4b0901db17b8
|
# |
11 |
4b0901db17b8
|
# This program is free software; you can redistribute it and/or |
12 |
4b0901db17b8
|
# modify it under the terms of the GNU General Public License |
13 |
4b0901db17b8
|
# as published by the Free Software Foundation; either version 2 |
14 |
4b0901db17b8
|
# of the License, or (at your option) any later version. |
15 |
4b0901db17b8
|
# |
16 |
4b0901db17b8
|
# This program is distributed in the hope that it will be useful, |
17 |
4b0901db17b8
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 |
4b0901db17b8
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 |
4b0901db17b8
|
# GNU General Public License for more details. |
20 |
4b0901db17b8
|
# |
21 |
4b0901db17b8
|
# You should have received a copy of the GNU General Public License |
22 |
4b0901db17b8
|
# along with this program; if not, write to the Free Software |
23 |
4b0901db17b8
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
24 |
4b0901db17b8
|
# |
25 |
4b0901db17b8
|
|
26 |
bffe619c6cec
|
import crack |
27 |
bffe619c6cec
|
|
28 |
e1117203c473
|
import datetime, gettext, math, os, random, shlex, string, StringIO, traceback |
29 |
e1117203c473
|
|
30 |
e1117203c473
|
_ = gettext.gettext |
31 |
4b0901db17b8
|
|
32 |
4b0901db17b8
|
|
33 |
355e05c49e3d
|
class SubstFormatError(Exception): |
34 |
355e05c49e3d
|
"Exception for parse_subst format errors" |
35 |
355e05c49e3d
|
pass |
36 |
355e05c49e3d
|
|
37 |
355e05c49e3d
|
|
38 |
355e05c49e3d
|
class SubstValueError(Exception): |
39 |
355e05c49e3d
|
"Exception for missing values in parse_subst" |
40 |
355e05c49e3d
|
pass |
41 |
355e05c49e3d
|
|
42 |
355e05c49e3d
|
|
43 |
4b0901db17b8
|
|
44 |
512fcbde2eeb
|
def check_password(password): |
45 |
bffe619c6cec
|
"Checks if a password is valid" |
46 |
bffe619c6cec
|
|
47 |
38bdd8348b41
|
# check for length |
48 |
e0508d11f099
|
if len(password) < 6: |
49 |
e1117203c473
|
raise ValueError, _('is too short') |
50 |
bffe619c6cec
|
|
51 |
bffe619c6cec
|
|
52 |
e0508d11f099
|
# check for entropy |
53 |
e0508d11f099
|
pwlen = len(password) |
54 |
e0508d11f099
|
ent = entropy(password) |
55 |
e0508d11f099
|
idealent = entropy_ideal(pwlen) |
56 |
bffe619c6cec
|
|
57 |
e0508d11f099
|
if (pwlen < 100 and ent / idealent < 0.8) or (pwlen >= 100 and ent < 5.3): |
58 |
e1117203c473
|
raise ValueError, _('isn\'t varied enough') |
59 |
bffe619c6cec
|
|
60 |
bffe619c6cec
|
|
61 |
512fcbde2eeb
|
# check the password strength |
62 |
e0508d11f099
|
lc, uc, d, o = 0, 0, 0, 0 |
63 |
512fcbde2eeb
|
|
64 |
512fcbde2eeb
|
for c in password: |
65 |
512fcbde2eeb
|
if c in string.ascii_lowercase: |
66 |
e0508d11f099
|
lc += 1 |
67 |
512fcbde2eeb
|
|
68 |
512fcbde2eeb
|
elif c in string.ascii_uppercase: |
69 |
e0508d11f099
|
uc += 1 |
70 |
512fcbde2eeb
|
|
71 |
512fcbde2eeb
|
elif c in string.digits: |
72 |
e0508d11f099
|
d += 1 |
73 |
2dd98538694e
|
|
74 |
2dd98538694e
|
else: |
75 |
e0508d11f099
|
o += 1 |
76 |
bffe619c6cec
|
|
77 |
e0508d11f099
|
classcount = [ lc, uc, d, o ] |
78 |
e0508d11f099
|
classcount.sort() |
79 |
e0508d11f099
|
classcount.reverse() |
80 |
e0508d11f099
|
|
81 |
e0508d11f099
|
cred = sum([ count * (1 + (weight ** 2.161 / 10)) for weight, count in zip(range(1, len(classcount) + 1), classcount) ]) |
82 |
e0508d11f099
|
|
83 |
e0508d11f099
|
if cred < 10: |
84 |
e1117203c473
|
raise ValueError, _('is too weak') |
85 |
38bdd8348b41
|
|
86 |
38bdd8348b41
|
|
87 |
8e465cb9084d
|
# check if the password is a palindrome |
88 |
8e465cb9084d
|
for i in range(len(password)): |
89 |
8e465cb9084d
|
if password[i] != password[-i - 1]: |
90 |
8e465cb9084d
|
break |
91 |
8e465cb9084d
|
|
92 |
8e465cb9084d
|
else: |
93 |
e1117203c473
|
raise ValueError, _('is a palindrome') |
94 |
8e465cb9084d
|
|
95 |
8e465cb9084d
|
|
96 |
38bdd8348b41
|
# check password with cracklib |
97 |
512fcbde2eeb
|
try: |
98 |
e0508d11f099
|
if len(password) < 100: |
99 |
e0508d11f099
|
crack.FascistCheck(password) |
100 |
bffe619c6cec
|
|
101 |
512fcbde2eeb
|
except ValueError, reason: |
102 |
512fcbde2eeb
|
|
103 |
512fcbde2eeb
|
# modify reason |
104 |
512fcbde2eeb
|
reason = str(reason).strip() |
105 |
512fcbde2eeb
|
reason = reason.replace("simplistic/systematic", "systematic") |
106 |
512fcbde2eeb
|
reason = reason.replace(" dictionary", "") |
107 |
512fcbde2eeb
|
|
108 |
512fcbde2eeb
|
if reason[:3] == "it ": |
109 |
512fcbde2eeb
|
reason = reason[3:] |
110 |
512fcbde2eeb
|
|
111 |
512fcbde2eeb
|
if reason[:5] == "it's ": |
112 |
512fcbde2eeb
|
reason = "is " + reason[5:] |
113 |
512fcbde2eeb
|
|
114 |
512fcbde2eeb
|
raise ValueError, reason |
115 |
512fcbde2eeb
|
|
116 |
512fcbde2eeb
|
except IOError: |
117 |
512fcbde2eeb
|
pass |
118 |
bffe619c6cec
|
|
119 |
bffe619c6cec
|
|
120 |
500cdfa073b3
|
def dom_text(node): |
121 |
500cdfa073b3
|
"Returns text content of a DOM node" |
122 |
500cdfa073b3
|
|
123 |
500cdfa073b3
|
text = "" |
124 |
500cdfa073b3
|
|
125 |
500cdfa073b3
|
for child in node.childNodes: |
126 |
500cdfa073b3
|
if child.nodeType == node.TEXT_NODE: |
127 |
6691f6ade6e7
|
text += child.nodeValue.encode("utf-8") |
128 |
500cdfa073b3
|
|
129 |
500cdfa073b3
|
return text |
130 |
500cdfa073b3
|
|
131 |
500cdfa073b3
|
|
132 |
42fd064f49d3
|
def entropy(string): |
133 |
42fd064f49d3
|
"Calculates the Shannon entropy of a string" |
134 |
42fd064f49d3
|
|
135 |
42fd064f49d3
|
# get probability of chars in string |
136 |
42fd064f49d3
|
prob = [ float(string.count(c)) / len(string) for c in dict.fromkeys(list(string)) ] |
137 |
42fd064f49d3
|
|
138 |
42fd064f49d3
|
# calculate the entropy |
139 |
42fd064f49d3
|
entropy = - sum([ p * math.log(p) / math.log(2.0) for p in prob ]) |
140 |
42fd064f49d3
|
|
141 |
42fd064f49d3
|
return entropy |
142 |
42fd064f49d3
|
|
143 |
42fd064f49d3
|
|
144 |
42fd064f49d3
|
def entropy_ideal(length): |
145 |
42fd064f49d3
|
"Calculates the ideal Shannon entropy of a string with given length" |
146 |
42fd064f49d3
|
|
147 |
42fd064f49d3
|
prob = 1.0 / length |
148 |
42fd064f49d3
|
|
149 |
42fd064f49d3
|
return -1.0 * length * prob * math.log(prob) / math.log(2.0) |
150 |
42fd064f49d3
|
|
151 |
42fd064f49d3
|
|
152 |
4b0901db17b8
|
def escape_markup(string): |
153 |
4b0901db17b8
|
"Escapes a string so it can be placed in a markup string" |
154 |
4b0901db17b8
|
|
155 |
dceea539b127
|
if string is None: |
156 |
dceea539b127
|
return "" |
157 |
dceea539b127
|
|
158 |
4b0901db17b8
|
string = string.replace("&", "&") |
159 |
4b0901db17b8
|
string = string.replace("<", "<") |
160 |
4b0901db17b8
|
string = string.replace(">", ">") |
161 |
4b0901db17b8
|
|
162 |
4b0901db17b8
|
return string |
163 |
4b0901db17b8
|
|
164 |
4b0901db17b8
|
|
165 |
4b0901db17b8
|
def execute(command): |
166 |
4b0901db17b8
|
"Runs a command, returns its status code and output" |
167 |
4b0901db17b8
|
|
168 |
4b0901db17b8
|
p = os.popen(command, "r") |
169 |
4b0901db17b8
|
output = p.read() |
170 |
4b0901db17b8
|
status = p.close() |
171 |
4b0901db17b8
|
|
172 |
4b0901db17b8
|
if status is None: |
173 |
4b0901db17b8
|
status = 0 |
174 |
4b0901db17b8
|
|
175 |
4b0901db17b8
|
status = status >> 8 |
176 |
4b0901db17b8
|
|
177 |
4b0901db17b8
|
return output, status |
178 |
4b0901db17b8
|
|
179 |
4b0901db17b8
|
|
180 |
4b0901db17b8
|
def execute_child(command): |
181 |
4b0901db17b8
|
"Runs a command as a child, returns its process ID" |
182 |
4b0901db17b8
|
|
183 |
6cf6849eed72
|
items = shlex.split(command.encode("iso-8859-1"), 0) |
184 |
2608864aba1a
|
|
185 |
4b0901db17b8
|
return os.spawnvp(os.P_NOWAIT, items[0], items) |
186 |
4b0901db17b8
|
|
187 |
4b0901db17b8
|
|
188 |
42b9154d2aaf
|
def generate_password(length): |
189 |
4b0901db17b8
|
"Generates a password" |
190 |
4b0901db17b8
|
|
191 |
2608864aba1a
|
# set up character sets |
192 |
42b9154d2aaf
|
d = string.digits.translate(string.maketrans("", ""), "015") |
193 |
42b9154d2aaf
|
lc = string.ascii_lowercase.translate(string.maketrans("", ""), "lqg") |
194 |
42b9154d2aaf
|
uc = string.ascii_uppercase.translate(string.maketrans("", ""), "IOS") |
195 |
e0508d11f099
|
fullset = d + uc + lc |
196 |
e0508d11f099
|
|
197 |
2608864aba1a
|
charsets = ( |
198 |
2608864aba1a
|
( d, 0.15 ), |
199 |
2608864aba1a
|
( uc, 0.24 ), |
200 |
2608864aba1a
|
( lc, 0.24 ), |
201 |
2608864aba1a
|
) |
202 |
2608864aba1a
|
|
203 |
2608864aba1a
|
|
204 |
2608864aba1a
|
# function for generating password |
205 |
7eb53b474921
|
def genpw(length): |
206 |
7eb53b474921
|
password = [] |
207 |
4b0901db17b8
|
|
208 |
2608864aba1a
|
for set, share in charsets: |
209 |
2608864aba1a
|
password.extend([ random.choice(set) for i in range(int(round(length * share))) ]) |
210 |
2608864aba1a
|
|
211 |
7eb53b474921
|
while len(password) < length: |
212 |
e0508d11f099
|
password.append(random.choice(fullset)) |
213 |
7eb53b474921
|
|
214 |
7eb53b474921
|
random.shuffle(password) |
215 |
7eb53b474921
|
|
216 |
2608864aba1a
|
return "".join(password) |
217 |
7eb53b474921
|
|
218 |
7eb53b474921
|
|
219 |
7eb53b474921
|
# check password, and regenerate if needed |
220 |
7eb53b474921
|
while 1: |
221 |
2608864aba1a
|
try: |
222 |
2608864aba1a
|
password = genpw(length) |
223 |
7eb53b474921
|
|
224 |
2608864aba1a
|
if length <= 6: |
225 |
2608864aba1a
|
return password |
226 |
2608864aba1a
|
|
227 |
2608864aba1a
|
check_password(password) |
228 |
2608864aba1a
|
|
229 |
7eb53b474921
|
return password |
230 |
7eb53b474921
|
|
231 |
7eb53b474921
|
except ValueError: |
232 |
7eb53b474921
|
continue |
233 |
4b0901db17b8
|
|
234 |
4b0901db17b8
|
|
235 |
500cdfa073b3
|
def pad_right(string, length, padchar = " "): |
236 |
500cdfa073b3
|
"Right-pads a string to a given length" |
237 |
500cdfa073b3
|
|
238 |
500cdfa073b3
|
if string is None: |
239 |
500cdfa073b3
|
return None |
240 |
500cdfa073b3
|
|
241 |
500cdfa073b3
|
if len(string) >= length: |
242 |
500cdfa073b3
|
return string |
243 |
500cdfa073b3
|
|
244 |
500cdfa073b3
|
return string + ((length - len(string)) * padchar) |
245 |
500cdfa073b3
|
|
246 |
500cdfa073b3
|
|
247 |
355e05c49e3d
|
def parse_subst(string, map): |
248 |
355e05c49e3d
|
"Parses a string for substitution variables" |
249 |
355e05c49e3d
|
|
250 |
355e05c49e3d
|
result = "" |
251 |
355e05c49e3d
|
|
252 |
355e05c49e3d
|
pos = 0 |
253 |
355e05c49e3d
|
while pos < len(string): |
254 |
355e05c49e3d
|
|
255 |
355e05c49e3d
|
char = string[pos] |
256 |
355e05c49e3d
|
next = pos + 1 < len(string) and string[pos + 1] or "" |
257 |
355e05c49e3d
|
|
258 |
355e05c49e3d
|
|
259 |
355e05c49e3d
|
# handle normal characters |
260 |
355e05c49e3d
|
if char != "%": |
261 |
355e05c49e3d
|
result += char |
262 |
355e05c49e3d
|
pos += 1 |
263 |
355e05c49e3d
|
|
264 |
355e05c49e3d
|
|
265 |
355e05c49e3d
|
# handle % escapes (%%) |
266 |
355e05c49e3d
|
elif next == "%": |
267 |
355e05c49e3d
|
result += "%" |
268 |
355e05c49e3d
|
pos += 2 |
269 |
355e05c49e3d
|
|
270 |
355e05c49e3d
|
|
271 |
355e05c49e3d
|
# handle optional substitution variables |
272 |
355e05c49e3d
|
elif next == "?": |
273 |
355e05c49e3d
|
if map.has_key(string[pos + 2]): |
274 |
355e05c49e3d
|
result += map[string[pos + 2]] |
275 |
355e05c49e3d
|
pos += 3 |
276 |
355e05c49e3d
|
|
277 |
355e05c49e3d
|
else: |
278 |
355e05c49e3d
|
raise SubstFormatError |
279 |
355e05c49e3d
|
|
280 |
355e05c49e3d
|
|
281 |
355e05c49e3d
|
# handle optional substring expansions |
282 |
355e05c49e3d
|
elif next == "(": |
283 |
355e05c49e3d
|
|
284 |
355e05c49e3d
|
try: |
285 |
355e05c49e3d
|
result += parse_subst(string[pos + 2:string.index("%)", pos + 1)], map) |
286 |
355e05c49e3d
|
|
287 |
355e05c49e3d
|
except ValueError: |
288 |
355e05c49e3d
|
raise SubstFormatError |
289 |
355e05c49e3d
|
|
290 |
355e05c49e3d
|
except SubstValueError: |
291 |
355e05c49e3d
|
pass |
292 |
355e05c49e3d
|
|
293 |
355e05c49e3d
|
pos = string.index("%)", pos + 1) + 2 |
294 |
355e05c49e3d
|
|
295 |
355e05c49e3d
|
|
296 |
355e05c49e3d
|
# handle required ("normal") substitution variables |
297 |
355e05c49e3d
|
elif map.has_key(next): |
298 |
355e05c49e3d
|
|
299 |
355e05c49e3d
|
if map[next] in [ "", None ]: |
300 |
355e05c49e3d
|
raise SubstValueError |
301 |
355e05c49e3d
|
|
302 |
355e05c49e3d
|
result += map[next] |
303 |
355e05c49e3d
|
pos += 2 |
304 |
355e05c49e3d
|
|
305 |
355e05c49e3d
|
|
306 |
355e05c49e3d
|
# otherwise, it's a format error |
307 |
355e05c49e3d
|
else: |
308 |
355e05c49e3d
|
raise SubstFormatError |
309 |
355e05c49e3d
|
|
310 |
355e05c49e3d
|
|
311 |
355e05c49e3d
|
return result |
312 |
355e05c49e3d
|
|
313 |
355e05c49e3d
|
|
314 |
4b0901db17b8
|
def random_string(length): |
315 |
4b0901db17b8
|
"Generates a random string" |
316 |
4b0901db17b8
|
|
317 |
4b0901db17b8
|
s = "" |
318 |
4b0901db17b8
|
for i in range(length): |
319 |
4b0901db17b8
|
s += chr(int(random.random() * 255)) |
320 |
4b0901db17b8
|
|
321 |
4b0901db17b8
|
return s |
322 |
4b0901db17b8
|
|
323 |
4b0901db17b8
|
|
324 |
4b0901db17b8
|
def time_period_rough(start, end): |
325 |
4b0901db17b8
|
"Returns the rough period from start to end in human-readable format" |
326 |
4b0901db17b8
|
|
327 |
4b0901db17b8
|
if end < start: |
328 |
e1117203c473
|
return _('%i seconds') % 0 |
329 |
4b0901db17b8
|
|
330 |
4b0901db17b8
|
start = datetime.datetime.utcfromtimestamp(float(start)) |
331 |
4b0901db17b8
|
end = datetime.datetime.utcfromtimestamp(float(end)) |
332 |
4b0901db17b8
|
delta = end - start |
333 |
4b0901db17b8
|
|
334 |
4b0901db17b8
|
|
335 |
4b0901db17b8
|
if delta.days >= 365: |
336 |
e1117203c473
|
period = delta.days / 365 |
337 |
e1117203c473
|
unit = period != 1 and _('years') or _('year') |
338 |
4b0901db17b8
|
|
339 |
4b0901db17b8
|
elif delta.days >= 31 or (end.month != start.month and (end.day > start.day or (end.day == start.day and end.time() >= start.time()))): |
340 |
4b0901db17b8
|
period = ((end.year - start.year) * 12) + end.month - start.month |
341 |
4b0901db17b8
|
|
342 |
4b0901db17b8
|
if end.day < start.day or (end.day == start.day and end.time() < start.time()): |
343 |
4b0901db17b8
|
period -= 1 |
344 |
4b0901db17b8
|
|
345 |
e1117203c473
|
unit = period != 1 and _('months') or _('month') |
346 |
4b0901db17b8
|
|
347 |
4b0901db17b8
|
elif delta.days >= 7: |
348 |
e1117203c473
|
period = delta.days / 7 |
349 |
e1117203c473
|
unit = period != 1 and _('weeks') or _('week') |
350 |
4b0901db17b8
|
|
351 |
4b0901db17b8
|
elif delta.days >= 1: |
352 |
e1117203c473
|
period = delta.days |
353 |
e1117203c473
|
unit = period != 1 and _('days') or _('day') |
354 |
4b0901db17b8
|
|
355 |
4b0901db17b8
|
elif delta.seconds >= 3600: |
356 |
e1117203c473
|
period = delta.seconds / 3600 |
357 |
e1117203c473
|
unit = period != 1 and _('hours') or _('hour') |
358 |
4b0901db17b8
|
|
359 |
4b0901db17b8
|
elif delta.seconds >= 60: |
360 |
e1117203c473
|
period = delta.seconds / 60 |
361 |
e1117203c473
|
unit = period != 1 and _('minutes') or _('minute') |
362 |
4b0901db17b8
|
|
363 |
4b0901db17b8
|
else: |
364 |
e1117203c473
|
period = delta.seconds |
365 |
e1117203c473
|
unit = period != 1 and _('seconds') or _('second') |
366 |
4b0901db17b8
|
|
367 |
4b0901db17b8
|
|
368 |
e1117203c473
|
return "%d %s" % ( period, unit ) |
369 |
4b0901db17b8
|
|
370 |
4b0901db17b8
|
|
371 |
4b0901db17b8
|
def trace_exception(type, value, tb): |
372 |
4b0901db17b8
|
"Returns an exception traceback as a string" |
373 |
4b0901db17b8
|
|
374 |
4b0901db17b8
|
trace = StringIO.StringIO() |
375 |
4b0901db17b8
|
traceback.print_exception(type, value, tb, None, trace) |
376 |
4b0901db17b8
|
|
377 |
4b0901db17b8
|
return trace.getvalue() |
378 |
4b0901db17b8
|
|
379 |
dcf64729d98b
|
|
380 |
dcf64729d98b
|
def unescape_markup(string): |
381 |
dcf64729d98b
|
"Unescapes a string to get literal values" |
382 |
dcf64729d98b
|
|
383 |
dcf64729d98b
|
string = string.replace("&", "&") |
384 |
dcf64729d98b
|
string = string.replace("<", "<") |
385 |
dcf64729d98b
|
string = string.replace(">", ">") |
386 |
dcf64729d98b
|
|
387 |
dcf64729d98b
|
return string |
388 |
dcf64729d98b
|