1<?php 2 3/** 4 * Places security information in a security log 5 * The logged data includes: 6 * <ul> 7 * <li>the ip address of the client browser</li> 8 * <li>the type of entry</li> 9 * <li>the user/user name</li> 10 * <li>the success/failure</li> 11 * <li>the <i>authority</i> granting/denying the request</li> 12 * <li>Additional information, for instance on failure, the password used</li> 13 * </ul> 14 * 15 * @author Stephen Billard (sbillard) 16 * @package plugins 17 * @subpackage security-logger 18 */ 19$plugin_is_filter = 100 | CLASS_PLUGIN; 20$plugin_description = gettext('Logs selected security events.'); 21$plugin_author = "Stephen Billard (sbillard)"; 22$plugin_category = gettext('Admin'); 23 24$option_interface = 'security_logger'; 25 26if (getOption('logger_log_admin')) { 27 zp_register_filter('admin_login_attempt', 'security_logger::adminLoginLogger'); 28 zp_register_filter('federated_login_attempt', 'security_logger::federatedLoginLogger'); 29} 30if (getOption('logger_log_guests')) { 31 zp_register_filter('guest_login_attempt', 'security_logger::guestLoginLogger'); 32} 33zp_register_filter('admin_allow_access', 'security_logger::adminGate'); 34zp_register_filter('authorization_cookie', 'security_logger::adminCookie', 0); 35zp_register_filter('admin_managed_albums_access', 'security_logger::adminAlbumGate'); 36zp_register_filter('save_user', 'security_logger::UserSave'); 37zp_register_filter('admin_XSRF_access', 'security_logger::admin_XSRF_access'); 38zp_register_filter('admin_log_actions', 'security_logger::log_action'); 39zp_register_filter('log_setup', 'security_logger::log_setup'); 40zp_register_filter('security_misc', 'security_logger::security_misc'); 41 42/** 43 * Option handler class 44 * 45 */ 46class security_logger { 47 48 /** 49 * class instantiation function 50 * 51 * @return security_logger 52 */ 53 function __construct() { 54 global $plugin_is_filter; 55 if (OFFSET_PATH == 2) { 56 setOptionDefault('zp_plugin_security-logger', $plugin_is_filter); 57 setOptionDefault('logger_log_guests', 1); 58 setOptionDefault('logger_log_admin', 1); 59 setOptionDefault('logger_log_type', 'all'); 60 setOptionDefault('logge_access_log_type', 'all_user'); 61 setOptionDefault('security_log_size', 5000000); 62 } 63 } 64 65 /** 66 * Reports the supported options 67 * 68 * @return array 69 */ 70 function getOptionsSupported() { 71 return array(gettext('Record logon attempts of') => array('key' => 'logger_log_allowed', 'type' => OPTION_TYPE_CHECKBOX_ARRAY, 72 'checkboxes' => array(gettext('Administrators') => 'logger_log_admin', gettext('Guests') => 'logger_log_guests'), 73 'desc' => gettext('If checked login attempts will be logged.')), 74 gettext('Record failed admin access') => array('key' => 'logge_access_log_type', 'type' => OPTION_TYPE_RADIO, 75 'buttons' => array(gettext('All attempts') => 'all', gettext('Only user attempts') => 'all_user'), 76 'desc' => gettext('Record admin page access failures.')), 77 gettext('Record logon') => array('key' => 'logger_log_type', 'type' => OPTION_TYPE_RADIO, 78 'buttons' => array(gettext('All attempts') => 'all', gettext('Successful attempts') => 'success', gettext('unsuccessful attempts') => 'fail'), 79 'desc' => gettext('Record login failures, successes, or all attempts.')) 80 ); 81 } 82 83 function handleOption($option, $currentValue) { 84 85 } 86 87 /** 88 * Does the log handling 89 * 90 * @param int $success 91 * @param string $user 92 * @param string $name 93 * @param string $ip 94 * @param string $type 95 * @param string $authority kind of login 96 * @param string $addl more info 97 */ 98 private static function Logger($success, $user, $name, $action, $authority, $addl = NULL) { 99 global $_zp_authority, $_zp_mutex; 100 $pattern = '~^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$~'; 101 $forwardedIP = NULL; 102 $ip = sanitize($_SERVER['REMOTE_ADDR']); 103 if (!preg_match($pattern, $ip)) { 104 $ip = NULL; 105 } else { 106 $ip = getAnonymIP($ip); 107 } 108 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 109 $forwardedIP = sanitize($_SERVER['HTTP_X_FORWARDED_FOR']); 110 if (preg_match($pattern, $forwardedIP)) { 111 $ip .= ' {' . getAnonymIP($forwardedIP) . '}'; 112 } 113 } 114 $admin = $_zp_authority->getMasterUser(); 115 $locale = $admin->getLanguage(); 116 if (empty($locale)) { 117 $locale = 'en_US'; 118 } 119 $cur_locale = getUserLocale(); 120 setupCurrentLocale($locale); // the log will be in the language of the master user. 121 switch ($action) { 122 case 'clear_log': 123 $type = gettext('Log reset'); 124 break; 125 case 'delete_log': 126 $type = gettext('Log deleted'); 127 break; 128 case 'download_log': 129 $type = gettext('Log downloaded'); 130 break; 131 case 'setup_install': 132 $type = gettext('Install'); 133 $addl = gettext('version') . ' ' . ZENPHOTO_VERSION; 134 if (!hasPrimaryScripts()) { 135 $addl .= ' ' . gettext('clone'); 136 } 137 break; 138 case 'setup_ignore_setup': 139 $type = gettext('Setup run request skipped.'); 140 break; 141 case 'setup_protect': 142 $type = gettext('Protect setup scripts'); 143 break; 144 case 'user_new': 145 $type = gettext('Request add user'); 146 break; 147 case 'user_update': 148 $type = gettext('Request update user'); 149 break; 150 case 'user_delete': 151 $type = gettext('Request delete user'); 152 break; 153 case 'XSRF_blocked': 154 $type = gettext('Cross Site Reference'); 155 break; 156 case 'blocked_album': 157 $type = gettext('Album access'); 158 break; 159 case 'blocked_access': 160 $type = gettext('Admin access'); 161 break; 162 case 'Front-end': 163 $type = gettext('Guest login'); 164 break; 165 case 'Back-end': 166 $type = gettext('Admin login'); 167 break; 168 case 'auth_cookie': 169 $type = gettext('Authorization cookie check'); 170 break; 171 default: 172 $type = $action; 173 break; 174 } 175 176 $file = SERVERPATH . '/' . DATA_FOLDER . '/security.log'; 177 $max = getOption('security_log_size'); 178 $_zp_mutex->lock(); 179 if ($max && @filesize($file) > $max) { 180 switchLog('security'); 181 } 182 $preexists = file_exists($file) && filesize($file) > 0; 183 $f = fopen($file, 'a'); 184 if ($f) { 185 if (!$preexists) { // add a header 186 fwrite($f, gettext('date' . "\t" . 'requestor’s IP' . "\t" . 'type' . "\t" . 'user ID' . "\t" . 'user name' . "\t" . 'outcome' . "\t" . 'authority' . "\tadditional information\n")); 187 } 188 $message = date('Y-m-d H:i:s') . "\t"; 189 $message .= $ip . "\t"; 190 $message .= $type . "\t"; 191 $message .= $user . "\t"; 192 $message .= $name . "\t"; 193 switch ($success) { 194 case 0: 195 $message .= gettext("Failed") . "\t"; 196 break; 197 case 1: 198 $message .= gettext("Success") . "\t"; 199 $message .= substr($authority, 0, strrpos($authority, '_auth')); 200 break; 201 case 2: 202 $message .= gettext("Blocked") . "\t"; 203 break; 204 default: 205 $message .= $success . "\t"; 206 } 207 if ($addl) { 208 $message .= "\t" . $addl; 209 } 210 fwrite($f, $message . "\n"); 211 fclose($f); 212 clearstatcache(); 213 if (!$preexists) { 214 @chmod($file, LOGS_MOD); 215 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { 216 $permission = fileperms($file) & 0700; // on Windows owner==group==public 217 $check = $permission != LOGS_MOD; 218 } else { 219 $permission = fileperms($file) & 0777; 220 $check = $permission != LOGS_MOD; 221 } 222 if ($check) { 223 $f = fopen($file, 'a'); 224 fwrite($f, "\t\t" . gettext('Set Security log permissions') . "\t\t\t" . gettext('Failed') . "\t\t" . sprintf(gettext('File permissions of Security log are %04o'), $permission) . "\n"); 225 fclose($f); 226 clearstatcache(); 227 } 228 } 229 } 230 $_zp_mutex->unlock(); 231 setupCurrentLocale($cur_locale); // restore to whatever was in effect. 232 } 233 234 /** 235 * returns the user id and name of the logged in user 236 */ 237 private static function populate_user() { 238 global $_zp_current_admin_obj; 239 if (is_object($_zp_current_admin_obj)) { 240 $user = $_zp_current_admin_obj->getUser(); 241 $name = $_zp_current_admin_obj->getName(); 242 } else { 243 $user = $name = ''; 244 } 245 return array($user, $name); 246 } 247 248 /** 249 * Logs an attempt to log onto the back-end or as an admin user 250 * Returns the rights to grant 251 * 252 * @param int $success the admin rights granted 253 * @param string $user 254 * @param string $pass 255 * @return int 256 */ 257 static function adminLoginLogger($success, $user, $pass, $auth = 'zp_admin_auth') { 258 switch (getOption('logger_log_type')) { 259 case 'all': 260 break; 261 case 'success': 262 if (!$success) 263 return false; 264 break; 265 case 'fail': 266 if ($success) 267 return true; 268 break; 269 } 270 $name = ''; 271 if ($success) { 272 $admin = Zenphoto_Authority::getAnAdmin(array('`user`=' => $user, '`valid`=' => 1)); 273 $pass = ''; // mask it from display 274 if (is_object($admin)) { 275 $name = $admin->getName(); 276 } 277 } 278 security_logger::Logger((int) ($success && true), $user, $name, 'Back-end', $auth, null); 279 return $success; 280 } 281 282 /** 283 * Logs an attempt to log on via the federated_logon plugin 284 * Returns the rights to grant 285 * 286 * @param int $success the admin rights granted 287 * @param string $user 288 * @param string $pass 289 * @return int 290 */ 291 static function federatedLoginLogger($success, $user) { 292 return security_logger::adminLoginLogger($success, $user, 'n/a', 'federated_logon_auth'); 293 } 294 295 /** 296 * Logs an attempt for a guest user to log onto the site 297 * Returns the "success" parameter. 298 * 299 * @param bool $success 300 * @param string $user 301 * @param string $pass 302 * @param string $athority what kind of login 303 * @return bool 304 */ 305 static function guestLoginLogger($success, $user, $pass, $athority) { 306 switch (getOption('logger_log_type')) { 307 case 'all': 308 break; 309 case 'success': 310 if (!$success) 311 return false; 312 break; 313 case 'fail': 314 if ($success) 315 return true; 316 break; 317 } 318 $name = ''; 319 if ($success) { 320 $admin = Zenphoto_Authority::getAnAdmin(array('`user`=' => $user, '`valid`=' => 1)); 321 $pass = ''; // mask it from display 322 if (is_object($admin)) { 323 $name = $admin->getName(); 324 } 325 } 326 security_logger::Logger((int) ($success && true), $user, $name, 'Front-end', $athority, null); 327 return $success; 328 } 329 330 /** 331 * Logs blocked accesses to Admin pages 332 * @param bool $allow set to true to override the block 333 * @param string $page the "return" link 334 */ 335 static function adminGate($allow, $page) { 336 list($user, $name) = security_logger::populate_user(); 337 switch (getOption('logger_log_type')) { 338 case 'all': 339 break; 340 case 'all_user': 341 if (!$user) 342 return $allow; 343 break; 344 } 345 security_logger::Logger(0, $user, $name, 'blocked_access', '', $page); 346 return $allow; 347 } 348 349 static function adminCookie($allow, $auth, $id) { 350 if (!$allow && $auth) { 351 switch (getOption('logger_log_type')) { 352 case 'all': 353 case 'fail': 354 security_logger::Logger(0, NULL, NULL, 'auth_cookie', '', $id . ':' . $auth); 355 } 356 } 357 return $allow; 358 } 359 360 /** 361 * Logs blocked accesses to Managed albums 362 * @param bool $allow set to true to override the block 363 * @param string $page the "return" link 364 */ 365 static function adminAlbumGate($allow, $page) { 366 list($user, $name) = security_logger::populate_user(); 367 switch (getOption('logger_log_type')) { 368 case 'all': 369 break; 370 case 'all_user': 371 if (!$user) 372 return $allow; 373 break; 374 } 375 if (!$allow) 376 security_logger::Logger(2, $user, $name, 'blocked_album', '', $page); 377 return $allow; 378 } 379 380 /** 381 * logs attempts to save on the user tab 382 * @param string $discard 383 * @param object $userobj user object upon which the save was targeted 384 * @param string $class what the action was. 385 */ 386 static function UserSave($discard, $userobj, $class) { 387 list($user, $name) = security_logger::populate_user(); 388 security_logger::Logger(1, $user, $name, 'user_' . $class, 'zp_admin_auth', $userobj->getUser()); 389 return $discard; 390 } 391 392 /** 393 * Loggs Cross Site Request Forgeries 394 * 395 * @param bool $discard 396 * @param string $token 397 * @return bool 398 */ 399 static function admin_XSRF_access($discard, $token) { 400 list($user, $name) = security_logger::populate_user(); 401 security_logger::Logger(2, $user, $name, 'XSRF_blocked', '', $token); 402 return false; 403 } 404 405 /** 406 * logs security log actions 407 * @param bool $allow 408 * @param string $log 409 * @param string $action 410 */ 411 static function log_action($allow, $log, $action) { 412 list($user, $name) = security_logger::populate_user(); 413 security_logger::Logger((int) ($allow && true), $user, $name, $action, 'zp_admin_auth', basename($log)); 414 return $allow; 415 } 416 417 /** 418 * Logs setup actions 419 * @param bool $success 420 * @param string $action 421 * @param string $file 422 */ 423 static function log_setup($success, $action, $txt) { 424 list($user, $name) = security_logger::populate_user(); 425 security_logger::Logger((int) ($success && true), $user, $name, 'setup_' . $action, 'zp_admin_auth', $txt); 426 return $success; 427 } 428 429 /** 430 * Catch all logger for miscellaneous security records 431 * @param bool $success 432 * @param string $requestor 433 * @param string $auth 434 * @param string $txt 435 */ 436 static function security_misc($success, $requestor, $auth, $txt) { 437 security_logger::Logger((int) ($success && true), NULL, NULL, $requestor, 'zp_admin_auth', $txt); 438 return $success; 439 } 440 441} 442 443?>