or verify your email address before making a post.

Click here for more information
or to verify your email.'); // --- function twister_captcha_output_data($data) { if (isset($_GET['framed'])) { twister_captcha_output_html($data); } else { header('Content-Type: application/json'); echo json_encode($data); } } function twister_captcha_output_html($data) { header('Content-Security-Policy: frame-ancestors https://*.' . TWISTER_DOMAIN . ';'); $now = $_SERVER['REQUEST_TIME']; ?>

You can now close this page and try getting a captcha again.

true, 'ticket' => false ]); } function twister_captcha_get_ticket_captcha_response() { if (isset($_GET['ticket_resp'])) { return $_GET['ticket_resp']; } return false; } function twister_captcha_get_hcaptcha_private_key() { $path = '/www/global/yotsuba/config/captcha_config.ini'; $cfg = file_get_contents($path); if (!$cfg) { return false; } $res = preg_match('/^HCAPTCHA_API_KEY_PRIVATE ?= ?([^\s]+)$/m', $cfg, $m); if (!$res || empty($m) || !$m[1]) { return false; } return $m[1]; } function twister_captcha_verify_ticket_captcha() { $response = twister_captcha_get_ticket_captcha_response(); if (!$response || strlen($response) > 4096) { return false; } $captcha_private_key = twister_captcha_get_hcaptcha_private_key(); if (!$captcha_private_key) { // Don't block in case of misconfiguration on our end return true; } $url = 'https://hcaptcha.com/siteverify'; $post = array( 'secret' => $captcha_private_key, 'response' => $response, 'remoteip' => $_SERVER['REMOTE_ADDR'], 'sitekey' => TWISTER_HCAPTCHA_SITEKEY, ); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 2); curl_setopt($curl, CURLOPT_TIMEOUT, 4); curl_setopt($curl, CURLOPT_USERAGENT, '4chan'); curl_setopt($curl, CURLOPT_POSTFIELDS, $post); $resp = curl_exec($curl); if ($resp === false) { curl_close($curl); return false; } $resp_status = curl_getinfo($curl, CURLINFO_HTTP_CODE); if ($resp_status >= 300) { curl_close($curl); return false; } curl_close($curl); $json = json_decode($resp, true); // BAD if (json_last_error() !== JSON_ERROR_NONE) { return false; } // GOOD if ($json && isset($json['success']) && $json['success']) { return true; } // BAD return false; } function twister_captcha_process_ticket_captcha($pcd, $now, $long_ip, $board, $msg = null, $bypassable = false) { // Captcha response was sent, verify it if (twister_captcha_get_ticket_captcha_response()) { if (twister_captcha_verify_ticket_captcha() === true) { // Captcha is valid, return a new ticket twister_captcha_output_new_ticket($pcd, $now, $long_ip, $board, $msg, $bypassable); } else { // Wrong captcha or captcha malfunction twister_captcha_error(TWISTER_ERR_GENERIC . ' (tcr0)'); } } // No captcha reponse provided, tell the frontend to show a ticket captcha else { twister_captcha_output_ticket_captcha(); } } function twister_captcha_output_new_ticket($pcd, $now, $long_ip, $board, $msg = null, $bypassable = false) { $ticket = twister_captcha_generate_ticket($now, $long_ip, $board); if (!$ticket) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (gt1)'); } twister_captcha_output_ticket_pcd($ticket, $pcd, $msg, $bypassable); } function twister_captcha_output_ticket_pcd($ticket, $pcd, $msg, $bypassable) { $data = []; if ($ticket) { $data['ticket'] = $ticket; } $data['pcd'] = $pcd; if ($msg) { $data['pcd_msg'] = $msg; } if ($bypassable) { $data['bpcd'] = true; } twister_captcha_output_data($data); } function twister_captcha_error($msg, $extra = null) { //http_response_code(500); $data = [ 'error' => $msg ]; if ($extra) { $data = array_merge($data, $extra); } twister_captcha_output_data($data); die(); } function twister_captcha_is_req_suspicious() { /* if (isset($_SERVER['HTTP_X_HTTP_VERSION'])) { if (strpos($_SERVER['HTTP_X_HTTP_VERSION'], 'HTTP/1') === 0) { return true; } } */ $no_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) === false; $no_accept = isset($_SERVER['HTTP_ACCEPT']) === false; if ($no_lang && $no_accept) { return true; } if ($no_lang && strpos($_SERVER['HTTP_USER_AGENT'], '; wv)') !== false) { return true; } if ($no_accept && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false) { return true; } if ($no_lang && isset($_SERVER['HTTP_REFERER'])) { $ref = $_SERVER['HTTP_REFERER']; if (strpos($ref, 'sys.4chan.org') !== false || strpos($ref, '/thread/') !== false) { return true; } } return false; } function twister_captcha_need_hcaptcha() { if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) { return true; } $ua = $_SERVER['HTTP_USER_AGENT']; $score = (int)$_SERVER['HTTP_X_BOT_SCORE']; // Skip Android Webviews if ($score == 1 && strpos($ua, '; wv)') !== false) { return false; } return $score < 99; } function twister_captcha_check_likely_automated($memcached, $now, $threshold = 29) { if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) { return false; } $ua = $_SERVER['HTTP_USER_AGENT']; // Skip Android Webviews if (strpos($ua, '; wv)') !== false) { return false; } // Skip iPhone Webviews if (preg_match('/iPhone|iPad/', $ua) && !preg_match('/Mobile|Safari/', $ua)) { return false; } $score = (int)$_SERVER['HTTP_X_BOT_SCORE']; if ($score > 1 && $score <= $threshold) { $key = 'bmbot' . $_SERVER['REMOTE_ADDR']; $memcached->set($key, 1, $now + 43200); return true; } return false; } function twister_captcha_get_pcd_penalty($board, $thread_id) { $count = 0; return 0; // FIXME // Reports if ($thread_id === 1 && $board !== '!') { return 0; } if (isset($_SERVER['HTTP_X_GEO_ASN'])) { $asn = (int)$_SERVER['HTTP_X_GEO_ASN']; } else { $asn = 0; } if (isset($_SERVER['HTTP_X_GEO_COUNTRY'])) { $country = $_SERVER['HTTP_X_GEO_COUNTRY']; } else { $country = null; } // Mobile clients if ($asn > 0 && in_array($asn, TWISTER_MOBILE_ASNS)) { $count++; } else if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Mobile|iPhone|; wv/', $_SERVER['HTTP_USER_AGENT'])) { $count++; } // Rare countries if ($country && !in_array($country, TWISTER_WHITELIST_COUNTRIES)) { $count++; } // Rare ISPs if ($asn > 0 && !in_array($asn, TWISTER_WHITELIST_ASNS)) { $count++; } return $count * TWISTER_PRE_CD_PENALITY; } function twister_captcha_store_challenge($memcached, $challenge_uid, $long_ip, $expiration) { if (!$challenge_uid || !$long_ip || !$expiration) { return false; } return $memcached->set("ch$long_ip", $challenge_uid, $expiration); } // Cooldowns function twister_captcha_store_cooldown($memcached, $long_ip, $expiration) { if (!$long_ip || !$expiration) { return false; } return $memcached->set("cd$long_ip", $expiration, $expiration); } function twister_captcha_get_cooldown($memcached, $long_ip) { if (!$long_ip) { return false; } $val = $memcached->get("cd$long_ip"); if ($val === false && $memcached->getResultCode() === Memcached::RES_NOTFOUND) { return 0; } return $val; } function twister_captcha_generate_ticket($now, $long_ip, $board) { if (!$long_ip || !$now) { return false; } $hmac_secret = base64_decode(TWISTER_HMAC_SECRET); if (!$hmac_secret) { return false; } $hash = hash_hmac('sha256', "$now.$long_ip.$board", $hmac_secret); if (!$hash) { return false; } return "$now.$hash"; } function twister_captcha_decode_ticket($ticket, $long_ip, $board) { if (!$long_ip || !$ticket) { return false; } $hmac_secret = base64_decode(TWISTER_HMAC_SECRET); if (!$hmac_secret) { return false; } list($ts, $hash) = explode('.', $ticket); $ts = (int)$ts; if (!$ts || !$hash) { return false; } $this_hash = hash_hmac('sha256', "$ts.$long_ip.$board", $hmac_secret); if ($this_hash === $hash) { return $ts; } return false; } function twister_captcha_should_purge_ticket($ticket, $now) { if (!$ticket || !$now) { return false; } list($ts, $hash) = explode('.', $ticket); if (!$ts) { return false; } return $ts + TWISTER_TICKET_TTL <= $now; } function twister_captcha_store_session($memcached, $long_ip, $count, $expiration) { if (!$long_ip || !$expiration || $count < 1) { return false; } $key = "us$long_ip"; $res = $memcached->replace($key, $count, $expiration); if ($res === false) { if ($memcached->getResultCode() === Memcached::RES_NOTSTORED) { return $memcached->set($key, $count, $expiration); } else { return false; } } return true; } function twister_captcha_get_session($memcached, $long_ip) { if (!$long_ip) { return false; } $val = $memcached->get("us$long_ip"); if ($val === false) { if ($memcached->getResultCode() === Memcached::RES_NOTFOUND) { return 0; } else { return false; } } return $val; } function twister_captcha_get_credits($memcached, $pwd) { if (!$pwd) { return false; } $key = "cr-$pwd"; $val = $memcached->get($key); if ($val === false) { return false; } $val = explode('.', $val); $count = (int)$val[0]; $ts = (int)$val[1]; if ($count <= 0 || $ts <= 0) { $memcached->delete($key); return false; } return [ $count, $ts ]; } function twister_captcha_get_userpwd($user_ip) { if (isset($_COOKIE['4chan_pass'])) { $_c = $_COOKIE['4chan_pass']; } else { $_c = null; } return new UserPwd($user_ip, TWISTER_DOMAIN, $_COOKIE['4chan_pass']); } // --- // Dummy page for cloudflare challenges if (isset($_GET['opened'])) { twister_captcha_output_dummy(); die(); } // --- // Block TOR immediately if ($_SERVER['HTTP_X_GEO_CONTINENT'] === 'T1') { twister_captcha_error(TWISTER_ERR_GENERIC . ' (T1)'); } // Check parameters if (isset($_GET['board']) && preg_match('/^[!0-9a-z]{1,10}$/', $_GET['board'])) { $param_board = $_GET['board']; } else { $param_board = '!'; } if (isset($_GET['thread_id']) && $_GET['thread_id']) { $param_thread_id = (int)$_GET['thread_id']; } else { $param_thread_id = 0; } $now = $_SERVER['REQUEST_TIME']; $user_ip = $_SERVER['REMOTE_ADDR']; $user_long_ip = ip2long($user_ip); if (!isset($_SERVER['HTTP_USER_AGENT'])) { $user_agent = '!'; } else { $user_agent = md5($_SERVER['HTTP_USER_AGENT']); } $userpwd = twister_captcha_get_userpwd($user_ip); if (!$userpwd) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (GU1)'); } if (!$userpwd->isNew() && $param_board !== '!signin') { $password = $userpwd->getPwd(); } else { $password = '!'; } // --- $use_static = false; $difficulty = TwisterCaptcha::LEVEL_NORMAL; $char_count = TWISTER_CHARS_MAX; // --- $m = new Memcached(); // Only call the following once (when getServerList() is empty) if using persistent connections //$m->setOption(Memcached::OPT_TCP_NODELAY, true); $m->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 1); $m->setOption(Memcached::OPT_SEND_TIMEOUT, 500000); // 500ms $m->setOption(Memcached::OPT_RECV_TIMEOUT, 500000); // 500ms // Only use one server. Having multiple servers will break the captcha // as "set" is used instead of "replace + add" if ($m->addServer(TWISTER_MEMCACHED_HOST, TWISTER_MEMCACHED_PORT) === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (c0)'); } // --- // If CF's bot score is low, store the information for 1h and use it during posting twister_captcha_check_likely_automated($m, $now); /** * Pre-cooldowns */ $pcd = 0; // Posting a thread if ($param_thread_id === 0) { if ($userpwd->threadCount() < 1 || ($userpwd->ipChanged() && $userpwd->postCount() < 2)) { $pcd = TWISTER_PRE_CD_THREAD; } } // Posting a reply (reporting uses a thread id of 1) else if ($param_thread_id !== 1) { if ($userpwd->postCount() < 1 || ($userpwd->ipChanged() && $userpwd->postCount() < 2)) { $pcd = TWISTER_PRE_CD_REPLY; } } // Reporting a post else if ($param_board !== '!') { if ($userpwd->reportCount() < 1 && $userpwd->postCount() < 1) { $pcd = TWISTER_PRE_CD_REPORT; } } if ($param_thread_id != 1 && !$userpwd->verifiedLevel()) { // Initial cooldown, bypassable by verifying your email if ($userpwd->postCount() < 1 || !$userpwd->isUserKnown(15)) { $pcd = 900; } // Cooldown for when the user is still new and the mask changes else if ($userpwd->postCount() > 0 && $userpwd->pwdLifetime() < 86400 && $userpwd->maskChanged()) { // 24h $pcd = 180; } } // Pre-cooldown needed if ($pcd > 0 && $param_board !== '!signin') { // Extra pre-cooldown for unverified users if (!$userpwd->verifiedLevel()) { $pcd += twister_captcha_get_pcd_penalty($param_board, $param_thread_id); } $ticket_ts = twister_captcha_decode_ticket($_GET['ticket'], $user_long_ip, $param_board); $bypassable = false; if ($param_thread_id === 0) { $pcd_msg = TWISTER_ERR_PCD_THREAD; } else if ($param_thread_id !== 1 && $param_board !== '!') { $pcd_msg = TWISTER_ERR_PCD_REPLY; } if ($param_thread_id !== 1 && $pcd >= 900) { $pcd_msg = TWISTER_ERR_PCD_SIGNIN; $bypassable = true; } if (!$ticket_ts) { //if (TWISTER_USE_TICKET_CAPTCHA && !in_array((int)$_SERVER['HTTP_X_GEO_ASN'], TWISTER_WHITELIST_ASNS)) { if (TWISTER_USE_TICKET_CAPTCHA && twister_captcha_need_hcaptcha()) { twister_captcha_process_ticket_captcha($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable); } else { twister_captcha_output_new_ticket($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable); } die(); } $ticket_lifetime = $now - $ticket_ts; if ($ticket_lifetime < $pcd) { $pcd = $pcd - $ticket_lifetime; twister_captcha_output_ticket_pcd(null, $pcd, $pcd_msg, $bypassable); die(); } // Ticket expired if ($ticket_lifetime >= TWISTER_TICKET_TTL) { if (TWISTER_USE_TICKET_CAPTCHA && twister_captcha_need_hcaptcha()) { twister_captcha_process_ticket_captcha($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable); } else { twister_captcha_output_new_ticket($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable); } die(); } } /** * Adjust difficulty */ $ip_ttl_static = TWISTER_PWD_TTL_IP_STATIC; $mask_ttl_static = TWISTER_PWD_TTL_MASK_STATIC; $ip_ttl_min = TWISTER_PWD_TTL_IP_MIN; $idle_ttl = TWISTER_PWD_TTL_IDLE; // Serve max len twister to bad actors $bad_actor = false; if (!isset($_SERVER['HTTP_USER_AGENT']) || !$_SERVER['HTTP_USER_AGENT']) { $bad_actor = true; } else if (preg_match(TWISTER_BAD_UA, $_SERVER['HTTP_USER_AGENT'])) { $bad_actor = true; } else if (twister_captcha_is_req_suspicious()) { $bad_actor = true; } // Serve static captcha to known users. // Serve max length captchas for unknown users. // Only applies to replies. Theads always use slider captchas. if ($param_thread_id !== 0 && !$bad_actor) { // Known and post count check if ($userpwd->isUserKnown(TWISTER_PWD_STATIC_KNOWN) && $userpwd->postCount() >= TWISTER_PWD_STATIC_POSTS) { // Inactivity and IP change checks for unverified users if ($userpwd->verifiedLevel()) { $use_static = true; $char_count = TWISTER_CHARS_MIN; } else if ($userpwd->idleLifetime() <= TWISTER_PWD_SLIDER_IDLE && !$userpwd->ipChanged()) { $use_static = true; $char_count = TWISTER_CHARS; } } } // Check captcha bypassing credits if (TWISTER_ALLOW_NOOP && !in_array($param_board, TWISTER_NO_NOOP_BOARDS) && !$bad_actor && $param_thread_id !== 0) { if ($userpwd->isUserKnown(TWISTER_PWD_NOOP_KNOWN) && $userpwd->postCount() >= TWISTER_PWD_NOOP_POSTS) { $credits = twister_captcha_get_credits($m, $userpwd->getPwd()); if ($credits !== false && $credits[0] > 0) { $data = [ 'challenge' => 'noop', 'ttl' => min(TWISTER_TTL, $credits[1] - $now), 'cd' => TWISTER_COOLDOWN ]; twister_captcha_output_data($data); die(); } } } // Check cooldown $should_cd_until = twister_captcha_get_cooldown($m, $user_long_ip); if ($should_cd_until === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (scdu)'); } if ($should_cd_until > 1) { twister_captcha_error(TWISTER_ERR_COOLDOWN, ['cd' => $should_cd_until - $now]); } // Number of unsolved captchas requested recently $unsolved_count = twister_captcha_get_session($m, $user_long_ip); if ($unsolved_count === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (gus1)'); } if ($unsolved_count > 2) { $cooldown = TWISTER_COOLDOWN_LONG * min($unsolved_count, 20); if ($unsolved_count > 10) { $cooldown = 300; } } else { $cooldown = TWISTER_COOLDOWN; } if (twister_captcha_store_cooldown($m, $user_long_ip, $now + $cooldown) === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (sc0)'); } // Generate images $c = new TwisterCaptcha(TWISTER_FONT_PATH); $c->setDifficulty($difficulty); // Adjust features // User is suspicious $should_harden = $bad_actor && ($userpwd->ipChanged() || !$userpwd->isUserKnownOrVerified(7200) || mt_rand(0, 9) === 9); $should_fog = false; if ($should_harden) { $use_static = false; if (mt_rand(0, 1)) { $c->useInkBlot(true); if (mt_rand(0, 1)) { $c->useNegateBlotFilter(true); } } else { $c->useEdgeBlock(true); } $c->useFakeCharPadding(true); $c->useJumpyMode(true); if (mt_rand(0, 9) === 9) { $c->setDifficulty(TwisterCaptcha::LEVEL_LUNATIC); } else { $c->setDifficulty(TwisterCaptcha::LEVEL_HARD); } if (mt_rand(0, 9) === 9) { $char_count = 8; } if (mt_rand(0, 1)) { $c->useGridLines(true); } else { $c->useScoreLines(true); } } // Other new users else if ($userpwd->postCount() < 5 || $userpwd->maskLifetime() < 3600) { if (!$use_static) { $_boards = [ 'a', 'v', 'vg', 'co', 'vp', 'g', 'biz', 'b', 'vt', 'mu', 'pol', 'tv', 'sp', 'int', 'soc', 'test' ]; if (true || in_array($param_board, $_boards) || $param_thread_id == 0) { $should_fog = true; } else { $c->useInkBlot(true); $c->useScoreLines(true); } } else { $c->useInkBlot(true); //$c->useFakeCharPadding(true); if (mt_rand(0, 1)) { $c->useJumpyMode(true); } } $char_count = mt_rand(TWISTER_CHARS, TWISTER_CHARS_MAX); } if ($param_board === '!signin') { $should_fog = true; $use_static = false; } if ($use_static) { if (mt_rand(0, 9) === 9) { $c->useScoreLines(true); } list($challenge_str, $img, $img_width, $img_height) = $c->generateStatic($char_count); $img_bg = null; } else { if ($should_fog) { if (false && $param_board == 'co' && $param_thread_id == 0) { $c->useEdgeDetect(true); $c->useSpecialRot(true); //$c->useOverlayId(5, true); $c->useInvert(true); //$c->useAltBlackWhite(true); $c->useGridLines(true); $c->useSimplexBg(true); //$c->useEmboss(true); //list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew($char_count); list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(7, 28, 28); //list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterV(3); } else { $_olid = mt_rand(0, 6); if ($_olid) { $c->useOverlayId($_olid, (bool)mt_rand(0, 1)); } if (mt_rand(0, 1)) { $c->useInvert(true); } if (mt_rand(0, 3) === 3) { $c->useFakeCharPadding(true); } if ($param_thread_id == 0) { $_olid = 2; } else { $_olid = mt_rand(1, 2); } $_olid = 2; // Simplex if ($_olid === 1) { if (mt_rand(0, 3) === 3) { $c->useScoreLines(true); } if (mt_rand(0, 1)) { $c->useInvert(true); } list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHSimplex($char_count); } // Fog New else if ($_olid === 2) { if (mt_rand(0, 1)) { $c->useSimplexBg(true); } if (mt_rand(0, 1)) { $c->useEmboss(true); } else { $c->useEdgeDetect(true); } if (mt_rand(0, 1)) { $c->useAltBlackWhite(true); } if (mt_rand(0, 1)) { $c->useInvert(true); } //if (mt_rand(0, 3) === 3) { //$c->useSpecialRot(true); //$char_count = 5; //} if (isset($_SERVER['HTTP_X_BOT_SCORE'])) { $_bot_score = (int)$_SERVER['HTTP_X_BOT_SCORE']; } else { $_bot_score = 100; } if ($param_board === '!signin') { if ($_bot_score > 95) { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(5, 30, 50); } else { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateSimpleTask(2); } } else { //$c->useSpecialRot(true); if ($_bot_score <= 80 && mt_rand(0, 1) === 0) { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateSimpleTask(2); } else { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(5, 30, 50); } } } // Default else { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew($char_count); } } } else { list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterH($char_count); } } if (!$challenge_str) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (ncs1)'); } list($challenge_uid, $challenge_hash) = TwisterCaptcha::getChallengeHash( $challenge_str, [$user_ip, $password, $user_agent, $param_board, $param_thread_id] ); if (!$challenge_uid || !$challenge_hash) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (gch1)'); } // Register challenge if (twister_captcha_store_challenge($m, $challenge_uid, $user_long_ip, $now + TWISTER_TTL) === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (sch1)'); } // Register unsolved session if (twister_captcha_store_session($m, $user_long_ip, $unsolved_count + 1, $now + TWISTER_TTL_UNSOLVED) === false) { twister_captcha_error(TWISTER_ERR_GENERIC . ' (sus1)'); } // Generate base 64 urls of images list($img_b64, $img_bg_b64) = TwisterCaptcha::getBase64Images($img, $img_bg); $data = [ 'challenge' => "$challenge_uid.$challenge_hash", 'ttl' => TWISTER_TTL, 'cd' => $cooldown, 'img' => $img_b64, 'img_width' => $img_width, 'img_height' => $img_height ]; if ($img_bg) { $data['bg'] = $img_bg_b64; $data['bg_width'] = $bg_width; } if (twister_captcha_should_purge_ticket($_GET['ticket'], $now)) { $data['ticket'] = false; } twister_captcha_output_data($data);