This file is indexed.

/usr/share/mediawiki-extensions/confirmedit/captcha.py is in mediawiki-extensions-confirmedit 2.5.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  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
#!/usr/bin/python
#
# Script to generate distorted text images for a captcha system.
#
# Copyright (C) 2005 Neil Harris
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# http://www.gnu.org/copyleft/gpl.html
#
# Further tweaks by Brion Vibber <brion@pobox.com>:
# 2006-01-26: Add command-line options for the various parameters
# 2007-02-19: Add --dirs param for hash subdirectory splits
# Tweaks by Greg Sabino Mullane <greg@turnstep.com>:
# 2008-01-06: Add regex check to skip words containing other than a-z

import random
import Image
import ImageFont
import ImageDraw
import ImageEnhance
import ImageOps
import math, string, md5
import getopt
import os
import sys
import re

# Does X-axis wobbly copy, sandwiched between two rotates
def wobbly_copy(src, wob, col, scale, ang):
	x, y = src.size
	f = random.uniform(4*scale, 5*scale)
	p = random.uniform(0, math.pi*2)
	rr = ang+random.uniform(-30, 30) # vary, but not too much
	int_d = Image.new('RGB', src.size, 0) # a black rectangle
	rot = src.rotate(rr, Image.BILINEAR)
	# Do a cheap bounding-box op here to try to limit work below
	bbx = rot.getbbox()
	if bbx == None:
		print "whoops"
		return src
	else:
		l, t, r, b= bbx
	# and only do lines with content on
	for i in range(t, b+1):
		# Drop a scan line in
		xoff = int(math.sin(p+(i*f/y))*wob)
		xoff += int(random.uniform(-wob*0.5, wob*0.5))
		int_d.paste(rot.crop((0, i, x, i+1)), (xoff, i))
	# try to stop blurring from building up
	int_d = int_d.rotate(-rr, Image.BILINEAR)
	enh = ImageEnhance.Sharpness(int_d)
	return enh.enhance(2)


def gen_captcha(text, fontname, fontsize, file_name):
	"""Generate a captcha image"""
	# white text on a black background
	bgcolor = 0x0
	fgcolor = 0xffffff
	# create a font object 
	font = ImageFont.truetype(fontname,fontsize)
	# determine dimensions of the text
	dim = font.getsize(text)
	# create a new image significantly larger that the text
	edge = max(dim[0], dim[1]) + 2*min(dim[0], dim[1])
	im = Image.new('RGB', (edge, edge), bgcolor)
	d = ImageDraw.Draw(im)
	x, y = im.size
	# add the text to the image
	d.text((x/2-dim[0]/2, y/2-dim[1]/2), text, font=font, fill=fgcolor)
	k = 3
	wob = 0.20*dim[1]/k
	rot = 45
	# Apply lots of small stirring operations, rather than a few large ones
	# in order to get some uniformity of treatment, whilst
	# maintaining randomness
	for i in range(k):
		im = wobbly_copy(im, wob, bgcolor, i*2+3, rot+0)
		im = wobbly_copy(im, wob, bgcolor, i*2+1, rot+45)
		im = wobbly_copy(im, wob, bgcolor, i*2+2, rot+90)
		rot += 30
	
	# now get the bounding box of the nonzero parts of the image
	bbox = im.getbbox()
	bord = min(dim[0], dim[1])/4 # a bit of a border
	im = im.crop((bbox[0]-bord, bbox[1]-bord, bbox[2]+bord, bbox[3]+bord))
	# and turn into black on white
	im = ImageOps.invert(im)
		
	# save the image, in format determined from filename
	im.save(file_name)

def gen_subdir(basedir, hash, levels):
	"""Generate a subdirectory path out of the first _levels_
	characters of _hash_, and ensure the directories exist
	under _basedir_."""
	subdir = None
	for i in range(0, levels):
		char = hash[i]
		if subdir:
			subdir = os.path.join(subdir, char)
		else:
			subdir = char
		fulldir = os.path.join(basedir, subdir)
		if not os.path.exists(fulldir):
			os.mkdir(fulldir)
	return subdir

def try_pick_word(words, blacklist, verbose):
	word1 = words[random.randint(0,len(words)-1)]
	word2 = words[random.randint(0,len(words)-1)]
	word = word1+word2
	if verbose:
		print "word is %s" % word
	r = re.compile('[^a-z]');
	if r.search(word):
		print "skipping word pair '%s' because it contains non-alphabetic characters" % word
		return None

	for naughty in blacklist:
		if naughty in word:
			if verbose:
				print "skipping word pair '%s' because it contains blacklisted word '%s'" % (word, naughty)
			return None
	return word

def pick_word(words, blacklist, verbose):
	while True:
		word = try_pick_word(words, blacklist, verbose)
		if word:
			return word

def read_wordlist(filename):
	return [string.lower(x.strip()) for x in open(wordlist).readlines()]

if __name__ == '__main__':
	"""This grabs random words from the dictionary 'words' (one
	word per line) and generates a captcha image for each one,
	with a keyed salted hash of the correct answer in the filename.
	
	To check a reply, hash it in the same way with the same salt and
	secret key, then compare with the hash value given.
	"""
	font = "VeraBd.ttf"
	wordlist = "awordlist.txt"
	blacklistfile = None
	key = "CHANGE_THIS_SECRET!"
	output = "."
	count = 20
	fill = 0
	dirs = 0
	verbose = False
	
	opts, args = getopt.getopt(sys.argv[1:], "", ["font=", "wordlist=", "blacklist=", "key=", "output=", "count=", "fill=", "dirs=", "verbose"])
	for o, a in opts:
		if o == "--font":
			font = a
		if o == "--wordlist":
			wordlist = a
		if o == "--blacklist":
			blacklistfile = a
		if o == "--key":
			key = a
		if o == "--output":
			output = a
		if o == "--count":
			count = int(a)
		if o == "--fill":
			fill = int(a)
		if o == "--dirs":
			dirs = int(a)
		if o == "--verbose":
			verbose = True
	
	if fill:
		# Option processing order is not guaranteed, so count the output
		# files after...
		count = max(0, fill - len(os.listdir(output)))
	
	words = read_wordlist(wordlist)
	words = [x for x in words
		if len(x) <= 5 and len(x) >= 4 and x[0] != "f"
		and x[0] != x[1] and x[-1] != x[-2]
		and (not "'" in x)]
	
	if blacklistfile:
		blacklist = read_wordlist(blacklistfile)
	else:
		blacklist = []
	
	for i in range(count):
		word = pick_word(words, blacklist, verbose)
		salt = "%08x" % random.randrange(2**32)
		# 64 bits of hash is plenty for this purpose
		hash = md5.new(key+salt+word+key+salt).hexdigest()[:16]
		filename = "image_%s_%s.png" % (salt, hash)
		if dirs:
			subdir = gen_subdir(output, hash, dirs)
			filename = os.path.join(subdir, filename)
		if verbose:
			print filename
		gen_captcha(word, font, 40, os.path.join(output, filename))