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 1874 2012-06-17 07:27:38Z sakamocchi $
19 class NP_SecurityEnforcer extends NucleusPlugin
21 public function getName()
23 return 'SecurityEnforcer';
26 public function getAuthor()
28 return 'Frank Truscott + Cacher + Mocchi';
31 public function getURL()
33 return 'http://revcetera.com/ftruscot';
36 public function getVersion()
41 public function getDescription()
43 return _SECURITYENFORCER_DESCRIPTION;
46 public function getMinNucleusVersion()
51 public function getTableList()
53 return array(sql_table('plug_securityenforcer'));
56 public function getEventList()
58 return array('QuickMenu','PrePasswordSet','CustomLogin','LoginSuccess','LoginFailed','PostRegister');
61 public function hasAdminArea() {
65 public function supportsFeature($what)
67 if ( $what == 'SqlTablePrefix' )
74 public function install()
78 // Need to make some options
79 $this->createOption('quickmenu', '_SECURITYENFORCER_OPT_QUICKMENU', 'yesno', 'yes');
80 $this->createOption('del_uninstall_data', '_SECURITYENFORCER_OPT_DEL_UNINSTALL_DATA', 'yesno','no');
81 $this->createOption('enable_security', '_SECURITYENFORCER_OPT_ENABLE', 'yesno','yes');
82 $this->createOption('pwd_min_length', '_SECURITYENFORCER_OPT_PWD_MIN_LENGTH', 'text','8');
83 $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');
84 $this->createOption('max_failed_login', '_SECURITYENFORCER_OPT_MAX_FAILED_LOGIN', 'text', '5');
85 $this->createOption('login_lockout', '_SECURITYENFORCER_OPT_LOGIN_LOCKOUT', 'text', '15');
87 // create needed tables
88 DB::execute('CREATE TABLE IF NOT EXISTS '. sql_table('plug_securityenforcer').
89 " (login varchar(255),
90 fails int(11) NOT NULL default '0',
91 lastfail bigint NOT NULL default '0',
92 KEY login (login)) ENGINE=MyISAM");
96 public function unInstall()
98 // if requested, delete the data table
99 if ( $this->getOption('del_uninstall_data') == 'yes' )
101 DB::execute('DROP TABLE '.sql_table('plug_securityenforcer'));
106 public function init()
108 // include translation file for this plugin
109 if ( file_exists($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php') )
111 include_once($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php');
115 include_once($this->getDirectory().'en_Latn_US.UTF-8.php');
118 $this->enable_security = $this->getOption('enable_security');
119 $this->pwd_min_length = (integer) $this->getOption('pwd_min_length');
120 $this->pwd_complexity = (integer) $this->getOption('pwd_complexity');
121 $this->max_failed_login = (integer) $this->getOption('max_failed_login');
122 $this->login_lockout = (integer) $this->getOption('login_lockout');
126 public function event_QuickMenu(&$data)
128 // only show when option enabled
130 if ( $this->getOption('quickmenu') != 'yes' || !$member->isAdmin() )
135 if ( !($member->isLoggedIn()) )
139 array_push($data['options'],
140 array('title' => 'Security Enforcer',
141 'url' => $this->getAdminURL(),
142 'tooltip' => _SECURITYENFORCER_ADMIN_TOOLTIP));
146 public function event_PrePasswordSet(&$data)
148 //password, errormessage, valid
149 if ( $this->enable_security == 'no' )
153 $password = $data['password'];
154 // conditional below not needed in 3.60 or higher. Used to keep from setting off error when password not being changed
155 if ( postVar('action') == 'changemembersettings' )
157 $emptyAllowed = true;
161 $emptyAllowed = false;
163 if ( (!$emptyAllowed)||$password )
165 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
168 $data['errormessage'] = _SECURITYENFORCER_INSUFFICIENT_COMPLEXITY . $message. "<br /><br />\n";
169 $data['valid'] = false;
175 public function event_PostRegister(&$data)
177 if ( $this->enable_security != 'yes' )
181 $password = postVar('password');
182 if( postVar('action') == 'memberadd' )
184 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
187 $errormessage = _SECURITYENFORCER_ACCOUNT_CREATED. $message. "<br /><br />\n";
189 $admin->error($errormessage);
195 public function event_CustomLogin(&$data)
197 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
202 //login,password,success,allowlocal
204 $login = $data['login'];
205 $ip = $_SERVER['REMOTE_ADDR'];
207 $query = "DELETE FROM %s WHERE lastfail < %d;";
208 $query = sprintf($query, sql_table('plug_securityenforcer'), (integer) (time() - ($this->login_lockout * 60)));
211 $query = "SELECT fails as result FROM %s WHERE login=%s;";
212 $query = sprintf($query, sql_table('plug_securityenforcer'), DB::quoteValue($login));
213 $flogin = DB::getValue($query);
215 $query = "SELECT fails as result FROM %s WHERE login=%s;";
216 $query = sprintf($query, sql_table('plug_securityenforcer'), DB::quoteValue($ip));
217 $fip = DB::getValue($query);
219 if ( $flogin >= $this->max_failed_login || $fip >= $this->max_failed_login )
221 $data['success'] = 0;
222 $data['allowlocal'] = 0;
223 $info = sprintf(_SECURITYENFORCER_LOGIN_DISALLOWED, Entity::hsc($login), Entity::hsc($ip));
224 ActionLog::add(INFO, $info);
229 public function event_LoginSuccess(&$data)
231 //member(obj),username
232 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
237 $login = $data['username'];
238 $ip = $_SERVER['REMOTE_ADDR'];
239 DB::execute('DELETE FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($login));
240 DB::execute('DELETE FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($ip));
244 public function event_LoginFailed(&$data)
247 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
252 $login = $data['username'];
253 $ip = $_SERVER['REMOTE_ADDR'];
254 $lres = DB::getResult('SELECT * FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($login));
255 if ( $lres->rowCount() > 0 )
257 DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($login));
261 DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($login) . ',1,' . time() . ')');
263 $lres = DB::getResult('SELECT * FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($ip));
264 if ( $lres->rowCount() > 0 )
266 DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($ip));
270 DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($ip) . ',1,' . time() . ')');
275 private function validate_and_messsage($passwd,$minlength = 6,$complexity = 0)
277 $minlength = intval($minlength);
278 $complexity = intval($complexity);
281 if ( $minlength < 6 )
285 if ( i18n::strlen($passwd) < $minlength )
287 $message = _SECURITYENFORCER_MIN_PWD_LENGTH . $this->pwd_min_length;
290 if ( $complexity > 4 )
298 $ochars = '[#-~!@\\$%^&*()_+=,.<>?:;|]';
299 $chartypes = array($ucchars, $lcchars, $numchars, $ochars);
300 $tot = array(0,0,0,0);
303 foreach ( $chartypes as $value )
305 $tot[$i] = preg_match("/" . $value . "/", $passwd);
309 if ( array_sum($tot) < $complexity )
311 $message .= _SECURITYENFORCER_PWD_COMPLEXITY . $this->pwd_complexity;