Added a ton of new log points for administrator actions; restructured security log view and enabled pagination for security logs; string change in ajax.php for RDNS operation failure
authorDan
Wed, 29 Aug 2007 23:35:06 -0400
changeset 109 93ef7df77847
parent 108 1c7f59df9474
child 110 68e030f8dfa6
Added a ton of new log points for administrator actions; restructured security log view and enabled pagination for security logs; string change in ajax.php for RDNS operation failure
ajax.php
includes/paths.php
plugins/SpecialAdmin.php
plugins/admin/SecurityLog.php
--- a/ajax.php	Wed Aug 29 18:23:37 2007 -0400
+++ b/ajax.php	Wed Aug 29 23:35:06 2007 -0400
@@ -188,7 +188,7 @@
       if(!$session->get_permissions('mod_misc')) die('Go somewhere else for your reverse DNS info!');
       $ip = $_GET['ip'];
       $rdns = gethostbyaddr($ip);
-      if($rdns == $ip) echo 'Unable to get reverse DNS information. Perhaps the IP address does not exist anymore.';
+      if($rdns == $ip) echo 'Unable to get reverse DNS information. Perhaps the DNS server is down or the PTR record no longer exists.';
       else echo $rdns;
       break;
     case 'acljson':
--- a/includes/paths.php	Wed Aug 29 18:23:37 2007 -0400
+++ b/includes/paths.php	Wed Aug 29 23:35:06 2007 -0400
@@ -89,8 +89,9 @@
     $this->addAdminNode('Users', 'Manage users', 'UserManager');
     $this->addAdminNode('Users', 'Edit groups', 'GroupManager');
     $this->addAdminNode('Users', 'COPPA support', 'COPPA');
-    $this->addAdminNode('Users', 'Ban control', 'BanControl');
     $this->addAdminNode('Users', 'Mass e-mail', 'MassEmail');
+    $this->addAdminNode('Security', 'Security log', 'SecurityLog');
+    $this->addAdminNode('Security', 'Ban control', 'BanControl');
     
     $code = $plugins->setHook('acl_rule_init');
     foreach ( $code as $cmd )
--- a/plugins/SpecialAdmin.php	Wed Aug 29 18:23:37 2007 -0400
+++ b/plugins/SpecialAdmin.php	Wed Aug 29 23:35:06 2007 -0400
@@ -41,6 +41,7 @@
 
 // Admin pages that were too enormous to be in this file were split off into the plugins/admin/ directory in 1.0.1
 require(ENANO_ROOT . '/plugins/admin/PageGroups.php');
+require(ENANO_ROOT . '/plugins/admin/SecurityLog.php');
 
 // function names are IMPORTANT!!! The name pattern is: page_<namespace ID>_<page URLname, without namespace>
 
@@ -104,61 +105,10 @@
   
   // Security log
   echo '<h3>Security log</h3>';
-  echo '<div class="tblholder" style="/* max-height: 500px; clip: rect(0px,auto,auto,0px); overflow: auto; */"><table border="0" cellspacing="1" cellpadding="4" width="100%">';
-  $cls = 'row2';                                                                                               
-  echo '<tr><th style="width: 60%;">Type</th><th>Date</th><th>Username</th><th>IP Address</th></tr>';
-  require('config.php');
-  $hash = md5($dbpasswd);
-  unset($dbname, $dbhost, $dbuser, $dbpasswd);
-  unset($dbname, $dbhost, $dbuser, $dbpasswd); // PHP5 Zend bug
-  if ( defined('ENANO_DEMO_MODE') && !isset($_GET[ $hash ]) && substr($_SERVER['REMOTE_ADDR'], 0, 8) != '192.168.' )
-  {
-    echo '<tr><td class="row1" colspan="4">Logs are recorded but not displayed for privacy purposes in the demo.</td></tr>';
-  }
-  else
-  {
-    if(isset($_GET['fulllog']))
-    {
-      $l = 'SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC;';
-    }
-    else
-    {
-      $l = 'SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC LIMIT 5';
-    }
-    $q = $db->sql_query($l);
-    while($r = $db->fetchrow())
-    {
-      if ( $r['action'] == 'illegal_page' )
-      {
-        list($illegal_id, $illegal_ns) = unserialize($r['page_text']);
-        $url = makeUrlNS($illegal_ns, $illegal_id, false, true);
-        $title = get_page_title_ns($illegal_id, $illegal_ns);
-        $class = ( isPage($paths->nslist[$illegal_ns] . $illegal_id) ) ? '' : ' class="wikilink-nonexistent"';
-        $illegal_link = '<a href="' . $url . '"' . $class . ' onclick="window.open(this.href); return false;">' . $title . '</a>';
-      }
-      if($cls == 'row2') $cls = 'row1';
-      else $cls = 'row2';
-      echo '<tr><td class="'.$cls.'">';
-      switch($r['action'])
-      {
-        case "admin_auth_good": echo 'Successful elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); echo "<br /><small>Authentication level: $level</small>"; } break;
-        case "admin_auth_bad":  echo 'Failed elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); echo "<br /><small>Attempted auth level: $level</small>"; } break;
-        case "activ_good": echo 'Successful account activation'; break;
-        case "auth_good": echo 'Successful regular user logon'; break;
-        case "activ_bad": echo 'Failed account activation'; break;
-        case "auth_bad": echo 'Failed regular user logon'; break;
-        case "sql_inject": echo 'SQL injection attempt<div style="max-width: 90%; clip: rect(0px,auto,auto,0px); overflow: auto; display: block; font-size: smaller;">Offending query: ' . htmlspecialchars($r['page_text']) . '</div>'; break;
-        case "db_backup": echo 'Database backup created<br /><small>Tables: ' . $r['page_text'] . '</small>'; break;
-        case "install_enano": echo "Installed Enano version {$r['page_text']}"; break;
-        case "upgrade_enano": echo "Upgraded Enano to version {$r['page_text']}"; break;
-        case "illegal_page": echo "Unauthorized viewing attempt<br /><small>Page: {$illegal_link}</small>"; break;
-      }
-      echo '</td><td class="'.$cls.'">'.date('d M Y h:i a', $r['time_id']).'</td><td class="'.$cls.'">'.$r['author'].'</td><td class="'.$cls.'" style="cursor: pointer;" onclick="ajaxReverseDNS(this);" title="Click for reverse DNS info">'.$r['edit_summary'].'</td></tr>';
-    }
-    $db->free_result();
-  }
-  echo '</table></div>';
-  if(!isset($_GET['fulllog'])) echo '<p><a href="#" onclick="ajaxPage(\''.$paths->nslist['Admin'].'Home&amp;fulllog\'); return false;">Full security log</a></p>';
+  $seclog = get_security_log(5);
+  echo $seclog;
+  
+  echo '<p><a href="#" onclick="ajaxPage(\''.$paths->nslist['Admin'].'SecurityLog\'); return false;">Full security log</a></p>';
   
 }
 
@@ -488,12 +438,69 @@
   
   if(isset($_POST['save']))
   {
-    if(isset($_POST['enable_uploads'])) setConfig('enable_uploads', '1'); else setConfig('enable_uploads', '0');
-    if(isset($_POST['enable_imagemagick'])) setConfig('enable_imagemagick', '1'); else setConfig('enable_imagemagick', '0');
-    if(isset($_POST['cache_thumbs'])) setConfig('cache_thumbs', '1'); else setConfig('cache_thumbs', '0');
-    if(isset($_POST['file_history'])) setConfig('file_history', '1'); else setConfig('file_history', '0');
-    if(file_exists($_POST['imagemagick_path'])) setConfig('imagemagick_path', $_POST['imagemagick_path']);
-    else echo '<span style="color: red"><b>Warning:</b> the file "'.$_POST['imagemagick_path'].'" was not found, and the ImageMagick file path was not updated.</span>';
+    if(isset($_POST['enable_uploads']) && getConfig('enable_uploads') != '1')
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","upload_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('enable_uploads', '1');
+    }
+    else if ( !isset($_POST['enable_uploads']) && getConfig('enable_uploads') == '1' )
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","upload_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('enable_uploads', '0');
+    }
+    if(isset($_POST['enable_imagemagick']) && getConfig('enable_imagemagick') != '1')
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","magick_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('enable_imagemagick', '1');
+    }
+    else if ( !isset($_POST['enable_imagemagick']) && getConfig('enable_imagemagick') == '1' )
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","magick_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('enable_imagemagick', '0');
+    }
+    if(isset($_POST['cache_thumbs']))
+    {
+      setConfig('cache_thumbs', '1');
+    }
+    else
+    {
+      setConfig('cache_thumbs', '0');
+    }
+    if(isset($_POST['file_history']) && getConfig('file_history') != '1' )
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","filehist_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('file_history', '1');
+    }
+    else if ( !isset($_POST['file_history']) && getConfig('file_history') == '1' )
+    {
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","filehist_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('file_history', '0');
+    }
+    if(file_exists($_POST['imagemagick_path']) && $_POST['imagemagick_path'] != getConfig('imagemagick_path'))
+    {
+      $old = getConfig('imagemagick_path');
+      $oldnew = "{$old}||{$_POST['imagemagick_path']}";
+      $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","magick_path",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($oldnew) . '");');
+      if ( !$q )
+        $db->_die();
+      setConfig('imagemagick_path', $_POST['imagemagick_path']);
+    }
+    else if ( $_POST['imagemagick_path'] != getConfig('imagemagick_path') )
+    {
+      echo '<span style="color: red"><b>Warning:</b> the file "'.htmlspecialchars($_POST['imagemagick_path']).'" was not found, and the ImageMagick file path was not updated.</span>';
+    }
     $max_upload = floor((float)$_POST['max_file_size'] * (int)$_POST['fs_units']);
     if ( $max_upload > 1048576 && defined('ENANO_DEMO_MODE') )
     {
@@ -531,7 +538,7 @@
   <p>Lastly, you can choose whether file history will be saved. If this option is turned on, you will be able to roll back any malicious
      changes made to uploaded files, but this requires a significant amount of database storage. You should probably leave this option
      enabled unless you have less than 250MB of MySQL database space.</p>
-  <p><label><input type="checkbox" name="file_history" <?php if(getConfig('file_history')=='1' && is_writable(ENANO_ROOT.'/cache/')) echo 'checked="checked"'; ?> /> Keep a history of uploaded files</label></p>
+  <p><label><input type="checkbox" name="file_history" <?php if(getConfig('file_history')=='1') echo 'checked="checked"'; ?> /> Keep a history of uploaded files</label></p>
   <hr style="margin-left: 1em;" />
   <p><input type="submit" name="save" value="Save changes" style="font-weight: bold;" /></p>
   <?php
@@ -551,6 +558,9 @@
     switch($_GET['action'])
     {
       case "enable":
+        $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","plugin_enable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
+        if ( !$q )
+          $db->_die();
         setConfig('plugin_'.$_GET['plugin'], '1');
         break;
       case "disable":
@@ -561,6 +571,9 @@
         }
         if ( !in_array($_GET['plugin'], $plugins->system_plugins) )
         {
+          $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author,page_text) VALUES("security","plugin_disable",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '","' . $db->escape($_GET['plugin']) . '");');
+          if ( !$q )
+            $db->_die();
           setConfig('plugin_'.$_GET['plugin'], '0');
         }
         else 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/admin/SecurityLog.php	Wed Aug 29 23:35:06 2007 -0400
@@ -0,0 +1,160 @@
+<?php
+
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.0.1 (Loch Ness)
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+ 
+function page_Admin_SecurityLog()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
+  {
+    echo '<h3>Error: Not authenticated</h3><p>It looks like your administration session is invalid or you are not authorized to access this administration page. Please <a href="' . makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true) . '">re-authenticate</a> to continue.</p>';
+    return;
+  }
+  
+  if ( defined('ENANO_DEMO_MODE') )
+  {
+    die('Security log is disabled in demo mode.');
+  }
+  
+  echo '<h3>System security log</h3>';
+  
+  // Not calling the real fetcher because we have to paginate the results
+  $offset = ( isset($_GET['offset']) ) ? intval($_GET['offset']) : 0;
+  $q = $db->sql_query('SELECT COUNT(time_id) as num FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC;');
+  if ( !$q )
+    $db->_die();
+  $row = $db->fetchrow();
+  $db->free_result();
+  $count = intval($row['num']);
+  $q = $db->sql_unbuffered_query('SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC;');
+  if ( !$q )
+    $db->_die();
+   
+  $html = paginate(
+      $q,
+      '{time_id}',
+      $count,
+      makeUrlNS('Special', 'Administration', 'module=' . $paths->nslist['Admin'] . 'SecurityLog&offset=%s'),
+      $offset,
+      50,
+      array('time_id' => 'seclog_format_inner'),
+      '<div class="tblholder" style="/* max-height: 500px; clip: rect(0px,auto,auto,0px); overflow: auto; */"><table border="0" cellspacing="1" cellpadding="4" width="100%">
+       <tr><th style="width: 60%;">Type</th><th>Date</th><th>Username</th><th>IP Address</th></tr>',
+      '</table></div>'
+    );
+  
+  echo $html;
+  
+}
+
+function get_security_log($num = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  if ( $session->auth_level < USER_LEVEL_ADMIN )
+  {
+    $q = $db->sql_query('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,edit_summary,author) VALUES("security","seclog_unauth",UNIX_TIMESTAMP(),"' . $db->escape($_SERVER['REMOTE_ADDR']) . '","' . $db->escape($session->username) . '");');
+    if ( !$q )
+      $db->_die();
+    die('Security log: unauthorized attempt to fetch. Call has been logged and reported to the administrators.');
+  }
+  
+  $return = '<div class="tblholder" style="/* max-height: 500px; clip: rect(0px,auto,auto,0px); overflow: auto; */"><table border="0" cellspacing="1" cellpadding="4" width="100%">';
+  $cls = 'row2';                                                                                               
+  $return .= '<tr><th style="width: 60%;">Type</th><th>Date</th><th>Username</th><th>IP Address</th></tr>';
+  $hash = sha1(microtime());
+  if ( defined('ENANO_DEMO_MODE') )
+  {
+    require('config.php');
+    $hash = md5($dbpasswd);
+    unset($dbname, $dbhost, $dbuser, $dbpasswd);
+    unset($dbname, $dbhost, $dbuser, $dbpasswd); // PHP5 Zend bug
+  }
+  if ( defined('ENANO_DEMO_MODE') && !isset($_GET[ $hash ]) && substr($_SERVER['REMOTE_ADDR'], 0, 8) != '192.168.' )
+  {
+    $return .= '<tr><td class="row1" colspan="4">Logs are recorded but not displayed for privacy purposes in the demo.</td></tr>';
+  }
+  else
+  {
+    if(is_int($num))
+    {
+      $l = 'SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC LIMIT '.$num.';';
+    }
+    else
+    {
+      $l = 'SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC;';
+    }
+    $q = $db->sql_query($l);
+    while($r = $db->fetchrow())
+    {
+      $return .= seclog_format_inner($r);
+    }
+    $db->free_result();
+  }
+  $return .= '</table></div>';
+  
+  return $return;
+}
+
+function seclog_format_inner($r, $f = false)
+{
+  if ( is_array($f) )
+  {
+    unset($r);
+    $r =& $f;
+  }
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  $return = '';
+  static $cls = 'row2';
+  if ( $r['action'] == 'illegal_page' )
+  {
+    list($illegal_id, $illegal_ns) = unserialize($r['page_text']);
+    $url = makeUrlNS($illegal_ns, $illegal_id, false, true);
+    $title = get_page_title_ns($illegal_id, $illegal_ns);
+    $class = ( isPage($paths->nslist[$illegal_ns] . $illegal_id) ) ? '' : ' class="wikilink-nonexistent"';
+    $illegal_link = '<a href="' . $url . '"' . $class . ' onclick="window.open(this.href); return false;">' . $title . '</a>';
+  }
+  else if ( $r['action'] == 'plugin_enable' || $r['action'] == 'plugin_disable' )
+  {
+    $row['page_text'] = htmlspecialchars($row['page_text']);
+  }
+  $cls = ( $cls == 'row2' ) ? 'row1' : 'row2';
+  $return .= '<tr><td class="'.$cls.'">';
+  switch($r['action'])
+  {
+    case "admin_auth_good":  $return .= 'Successful elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); $return .= "<br /><small>Authentication level: $level</small>"; } break;
+    case "admin_auth_bad":   $return .= 'Failed elevated authentication'; if ( !empty($r['page_text']) ) { $level = $session->userlevel_to_string( intval($r['page_text']) ); $return .= "<br /><small>Attempted auth level: $level</small>"; } break;
+    case "activ_good":       $return .= 'Successful account activation'; break;
+    case "auth_good":        $return .= 'Successful regular user logon'; break;
+    case "activ_bad":        $return .= 'Failed account activation'; break;
+    case "auth_bad":         $return .= 'Failed regular user logon'; break;
+    case "sql_inject":       $return .= 'SQL injection attempt<div style="max-width: 90%; clip: rect(0px,auto,auto,0px); overflow: auto; display: block; font-size: smaller;">Offending query: ' . htmlspecialchars($r['page_text']) . '</div>'; break;
+    case "db_backup":        $return .= 'Database backup created<br /><small>Tables: ' . $r['page_text'] . '</small>'; break;
+    case "install_enano":    $return .= "Installed Enano version {$r['page_text']}"; break;
+    case "upgrade_enano":    $return .= "Upgraded Enano to version {$r['page_text']}"; break;
+    case "illegal_page":     $return .= "Unauthorized viewing attempt<br /><small>Page: {$illegal_link}</small>"; break;
+    case "upload_enable":    $return .= "Enabled file uploads"; break;
+    case "upload_disable":   $return .= "Disabled file uploads"; break;
+    case "magick_enable":    $return .= "Enabled ImageMagick for uploaded images"; break;
+    case "magick_disable":   $return .= "Disabled ImageMagick for uploaded images"; break;
+    case "filehist_enable":  $return .= "Enabled revision tracking for uploaded files"; break;
+    case "filehist_disable": $return .= "Disabled revision tracking for uploaded files"; break;
+    case "magick_path":      $return .= "Changed path to ImageMagick executable"; break;
+    case "plugin_disable":   $return .= "Disabled plugin: {$r['page_text']}"; break;
+    case "plugin_enable":    $return .= "Enabled plugin: {$r['page_text']}"; break;
+    case "seclog_unauth":    $return .= "Unauthorized attempt to call security log fetcher"; break;
+  }
+  $return .= '</td><td class="'.$cls.'">'.date('d M Y h:i a', $r['time_id']).'</td><td class="'.$cls.'">'.$r['author'].'</td><td class="'.$cls.'" style="cursor: pointer;" onclick="ajaxReverseDNS(this);" title="Click for reverse DNS info">'.$r['edit_summary'].'</td></tr>';
+  return $return;
+}
+
+?>