7 use lib
"$ENV{LJHOME}/cgi-bin";
9 use LJ
::Auth
::Challenge
;
11 ### get_visual_id() -> ( $capid, $anum )
12 sub get_visual_id
{ get_id
('image') }
13 sub get_audio_id
{ get_id
('audio') }
15 ### get_id( $type ) -> ( $capid, $anum )
20 $dbh, # Database handle (writer)
24 $anum, # Unseries-ifier number
25 $issuedate, # unixtime of issue
28 # Fetch database handle and lock the captcha table
29 $dbh = LJ
::get_db_writer
()
30 or return LJ
::error
( "Couldn't fetch a db writer." );
31 $dbh->selectrow_array("SELECT GET_LOCK('get_captcha', 10)")
32 or return LJ
::error
( "Failed lock on getting a captcha." );
34 # Fetch the first unassigned row
43 $row = $dbh->selectrow_arrayref( $sql, undef, $type )
44 or $dbh->do("DO RELEASE_LOCK('get_captcha')") && die "No $type captchas available";
45 die "selectrow_arrayref: $sql: ", $dbh->errstr if $dbh->err;
46 ( $capid, $anum ) = @
$row;
48 # Mark the captcha as issued
52 SET issuetime
= $issuedate
55 $dbh->do( $sql ) or die "do: $sql: ", $dbh->errstr;
56 $dbh->do("DO RELEASE_LOCK('get_captcha')");
58 return ( $capid, $anum );
62 ### get_visual_data( $capid, $anum, $want_paths )
63 # if want_paths is true, this function may return an arrayref containing
64 # one or more paths (disk or HTTP) to the resource
67 my ( $capid, $anum, $want_paths ) = @_;
71 $dbr, # Database handle (reader)
73 $valid, # Are the capid/anum valid?
76 $location, # Location of the file (mogile/blob)
79 $dbr = LJ
::get_db_reader
();
81 SELECT capid, location
88 ( $valid, $location ) = $dbr->selectrow_array( $sql, undef, $capid, $anum );
89 return undef unless $valid;
91 if ($location eq 'mogile') {
92 die "MogileFS object not loaded.\n" unless LJ
::mogclient
();
94 # return path(s) to the content if they want
95 my @paths = LJ
::mogclient
()->get_paths("captcha:$capid");
98 $data = ${LJ
::mogclient
()->get_file_data("captcha:$capid")};
101 $u = LJ
::load_user
( "system" )
102 or die "Couldn't load the system user.";
104 $data = LJ
::Blob
::get
( $u, 'captcha_image', 'png', $capid )
105 or die "Failed to fetch captcha_image $capid from media server";
111 ### get_audio_data( $capid, $anum, $want_paths )
112 # if want_paths is true, this function may return an arrayref containing
113 # one or more paths (disk or HTTP) to the resource
116 my ( $capid, $anum, $want_paths ) = @_;
117 $capid = int($capid);
120 $dbr, # Database handle (reader)
121 $sql, # SQL statement
122 $valid, # Are the capid/anum valid?
123 $data, # The PNG data
125 $location, # Location of the file (mogile/blob)
128 $dbr = LJ
::get_db_reader
();
130 SELECT capid, location
137 ( $valid, $location ) = $dbr->selectrow_array( $sql, undef, $capid, $anum );
138 return undef unless $valid;
140 if ($location eq 'mogile') {
141 die "MogileFS object not loaded.\n" unless LJ
::mogclient
();
143 # return path(s) to the content if they want
144 my @paths = LJ
::mogclient
()->get_paths("captcha:$capid");
147 $data = ${LJ
::mogclient
()->get_file_data("captcha:$capid")};
150 $u = LJ
::load_user
( "system" )
151 or die "Couldn't load the system user.";
153 $data = LJ
::Blob
::get
( $u, 'captcha_audio', 'wav', $capid )
154 or die "Failed to fetch captcha_audio $capid from media server";
159 ### check_code( $capid, $anum, $code, $u ) -> <true value if code is correct>
161 my ( $capid, $anum, $code, $u ) = @_;
164 $dbr, # Database handle (reader)
166 $answer, # Challenge answer
167 $userid, # userid of previous answerer (or 0 if none)
171 SELECT answer, userid
178 # Fetch the challenge's answer based on id and anum.
179 $dbr = LJ
::get_db_writer
();
180 ( $answer, $userid ) = $dbr->selectrow_array( $sql, undef, $capid, $anum );
182 # if it's already been answered, it must have been answered by the $u
183 # given to this function (double-click protection)
184 return 0 if $userid && ( ! $u || $u->{userid
} != $userid );
186 # otherwise, just check answer.
187 return lc $answer eq lc $code;
190 # Verify captcha answer if using a captcha session.
191 # (captcha challenge, code, $u)
192 # Returns capid and anum if answer correct. (for expire)
193 sub session_check_code
{
194 my ($sess, $code, $u) = @_;
195 return 0 unless $sess && $code;
196 $sess = LJ
::get_challenge_attributes
($sess);
198 $u = LJ
::load_user
('system') unless $u;
200 my $dbcm = LJ
::get_cluster_master
($u);
201 my $dbr = LJ
::get_db_reader
();
203 my ($lcapid, $try) = # clustered
204 $dbcm->selectrow_array('SELECT lastcapid, trynum ' .
205 'FROM captcha_session ' .
206 'WHERE sess=?', undef, $sess);
207 my ($capid, $anum) = # global
208 $dbr->selectrow_array('SELECT capid,anum ' .
210 'WHERE capid=?', undef, $lcapid);
211 if (! LJ
::Captcha
::check_code
($capid, $anum, $code, $u)) {
212 # update try and lastcapid
213 $u->do('UPDATE captcha_session SET lastcapid=NULL, ' .
214 'trynum=trynum+1 WHERE sess=?', undef, $sess);
217 return ($capid, $anum);
220 ### expire( $capid ) -> <true value if code was expired successfully>
222 my ( $capid, $anum, $userid ) = @_;
225 $dbh, # Database handle (writer)
226 $sql, # SQL update query
232 WHERE capid = ? AND anum = ? AND userid = 0
235 # Fetch the challenge's answer based on id and anum.
236 $dbh = LJ
::get_db_writer
();
237 $dbh->do( $sql, undef, $userid, $capid, $anum ) or return undef;
242 # Update/create captcha sessions, return new capid/anum pairs on success.
243 # challenge, type, optional journalu->{clusterid} for clustering.
244 # Type is either 'image' or 'audio'
246 my ($chal, $type, $cid) = @_;
247 return unless $chal && $type;
249 return unless LJ
::Auth
::Challenge
->check($chal);
251 my $sess = LJ
::get_challenge_attributes
($chal);
252 my ($capid, $anum) = ($type eq 'image') ?
253 LJ
::Captcha
::get_visual_id
() :
254 LJ
::Captcha
::get_audio_id
();
257 $cid = LJ
::load_user
('system')->{clusterid
} unless $cid;
258 my $dbcm = LJ
::get_cluster_master
($cid);
261 my $try = $dbcm->selectrow_array('SELECT trynum FROM captcha_session ' .
262 'WHERE sess=?', undef, $sess);
265 $dbcm->do('REPLACE INTO captcha_session SET sess=?, sesstime=?, '.
266 'lastcapid=?, trynum=?', undef, $sess, time(), $capid, $try);
267 return ($capid, $anum);