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 again

erikg / Revelation

Revelation is a password manager for the GNOME desktop, released under the GNU GPL license. It stores all your accounts and passwords in a single, secure place, and gives you access to it through a user-friendly graphical interface.

Clone this repository (size: 2.1 MB): HTTPS / SSH
hg clone https://bitbucket.org/erikg/revelation
hg clone ssh://hg@bitbucket.org/erikg/revelation

Revelation / 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("&", "&amp;")
159
4b0901db17b8
	string = string.replace("<", "&lt;")
160
4b0901db17b8
	string = string.replace(">", "&gt;")
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("&amp;", "&")
384
dcf64729d98b
	string = string.replace("&lt;", "<")
385
dcf64729d98b
	string = string.replace("&gt;", ">")
386
dcf64729d98b
387
dcf64729d98b
	return string
388
dcf64729d98b