-
Notifications
You must be signed in to change notification settings - Fork 5
/
CaptchasDotNet.py
235 lines (202 loc) · 8.56 KB
/
CaptchasDotNet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#---------------------------------------------------------------------
# Python module for easy utilization of http://captchas.net
#
# For documentation look at http://captchas.net/sample/python/
#
# Written by Sebastian Wilhelmi <seppi@seppi.de> and
# Felix Holderied <felix@holderied.de>
# This file is in the public domain.
#
# ChangeLog:
#
# 2006-09-08: Add new optional parameters alphabet, letters
# height an width. Add audio_url.
#
# 2006-03-01: Only delete the random string from the repository in
# case of a successful verification.
#
# 2006-02-14: Add new image() method returning an HTML/JavaScript
# snippet providing a fault tolerant service.
#
# 2005-06-02: Initial version.
#
#---------------------------------------------------------------------
import os
import md5
import random
import time
class CaptchasDotNet:
def __init__ (self, client, secret,
alphabet = 'abcdefghkmnopqrstuvwxyz',
letters = 6,
width = 240,
height = 80,
random_repository = '/tmp/captchasnet-random-strings',
cleanup_time = 3600
):
self.__client = client
self.__secret = secret
self.__alphabet = alphabet
self.__letters = letters
self.__width = width
self.__height = height
self.__random_repository = random_repository
self.__cleanup_time = cleanup_time
self.__time_stamp_file = os.path.join (random_repository,
'__time_stamp__')
# Return a random string
def __random_string (self):
# The random string shall consist of small letters, big letters
# and digits.
letters = "abcdefghijklmnopqrstuvwxyz"
letters += letters.upper () + "0123456789"
# The random starts out empty, then 40 random possible characters
# are appended.
random_string = ''
for i in range (40):
random_string += random.choice (letters)
# Return the random string.
return random_string
# Create a new random string and register it.
def random (self):
# If the repository directory is does not yet exist, create it.
if not os.path.isdir (self.__random_repository):
os.makedirs (self.__random_repository)
# If the time stamp file does not yet exist, create it.
if not os.path.isfile (self.__time_stamp_file):
os.close (os.open (self.__time_stamp_file, os.O_CREAT, 0700))
# Get the current time.
now = time.time ()
# Determine the time, before which to remove random strings.
cleanup_time = now - self.__cleanup_time
# If the last cleanup is older than specified, cleanup the
# directory.
if os.stat (self.__time_stamp_file).st_mtime < cleanup_time:
os.utime (self.__time_stamp_file, (now, now))
for file_name in os.listdir (self.__random_repository):
file_name = os.path.join (self.__random_repository, file_name)
if os.stat (file_name).st_mtime < cleanup_time:
os.unlink (file_name)
# loop until a valid random string has been found and registered.
while True:
# generate a new random string.
random = self.__random_string ()
# open a file with the corresponding name in the repository
# directory in such a way, that the creation fails, when the
# file already exists. That should be near to impossible with
# good seeding of the random number generator, but it's better
# to play safe.
try:
os.close (os.open (os.path.join (self.__random_repository,
random),
os.O_EXCL | os.O_CREAT, 0700))
except EnvironmentError, error:
# if the file already existed, rerun the loop to try the
# next string.
if error.errno == errno.EEXIST:
continue
else:
# other errors will certainly persist for other random
# strings, so raise the exception.
raise
# return the successfully registered random string.
self.__random = random
return random
def image_url (self, random = None, base = 'http://image.captchas.net/'):
if not random:
random = self.__random
url = base
url += '?client=%s&random=%s' % (self.__client, random)
if self.__alphabet != "abcdefghijklmnopqrstuvwxyz":
url += '&alphabet=%s' % self.__alphabet
if self.__letters != 6:
url += '&letters=%s' % self.__letters
if self.__width != 240:
url += '&width=%s' % self.__width
if self.__height != 80:
url += '&height=%s' % self.__height
return url
def audio_url (self, random = None, base = 'http://audio.captchas.net/'):
if not random:
random = self.__random
url = base
url += '?client=%s&random=%s' % (self.__client, random)
if self.__alphabet != "abcdefghijklmnopqrstuvwxyz":
url += '&alphabet=%s' % self.__alphabet
if self.__letters != 6:
url += '&letters=%s' % self.__letters
return url
def image (self, random = None, id = 'captchas.net'):
return '''
<a href="http://captchas.net"><img
style="border: none; vertical-align: bottom"
id="%s" src="%s" width="%d" height="%d"
alt="The CAPTCHA image" /></a>
<script type="text/javascript">
<!--
function captchas_image_error (image)
{
if (!image.timeout) return true;
image.src = image.src.replace (/^http:\/\/image\.captchas\.net/,
'http://image.backup.captchas.net');
return captchas_image_loaded (image);
}
function captchas_image_loaded (image)
{
if (!image.timeout) return true;
window.clearTimeout (image.timeout);
image.timeout = false;
return true;
}
var image = document.getElementById ('%s');
image.onerror = function() {return captchas_image_error (image);};
image.onload = function() {return captchas_image_loaded (image);};
image.timeout
= window.setTimeout(
"captchas_image_error (document.getElementById ('%s'))",
10000);
image.src = image.src;
//-->
</script>''' % (id, self.image_url (random), self.__width, self.__height, id, id)
def validate (self, random):
self.__random = random
file_name = os.path.join (self.__random_repository, random)
# Find out, whether the file exists
result = os.path.isfile (file_name)
# if the file exists, remember it.
if result:
self.__random_file = file_name
# the random string was valid, if and only if the
# corresponding file existed.
return result
def verify (self, input, random = None):
if not random:
random = self.__random
# The format of the password.
password_alphabet = self.__alphabet
password_length = self.__letters
# If the user input has the wrong lenght, it can't be correct.
if len (input) != password_length:
return False
# Calculate the MD5 digest of the concatenation of secret key and
# random string.
encryption_base = self.__secret + random
if (password_alphabet != "abcdefghijklmnopqrstuvwxyz") or (password_length != 6):
encryption_base += ":" + password_alphabet + ":" + str(password_length)
digest = md5.new (encryption_base).digest ()
# Compute password
correct_password = ''
for pos in range (password_length):
letter_num = ord (digest[pos]) % len (password_alphabet)
correct_password += password_alphabet[letter_num]
# Check password
if input != correct_password:
return False
# Remove the correspondig random file, if it exists.
try:
os.unlink (self.__random_file)
del self.__random_file
except:
pass
# The user input was correct.
return True