4 This software is published under the same license as NucleusCMS, namely
5 the GNU General Public License. See http://www.gnu.org/licenses/gpl.html for
6 details about the conditions of this license.
8 In general, this program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2 of the License, or (at your option) any
13 This program is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 * @version $Id: NP_SecurityEnforcer.php 1721 2012-03-31 10:18:25Z sakamocchi $
19 class NP_SecurityEnforcer extends NucleusPlugin
22 public function getName()
24 return 'SecurityEnforcer';
27 public function getAuthor()
29 return 'Frank Truscott + Cacher + Mocchi';
32 public function getURL()
34 return 'http://revcetera.com/ftruscot';
37 public function getVersion()
42 public function getDescription()
44 return _SECURITYENFORCER_DESCRIPTION;
47 public function getMinNucleusVersion()
52 public function getTableList()
54 return array(sql_table('plug_securityenforcer'));
57 public function getEventList()
59 return array('QuickMenu','PrePasswordSet','CustomLogin','LoginSuccess','LoginFailed','PostRegister');
62 public function hasAdminArea() {
66 public function supportsFeature($what)
68 if ( $what == 'SqlTablePrefix' )
75 public function install()
79 // Need to make some options
80 $this->createOption('quickmenu', '_SECURITYENFORCER_OPT_QUICKMENU', 'yesno', 'yes');
81 $this->createOption('del_uninstall_data', '_SECURITYENFORCER_OPT_DEL_UNINSTALL_DATA', 'yesno','no');
82 $this->createOption('enable_security', '_SECURITYENFORCER_OPT_ENABLE', 'yesno','yes');
83 $this->createOption('pwd_min_length', '_SECURITYENFORCER_OPT_PWD_MIN_LENGTH', 'text','8');
84 $this->createOption('pwd_complexity', '_SECURITYENFORCER_OPT_PWD_COMPLEXITY', 'select','0', '_SECURITYENFORCER_OPT_SELECT_OFF_COMP|0|_SECURITYENFORCER_OPT_SELECT_ONE_COMP|1|_SECURITYENFORCER_OPT_SELECT_TWO_COMP|2|_SECURITYENFORCER_OPT_SELECT_THREE_COMP|3|_SECURITYENFORCER_OPT_SELECT_FOUR_COMP|4;datatype=numerical');
85 $this->createOption('max_failed_login', '_SECURITYENFORCER_OPT_MAX_FAILED_LOGIN', 'text', '5');
86 $this->createOption('login_lockout', '_SECURITYENFORCER_OPT_LOGIN_LOCKOUT', 'text', '15');
88 // create needed tables
89 DB::execute('CREATE TABLE IF NOT EXISTS '. sql_table('plug_securityenforcer').
90 " (login varchar(255),
91 fails int(11) NOT NULL default '0',
92 lastfail bigint NOT NULL default '0',
93 KEY login (login)) ENGINE=MyISAM");
97 public function unInstall()
99 // if requested, delete the data table
100 if ( $this->getOption('del_uninstall_data') == 'yes' )
102 DB::execute('DROP TABLE '.sql_table('plug_securityenforcer'));
107 public function init()
109 // include translation file for this plugin
110 if ( file_exists($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php') )
112 include_once($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php');
116 include_once($this->getDirectory().'en_Latn_US.UTF-8.php');
119 $this->enable_security = $this->getOption('enable_security');
120 $this->pwd_min_length = (integer) $this->getOption('pwd_min_length');
121 $this->pwd_complexity = (integer) $this->getOption('pwd_complexity');
122 $this->max_failed_login = (integer) $this->getOption('max_failed_login');
123 $this->login_lockout = (integer) $this->getOption('login_lockout');
127 public function event_QuickMenu($data)
129 // only show when option enabled
131 if ( $this->getOption('quickmenu') != 'yes' || !$member->isAdmin() )
136 if ( !($member->isLoggedIn()) )
140 array_push($data['options'],
141 array('title' => 'Security Enforcer',
142 'url' => $this->getAdminURL(),
143 'tooltip' => _SECURITYENFORCER_ADMIN_TOOLTIP));
147 public function event_PrePasswordSet($data)
149 //password, errormessage, valid
150 if ( $this->enable_security == 'no' )
154 $password = $data['password'];
155 // conditional below not needed in 3.60 or higher. Used to keep from setting off error when password not being changed
156 if ( postVar('action') == 'changemembersettings' )
158 $emptyAllowed = true;
162 $emptyAllowed = false;
164 if ( (!$emptyAllowed)||$password )
166 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
169 $data['errormessage'] = _SECURITYENFORCER_INSUFFICIENT_COMPLEXITY . $message. "<br /><br />\n";
170 $data['valid'] = false;
176 public function event_PostRegister($data)
178 if ( $this->enable_security != 'yes' )
182 $password = postVar('password');
183 if( postVar('action') == 'memberadd' )
185 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
188 $errormessage = _SECURITYENFORCER_ACCOUNT_CREATED. $message. "<br /><br />\n";
190 $admin->error($errormessage);
196 public function event_CustomLogin($data)
198 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
203 //login,password,success,allowlocal
205 $login = $data['login'];
206 $ip = $_SERVER['REMOTE_ADDR'];
208 $query = "DELETE FROM %s WHERE lastfail < %d;";
209 $query = sprintf($query, sql_table('plug_securityenforcer'), (integer) (time() - ($this->login_lockout * 60)));
212 $query = "SELECT fails as result FROM %s WHERE login=%s;";
213 $query = sprintf($query, sql_table('plug_securityenforcer'), DB::quoteValue($login));
214 $flogin = DB::getValue($query);
216 $query = "SELECT fails as result FROM %s WHERE login=%s;";
217 $query = sprintf($query, sql_table('plug_securityenforcer'), DB::quoteValue($ip));
218 $fip = DB::getValue($query);
220 if ( $flogin >= $this->max_failed_login || $fip >= $this->max_failed_login )
222 $data['success'] = 0;
223 $data['allowlocal'] = 0;
224 $info = sprintf(_SECURITYENFORCER_LOGIN_DISALLOWED, Entity::hsc($login), Entity::hsc($ip));
225 ActionLog::add(INFO, $info);
230 public function event_LoginSuccess($data)
232 //member(obj),username
233 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
238 $login = $data['username'];
239 $ip = $_SERVER['REMOTE_ADDR'];
240 DB::execute('DELETE FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($login));
241 DB::execute('DELETE FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($ip));
245 public function event_LoginFailed($data)
248 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
253 $login = $data['username'];
254 $ip = $_SERVER['REMOTE_ADDR'];
255 $lres = DB::getResult('SELECT * FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($login));
256 if ( $lres->rowCount() > 0 )
258 DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($login));
262 DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($login) . ',1,' . time() . ')');
264 $lres = DB::getResult('SELECT * FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($ip));
265 if ( $lres->rowCount() > 0 )
267 DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($ip));
271 DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($ip) . ',1,' . time() . ')');
276 private function validate_and_messsage($passwd,$minlength = 6,$complexity = 0)
278 $minlength = intval($minlength);
279 $complexity = intval($complexity);
282 if ( $minlength < 6 )
286 if ( i18n::strlen($passwd) < $minlength )
288 $message = _SECURITYENFORCER_MIN_PWD_LENGTH . $this->pwd_min_length;
291 if ( $complexity > 4 )
299 $ochars = '[#-~!@\\$%^&*()_+=,.<>?:;|]';
300 $chartypes = array($ucchars, $lcchars, $numchars, $ochars);
301 $tot = array(0,0,0,0);
304 foreach ( $chartypes as $value )
306 $tot[$i] = preg_match("/" . $value . "/", $passwd);
310 if ( array_sum($tot) < $complexity )
312 $message .= _SECURITYENFORCER_PWD_COMPLEXITY . $this->pwd_complexity;