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 class NP_SecurityEnforcer extends NucleusPlugin
19 public function getName() { return 'SecurityEnforcer'; }
20 public function getAuthor() { return 'Frank Truscott + Cacher + Mocchi'; }
21 public function getURL() { return 'http://revcetera.com/ftruscot'; }
22 public function getVersion() { return '1.02'; }
23 public function getDescription() { return _SECURITYENFORCER_DESCRIPTION; }
24 public function getMinNucleusVersion() { return 400; }
25 public function getTableList() { return array(sql_table('plug_securityenforcer')); }
26 public function getEventList() { return array('QuickMenu','PrePasswordSet','CustomLogin','LoginSuccess','LoginFailed','PostRegister'); }
27 public function hasAdminArea() { return 1; }
29 public function supportsFeature($what)
31 if ( $what == 'SqlTablePrefix' )
38 public function install()
42 // Need to make some options
43 $this->createOption('quickmenu', '_SECURITYENFORCER_OPT_QUICKMENU', 'yesno', 'yes');
44 $this->createOption('del_uninstall_data', '_SECURITYENFORCER_OPT_DEL_UNINSTALL_DATA', 'yesno','no');
45 $this->createOption('enable_security', '_SECURITYENFORCER_OPT_ENABLE', 'yesno','yes');
46 $this->createOption('pwd_min_length', '_SECURITYENFORCER_OPT_PWD_MIN_LENGTH', 'text','8');
47 $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');
48 $this->createOption('max_failed_login', '_SECURITYENFORCER_OPT_MAX_FAILED_LOGIN', 'text', '5');
49 $this->createOption('login_lockout', '_SECURITYENFORCER_OPT_LOGIN_LOCKOUT', 'text', '15');
51 // create needed tables
52 sql_query("CREATE TABLE IF NOT EXISTS ". sql_table('plug_securityenforcer').
53 " (`login` varchar(255),
54 `fails` int(11) NOT NULL default '0',
55 `lastfail` bigint NOT NULL default '0',
56 KEY `login` (`login`)) ENGINE=MyISAM");
60 public function unInstall()
62 // if requested, delete the data table
63 if ( $this->getOption('del_uninstall_data') == 'yes' )
65 sql_query('DROP TABLE '.sql_table('plug_securityenforcer'));
70 public function init()
72 // include language file for this plugin
73 if ( file_exists($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php') )
75 include_once($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php');
79 include_once($this->getDirectory().'en_Latn_US.UTF-8.php');
82 $this->enable_security = $this->getOption('enable_security');
83 $this->pwd_min_length = (integer) $this->getOption('pwd_min_length');
84 $this->pwd_complexity = (integer) $this->getOption('pwd_complexity');
85 $this->max_failed_login = (integer) $this->getOption('max_failed_login');
86 $this->login_lockout = (integer) $this->getOption('login_lockout');
90 public function event_QuickMenu($data)
92 // only show when option enabled
94 if ( $this->getOption('quickmenu') != 'yes' || !$member->isAdmin() )
99 if ( !($member->isLoggedIn()) )
103 array_push($data['options'],
104 array('title' => 'Security Enforcer',
105 'url' => $this->getAdminURL(),
106 'tooltip' => _SECURITYENFORCER_ADMIN_TOOLTIP));
110 public function event_PrePasswordSet($data)
112 //password, errormessage, valid
113 if ( $this->enable_security == 'no' )
117 $password = $data['password'];
118 // conditional below not needed in 3.60 or higher. Used to keep from setting off error when password not being changed
119 if ( postVar('action') == 'changemembersettings' )
121 $emptyAllowed = true;
125 $emptyAllowed = false;
127 if ( (!$emptyAllowed)||$password )
129 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
132 $data['errormessage'] = _SECURITYENFORCER_INSUFFICIENT_COMPLEXITY . $message. "<br /><br />\n";
133 $data['valid'] = false;
139 public function event_PostRegister($data)
141 if ( $this->enable_security != 'yes' )
145 $password = postVar('password');
146 if( postVar('action') == 'memberadd' )
148 $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
151 $errormessage = _SECURITYENFORCER_ACCOUNT_CREATED. $message. "<br /><br />\n";
153 $admin->error($errormessage);
159 public function event_CustomLogin($data)
161 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
166 //login,password,success,allowlocal
168 $login = $data['login'];
169 $ip = $_SERVER['REMOTE_ADDR'];
170 sql_query("DELETE FROM ".sql_table('plug_securityenforcer')." WHERE lastfail < ".(time() - ($this->login_lockout * 60)));
171 $query = "SELECT fails as result FROM ".sql_table('plug_securityenforcer')." ";
172 $query .= "WHERE login='".sql_real_escape_string($login)."'";
173 $flogin = quickQuery($query);
174 $query = "SELECT fails as result FROM ".sql_table('plug_securityenforcer')." ";
175 $query .= "WHERE login='".sql_real_escape_string($ip)."'";
176 $fip = quickQuery($query);
178 if ( $flogin >= $this->max_failed_login || $fip >= $this->max_failed_login )
180 $data['success'] = 0;
181 $data['allowlocal'] = 0;
182 $info = sprintf(_SECURITYENFORCER_LOGIN_DISALLOWED, ENTITY::hsc($login), ENTITY::hsc($ip));
183 ACTIONLOG::add(INFO, $info);
188 public function event_LoginSuccess($data)
190 //member(obj),username
191 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
196 $login = $data['username'];
197 $ip = $_SERVER['REMOTE_ADDR'];
198 sql_query("DELETE FROM ".sql_table('plug_securityenforcer')." WHERE login='".sql_real_escape_string($login)."'");
199 sql_query("DELETE FROM ".sql_table('plug_securityenforcer')." WHERE login='".sql_real_escape_string($ip)."'");
203 public function event_LoginFailed($data)
206 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
211 $login = $data['username'];
212 $ip = $_SERVER['REMOTE_ADDR'];
213 $lres = sql_query("SELECT * FROM ".sql_table('plug_securityenforcer')." WHERE login='".sql_real_escape_string($login)."'");
214 if ( sql_num_rows($lres) )
216 sql_query("UPDATE ".sql_table('plug_securityenforcer')." SET fails=fails+1, lastfail=".time()." WHERE login='".sql_real_escape_string($login)."'");
220 sql_query("INSERT INTO ".sql_table('plug_securityenforcer')." (login,fails,lastfail) VALUES ('".sql_real_escape_string($login)."',1,".time().")");
222 $lres = sql_query("SELECT * FROM ".sql_table('plug_securityenforcer')." WHERE login='".sql_real_escape_string($ip)."'");
223 if ( sql_num_rows($lres) )
225 sql_query("UPDATE ".sql_table('plug_securityenforcer')." SET fails=fails+1, lastfail=".time()." WHERE login='".sql_real_escape_string($ip)."'");
229 sql_query("INSERT INTO ".sql_table('plug_securityenforcer')." (login,fails,lastfail) VALUES ('".sql_real_escape_string($ip)."',1,".time().")");
234 private function validate_and_messsage($passwd,$minlength = 6,$complexity = 0)
236 $minlength = intval($minlength);
237 $complexity = intval($complexity);
239 if ( $minlength < 6 )
243 if ( i18n::strlen($passwd) < $minlength )
245 $message = _SECURITYENFORCER_MIN_PWD_LENGTH . $this->pwd_min_length;
248 if ( $complexity > 4 )
256 $ochars = "[-~!@#$%^&*()_+=,.<>?:;|]";
257 $chartypes = array($ucchars, $lcchars, $numchars, $ochars);
258 $tot = array(0,0,0,0);
261 foreach ( $chartypes as $value )
263 $tot[$i] = preg_match("#{$value}#", $passwd);
267 if ( array_sum($tot) < $complexity )
269 $message .= _SECURITYENFORCER_PWD_COMPLEXITY . $this->pwd_complexity;