OSDN Git Service

FIX:NP_SecurityEnforcerのpreg_matchのデリミタを変更して「#」のエスケープを削除
[nucleus-jp/nucleus-next.git] / nucleus / plugins / NP_SecurityEnforcer.php
1 <?php
2 /*
3 License:
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.
7
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
11 later version.
12
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.
16
17 * @version $Id: NP_SecurityEnforcer.php 1721 2012-03-31 10:18:25Z sakamocchi $
18 */
19 class NP_SecurityEnforcer extends NucleusPlugin
20 {
21
22         public function getName()
23         {
24                 return 'SecurityEnforcer';
25         }
26
27         public function getAuthor()
28         {
29                 return 'Frank Truscott + Cacher + Mocchi';
30         }
31
32         public function getURL()
33         {
34                 return 'http://revcetera.com/ftruscot';
35         }
36
37         public function getVersion()
38         {
39                 return '1.02';
40         }
41
42         public function getDescription()
43         {
44                 return _SECURITYENFORCER_DESCRIPTION;
45         }
46
47         public function getMinNucleusVersion()
48         {
49                 return 400;
50         }
51
52         public function getTableList()
53         {
54                 return array(sql_table('plug_securityenforcer'));
55         }
56
57         public function getEventList()
58         {
59                 return array('QuickMenu','PrePasswordSet','CustomLogin','LoginSuccess','LoginFailed','PostRegister');
60         }
61
62         public function hasAdminArea() {
63                 return 1;
64         }
65         
66         public function supportsFeature($what)
67         {
68                 if ( $what == 'SqlTablePrefix' )
69                 {
70                         return 1;
71                 }
72                 return 0;
73         }
74         
75         public function install()
76         {
77                 global $CONF;
78                 
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');
87                 
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");
94                 return;
95         }
96         
97         public function unInstall()
98         {
99                 // if requested, delete the data table
100                 if ( $this->getOption('del_uninstall_data') == 'yes' )
101                 {
102                         DB::execute('DROP TABLE '.sql_table('plug_securityenforcer'));
103                 }
104                 return;
105         }
106         
107         public function init()
108         {
109                 // include translation file for this plugin
110                 if ( file_exists($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php') )
111                 {
112                         include_once($this->getDirectory() . i18n::get_current_locale() . '.' . i18n::get_current_charset() . '.php');
113                 }
114                 else
115                 {
116                         include_once($this->getDirectory().'en_Latn_US.UTF-8.php');
117                 }
118                 
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');
124                 return;
125         }
126         
127         public function event_QuickMenu($data)
128         {
129                 // only show when option enabled
130                 global $member;
131                 if ( $this->getOption('quickmenu') != 'yes' || !$member->isAdmin() )
132                 {
133                         return;
134                 }
135                 //global $member;
136                 if ( !($member->isLoggedIn()) )
137                 {
138                         return;
139                 }
140                 array_push($data['options'],
141                         array('title' => 'Security Enforcer',
142                         'url' => $this->getAdminURL(),
143                         'tooltip' => _SECURITYENFORCER_ADMIN_TOOLTIP));
144                 return;
145         }
146         
147         public function event_PrePasswordSet($data)
148         {
149                 //password, errormessage, valid
150                 if ( $this->enable_security == 'no' )
151                 {
152                         return;
153                 }
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' )
157                 {
158                         $emptyAllowed = true;
159                 }
160                 else
161                 {
162                         $emptyAllowed = false;
163                 }
164                 if ( (!$emptyAllowed)||$password )
165                 {
166                         $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
167                         if ( $message )
168                         {
169                                 $data['errormessage'] = _SECURITYENFORCER_INSUFFICIENT_COMPLEXITY . $message. "<br /><br />\n";
170                                 $data['valid'] = false;
171                         }
172                 }
173                 return;
174         }
175         
176         public function event_PostRegister($data)
177         {
178                 if ( $this->enable_security != 'yes' )
179                 {
180                         return;
181                 }
182                 $password = postVar('password');
183                 if( postVar('action') == 'memberadd' )
184                 {
185                         $message = $this->validate_and_messsage($password,$this->pwd_min_length, $this->pwd_complexity);
186                         if ( $message )
187                         {
188                                 $errormessage = _SECURITYENFORCER_ACCOUNT_CREATED. $message. "<br /><br />\n";
189                                 global $admin;
190                                 $admin->error($errormessage);
191                         }
192                 }
193                 return;
194         }
195         
196         public function event_CustomLogin($data)
197         {
198                 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
199                 {
200                         return;
201                 }
202                 
203                 //login,password,success,allowlocal
204                 global $_SERVER;
205                 $login = $data['login'];
206                 $ip = $_SERVER['REMOTE_ADDR'];
207                 
208                 $query = "DELETE FROM %s WHERE lastfail < %d;";
209                 $query = sprintf($query, sql_table('plug_securityenforcer'), (integer) (time() - ($this->login_lockout * 60)));
210                 DB::execute($query);
211                 
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); 
215                 
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); 
219                 
220                 if ( $flogin >= $this->max_failed_login || $fip >= $this->max_failed_login )
221                 {
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);
226                 }
227                 return;
228         }
229         
230         public function event_LoginSuccess($data)
231         {
232                 //member(obj),username
233                 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
234                 {
235                         return;
236                 }
237                 global $_SERVER;
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));
242                 return;
243         }
244         
245         public function event_LoginFailed($data)
246         {
247                 //username
248                 if ( $this->enable_security != 'yes' || $this->max_failed_login <= 0 )
249                 {
250                         return;
251                 }
252                 global $_SERVER;
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 )
257                 {
258                         DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($login));
259                 }
260                 else
261                 {
262                         DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($login) . ',1,' . time() . ')');
263                 }
264                 $lres = DB::getResult('SELECT * FROM ' . sql_table('plug_securityenforcer') . ' WHERE login=' . DB::quoteValue($ip));
265                 if ( $lres->rowCount() > 0 )
266                 {
267                         DB::execute('UPDATE ' . sql_table('plug_securityenforcer') . ' SET fails=fails+1, lastfail=' . time() . ' WHERE login=' . DB::quoteValue($ip));
268                 }
269                 else
270                 {
271                         DB::execute('INSERT INTO ' . sql_table('plug_securityenforcer') . ' (login,fails,lastfail) VALUES (' . DB::quoteValue($ip) . ',1,' . time() . ')');
272                 }
273                 return;
274         }
275         
276         private function validate_and_messsage($passwd,$minlength = 6,$complexity = 0)
277         {
278                 $minlength = intval($minlength);
279                 $complexity = intval($complexity);
280                 $message = '';
281                 
282                 if ( $minlength < 6 )
283                 {
284                         $minlength = 6;
285                 }
286                 if ( i18n::strlen($passwd) < $minlength )
287                 {
288                         $message = _SECURITYENFORCER_MIN_PWD_LENGTH . $this->pwd_min_length;
289                 }
290                 
291                 if ( $complexity > 4 )
292                 {
293                         $complexity = 4;
294                 }
295                 
296                 $ucchars        = '[A-Z]';
297                 $lcchars        = '[a-z]';
298                 $numchars       = '[0-9]';
299                 $ochars         = '[#-~!@\\$%^&*()_+=,.<>?:;|]';
300                 $chartypes      = array($ucchars, $lcchars, $numchars, $ochars);
301                 $tot            = array(0,0,0,0);
302                 $i                      = 0;
303                 
304                 foreach ( $chartypes as $value )
305                 {
306                         $tot[$i] = preg_match("/" . $value . "/", $passwd);
307                         $i = $i + 1;
308                 }
309                 
310                 if ( array_sum($tot) < $complexity )
311                 {
312                         $message .= _SECURITYENFORCER_PWD_COMPLEXITY . $this->pwd_complexity;
313                 }
314                 return $message;
315         }
316 }