diff options
-rw-r--r-- | phpBB/develop/create_schema_files.php | 20 | ||||
-rw-r--r-- | phpBB/includes/acp/acp_board.php | 3 | ||||
-rw-r--r-- | phpBB/includes/auth.php | 2 | ||||
-rw-r--r-- | phpBB/includes/auth/auth_db.php | 65 | ||||
-rw-r--r-- | phpBB/includes/constants.php | 1 | ||||
-rw-r--r-- | phpBB/includes/db/db_tools.php | 13 | ||||
-rw-r--r-- | phpBB/includes/session.php | 4 | ||||
-rw-r--r-- | phpBB/install/database_update.php | 293 | ||||
-rw-r--r-- | phpBB/install/schemas/firebird_schema.sql | 32 | ||||
-rw-r--r-- | phpBB/install/schemas/mssql_schema.sql | 38 | ||||
-rw-r--r-- | phpBB/install/schemas/mysql_40_schema.sql | 20 | ||||
-rw-r--r-- | phpBB/install/schemas/mysql_41_schema.sql | 20 | ||||
-rw-r--r-- | phpBB/install/schemas/oracle_schema.sql | 43 | ||||
-rw-r--r-- | phpBB/install/schemas/postgres_schema.sql | 24 | ||||
-rw-r--r-- | phpBB/install/schemas/schema_data.sql | 3 | ||||
-rw-r--r-- | phpBB/install/schemas/sqlite_schema.sql | 19 | ||||
-rw-r--r-- | phpBB/language/en/acp/board.php | 12 |
17 files changed, 596 insertions, 16 deletions
diff --git a/phpBB/develop/create_schema_files.php b/phpBB/develop/create_schema_files.php index 87670722aa..f1e2858848 100644 --- a/phpBB/develop/create_schema_files.php +++ b/phpBB/develop/create_schema_files.php @@ -1207,6 +1207,26 @@ function get_schema_struct() ), ); + $schema_data['phpbb_login_attempts'] = array( + 'COLUMNS' => array( + 'attempt_id' => array('UINT', NULL, 'auto_increment'), + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + 'PRIMARY_KEY' => 'attempt_id', + 'KEYS' => array( + 'attempt_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'attempt_forwarded_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'attempt_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ); + $schema_data['phpbb_moderator_cache'] = array( 'COLUMNS' => array( 'forum_id' => array('UINT', 0), diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index d38c4d58ba..9f00145f3b 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -386,6 +386,9 @@ class acp_board 'pass_complex' => array('lang' => 'PASSWORD_TYPE', 'validate' => 'string', 'type' => 'select', 'method' => 'select_password_chars', 'explain' => true), 'chg_passforce' => array('lang' => 'FORCE_PASS_CHANGE', 'validate' => 'int:0', 'type' => 'text:3:3', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), 'max_login_attempts' => array('lang' => 'MAX_LOGIN_ATTEMPTS', 'validate' => 'int:0', 'type' => 'text:3:3', 'explain' => true), + 'ip_login_limit_max' => array('lang' => 'IP_LOGIN_LIMIT_MAX', 'validate' => 'int:0', 'type' => 'text:3:3', 'explain' => true), + 'ip_login_limit_time' => array('lang' => 'IP_LOGIN_LIMIT_TIME', 'validate' => 'int:0', 'type' => 'text:5:5', 'explain' => true), + 'ip_login_limit_use_forwarded' => array('lang' => 'IP_LOGIN_LIMIT_USE_FORWARDED', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'tpl_allow_php' => array('lang' => 'TPL_ALLOW_PHP', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'form_token_lifetime' => array('lang' => 'FORM_TIME_MAX', 'validate' => 'int:-1', 'type' => 'text:5:5', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), 'form_token_sid_guests' => array('lang' => 'FORM_SID_GUESTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), diff --git a/phpBB/includes/auth.php b/phpBB/includes/auth.php index 8324cb4977..5564de2943 100644 --- a/phpBB/includes/auth.php +++ b/phpBB/includes/auth.php @@ -908,7 +908,7 @@ class auth $method = 'login_' . $method; if (function_exists($method)) { - $login = $method($username, $password); + $login = $method($username, $password, $user->ip, $user->browser, $user->forwarded_for); // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE) diff --git a/phpBB/includes/auth/auth_db.php b/phpBB/includes/auth/auth_db.php index e04a6307e9..02c9386f33 100644 --- a/phpBB/includes/auth/auth_db.php +++ b/phpBB/includes/auth/auth_db.php @@ -23,8 +23,21 @@ if (!defined('IN_PHPBB')) /** * Login function +* +* @param string $username +* @param string $password +* @param string $ip IP address the login is taking place from. Used to +* limit the number of login attempts per IP address. +* @param string $browser The user agent used to login +* @param string $forwarded_for X_FORWARDED_FOR header sent with login request +* @return array A associative array of the format +* array( +* 'status' => status constant +* 'error_msg' => string +* 'user_row' => array +* ) */ -function login_db(&$username, &$password) +function login_db($username, $password, $ip = '', $browser = '', $forwarded_for = '') { global $db, $config; @@ -47,13 +60,51 @@ function login_db(&$username, &$password) ); } + $username_clean = utf8_clean_string($username); + $sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts FROM ' . USERS_TABLE . " - WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + WHERE username_clean = '" . $db->sql_escape($username_clean) . "'"; $result = $db->sql_query($sql); $row = $db->sql_fetchrow($result); $db->sql_freeresult($result); + if (($ip && !$config['ip_login_limit_use_forwarded']) || + ($forwarded_for && $config['ip_login_limit_use_forwarded'])) + { + $sql = 'SELECT COUNT(attempt_id) AS count + FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time > ' . (time() - (int) $config['ip_login_limit_time']); + if ($config['ip_login_limit_use_forwarded']) + { + $sql .= " AND attempt_forwarded_for = '" . $db->sql_escape($forwarded_for) . "'"; + } + else + { + $sql .= " AND attempt_ip = '" . $db->sql_escape($ip) . "' "; + } + + $result = $db->sql_query($sql); + $attempts = (int) $db->sql_fetchfield('count'); + $db->sql_freeresult($result); + + $attempt_data = array( + 'attempt_ip' => $ip, + 'attempt_browser' => $browser, + 'attempt_forwarded_for' => $forwarded_for, + 'attempt_time' => time(), + 'user_id' => ($row) ? (int) $row['user_id'] : 0, + 'username' => $username, + 'username_clean' => $username_clean, + ); + $sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $db->sql_build_array('INSERT', $attempt_data); + $result = $db->sql_query($sql); + } + else + { + $attempts = 0; + } + if (!$row) { return array( @@ -62,7 +113,9 @@ function login_db(&$username, &$password) 'user_row' => array('user_id' => ANONYMOUS), ); } - $show_captcha = $config['max_login_attempts'] && $row['user_login_attempts'] >= $config['max_login_attempts']; + + $show_captcha = ($config['max_login_attempts'] && $row['user_login_attempts'] >= $config['max_login_attempts']) || + ($config['ip_login_limit_max'] && $attempts >= $config['ip_login_limit_max']); // If there are too much login attempts, we need to check for an confirm image // Every auth module is able to define what to do by itself... @@ -90,7 +143,7 @@ function login_db(&$username, &$password) { $captcha->reset(); } - + } // If the password convert flag is set we need to convert it @@ -165,6 +218,10 @@ function login_db(&$username, &$password) $row['user_password'] = $hash; } + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE user_id = ' . $row['user_id']; + $db->sql_query($sql); + if ($row['user_login_attempts'] != 0) { // Successful, reset login attempts (the user passed all stages) diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index ea34eb8e81..b5a0aa893a 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -236,6 +236,7 @@ define('GROUPS_TABLE', $table_prefix . 'groups'); define('ICONS_TABLE', $table_prefix . 'icons'); define('LANG_TABLE', $table_prefix . 'lang'); define('LOG_TABLE', $table_prefix . 'log'); +define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); define('MODULES_TABLE', $table_prefix . 'modules'); define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php index 483ceee043..fdefda9e26 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -638,6 +638,19 @@ class phpbb_db_tools $sqlite = true; } + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + // Change columns? if (!empty($schema_changes['change_columns'])) { diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index 7db319493b..7ef6e02a8d 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -1006,6 +1006,10 @@ class session include($phpbb_root_path . "includes/captcha/captcha_factory." . $phpEx); } phpbb_captcha_factory::garbage_collect($config['captcha_plugin']); + + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); + $db->sql_query($sql); } return; diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 3d32a82cc6..24a69ab99b 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -916,9 +916,29 @@ function database_update_info() '3.0.7-PL1' => array(), // No changes from 3.0.8-RC1 to 3.0.8 '3.0.8-RC1' => array(), - // Changes from 3.0.8 to 3.0.9-RC1 '3.0.8' => array( + 'add_tables' => array( + LOGIN_ATTEMPT_TABLE => array( + 'COLUMNS' => array( + 'attempt_id' => array('UINT', NULL, 'auto_increment'), + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + 'PRIMARY_KEY' => 'attempt_id', + 'KEYS' => array( + 'attempt_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'attempt_forwarded_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'attempt_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ), + ), 'change_columns' => array( BBCODES_TABLE => array( 'bbcode_id' => array('USINT', 0), @@ -1870,6 +1890,10 @@ function change_database_data(&$no_updates, $version) // Changes from 3.0.8 to 3.0.9-RC1 case '3.0.8': + set_config('ip_login_limit_max', '50'); + set_config('ip_login_limit_time', '21600'); + set_config('ip_login_limit_use_forwarded', '0'); + // Update file extension group names to use language strings, again. $sql = 'SELECT group_id, group_name FROM ' . EXTENSION_GROUPS_TABLE . ' @@ -2226,6 +2250,260 @@ class updater_db_tools } /** + * Check if table exists + * + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + function sql_table_exists($table_name) + { + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); + $this->db->sql_return_on_error(false); + + if ($result) + { + $this->db->sql_freeresult($result); + return true; + } + + return false; + } + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array Statements if $return_statements is true. + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table must be created with TEXTIMAGE + $create_textimage = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + break; + + default: + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + break; + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + // here we add the definition of the new column to the list of columns + switch ($this->sql_layer) + { + case 'mssql': + case 'mssqlnative': + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + break; + + default: + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + break; + } + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create textimage DDL based off of the existance of certain column types + if (!$create_textimage) + { + $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + switch ($this->sql_layer) + { + case 'firebird': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'mssql': + case 'mssqlnative': + $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); + $statements[] = $table_sql; + break; + } + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'postgres': + case 'sqlite': + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + + case 'firebird': + case 'mssql': + case 'mssqlnative': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + break; + + case 'oracle': + $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + } + } + } + + // close the table + switch ($this->sql_layer) + { + case 'mysql_41': + // make sure the table is in UTF-8 mode + $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; + $statements[] = $table_sql; + break; + + case 'mysql_40': + case 'sqlite': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'postgres': + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'oracle': + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // do we need to add a sequence and a tigger for auto incrementing columns? + if ($create_sequence) + { + // create the actual sequence + $statements[] = "CREATE SEQUENCE {$table_name}_seq"; + + // the trigger is the mechanism by which we increment the counter + $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; + $trigger .= "BEFORE INSERT ON {$table_name}\n"; + $trigger .= "FOR EACH ROW WHEN (\n"; + $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; + $trigger .= ")\n"; + $trigger .= "BEGIN\n"; + $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; + $trigger .= "\tINTO :new.{$create_sequence}\n"; + $trigger .= "\tFROM dual\n"; + $trigger .= "END;"; + + $statements[] = $trigger; + } + break; + + case 'firebird': + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + break; + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** * Handle passed database update array. * Expected structure... * Key being one of the following @@ -2262,6 +2540,19 @@ class updater_db_tools $sqlite = true; } + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + // Change columns? if (!empty($schema_changes['change_columns'])) { diff --git a/phpBB/install/schemas/firebird_schema.sql b/phpBB/install/schemas/firebird_schema.sql index 85f86781de..24ebd0f1af 100644 --- a/phpBB/install/schemas/firebird_schema.sql +++ b/phpBB/install/schemas/firebird_schema.sql @@ -1,5 +1,5 @@ # -# $Id$ +# $Id: $ # @@ -545,6 +545,36 @@ BEGIN END;; +# Table: 'phpbb_login_attempts' +CREATE TABLE phpbb_login_attempts ( + attempt_id INTEGER NOT NULL, + attempt_ip VARCHAR(40) CHARACTER SET NONE DEFAULT '' NOT NULL, + attempt_browser VARCHAR(150) CHARACTER SET NONE DEFAULT '' NOT NULL, + attempt_forwarded_for VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + attempt_time INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + username VARCHAR(255) CHARACTER SET UTF8 DEFAULT 0 NOT NULL COLLATE UNICODE, + username_clean VARCHAR(255) CHARACTER SET UTF8 DEFAULT 0 NOT NULL COLLATE UNICODE +);; + +ALTER TABLE phpbb_login_attempts ADD PRIMARY KEY (attempt_id);; + +CREATE INDEX phpbb_login_attempts_attempt_ip ON phpbb_login_attempts(attempt_ip, attempt_time);; +CREATE INDEX phpbb_login_attempts_attempt_forwarded_for ON phpbb_login_attempts(attempt_forwarded_for, attempt_time);; +CREATE INDEX phpbb_login_attempts_attempt_time ON phpbb_login_attempts(attempt_time);; +CREATE INDEX phpbb_login_attempts_user_id ON phpbb_login_attempts(user_id);; + +CREATE GENERATOR phpbb_login_attempts_gen;; +SET GENERATOR phpbb_login_attempts_gen TO 0;; + +CREATE TRIGGER t_phpbb_login_attempts FOR phpbb_login_attempts +BEFORE INSERT +AS +BEGIN + NEW.attempt_id = GEN_ID(phpbb_login_attempts_gen, 1); +END;; + + # Table: 'phpbb_moderator_cache' CREATE TABLE phpbb_moderator_cache ( forum_id INTEGER DEFAULT 0 NOT NULL, diff --git a/phpBB/install/schemas/mssql_schema.sql b/phpBB/install/schemas/mssql_schema.sql index 0827b14cc2..dbbd144aa0 100644 --- a/phpBB/install/schemas/mssql_schema.sql +++ b/phpBB/install/schemas/mssql_schema.sql @@ -1,6 +1,6 @@ /* - $Id$ + $Id: $ */ @@ -650,6 +650,41 @@ GO /* + Table: 'phpbb_login_attempts' +*/ +CREATE TABLE [phpbb_login_attempts] ( + [attempt_id] [int] IDENTITY (1, 1) NOT NULL , + [attempt_ip] [varchar] (40) DEFAULT ('') NOT NULL , + [attempt_browser] [varchar] (150) DEFAULT ('') NOT NULL , + [attempt_forwarded_for] [varchar] (255) DEFAULT ('') NOT NULL , + [attempt_time] [int] DEFAULT (0) NOT NULL , + [user_id] [int] DEFAULT (0) NOT NULL , + [username] [varchar] (255) DEFAULT (0) NOT NULL , + [username_clean] [varchar] (255) DEFAULT (0) NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [phpbb_login_attempts] WITH NOCHECK ADD + CONSTRAINT [PK_phpbb_login_attempts] PRIMARY KEY CLUSTERED + ( + [attempt_id] + ) ON [PRIMARY] +GO + +CREATE INDEX [attempt_ip] ON [phpbb_login_attempts]([attempt_ip], [attempt_time]) ON [PRIMARY] +GO + +CREATE INDEX [attempt_forwarded_for] ON [phpbb_login_attempts]([attempt_forwarded_for], [attempt_time]) ON [PRIMARY] +GO + +CREATE INDEX [attempt_time] ON [phpbb_login_attempts]([attempt_time]) ON [PRIMARY] +GO + +CREATE INDEX [user_id] ON [phpbb_login_attempts]([user_id]) ON [PRIMARY] +GO + + +/* Table: 'phpbb_moderator_cache' */ CREATE TABLE [phpbb_moderator_cache] ( @@ -1730,3 +1765,4 @@ ALTER TABLE [phpbb_zebra] WITH NOCHECK ADD ) ON [PRIMARY] GO + diff --git a/phpBB/install/schemas/mysql_40_schema.sql b/phpBB/install/schemas/mysql_40_schema.sql index eeaec4ccf6..bce04eedfb 100644 --- a/phpBB/install/schemas/mysql_40_schema.sql +++ b/phpBB/install/schemas/mysql_40_schema.sql @@ -1,5 +1,5 @@ # -# $Id$ +# $Id: $ # # Table: 'phpbb_attachments' @@ -369,6 +369,24 @@ CREATE TABLE phpbb_log ( ); +# Table: 'phpbb_login_attempts' +CREATE TABLE phpbb_login_attempts ( + attempt_id mediumint(8) UNSIGNED NOT NULL auto_increment, + attempt_ip varbinary(40) DEFAULT '' NOT NULL, + attempt_browser varbinary(150) DEFAULT '' NOT NULL, + attempt_forwarded_for varbinary(255) DEFAULT '' NOT NULL, + attempt_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + username blob NOT NULL, + username_clean blob NOT NULL, + PRIMARY KEY (attempt_id), + KEY attempt_ip (attempt_ip, attempt_time), + KEY attempt_forwarded_for (attempt_forwarded_for, attempt_time), + KEY attempt_time (attempt_time), + KEY user_id (user_id) +); + + # Table: 'phpbb_moderator_cache' CREATE TABLE phpbb_moderator_cache ( forum_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/mysql_41_schema.sql b/phpBB/install/schemas/mysql_41_schema.sql index 3a3b4ab2fd..e77ad44dd8 100644 --- a/phpBB/install/schemas/mysql_41_schema.sql +++ b/phpBB/install/schemas/mysql_41_schema.sql @@ -1,5 +1,5 @@ # -# $Id$ +# $Id: $ # # Table: 'phpbb_attachments' @@ -369,6 +369,24 @@ CREATE TABLE phpbb_log ( ) CHARACTER SET `utf8` COLLATE `utf8_bin`; +# Table: 'phpbb_login_attempts' +CREATE TABLE phpbb_login_attempts ( + attempt_id mediumint(8) UNSIGNED NOT NULL auto_increment, + attempt_ip varchar(40) DEFAULT '' NOT NULL, + attempt_browser varchar(150) DEFAULT '' NOT NULL, + attempt_forwarded_for varchar(255) DEFAULT '' NOT NULL, + attempt_time int(11) UNSIGNED DEFAULT '0' NOT NULL, + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + username varchar(255) DEFAULT '0' NOT NULL, + username_clean varchar(255) DEFAULT '0' NOT NULL, + PRIMARY KEY (attempt_id), + KEY attempt_ip (attempt_ip, attempt_time), + KEY attempt_forwarded_for (attempt_forwarded_for, attempt_time), + KEY attempt_time (attempt_time), + KEY user_id (user_id) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + # Table: 'phpbb_moderator_cache' CREATE TABLE phpbb_moderator_cache ( forum_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, diff --git a/phpBB/install/schemas/oracle_schema.sql b/phpBB/install/schemas/oracle_schema.sql index 9c25af2512..5a1e4930b2 100644 --- a/phpBB/install/schemas/oracle_schema.sql +++ b/phpBB/install/schemas/oracle_schema.sql @@ -1,6 +1,6 @@ /* - $Id$ + $Id: $ */ @@ -741,6 +741,47 @@ END; /* + Table: 'phpbb_login_attempts' +*/ +CREATE TABLE phpbb_login_attempts ( + attempt_id number(8) NOT NULL, + attempt_ip varchar2(40) DEFAULT '' , + attempt_browser varchar2(150) DEFAULT '' , + attempt_forwarded_for varchar2(255) DEFAULT '' , + attempt_time number(11) DEFAULT '0' NOT NULL, + user_id number(8) DEFAULT '0' NOT NULL, + username varchar2(765) DEFAULT '0' NOT NULL, + username_clean varchar2(255) DEFAULT '0' NOT NULL, + CONSTRAINT pk_phpbb_login_attempts PRIMARY KEY (attempt_id) +) +/ + +CREATE INDEX phpbb_login_attempts_attempt_ip ON phpbb_login_attempts (attempt_ip, attempt_time) +/ +CREATE INDEX phpbb_login_attempts_attempt_forwarded_for ON phpbb_login_attempts (attempt_forwarded_for, attempt_time) +/ +CREATE INDEX phpbb_login_attempts_attempt_time ON phpbb_login_attempts (attempt_time) +/ +CREATE INDEX phpbb_login_attempts_user_id ON phpbb_login_attempts (user_id) +/ + +CREATE SEQUENCE phpbb_login_attempts_seq +/ + +CREATE OR REPLACE TRIGGER t_phpbb_login_attempts +BEFORE INSERT ON phpbb_login_attempts +FOR EACH ROW WHEN ( + new.attempt_id IS NULL OR new.attempt_id = 0 +) +BEGIN + SELECT phpbb_login_attempts_seq.nextval + INTO :new.attempt_id + FROM dual; +END; +/ + + +/* Table: 'phpbb_moderator_cache' */ CREATE TABLE phpbb_moderator_cache ( diff --git a/phpBB/install/schemas/postgres_schema.sql b/phpBB/install/schemas/postgres_schema.sql index a2d4dc3e0b..c4c2307c3a 100644 --- a/phpBB/install/schemas/postgres_schema.sql +++ b/phpBB/install/schemas/postgres_schema.sql @@ -1,6 +1,6 @@ /* - $Id$ + $Id: $ */ @@ -525,6 +525,28 @@ CREATE INDEX phpbb_log_reportee_id ON phpbb_log (reportee_id); CREATE INDEX phpbb_log_user_id ON phpbb_log (user_id); /* + Table: 'phpbb_login_attempts' +*/ +CREATE SEQUENCE phpbb_login_attempts_seq; + +CREATE TABLE phpbb_login_attempts ( + attempt_id INT4 DEFAULT nextval('phpbb_login_attempts_seq'), + attempt_ip varchar(40) DEFAULT '' NOT NULL, + attempt_browser varchar(150) DEFAULT '' NOT NULL, + attempt_forwarded_for varchar(255) DEFAULT '' NOT NULL, + attempt_time INT4 DEFAULT '0' NOT NULL CHECK (attempt_time >= 0), + user_id INT4 DEFAULT '0' NOT NULL CHECK (user_id >= 0), + username varchar(255) DEFAULT '0' NOT NULL, + username_clean varchar_ci DEFAULT '0' NOT NULL, + PRIMARY KEY (attempt_id) +); + +CREATE INDEX phpbb_login_attempts_attempt_ip ON phpbb_login_attempts (attempt_ip, attempt_time); +CREATE INDEX phpbb_login_attempts_attempt_forwarded_for ON phpbb_login_attempts (attempt_forwarded_for, attempt_time); +CREATE INDEX phpbb_login_attempts_attempt_time ON phpbb_login_attempts (attempt_time); +CREATE INDEX phpbb_login_attempts_user_id ON phpbb_login_attempts (user_id); + +/* Table: 'phpbb_moderator_cache' */ CREATE TABLE phpbb_moderator_cache ( diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index ea4157d6a3..08d6c18ee8 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -136,6 +136,9 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_thumb_widt INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_width', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_min_thumb_filesize', '12000'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_check', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_max', '50'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_time', '21600'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_use_forwarded', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_enable', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_host', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_password', ''); diff --git a/phpBB/install/schemas/sqlite_schema.sql b/phpBB/install/schemas/sqlite_schema.sql index 8661bb7578..9668c8d5f1 100644 --- a/phpBB/install/schemas/sqlite_schema.sql +++ b/phpBB/install/schemas/sqlite_schema.sql @@ -1,5 +1,5 @@ # -# $Id$ +# $Id: $ # BEGIN TRANSACTION; @@ -357,6 +357,23 @@ CREATE INDEX phpbb_log_topic_id ON phpbb_log (topic_id); CREATE INDEX phpbb_log_reportee_id ON phpbb_log (reportee_id); CREATE INDEX phpbb_log_user_id ON phpbb_log (user_id); +# Table: 'phpbb_login_attempts' +CREATE TABLE phpbb_login_attempts ( + attempt_id INTEGER PRIMARY KEY NOT NULL , + attempt_ip varchar(40) NOT NULL DEFAULT '', + attempt_browser varchar(150) NOT NULL DEFAULT '', + attempt_forwarded_for varchar(255) NOT NULL DEFAULT '', + attempt_time INTEGER UNSIGNED NOT NULL DEFAULT '0', + user_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + username varchar(255) NOT NULL DEFAULT '0', + username_clean varchar(255) NOT NULL DEFAULT '0' +); + +CREATE INDEX phpbb_login_attempts_attempt_ip ON phpbb_login_attempts (attempt_ip, attempt_time); +CREATE INDEX phpbb_login_attempts_attempt_forwarded_for ON phpbb_login_attempts (attempt_forwarded_for, attempt_time); +CREATE INDEX phpbb_login_attempts_attempt_time ON phpbb_login_attempts (attempt_time); +CREATE INDEX phpbb_login_attempts_user_id ON phpbb_login_attempts (user_id); + # Table: 'phpbb_moderator_cache' CREATE TABLE phpbb_moderator_cache ( forum_id INTEGER UNSIGNED NOT NULL DEFAULT '0', diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index 3a63e72b8f..5b8af7ab61 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -458,12 +458,18 @@ $lang = array_merge($lang, array( 'FORM_TIME_MAX_EXPLAIN' => 'The time a user has to submit a form. Use -1 to disable. Note that a form might become invalid if the session expires, regardless of this setting.', 'FORM_SID_GUESTS' => 'Tie forms to guest sessions', 'FORM_SID_GUESTS_EXPLAIN' => 'If enabled, the form token issued to guests will be session-exclusive. This can cause problems with some ISPs.', - 'FORWARDED_FOR_VALID' => 'Validated <var>X_FORWARDED_FOR</var> header', + 'FORWARDED_FOR_VALID' => 'Validate <var>X_FORWARDED_FOR</var> header', 'FORWARDED_FOR_VALID_EXPLAIN' => 'Sessions will only be continued if the sent <var>X_FORWARDED_FOR</var> header equals the one sent with the previous request. Bans will be checked against IPs in <var>X_FORWARDED_FOR</var> too.', 'IP_VALID' => 'Session IP validation', 'IP_VALID_EXPLAIN' => 'Determines how much of the users IP is used to validate a session; <samp>All</samp> compares the complete address, <samp>A.B.C</samp> the first x.x.x, <samp>A.B</samp> the first x.x, <samp>None</samp> disables checking. On IPv6 addresses <samp>A.B.C</samp> compares the first 4 blocks and <samp>A.B</samp> the first 3 blocks.', - 'MAX_LOGIN_ATTEMPTS' => 'Maximum number of login attempts', - 'MAX_LOGIN_ATTEMPTS_EXPLAIN' => 'After this number of failed logins the user needs to additionally solve the anti-spambot task.', + 'IP_LOGIN_LIMIT_MAX' => 'Maximum number of login attempts per IP address', + 'IP_LOGIN_LIMIT_MAX_EXPLAIN' => 'The threshold of login attempts allowed from a single IP address before an anti-spambot task is triggered.Enter 0 to prevent the anti-spambot task from being triggered by IP addresses.', + 'IP_LOGIN_LIMIT_TIME' => 'IP address login attempt expiration time', + 'IP_LOGIN_LIMIT_TIME_EXPLAIN' => 'Login attempts expire after this period, in seconds.', + 'IP_LOGIN_LIMIT_USE_FORWARDED' => 'Limit login attempts by <var>X_FORWARDED_FOR</var> header', + 'IP_LOGIN_LIMIT_USE_FORWARDED_EXPLAIN' => 'Instead of limiting login attempts by IP address they are limited by <var>X_FORWARDED_FOR</var> values. <br /><em><strong>Warning:</strong> Only enable this if you are operating a proxy server that sets <var>X_FORWARDED_FOR</var> to trustworthy values.</em>', + 'MAX_LOGIN_ATTEMPTS' => 'Maximum number of login attempts per username', + 'MAX_LOGIN_ATTEMPTS_EXPLAIN' => 'The number of login attempts allowed for a single account before the anti-spambot task is triggered. Enter 0 to prevent the anti-spambot task from being trigger for distinct user accounts.', 'NO_IP_VALIDATION' => 'None', 'NO_REF_VALIDATION' => 'None', 'PASSWORD_TYPE' => 'Password complexity', |