From 461be11e8d0f5d4bd879cf95bd1472dbe985e0b6 Mon Sep 17 00:00:00 2001 From: Henry Sudhof Date: Fri, 19 Sep 2008 13:17:30 +0000 Subject: [PATCH] Okay, that is pretty raw, but better to have it in place than trying to play catch-up. Introducing an early stage of CAPTCHA modules. git-svn-id: file:///svn/phpbb/trunk@8889 89ea8834-ac86-4346-8a33-228a782c2dd0 --- phpBB/adm/style/acp_captcha.html | 53 ++- phpBB/adm/style/captcha_default_acp_demo.html | 5 + phpBB/adm/style/captcha_gd_acp.html | 53 +++ phpBB/includes/acp/acp_captcha.php | 177 +++++----- phpBB/includes/auth/auth_db.php | 41 +-- phpBB/includes/captcha/captcha_factory.php | 90 ++++++ phpBB/includes/captcha/captcha_non_gd.php | 6 +- phpBB/includes/captcha/captcha_plugin.php | 97 ++++++ .../captcha/plugins/captcha_abstract.php | 301 ++++++++++++++++++ .../plugins/phpbb_captcha_gd_plugin.php | 101 ++++++ .../plugins/phpbb_captcha_nogd_plugin.php | 58 ++++ phpBB/includes/functions.php | 30 +- phpBB/includes/session.php | 38 +-- phpBB/includes/ucp/ucp_confirm.php | 43 +-- phpBB/includes/ucp/ucp_register.php | 139 +++----- phpBB/install/install_install.php | 4 +- phpBB/install/schemas/schema_data.sql | 2 +- phpBB/language/en/acp/board.php | 16 +- phpBB/posting.php | 62 +--- .../prosilver/template/captcha_default.html | 8 + .../styles/prosilver/template/login_body.html | 10 +- .../prosilver/template/posting_editor.html | 6 +- .../prosilver/template/ucp_register.html | 12 +- 23 files changed, 937 insertions(+), 415 deletions(-) create mode 100755 phpBB/adm/style/captcha_default_acp_demo.html create mode 100755 phpBB/adm/style/captcha_gd_acp.html create mode 100755 phpBB/includes/captcha/captcha_factory.php create mode 100755 phpBB/includes/captcha/captcha_plugin.php create mode 100755 phpBB/includes/captcha/plugins/captcha_abstract.php create mode 100755 phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php create mode 100755 phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php create mode 100755 phpBB/styles/prosilver/template/captcha_default.html diff --git a/phpBB/adm/style/acp_captcha.html b/phpBB/adm/style/acp_captcha.html index d9d087d6ba..5d04938778 100644 --- a/phpBB/adm/style/acp_captcha.html +++ b/phpBB/adm/style/acp_captcha.html @@ -22,47 +22,34 @@
- -
-

{L_CAPTCHA_GD_EXPLAIN}
-
-
-
-
-

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
-
-
-
-
-

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
-
-
-
-

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
-
-
- + +
+{L_AVAILABLE_CAPTCHAS} + +
+

{L_CAPTCHA_SELECT_EXPLAIN}
+
+
+
+

{L_CAPTCHA_CONFIGURE_EXPLAIN}
+
+
+ +
+ +
{L_PREVIEW} - -
-

{L_WARNING}

-

{L_CAPTCHA_PREVIEW_MSG}

-
- -
-

{L_CAPTCHA_PREVIEW_EXPLAIN}
-
{L_PREVIEW}width="360" height="96" width="320" height="50" id="captcha_preview" />
-
+{CAPTCHA_PREVIEW}
+
{L_SUBMIT} -   -   - +   +   {S_FORM_TOKEN}
diff --git a/phpBB/adm/style/captcha_default_acp_demo.html b/phpBB/adm/style/captcha_default_acp_demo.html new file mode 100755 index 0000000000..a714386e51 --- /dev/null +++ b/phpBB/adm/style/captcha_default_acp_demo.html @@ -0,0 +1,5 @@ + +
+

{L_CAPTCHA_PREVIEW_EXPLAIN}
+
{L_PREVIEW}
+
\ No newline at end of file diff --git a/phpBB/adm/style/captcha_gd_acp.html b/phpBB/adm/style/captcha_gd_acp.html new file mode 100755 index 0000000000..ff1500e7d0 --- /dev/null +++ b/phpBB/adm/style/captcha_gd_acp.html @@ -0,0 +1,53 @@ + + + + +

{L_ACP_VC_SETTINGS}

+ +

{L_ACP_VC_SETTINGS_EXPLAIN}

+ + +
+ +
+{L_GENERAL_OPTIONS} + +
+

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
+
+
+
+
+

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
+
+
+
+

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
+
+
+ + +
+
+ {L_PREVIEW} + +
+

{L_WARNING}

+

{L_CAPTCHA_PREVIEW_MSG}

+
+ +{CAPTCHA_PREVIEW} +
+ +
+ {L_SUBMIT} +   +   + + + + {S_FORM_TOKEN} +
+
+ + diff --git a/phpBB/includes/acp/acp_captcha.php b/phpBB/includes/acp/acp_captcha.php index e61b8c2170..57de03c4c9 100644 --- a/phpBB/includes/acp/acp_captcha.php +++ b/phpBB/includes/acp/acp_captcha.php @@ -10,6 +10,8 @@ /** * @ignore */ + + if (!defined('IN_PHPBB')) { exit; @@ -28,94 +30,115 @@ class acp_captcha $user->add_lang('acp/board'); + include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_factory.' . PHP_EXT); - $captcha_vars = array( - 'captcha_gd_x_grid' => 'CAPTCHA_GD_X_GRID', - 'captcha_gd_y_grid' => 'CAPTCHA_GD_Y_GRID', - 'captcha_gd_foreground_noise' => 'CAPTCHA_GD_FOREGROUND_NOISE', - 'captcha_gd' => 'CAPTCHA_GD_PREVIEWED' - ); - - if (isset($_GET['demo'])) + $selected = request_var('select_captcha', $config['captcha_plugin']); + $configure = request_var('configure', false); + + // Oh, they are just here for the view + if (isset($_GET['captcha_demo'])) { - $captcha_vars = array_keys($captcha_vars); - foreach ($captcha_vars as $captcha_var) - { - $config[$captcha_var] = (isset($_REQUEST[$captcha_var])) ? request_var($captcha_var, 0) : $config[$captcha_var]; - } - - if ($config['captcha_gd']) - { - include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_gd.' . PHP_EXT); - } - else - { - include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_non_gd.' . PHP_EXT); - } - captcha::execute(gen_rand_string(mt_rand(5, 8)), time()); - exit; + $this->deliver_demo($selected); } - - $config_vars = array( - 'enable_confirm' => 'REG_ENABLE', - 'enable_post_confirm' => 'POST_ENABLE', - 'captcha_gd' => 'CAPTCHA_GD', - ); - - $this->tpl_name = 'acp_captcha'; - $this->page_title = 'ACP_VC_SETTINGS'; - $form_key = 'acp_captcha'; - add_form_key($form_key); - - $submit = request_var('submit', ''); - - if ($submit && check_form_key($form_key)) + + // Delegate + if ($configure) { - $config_vars = array_keys($config_vars); - foreach ($config_vars as $config_var) - { - set_config($config_var, request_var($config_var, '')); - } - $captcha_vars = array_keys($captcha_vars); - foreach ($captcha_vars as $captcha_var) - { - $value = request_var($captcha_var, 0); - if ($value >= 0) - { - set_config($captcha_var, $value); - } - } - trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); - } - else if ($submit) - { - trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action)); + $config_captcha = phpbb_captcha_factory::get_instance($selected); + $config_captcha->acp_page($id, $this); } else { + $captchas = phpbb_captcha_factory::get_captcha_types(); - $preview_image_src = append_sid(append_sid(PHPBB_ADMIN_PATH . 'index.' . PHP_EXT, "i=$id&demo=demo")); - if (@extension_loaded('gd')) - { - $template->assign_var('GD', true); - } - foreach ($config_vars as $config_var => $template_var) - { - $template->assign_var($template_var, (isset($_REQUEST[$config_var])) ? request_var($config_var, '') : $config[$config_var]) ; - } - foreach ($captcha_vars as $captcha_var => $template_var) - { - $var = (isset($_REQUEST[$captcha_var])) ? request_var($captcha_var, 0) : $config[$captcha_var]; - $template->assign_var($template_var, $var); - $preview_image_src .= "&$captcha_var=" . $var; - } - $template->assign_vars(array( - 'CAPTCHA_PREVIEW' => $preview_image_src, - 'PREVIEW' => isset($_POST['preview']), - )); + $config_vars = array( + 'enable_confirm' => 'REG_ENABLE', + 'enable_post_confirm' => 'POST_ENABLE', + ); + $this->tpl_name = 'acp_captcha'; + $this->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = request_var('main_submit', false); + + if ($submit && check_form_key($form_key)) + { + $config_vars = array_keys($config_vars); + foreach ($config_vars as $config_var) + { + set_config($config_var, request_var($config_var, false)); + } + if ($selected !== $config['captcha_plugin']) + { + // sanity check + if (isset($captchas['available'][$selected])) + { + $old_captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $old_captcha->uninstall(); + set_config('captcha_plugin', $selected); + $new_captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $old_captcha->install(); + } + else + { + trigger_error($user->lang['CAPTCHA_UNAVAILABLE'] . adm_back_link($this->u_action)); + } + } + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + } + else if ($submit) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link()); + } + else + { + $captcha_select = ''; + foreach ($captchas['available'] as $value => $title) + { + $current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; + $captcha_select .= ''; + } + foreach ($captchas['unavailable'] as $value => $title) + { + $captcha_select .= ''; + } + + $demo_captcha = phpbb_captcha_factory::get_instance($selected); + + foreach ($config_vars as $config_var => $template_var) + { + $template->assign_var($template_var, (isset($_REQUEST[$config_var])) ? request_var($config_var, '') : $config[$config_var]) ; + } + + $template->assign_vars(array( + 'CAPTCHA_PREVIEW' => $demo_captcha->get_demo_template($id), + 'CAPTCHA_SELECT' => $captcha_select, + )); + } + } } + + + /** + * Entry point for delivering image CAPTCHAs in the ACP. + */ + function deliver_demo($selected) + { + global $db, $user, $config; + + $captcha = phpbb_captcha_factory::get_instance($selected); + $captcha->init(CONFIRM_REG); + $captcha->execute_demo(); + garbage_collection(); + exit_handler(); + } + + + + } ?> \ No newline at end of file diff --git a/phpBB/includes/auth/auth_db.php b/phpBB/includes/auth/auth_db.php index fa99a2fe4c..402f22f827 100644 --- a/phpBB/includes/auth/auth_db.php +++ b/phpBB/includes/auth/auth_db.php @@ -81,42 +81,15 @@ function login_db(&$username, &$password) } else { - global $user; - - $sql = 'SELECT code - FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_LOGIN; - $result = $db->sql_query($sql); - $confirm_row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ($confirm_row) - { - if (strcasecmp($confirm_row['code'], $confirm_code) === 0) - { - $sql = 'DELETE FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_LOGIN; - $db->sql_query($sql); - } - else - { - return array( - 'status' => LOGIN_ERROR_ATTEMPTS, - 'error_msg' => 'CONFIRM_CODE_WRONG', - 'user_row' => $row, - ); - } - } - else + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + $vc_response = $captcha->validate(); + if ($vc_response) { return array( - 'status' => LOGIN_ERROR_ATTEMPTS, - 'error_msg' => 'CONFIRM_CODE_WRONG', - 'user_row' => $row, + 'status' => LOGIN_ERROR_ATTEMPTS, + 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', + 'user_row' => $row, ); } } diff --git a/phpBB/includes/captcha/captcha_factory.php b/phpBB/includes/captcha/captcha_factory.php new file mode 100755 index 0000000000..dfbe605917 --- /dev/null +++ b/phpBB/includes/captcha/captcha_factory.php @@ -0,0 +1,90 @@ +width; $j++) + for ($j = 0; $j < self::width; $j++) { $image .= chr(mt_rand(140, 255)); } @@ -93,7 +93,7 @@ class captcha } unset($hold_chars); - $image = self::create_png($image, $this->width, $this->height); + $image = self::create_png($image, self::width, self::height); // Output image header('Content-Type: image/png'); @@ -149,7 +149,7 @@ class captcha * png because it's a fully recognised open standard and supported * by practically all modern browsers and OSs */ - function create_png($raw_image, $width, $height) + static function create_png($raw_image, $width, $height) { // SIG $image = pack('C8', 137, 80, 78, 71, 13, 10, 26, 10); diff --git a/phpBB/includes/captcha/captcha_plugin.php b/phpBB/includes/captcha/captcha_plugin.php new file mode 100755 index 0000000000..08a149764c --- /dev/null +++ b/phpBB/includes/captcha/captcha_plugin.php @@ -0,0 +1,97 @@ +confirm_id = request_var('confirm_id', ''); + $this->confirm_code = request_var('confirm_code', ''); + $this->type = (int) $type; + + if (!strlen($this->confirm_id)) + { + // we have no confirm ID, better get ready to display something + $this->generate_code(); + } + } + + function execute_demo() + { + global $user; + + $this->code = gen_rand_string(mt_rand(5, 8)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + captcha::execute($this->code, $this->seed); + } + + + function execute() + { + if (empty($this->code)) + { + if (!$this->load_code()) + { + // invalid request, bail out + return false; + } + } + captcha::execute($this->code, $this->seed); + } + + + function get_template() + { + global $config, $user, $template; + + $template->set_filenames(array( + 'captcha' => 'captcha_default.html') + ); + + $template->assign_vars(array( + 'CONFIRM_IMAGE' => append_sid('ucp', 'mode=confirm&confirm_id=' . $this->confirm_id . '&type=' . $this->type), + 'CONFIRM_ID' => $this->confirm_id, + )); + + return $template->assign_display('captcha'); + } + + function get_demo_template($id) + { + global $config, $user, $template; + + $template->set_filenames(array( + 'captcha_demo' => 'captcha_default_acp_demo.html') + ); + // acp_captcha has a delivery function; let's use it + $template->assign_vars(array( + 'CONFIRM_IMAGE' => append_sid(PHPBB_ADMIN_PATH . 'index.' . PHP_EXT, 'captcha_demo=1&mode=visual&i=' . $id . '&select_captcha=' . $this->get_class_name()), + 'CONFIRM_ID' => $this->confirm_id, + )); + + return $template->assign_display('captcha_demo'); + } + + function get_hidden_fields() + { + $hidden_fields = array(); + + // this is required for postig.php - otherwise we would forget about the captcha being already solved + if ($this->solved) + { + $hidden_fields['confirm_code'] = $this->confirm_code; + } + $hidden_fields['confirm_id'] = $this->confirm_id; + return $hidden_fields; + } + + static function garbage_collect($type) + { + global $db, $config; + + $sql = 'SELECT DISTINCT c.session_id + FROM ' . CONFIRM_TABLE . ' c + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id) + WHERE s.session_id IS NULL' . + ((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type); + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $sql_in = array(); + do + { + $sql_in[] = (string) $row['session_id']; + } + while ($row = $db->sql_fetchrow($result)); + + if (sizeof($sql_in)) + { + $sql = 'DELETE FROM ' . CONFIRM_TABLE . ' + WHERE ' . $db->sql_in_set('session_id', $sql_in); + $db->sql_query($sql); + } + } + $db->sql_freeresult($result); + } + + function uninstall() + { + self::garbage_collect(0); + } + + function install() + { + return; + } + + function validate() + { + global $config, $db, $user; + + $this->confirm_code = request_var('confirm_code', ''); + + if (!$this->confirm_id) + { + $error = $user->lang['CONFIRM_CODE_WRONG']; + } + else + { + if ($this->check_code()) + { + // $this->delete_code(); commented out to allow posting.php to repeat the question + $this->solved = true; + } + else + { + $error = $user->lang['CONFIRM_CODE_WRONG']; + } + } + + if (strlen($error)) + { + // okay, inorect answer. Let's ask a new question + $this->reset(); + return $error; + } + else + { + return false; + } + } + + + /** + * The old way to generate code, suitable for GD and non-GD. Resets the internal state. + */ + protected function generate_code() + { + global $db, $user; + + $this->code = gen_rand_string(mt_rand(5, 8)); + $this->confirm_id = md5(unique_id($user->ip)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + $this->solved = false; + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'confirm_id' => (string) $this->confirm_id, + 'session_id' => (string) $user->session_id, + 'confirm_type' => (int) $this->type, + 'code' => (string) $this->code, + 'seed' => (int) $this->seed) + ); + $db->sql_query($sql); + } + + /** + * Look up everything we need for painting&checking. + */ + protected function load_code() + { + global $db, $user; + $sql = 'SELECT code, seed + FROM ' . CONFIRM_TABLE . " + WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . $this->type; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + if ($row) + { + $this->code = $row['code']; + $this->seed = $row['seed']; + return true; + } + return false; + + } + + protected function check_code() + { + global $db; + + if (empty($this->code)) + { + if (!$this->load_code()) + { + return false; + } + } + return (strcasecmp($this->code, $this->confirm_code) === 0); + } + + protected function delete_code() + { + global $db, $user; + + $sql = 'DELETE FROM ' . CONFIRM_TABLE . " + WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . $this->type; + $db->sql_query($sql); + } + + function get_attempt_count() + { + global $db, $user; + + $sql = 'SELECT COUNT(session_id) AS attempts + FROM ' . CONFIRM_TABLE . " + WHERE session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . $this->type; + $result = $db->sql_query($sql); + $attempts = (int) $db->sql_fetchfield('attempts'); + $db->sql_freeresult($result); + + return $attempts; + } + + + function reset() + { + global $db, $user; + + $sql = 'DELETE FROM ' . CONFIRM_TABLE . " + WHERE session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . (int) $this->type; + $db->sql_query($sql); + + // we leave the class usable by generating a new question + $this->generate_code(); + } + +} + diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php new file mode 100755 index 0000000000..e4f0bff87e --- /dev/null +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_gd_plugin.php @@ -0,0 +1,101 @@ + 'CAPTCHA_GD_X_GRID', + 'captcha_gd_y_grid' => 'CAPTCHA_GD_Y_GRID', + 'captcha_gd_foreground_noise' => 'CAPTCHA_GD_FOREGROUND_NOISE', + 'captcha_gd' => 'CAPTCHA_GD_PREVIEWED' + ); + + $module->tpl_name = 'captcha_gd_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = request_var('submit', ''); + + if ($submit && check_form_key($form_key)) + { + $captcha_vars = array_keys($captcha_vars); + foreach ($captcha_vars as $captcha_var) + { + $value = request_var($captcha_var, 0); + if ($value >= 0) + { + set_config($captcha_var, $value); + } + } + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action)); + } + else if ($submit) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($module->u_action)); + } + else + { + foreach ($captcha_vars as $captcha_var => $template_var) + { + $var = (isset($_REQUEST[$captcha_var])) ? request_var($captcha_var, 0) : $config[$captcha_var]; + $template->assign_var($template_var, $var); + } + $template->assign_vars(array( + 'CAPTCHA_PREVIEW' => $this->get_demo_template($id), + 'CAPTCHA_NAME' => $this->get_class_name(), + )); + + } + } +} + diff --git a/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php b/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php new file mode 100755 index 0000000000..a2521a330e --- /dev/null +++ b/phpBB/includes/captcha/plugins/phpbb_captcha_nogd_plugin.php @@ -0,0 +1,58 @@ +lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action)); + } +} + diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 8fef702ab1..734a9716c8 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -2365,6 +2365,7 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa { global $db, $user, $template, $auth, $config; + include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_factory.' . PHP_EXT); $err = ''; @@ -2483,34 +2484,13 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa { case LOGIN_ERROR_ATTEMPTS: - // Show confirm image - $sql = 'DELETE FROM ' . CONFIRM_TABLE . " - WHERE session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_LOGIN; - $db->sql_query($sql); - - // Generate code - $code = gen_rand_string(mt_rand(5, 8)); - $confirm_id = md5(unique_id($user->ip)); - $seed = hexdec(substr(unique_id(), 4, 10)); - - // compute $seed % 0x7fffffff - $seed -= 0x7fffffff * floor($seed / 0x7fffffff); - - $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array( - 'confirm_id' => (string) $confirm_id, - 'session_id' => (string) $user->session_id, - 'confirm_type' => (int) CONFIRM_LOGIN, - 'code' => (string) $code, - 'seed' => (int) $seed) - ); - $db->sql_query($sql); + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + $captcha->reset(); $template->assign_vars(array( 'S_CONFIRM_CODE' => true, - 'CONFIRM_ID' => $confirm_id, - 'CONFIRM_IMAGE' => '', - 'L_LOGIN_CONFIRM_EXPLAIN' => sprintf($user->lang['LOGIN_CONFIRM_EXPLAIN'], '', ''), + 'CONFIRM' => $captcha->get_template(''), )); $err = $user->lang[$result['error_msg']]; diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index b5f2a9b6a1..450f044414 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -923,42 +923,18 @@ class session WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time'])); $db->sql_query($sql); } - $this->confirm_gc(); + + // only called from CRON; should be a safe workaround until the infrastructure gets going + if (!class_exists('captcha_factory')) + { + include(PHPBB_ROOT_PATH . "includes/captcha/captcha_factory." . PHP_EXT); + } + captcha_factory::garbage_collect($config['captcha_plugin']); } return; } - function confirm_gc($type = 0) - { - global $db, $config; - - $sql = 'SELECT DISTINCT c.session_id - FROM ' . CONFIRM_TABLE . ' c - LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id) - WHERE s.session_id IS NULL' . - ((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type); - $result = $db->sql_query($sql); - - if ($row = $db->sql_fetchrow($result)) - { - $sql_in = array(); - do - { - $sql_in[] = (string) $row['session_id']; - } - while ($row = $db->sql_fetchrow($result)); - - if (sizeof($sql_in)) - { - $sql = 'DELETE FROM ' . CONFIRM_TABLE . ' - WHERE ' . $db->sql_in_set('session_id', $sql_in); - $db->sql_query($sql); - } - } - $db->sql_freeresult($result); - } - /** * Sets a cookie diff --git a/phpBB/includes/ucp/ucp_confirm.php b/phpBB/includes/ucp/ucp_confirm.php index 94fb9a729f..26ffc3c5fb 100644 --- a/phpBB/includes/ucp/ucp_confirm.php +++ b/phpBB/includes/ucp/ucp_confirm.php @@ -3,7 +3,7 @@ * * @package VC * @version $Id$ -* @copyright (c) 2005 phpBB Group +* @copyright (c) 2005 2008 phpBB Group * @license http://opensource.org/licenses/gpl-license.php GNU Public License * */ @@ -36,43 +36,10 @@ class ucp_confirm function main($id, $mode) { global $db, $user, $config; - - // Do we have an id? No, then just exit - $confirm_id = request_var('id', ''); - $type = request_var('type', 0); - - if (!$confirm_id || !$type) - { - exit; - } - - // Try and grab code for this id and session - $sql = 'SELECT code, seed - FROM ' . CONFIRM_TABLE . " - WHERE session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND confirm_type = $type"; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - // If we have a row then grab data else create a new id - if (!$row) - { - exit; - } - - if ($config['captcha_gd']) - { - include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_gd.' . PHP_EXT); - } - else - { - include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_non_gd.' . PHP_EXT); - } - - captcha::execute($row['code'], $row['seed']); - + include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_factory.' . PHP_EXT); + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(request_var('type', 0)); + $captcha->execute(); garbage_collection(); exit_handler(); } diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 0246e96061..c0ebd5f2c5 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -37,7 +37,6 @@ class ucp_register include(PHPBB_ROOT_PATH . 'includes/functions_profile_fields.' . PHP_EXT); - $confirm_id = request_var('confirm_id', ''); $coppa = (isset($_REQUEST['coppa'])) ? ((!empty($_REQUEST['coppa'])) ? 1 : 0) : false; $agreed = (!empty($_POST['agreed'])) ? 1 : 0; $submit = (isset($_POST['submit'])) ? true : false; @@ -53,7 +52,14 @@ class ucp_register add_form_key('ucp_register_terms'); } - + + if ($config['enable_confirm']) + { + include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_factory.' . PHP_EXT); + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_REG); + } + if ($change_lang || $user_lang != $config['default_lang']) { $use_lang = ($change_lang) ? basename($change_lang) : basename($user_lang); @@ -88,8 +94,8 @@ class ucp_register { $add_lang = ($change_lang) ? '&change_lang=' . urlencode($change_lang) : ''; $add_coppa = ($coppa !== false) ? '&coppa=' . $coppa : ''; - - $s_hidden_fields = ($confirm_id) ? array('confirm_id' => $confirm_id) : array(); + + $s_hidden_fields = array(); // If we change the language, we want to pass on some more possible parameter. if ($change_lang) @@ -99,11 +105,14 @@ class ucp_register 'username' => utf8_normalize_nfc(request_var('username', '', true)), 'email' => strtolower(request_var('email', '')), 'email_confirm' => strtolower(request_var('email_confirm', '')), - 'confirm_code' => request_var('confirm_code', ''), - 'confirm_id' => request_var('confirm_id', ''), 'lang' => $user->lang_name, 'tz' => request_var('tz', (float) $config['board_timezone']), )); + + if ($config['enable_confirm']) + { + $s_hidden_fields = array_merge($s_hidden_fields, $captcha->get_hidden_fields()); + } } if ($coppa === false && $config['coppa_enable']) @@ -167,7 +176,6 @@ class ucp_register 'password_confirm' => request_var('password_confirm', '', true), 'email' => strtolower(request_var('email', '')), 'email_confirm' => strtolower(request_var('email_confirm', '')), - 'confirm_code' => request_var('confirm_code', ''), 'lang' => basename(request_var('lang', $user->lang_name)), 'tz' => request_var('tz', (float) $timezone), ); @@ -187,7 +195,6 @@ class ucp_register array('string', false, 6, 60), array('email')), 'email_confirm' => array('string', false, 6, 60), - 'confirm_code' => array('string', !$config['enable_confirm'], 5, 8), 'tz' => array('num', false, -14, 14), 'lang' => array('match', false, '#^[a-z_\-]{2,}$#i'), )); @@ -198,6 +205,22 @@ class ucp_register // Replace "error" strings with their real, localised form $error = preg_replace('#^([A-Z_]+)$#e', "(!empty(\$user->lang['\\1'])) ? \$user->lang['\\1'] : '\\1'", $error); + if ($config['enable_confirm']) + { + $vc_response = $captcha->validate(); + if ($vc_response) + { + $error[] = $vc_response; + } + else + { + $captcha->reset(); + } + if ($config['max_reg_attempts'] && $captcha->get_attempt_count() > $config['max_reg_attempts']) + { + $error[] = $user->lang['TOO_MANY_REGISTERS']; + } + } // DNSBL check if ($config['check_dnsbl']) { @@ -210,50 +233,6 @@ class ucp_register // validate custom profile fields $cp->submit_cp_field('register', $user->get_iso_lang_id(), $cp_data, $error); - // Visual Confirmation handling - $wrong_confirm = false; - if ($config['enable_confirm']) - { - if (!$confirm_id) - { - $error[] = $user->lang['CONFIRM_CODE_WRONG']; - $wrong_confirm = true; - } - else - { - $sql = 'SELECT code - FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_REG; - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if ($row) - { - if (strcasecmp($row['code'], $data['confirm_code']) === 0) - { - $sql = 'DELETE FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_REG; - $db->sql_query($sql); - } - else - { - $error[] = $user->lang['CONFIRM_CODE_WRONG']; - $wrong_confirm = true; - } - } - else - { - $error[] = $user->lang['CONFIRM_CODE_WRONG']; - $wrong_confirm = true; - } - } - } - if (!sizeof($error)) { if ($data['new_password'] != $data['password_confirm']) @@ -451,57 +430,16 @@ class ucp_register if ($change_lang) { $str = '&change_lang=' . $change_lang; - $sql = 'SELECT code - FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_REG; - $result = $db->sql_query($sql); - if (!$row = $db->sql_fetchrow($result)) - { - $confirm_id = ''; - } - $db->sql_freeresult($result); } else { $str = ''; } - if (!$change_lang || !$confirm_id) - { - $user->confirm_gc(CONFIRM_REG); - - $sql = 'SELECT COUNT(session_id) AS attempts - FROM ' . CONFIRM_TABLE . " - WHERE session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_REG; - $result = $db->sql_query($sql); - $attempts = (int) $db->sql_fetchfield('attempts'); - $db->sql_freeresult($result); - - if ($config['max_reg_attempts'] && $attempts > $config['max_reg_attempts']) - { - trigger_error('TOO_MANY_REGISTERS'); - } - - $code = gen_rand_string(mt_rand(5, 8)); - $confirm_id = md5(unique_id($user->ip)); - $seed = hexdec(substr(unique_id(), 4, 10)); - - // compute $seed % 0x7fffffff - $seed -= 0x7fffffff * floor($seed / 0x7fffffff); - - $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array( - 'confirm_id' => (string) $confirm_id, - 'session_id' => (string) $user->session_id, - 'confirm_type' => (int) CONFIRM_REG, - 'code' => (string) $code, - 'seed' => (int) $seed) - ); - $db->sql_query($sql); - } - $confirm_image = ''; - $s_hidden_fields .= ''; + + $template->assign_vars(array( + 'L_CONFIRM_EXPLAIN' => sprintf($user->lang['CONFIRM_EXPLAIN'], '', ''), + 'S_CAPTCHA' => $captcha->get_template(), + )); } // @@ -516,7 +454,7 @@ class ucp_register $l_reg_cond = $user->lang['UCP_ADMIN_ACTIVATE']; break; } - + $template->assign_vars(array( 'ERROR' => (sizeof($error)) ? implode('
', $error) : '', 'USERNAME' => $data['username'], @@ -524,16 +462,13 @@ class ucp_register 'PASSWORD_CONFIRM' => $data['password_confirm'], 'EMAIL' => $data['email'], 'EMAIL_CONFIRM' => $data['email_confirm'], - 'CONFIRM_IMG' => $confirm_image, - 'L_CONFIRM_EXPLAIN' => sprintf($user->lang['CONFIRM_EXPLAIN'], '', ''), 'L_REG_COND' => $l_reg_cond, 'L_USERNAME_EXPLAIN' => sprintf($user->lang[$config['allow_name_chars'] . '_EXPLAIN'], $config['min_name_chars'], $config['max_name_chars']), 'L_PASSWORD_EXPLAIN' => sprintf($user->lang[$config['pass_complex'] . '_EXPLAIN'], $config['min_pass_chars'], $config['max_pass_chars']), 'S_LANG_OPTIONS' => language_select($data['lang']), 'S_TZ_OPTIONS' => tz_select($data['tz']), - 'S_CONFIRM_CODE' => ($config['enable_confirm']) ? true : false, 'S_COPPA' => $coppa, 'S_HIDDEN_FIELDS' => $s_hidden_fields, 'S_UCP_ACTION' => append_sid('ucp', 'mode=register'), diff --git a/phpBB/install/install_install.php b/phpBB/install/install_install.php index 1c3f490ccb..3b0fdc5011 100644 --- a/phpBB/install/install_install.php +++ b/phpBB/install/install_install.php @@ -1387,8 +1387,8 @@ class install_install extends module if (@extension_loaded('gd') || can_load_dll('gd')) { $sql_ary[] = 'UPDATE ' . $data['table_prefix'] . "config - SET config_value = '1' - WHERE config_name = 'captcha_gd'"; + SET config_value = 'phpbb_captcha_gd' + WHERE config_name = 'captcha_plugin'"; } // We set a (semi-)unique cookie name to bypass login issues related to the cookie name. diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index aa9740e8ac..a6d0c328a7 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -60,7 +60,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('browser_check', '1 INSERT INTO phpbb_config (config_name, config_value) VALUES ('bump_interval', '10'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('bump_type', 'd'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('cache_gc', '7200'); -INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_plugin', 'phpbb_captcha_nogd'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_foreground_noise', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_x_grid', '25'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_y_grid', '25'); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 81d2504a1c..e61f04904b 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -230,8 +230,9 @@ $lang = array_merge($lang, array( // Visual Confirmation Settings $lang = array_merge($lang, array( - 'ACP_VC_SETTINGS_EXPLAIN' => 'Here you are able to define visual confirmation defaults and CAPTCHA settings.', - + 'ACP_VC_SETTINGS_EXPLAIN' => 'Here you are able to define visual confirmation defaults and CAPTCHA settings.', + 'AVAILABLE_CAPTCHAS' => 'Available plugins', + 'CAPTCHA_UNAVAILABLE' => 'The CAPTCHA cannot be selected as its requirements are not met.', 'CAPTCHA_GD' => 'GD CAPTCHA', 'CAPTCHA_GD_FOREGROUND_NOISE' => 'GD CAPTCHA foreground noise', 'CAPTCHA_GD_EXPLAIN' => 'Use GD to make a more advanced CAPTCHA.', @@ -241,8 +242,17 @@ $lang = array_merge($lang, array( 'CAPTCHA_GD_Y_GRID' => 'GD CAPTCHA background noise y-axis', 'CAPTCHA_GD_Y_GRID_EXPLAIN' => 'Use lower settings of this to make the GD based CAPTCHA harder. 0 will disable y-axis background noise.', + 'CAPTCHA_NO_GD' => 'CAPTCHA without GD', + 'CAPTCHA_PREVIEW_MSG' => 'Your changes to the visual confirmation setting were not saved. This is just a preview.', - 'CAPTCHA_PREVIEW_EXPLAIN' => 'The CAPTCHA as it will look like using the current settings. Use the preview button to refresh. Note that captchas are randomized and will differ from one view to the next.', + 'CAPTCHA_PREVIEW_EXPLAIN' => 'The CAPTCHA as it would look like using the current selection.', + + 'CAPTCHA_SELECT' => 'Installed CAPTCHA plugins', + 'CAPTCHA_SELECT_EXPLAIN' => 'The dropdown holds the CAPTCHA plugins recognized by the board. Gray entries are not available right now and might need configuration prior to use.', + 'CAPTCHA_CONFIGURE' => 'Configure CAPTCHAs', + 'CAPTCHA_CONFIGURE_EXPLAIN' => 'Change the settings for the selected CAPTCHA.', + 'CONFIGURE' => 'Configure', + 'CAPTCHA_NO_OPTIONS' => 'This CAPTCHA has no configuration options.', 'VISUAL_CONFIRM_POST' => 'Enable visual confirmation for guest postings', 'VISUAL_CONFIRM_POST_EXPLAIN' => 'Requires anonymous users to enter a random code matching an image to help prevent mass postings.', 'VISUAL_CONFIRM_REG' => 'Enable visual confirmation for registrations', diff --git a/phpBB/posting.php b/phpBB/posting.php index 947d34925e..d1e6684152 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -45,7 +45,13 @@ $mode = ($delete && !$preview && !$refresh && $submit) ? 'delete' : request_var $error = $post_data = array(); $current_time = time(); - +if ($config['enable_post_confirm'] && !$user->data['is_registered']) +{ + include(PHPBB_ROOT_PATH . 'includes/captcha/captcha_factory.' . PHP_EXT); + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_POST); +} + // Was cancel pressed? If so then redirect to the appropriate page if ($cancel || ($current_time - $lastclick < 2 && $submit)) { @@ -741,21 +747,10 @@ if ($submit || $preview || $refresh) if ($config['enable_post_confirm'] && !$user->data['is_registered'] && in_array($mode, array('quote', 'post', 'reply'))) { - $confirm_id = request_var('confirm_id', ''); - $confirm_code = request_var('confirm_code', ''); - - $sql = 'SELECT code - FROM ' . CONFIRM_TABLE . " - WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "' - AND session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_POST; - $result = $db->sql_query($sql); - $confirm_row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (empty($confirm_row['code']) || strcasecmp($confirm_row['code'], $confirm_code) !== 0) + $vc_response = $captcha->validate(); + if ($vc_response) { - $error[] = $user->lang['CONFIRM_CODE_WRONG']; + $error += $vc_response; } else { @@ -999,7 +994,10 @@ if ($submit || $preview || $refresh) } $redirect_url = submit_post($mode, $post_data['post_subject'], $post_data['username'], $post_data['topic_type'], $poll, $data, $update_message); - + if ($config['enable_post_confirm'] && !$user->data['is_registered'] && in_array($mode, array('quote', 'post', 'reply'))) + { + $captcha->reset(); + } // Check the permissions for post approval, as well as the queue trigger where users are put on approval with a post count lower than specified. Moderators are not affected. if (($config['enable_queue_trigger'] && $user->data['user_posts'] < $config['queue_trigger_posts'] && !$auth->acl_get('m_approve', $data['forum_id'])) || !$auth->acl_get('f_noapprove', $data['forum_id'])) { @@ -1220,34 +1218,11 @@ generate_forum_rules($post_data); if ($config['enable_post_confirm'] && !$user->data['is_registered'] && $solved_captcha === false && ($mode == 'post' || $mode == 'reply' || $mode == 'quote')) { - // Show confirm image - $sql = 'DELETE FROM ' . CONFIRM_TABLE . " - WHERE session_id = '" . $db->sql_escape($user->session_id) . "' - AND confirm_type = " . CONFIRM_POST; - $db->sql_query($sql); - - // Generate code - $code = gen_rand_string(mt_rand(5, 8)); - $confirm_id = md5(unique_id($user->ip)); - $seed = hexdec(substr(unique_id(), 4, 10)); - - // compute $seed % 0x7fffffff - $seed -= 0x7fffffff * floor($seed / 0x7fffffff); - - $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array( - 'confirm_id' => (string) $confirm_id, - 'session_id' => (string) $user->session_id, - 'confirm_type' => (int) CONFIRM_POST, - 'code' => (string) $code, - 'seed' => (int) $seed) - ); - $db->sql_query($sql); + $captcha->reset(); $template->assign_vars(array( 'S_CONFIRM_CODE' => true, - 'CONFIRM_ID' => $confirm_id, - 'CONFIRM_IMAGE' => '', - 'L_POST_CONFIRM_EXPLAIN' => sprintf($user->lang['POST_CONFIRM_EXPLAIN'], '', ''), + 'CONFIRM' => $captcha->get_template(), )); } @@ -1258,10 +1233,7 @@ $s_hidden_fields .= ($draft_id || isset($_REQUEST['draft_loaded'])) ? ' request_var('confirm_id', ''), - 'confirm_code' => request_var('confirm_code', '')) - ); + $s_hidden_fields .= build_hidden_fields($captcha->get_hidden_fields()); } $form_enctype = (@ini_get('file_uploads') == '0' || strtolower(@ini_get('file_uploads')) == 'off' || !$config['allow_attachments'] || !$auth->acl_get('u_attach') || !$auth->acl_get('f_attach', $forum_id)) ? '' : ' enctype="multipart/form-data"'; diff --git a/phpBB/styles/prosilver/template/captcha_default.html b/phpBB/styles/prosilver/template/captcha_default.html new file mode 100755 index 0000000000..2cfc41338d --- /dev/null +++ b/phpBB/styles/prosilver/template/captcha_default.html @@ -0,0 +1,8 @@ + +
+
+
+
+
+
{L_CONFIRM_CODE_EXPLAIN}
+
\ No newline at end of file diff --git a/phpBB/styles/prosilver/template/login_body.html b/phpBB/styles/prosilver/template/login_body.html index ac7ada28c9..a32c2d1e42 100644 --- a/phpBB/styles/prosilver/template/login_body.html +++ b/phpBB/styles/prosilver/template/login_body.html @@ -21,21 +21,16 @@
{L_RESEND_ACTIVATION}
- -
-

{L_CONFIRM_CODE_EXPLAIN}
-
{CONFIRM_IMAGE}
-
-
+ {CONFIRM} -
+
 
{S_HIDDEN_FIELDS}
@@ -46,6 +41,7 @@ +
diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index f255862430..89c11703b7 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -100,11 +100,7 @@
-
-

{L_CONFIRM_CODE_EXPLAIN}
-
{CONFIRM_IMAGE}
-
-
+ {CONFIRM} diff --git a/phpBB/styles/prosilver/template/ucp_register.html b/phpBB/styles/prosilver/template/ucp_register.html index 721028cef6..8d635198d3 100644 --- a/phpBB/styles/prosilver/template/ucp_register.html +++ b/phpBB/styles/prosilver/template/ucp_register.html @@ -71,8 +71,7 @@ - - + @@ -83,15 +82,10 @@

{L_CONFIRM_EXPLAIN}

-
-
-
{CONFIRM_IMG}
-
-
{L_CONFIRM_CODE_EXPLAIN}
-
+ {S_CAPTCHA}
- +