diff --git a/class2.php b/class2.php index af5cc9a7c..896440360 100644 --- a/class2.php +++ b/class2.php @@ -223,6 +223,9 @@ $e107_paths = compact('ADMIN_DIRECTORY', 'FILES_DIRECTORY', 'IMAGES_DIRECTORY', $sql_info = compact('mySQLserver', 'mySQLuser', 'mySQLpassword', 'mySQLdefaultdb', 'mySQLprefix'); $e107 = e107::getInstance()->initCore($e107_paths, realpath(dirname(__FILE__)), $sql_info, varset($E107_CONFIG, array())); +e107::getSingleton('eIPHandler'); // This auto-handles bans etc + + ### NEW Register Autoload - do it asap if(!function_exists('spl_autoload_register')) { @@ -777,8 +780,8 @@ if (!class_exists('e107table', false)) $ns = e107::getRender(); //TODO - find & replace $ns, $e107->ns // EONE-134 - bad e_module could destroy e107 instance -$e107 = e107::getInstance(); -$e107->ban(); +$e107 = e107::getInstance(); // Is this needed now? +e107::getIPHandler()->ban(); if(varset($pref['force_userupdate']) && USER && !isset($_E107['no_forceuserupdate'])) { diff --git a/e107_admin/banlist.php b/e107_admin/banlist.php index 97e401f55..777ec5a6c 100644 --- a/e107_admin/banlist.php +++ b/e107_admin/banlist.php @@ -2,7 +2,7 @@ /* * e107 website system * - * Copyright (C) 2008-2010 e107 Inc (e107.org) + * Copyright (C) 2008-2012 e107 Inc (e107.org) * Released under the terms and conditions of the * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * @@ -21,70 +21,105 @@ * @version $Id$; */ -define('BAN_TIME_FORMAT', "%d-%m-%Y %H:%M"); -define('BAN_REASON_COUNT', 7); // Update as more ban reasons added (max 10 supported) + +require_once('../class2.php'); +/* +@todo should this be here? +if(count($_POST) && !varset($_POST['e-token'])) +{ + die('Access denied - bl'); +} +*/ -define('BAN_TYPE_MANUAL', 1); // Manually entered bans -define('BAN_TYPE_IMPORTED', 5); // Imported bans -define('BAN_TYPE_TEMPORARY', 9); // Used during CSV import - - -define('BAN_TYPE_WHITELIST', 100); // Entry for whitelist - - -require_once ('../class2.php'); -if(!getperms('4')) +if (!getperms('4')) { header('location:'.e_BASE.'index.php'); exit(); } + + +require_once(e_HANDLER.'iphandler_class.php'); // This is probably already loaded in class2.php + include_lan(e_LANGUAGEDIR.e_LANGUAGE.'/admin/lan_'.e_PAGE); $e_sub_cat = 'banlist'; -require_once ('auth.php'); -require_once (e_HANDLER.'form_handler.php'); +require_once('auth.php'); +require_once(e_HANDLER.'form_handler.php'); $frm = new e_form(true); + require_once(e_HANDLER.'message_handler.php'); $emessage = &eMessage::getInstance(); +$pref = e107::getPref(); + +// Set a default to avoid issues with legacy systems +if (!isset($pref['ban_date_format'])) $pref['ban_date_format'] = '%H:%M %d-%m-%y'; + +$ipAdministrator = new banlistManager; + +// Character options for import & export +$separator_char = array(1 => ',', 2 => '|'); +$quote_char = array(1 => '(none)', 2 => "'", 3 => '"'); + + $action = 'list'; -if(e_QUERY) +if (e_QUERY) { - $tmp = explode('-', e_QUERY); // Use '-' instead of '.' to avoid confusion with IP addresses + $tmp = explode('-', e_QUERY); // Use '-' instead of '.' to avoid confusion with IP addresses $action = $tmp[0]; $sub_action = varset($tmp[1], ''); - if($sub_action) - $sub_action = preg_replace('/[^\w@\.:]*/', '', urldecode($sub_action)); + if ($sub_action) $sub_action = preg_replace('/[^\w*@\.:]*/', '', urldecode($sub_action)); $id = intval(varset($tmp[2], 0)); unset($tmp); } -$images_path = e_IMAGE_ABS.'admin_images/'; - -if(isset($_POST['update_ban_prefs'])) +if (isset($_POST['update_ban_prefs'])) // Update ban messages { - for($i = 0; $i < BAN_REASON_COUNT; $i ++) + $changed = FALSE; + + foreach ($ipAdministrator->getValidReasonList() as $bt) { - $pref['ban_messages'][$i] = $tp->toDB(varset($_POST['ban_text_'.($i+1)], '')); - $pref['ban_durations'][$i] = intval(varset($_POST['ban_time_'.($i+1)], 0)); + $i = abs($bt) + 1; // Forces a single-digit positive number for part of field name + $t1 = $tp->toDB(varset($_POST['ban_text_'.($i)],'')); + $t2 = intval(varset($_POST['ban_time_'.($i)],0)); + if ($pref['ban_messages'][$bt] != $t1) + { + $pref['ban_messages'][$bt] = $t1; + $changed = TRUE; + } + if ($pref['ban_durations'][$bt] != $t2) + { + $pref['ban_durations'][$bt] = $t2; + $changed = TRUE; + } + } + if ($changed) + { + // @todo write actual prefs changes to log file (different methods for prefs?) + save_prefs(); + /***************************************** + Write messages and times to disc file + *****************************************/ + $ipAdministrator->writeBanMessageFile(); + banlist_adminlog('08',''); + //$ns->tablerender(BANLAN_9, "
".BANLAN_33.'
'); + $emessage->add(BANLAN_33, E_MESSAGE_SUCCESS); } - save_prefs(); - banlist_adminlog('08', ""); - //$ns->tablerender(BANLAN_9, "
".BANLAN_33.'
'); - $emessage->add(BANLAN_33, E_MESSAGE_SUCCESS); } -if(isset($_POST['ban_ip'])) + +$writeBanFile = FALSE; +if (isset($_POST['ban_ip'])) { $_POST['ban_ip'] = trim($_POST['ban_ip']); - $new_ban_ip = preg_replace('/[^\w@\.\*]*/', '', urldecode($_POST['ban_ip'])); - if($new_ban_ip != $_POST['ban_ip']) + $new_ban_ip = preg_replace('/[^\w*@\.:]*/', '', urldecode($_POST['ban_ip'])); + if ($new_ban_ip != $_POST['ban_ip']) { $message = BANLAN_27.' '.$new_ban_ip; //$ns->tablerender(BANLAN_9, $message); @@ -92,100 +127,114 @@ if(isset($_POST['ban_ip'])) $_POST['ban_ip'] = $new_ban_ip; } - if(isset($_POST['entry_intent']) && (isset($_POST['add_ban']) || isset($_POST['update_ban'])) && $_POST['ban_ip'] != "" && strpos($_POST['ban_ip'], ' ') === false) + if (isset($_POST['entry_intent']) && (isset($_POST['add_ban']) || isset($_POST['update_ban'])) && $_POST['ban_ip'] != "" && strpos($_POST['ban_ip'], ' ') === false) { /* $_POST['entry_intent'] says why we're here: - 'edit' - Editing blacklist - 'add' - Adding to blacklist - 'whedit' - Editing whitelist - 'whadd' - Adding to whitelist -*/ - if($e107->whatIsThis($new_ban_ip) == 'ip') + 'edit' - Editing blacklist + 'add' - Adding to blacklist + 'whedit' - Editing whitelist + 'whadd' - Adding to whitelist + */ + if(e107::getIPHandler()->whatIsThis($new_ban_ip) == 'ip') { - $new_ban_ip = $e107->IPencode($new_ban_ip); // Normalise numeric IP addresses + $new_ban_ip = e107::getIPHandler()->IPencode($new_ban_ip, TRUE); // Normalise numeric IP addresses (allow wildcards) } $new_vals = array('banlist_ip' => $new_ban_ip); - if(isset($_POST['add_ban'])) + if (isset($_POST['add_ban'])) { $new_vals['banlist_datestamp'] = time(); - if($_POST['entry_intent'] == 'add') - $new_vals['banlist_bantype'] = BAN_TYPE_MANUAL; // Manual ban - if($_POST['entry_intent'] == 'whadd') - $new_vals['banlist_bantype'] = BAN_TYPE_WHITELIST; + if ($_POST['entry_intent'] == 'add') $new_vals['banlist_bantype'] = eIPHandler::BAN_TYPE_MANUAL; // Manual ban + if ($_POST['entry_intent'] == 'whadd') $new_vals['banlist_bantype'] = eIPHandler::BAN_TYPE_WHITELIST; } $new_vals['banlist_admin'] = ADMINID; - if(varsettrue($_POST['ban_reason'])) - $new_vals['banlist_reason'] = $tp->toDB($_POST['ban_reason']); + $new_vals['banlist_reason'] = $tp->toDB(varset($_POST['ban_reason'], '')); $new_vals['banlist_notes'] = $tp->toDB($_POST['ban_notes']); - if(isset($_POST['ban_time']) && is_numeric($_POST['ban_time']) && ($_POST['entry_intent'] == 'edit' || $_POST['entry_intent'] == 'add')) + if (isset($_POST['ban_time']) && is_numeric($_POST['ban_time']) && (($_POST['entry_intent']== 'edit') || ($_POST['entry_intent'] == 'add'))) { $bt = intval($_POST['ban_time']); - $new_vals['banlist_banexpires'] = $bt ? time() + ($bt * 60 * 60) : 0; + $new_vals['banlist_banexpires'] = $bt ? time() + ($bt*60*60) : 0; } - if(isset($_POST['add_ban'])) - { // Insert new value - can just pass an array - admin_update($sql->db_Insert("banlist", $new_vals), 'insert', false, false, false); - if($_POST['entry_intent'] == 'add') + if (isset($_POST['add_ban'])) + { // Insert new value - can just pass an array + admin_update($sql->db_Insert('banlist', $new_vals), 'insert'); + if ($_POST['entry_intent'] == 'add') { - banlist_adminlog('01', $new_vals['banlist_ip']); + banlist_adminlog('01', $new_vals['banlist_ip']); // Write to banlist } else { - banlist_adminlog('04', $new_vals['banlist_ip']); + banlist_adminlog('04', $new_vals['banlist_ip']); // Write to whitelist } } else - { // Update existing value + { // Update existing value $qry = ''; $spacer = ''; - foreach($new_vals as $k => $v) + foreach ($new_vals as $k => $v) { $qry .= $spacer."`{$k}`='$v'"; $spacer = ', '; } - admin_update($sql->db_Update("banlist", $qry." WHERE banlist_ip='".$_POST['old_ip']."'"), 'update', false, false, false); - if($_POST['entry_intent'] == 'edit') + admin_update($sql->db_Update('banlist', $qry." WHERE banlist_ip='".$_POST['old_ip']."'")); + if ($_POST['entry_intent'] == 'edit') { - banlist_adminlog("09", $new_vals['banlist_ip']); + banlist_adminlog('09',$new_vals['banlist_ip']); } else { - banlist_adminlog("10", $new_vals['banlist_ip']); + banlist_adminlog('10',$new_vals['banlist_ip']); } } unset($ban_ip); + $writeBanFile = TRUE; } } // Remove a ban -if(($action == "remove" || $action == "whremove") && varsettrue($_POST['ban_secure'])) -//if ($action == "remove") +if (($action == 'remove' || $action == 'whremove') && isset($_POST['ban_secure'])) { - $sql->db_Delete("generic", "gen_type='failed_login' AND gen_ip='{$sub_action}'"); - admin_update($sql->db_Delete("banlist", "banlist_ip='{$sub_action}'"), 'delete', false, false, false); - if($action == "remove") + $sql->db_Delete('generic', "gen_type='failed_login' AND gen_ip='{$sub_action}'"); + admin_update($sql->db_Delete('banlist', "banlist_ip='{$sub_action}'"), 'delete'); + if ($action == "remove") { $action = 'list'; - banlist_adminlog("02", $sub_action); + banlist_adminlog('02', $sub_action); } else { $action = 'white'; - banlist_adminlog("05", $sub_action); + banlist_adminlog('05', $sub_action); } + $writeBanFile = TRUE; } // Update the ban expiry time/date - timed from now (only done on banlist) -if($action == 'newtime') +if ($action == 'newtime') { - $end_time = $id ? time() + ($id * 60 * 60) : 0; - admin_update($sql->db_Update("banlist", "banlist_banexpires='".intval($end_time)."' WHERE banlist_ip='".$sub_action."'"), 'update', false, false, false); + $end_time = $id ? time() + ($id*60*60) : 0; + admin_update($sql->db_Update('banlist', 'banlist_banexpires='.intval($end_time)." WHERE banlist_ip='".$sub_action."'")); banlist_adminlog('03', $sub_action); $action = 'list'; + $writeBanFile = TRUE; +} + + +if ($writeBanFile) +{ +/************************************************ + update list of banned IPs +*************************************************/ + $ipAdministrator->writeBanListFiles('ip,htaccess'); + if (!$ipAdministrator->doesMessageFileExist()) + { + $ipAdministrator->writeBanMessageFile(); // Message file must exist - may not on fresh site + banlist_adminlog('08',''); + $emessage->add(BANLAN_33, E_MESSAGE_SUCCESS); + } } @@ -194,7 +243,7 @@ if($action == 'newtime') * @todo - eliminate extract(); */ // Edit modes - get existing entry -if($action == 'edit' || $action == 'whedit') +if ($action == 'edit' || $action == 'whedit') { $sql->db_Select('banlist', '*', "banlist_ip='{$sub_action}'"); $row = $sql->db_Fetch(); @@ -203,7 +252,7 @@ if($action == 'edit' || $action == 'whedit') else { unset($banlist_ip, $banlist_reason); - if(e_QUERY && ($action == 'add' || $action == 'whadd') && strpos($_SERVER["HTTP_REFERER"], "userinfo")) + if (e_QUERY && ($action == 'add' || $action == 'whadd') && strpos($_SERVER["HTTP_REFERER"], "userinfo")) { $banlist_ip = $sub_action; } @@ -211,6 +260,9 @@ else +/** + * Create dropdown with options for ban time - uses internal fixed list of reasonable values + */ function ban_time_dropdown($click_js = '', $zero_text = BANLAN_21, $curval = -1, $drop_name = 'ban_time') { global $frm; @@ -218,13 +270,13 @@ function ban_time_dropdown($click_js = '', $zero_text = BANLAN_21, $curval = -1, $ret = $frm->select_open($drop_name, array('other' => $click_js, 'id' => false)); $ret .= $frm->option(' ', ''); - foreach($intervals as $i) + foreach ($intervals as $i) { - if($i == 0) + if ($i == 0) { $words = $zero_text ? $zero_text : BANLAN_21; } - elseif(($i % 24) == 0) + elseif (($i % 24) == 0) { $words = floor($i / 24).' '.BANLAN_23; } @@ -240,16 +292,16 @@ function ban_time_dropdown($click_js = '', $zero_text = BANLAN_21, $curval = -1, -// Character options for import & export -$separator_char = array(1 => ',', 2 => '|'); -$quote_char = array(1 => '(none)', 2 => "'", 3 => '"'); +/** + * Create generic dropdown from array of data + */ function select_box($name, $data, $curval = FALSE) { global $frm; $ret = $frm->select_open($name, array('class' => 'tbox', 'id' => false)); - foreach($data as $k => $v) + foreach ($data as $k => $v) { $ret .= $frm->option($v, $k, ($curval !== FALSE) && ($curval == $k)); } @@ -258,14 +310,17 @@ function select_box($name, $data, $curval = FALSE) } -// Drop-down box for access counts + +/** + * Create dropdown with options for access counts before ban - uses internal fixed list of reasonable values + */ function drop_box($box_name, $curval) { global $frm; $opts = array(50, 100, 150, 200, 250, 300, 400, 500); $ret = $frm->select_open($box_name, array('class' => 'tbox')); - foreach($opts as $o) + foreach ($opts as $o) { $ret .= $frm->option($o, $o, ($curval == $o)); } @@ -278,25 +333,95 @@ function drop_box($box_name, $curval) $text = ''; -switch($action) +switch ($action) { - case 'options': - if(!getperms("0")) + case 'banlog' : + if(!getperms('0')) exit; + if (isset($_POST['delete_ban_log'])) + { + $message = ($ipAdministrator->deleteLogFile() ? BANLAN_89 : BANLAN_90); + e107::getRender()->tablerender(BANLAN_88, "
".$message."
"); + } + $from = 0; + $amount = 20; // Number per page - could make configurable later if required + if ($sub_action) $from = intval($sub_action); + +// @todo format form the 0.8 way + $text = "
+
+ + + + + + + "; + + // Get entries + $banLogEntries = $ipAdministrator->getLogEntries($from, $amount, $num_entry); + if (count($banLogEntries) == 0) + { + $text .= ""; + $num_entry = 0; + } + else + { + $text .= " + "; + foreach ($banLogEntries as $ban) + { + $row = $ipAdministrator->splitLogEntry($ban); + $text .= " + + + + + + "; + } // End while + + // Next-Previous. ========================== + if ($num_entry > $amount) + { + $parms = "{$num_entry},{$amount},{$from},".e_SELF."?".$action.'-[FROM]'; + $text .= "'); + } + $text .= " + "; + } + $text .= "
".BANLAN_82."
".BANLAN_83."".BANLAN_84."".BANLAN_7."".BANLAN_85."
".strftime($pref['ban_date_format'], $row['banDate'])."".e107::getIPHandler()->ipDecode($row['banIP'])."".$ipAdministrator->getBanTypeString($row['banReason'], FALSE)."".$row['banNotes']."

".$tp->parseTemplate("{NEXTPREV={$parms}}".'

+ + +
"; + + if (count($banLogEntries)) + { + $text .= "   ".str_replace('--NUM--', $num_entry, BANLAN_87); + } + + e107::getRender()->tablerender("
".BANLAN_86.'
', $text); + break; + + + case 'options' : + if (!getperms('0')) exit(); - if(isset($_POST['update_ban_options'])) + if (isset($_POST['update_ban_options'])) { $pref['enable_rdns'] = intval($_POST['ban_rdns_on_access']); $pref['enable_rdns_on_ban'] = intval($_POST['ban_rdns_on_ban']); $pref['ban_max_online_access'] = intval($_POST['ban_access_guest']).','.intval($_POST['ban_access_member']); $pref['ban_retrigger'] = intval($_POST['ban_retrigger']); - save_prefs(); + $pref['ban_date_format'] = $tp->toDB($_POST['ban_date_format']); + save_prefs(); // @todo FIXME log detail of changes. Right prefs to use? $emessage->add(LAN_SETSAVED, E_MESSAGE_SUCCESS); } - if(isset($_POST['remove_expired_bans'])) + if (isset($_POST['remove_expired_bans'])) { - //FIXME - proper messages - admin_update($sql->db_Delete('banlist', "`banlist_bantype` < ".BAN_TYPE_WHITELIST." AND `banlist_banexpires` > 0 AND `banlist_banexpires` < ".time()), 'delete', false, false, false); + $result = $sql->db_Delete('banlist',"`banlist_bantype` < ".eIPHandler::BAN_TYPE_WHITELIST." AND `banlist_banexpires` > 0 AND `banlist_banexpires` < ".time()); + banlist_adminlog('12', $result); + $emessage->add(str_replace('--NUM--', $result, BANLAN_48), E_MESSAGE_SUCCESS); } list($ban_access_guest, $ban_access_member) = explode(',', varset($pref['ban_max_online_access'], '100,200')); @@ -346,10 +471,19 @@ switch($action) + + + ".BANLAN_91." + + ".$frm->text('ban_date_format', varset($pref['ban_date_format'], '%H:%M %d-%m-%y'), 40)." +
".BANLAN_92."
+ +
".$frm->admin_button('update_ban_options', LAN_UPDATE, 'update')." +
@@ -364,6 +498,7 @@ switch($action) ".BANLAN_75." ".$frm->admin_button('remove_expired_bans', BANLAN_76, 'delete')." + @@ -371,20 +506,26 @@ switch($action)
"; - $e107->ns->tablerender(BANLAN_72, $emessage->render().$text); + e107::getRender()->tablerender(BANLAN_72, $emessage->render().$text); break; - case 'times': - if(!getperms("0")) + case 'times' : + if (!getperms('0')) exit(); $text = ''; - if((!isset($pref['ban_messages'])) || !is_array($pref['ban_messages'])) + if ((!isset($pref['ban_messages'])) || !is_array($pref['ban_messages'])) { - $pref['ban_messages'] = array_fill(0, BAN_REASON_COUNT - 1, ''); + foreach ($ipAdministrator->getValidReasonList() as $bt) + { + $pref['ban_messages'][$bt] = ''; + } } - if((!isset($pref['ban_durations'])) || !is_array($pref['ban_durations'])) + if ((!isset($pref['ban_durations'])) || !is_array($pref['ban_durations'])) { - $pref['ban_durations'] = array_fill(0, BAN_REASON_COUNT - 1, 0); + foreach ($ipAdministrator->getValidReasonList() as $bt) + { + $pref['ban_durations'][$bt] = 0; + } } $text .= " @@ -406,19 +547,20 @@ switch($action) "; - for($i = 0; $i < BAN_REASON_COUNT; $i ++) + foreach ($ipAdministrator->getValidReasonList() as $bt) { + $i = abs($bt) + 1; // Forces a single-digit positive number $text .= " - - - ".constant('BANLAN_10'.$i)." -
".constant('BANLAN_11'.$i)."
- - - ".$frm->textarea('ban_text_'.($i+1), $pref['ban_messages'][$i], 4, 15)." - - ".ban_time_dropdown('', BANLAN_32, $pref['ban_durations'][$i], 'ban_time_'.($i+1))." - + + + ".$ipAdministrator->getBanTypeString($bt, FALSE)." +
".$ipAdministrator->getBanTypeString($bt, TRUE)."
+ + + ".$frm->textarea('ban_text_'.($i), $pref['ban_messages'][$bt], 4, 15)." + + ".ban_time_dropdown('', BANLAN_32, $pref['ban_durations'][$bt], 'ban_time_'.($i))." + "; } $text .= " @@ -426,21 +568,23 @@ switch($action)
".$frm->admin_button('update_ban_prefs', LAN_UPDATE, 'update')." +
"; - $e107->ns->tablerender(BANLAN_77, $emessage->render().$text); + e107::getRender()->tablerender(BANLAN_77, $emessage->render().$text); break; - case 'edit': // Edit an existing ban - case 'add': // Add a new ban - case 'whedit': // Edit existing whitelist entry - case 'whadd': // Add a new whitelist entry + case 'edit' : // Edit an existing ban + case 'add' : // Add a new ban + case 'whedit' : // Edit existing whitelist entry + case 'whadd' : // Add a new whitelist entry if (!isset($banlist_reason)) $banlist_reason = ''; if (!isset($banlist_ip)) $banlist_ip = ''; if (!isset($banlist_notes)) $banlist_notes = ''; + $page_title = array('edit' => BANLAN_60, 'add' => BANLAN_9, 'whedit' => BANLAN_59, 'whadd' => BANLAN_58); $rdns_warn = varsettrue($pref['enable_rdns']) ? '' : '
'.BANLAN_12.'
'; $next = ($action == 'whedit' || $action == 'whadd') ? '?white' : '?list'; @@ -459,18 +603,18 @@ switch($action) ".BANLAN_5.":
- ".BANLAN_13." + ".BANLAN_13."
- ".$frm->text('ban_ip', $e107->ipDecode($banlist_ip), 200)." + ".$frm->text('ban_ip', e107::getIPHandler()->ipDecode($banlist_ip), 200)." {$rdns_warn} "; - if(($action == 'add') || ($action == 'whadd') || ($banlist_bantype <= 1) || ($banlist_bantype >= BAN_TYPE_WHITELIST)) + if (($action == 'add') || ($action == 'whadd') || ($banlist_bantype <= 1) || ($banlist_bantype >= eIPHandler::BAN_TYPE_WHITELIST)) { // Its a manual or unknown entry - only allow edit of reason on those $text .= " @@ -481,7 +625,7 @@ switch($action) "; } - elseif($action == 'edit') + elseif ($action == 'edit') { $text .= " @@ -491,12 +635,12 @@ switch($action) "; } - if($action == 'edit') + if ($action == 'edit') { $text .= " ".BANLAN_28.": - ".constant('BANLAN_10'.$banlist_bantype)." - ".constant('BANLAN_11'.$banlist_bantype)." + ".$ipAdministrator->getBanTypeString($banlist_bantype, FALSE)." - ".$ipAdministrator->getBanTypeString($banlist_bantype, TRUE)." "; } @@ -510,9 +654,9 @@ switch($action) "; - if($action == 'edit' || $action == 'add') + if ($action == 'edit' || $action == 'add') { - $inhelp = (($action == 'edit') ? '
'.BANLAN_26.($banlist_banexpires ? strftime(BAN_TIME_FORMAT, $banlist_banexpires) : BANLAN_21).'
' : ''); + $inhelp = (($action == 'edit') ? '
'.BANLAN_26.($banlist_banexpires ? strftime($pref['ban_date_format'], $banlist_banexpires) : BANLAN_21).'
' : ''); $text .= " @@ -525,6 +669,7 @@ switch($action) $text .= " +
"; @@ -532,13 +677,13 @@ switch($action) /* FORM NOTE EXAMPLE - not needed here as this note is added as label-note (see below) $text .= "
- ".BANLAN_13." + ".BANLAN_13."
"; */ - if($action == 'edit' || $action == 'whedit') + if ($action == 'edit' || $action == 'whedit') { $text .= " ".$frm->admin_button('update_ban', LAN_UPDATE, 'update'); @@ -553,17 +698,17 @@ switch($action) "; - $e107->ns->tablerender($page_title[$action], $emessage->render().$text); - break; // End of 'Add' and 'Edit' + e107::getRender()->tablerender($page_title[$action], $emessage->render().$text); + break; // End of 'Add' and 'Edit' - case 'transfer': + case 'transfer' : $message = ''; $error = false; - if(isset($_POST['ban_import'])) + if (isset($_POST['ban_import'])) { // Got a file to import - require_once (e_HANDLER.'upload_handler.php'); - if(($files = process_uploaded_files(e_UPLOAD, FALSE, array('overwrite' => TRUE, 'max_file_count' => 1, 'file_mask' => 'csv'))) === FALSE) + require_once(e_HANDLER.'upload_handler.php'); + if (($files = process_uploaded_files(e_UPLOAD, FALSE, array('overwrite' => TRUE, 'max_file_count' => 1, 'file_mask' => 'csv'))) === FALSE) { // Invalid file $error = true; $message = BANLAN_47; @@ -577,8 +722,12 @@ switch($action) } if(!$error) { // Got a file of some sort - $message = process_csv(e_UPLOAD.$files[0]['name'], intval(varset($_POST['ban_over_import'], 0)), intval(varset($_POST['ban_over_expiry'], 0)), $separator_char[intval(varset($_POST['ban_separator'], 1))], $quote_char[intval(varset($_POST['ban_quote'], 3))]); - banlist_adminlog("07", 'File: '.e_UPLOAD.$files[0]['name'].'
'.$message); + $message = process_csv(e_UPLOAD.$files[0]['name'], + intval(varset($_POST['ban_over_import'], 0)), + intval(varset($_POST['ban_over_expiry'], 0)), + $separator_char[intval(varset($_POST['ban_separator'], 1))], + $quote_char[intval(varset($_POST['ban_quote'], 3))]); + banlist_adminlog('07', 'File: '.e_UPLOAD.$files[0]['name'].'
'.$message); } } @@ -601,14 +750,14 @@ switch($action) "; - for($i = 0; $i < BAN_REASON_COUNT; $i ++) + foreach ($ipAdministrator->getValidReasonList() as $i) { $text .= " - - - ".$frm->checkbox("ban_types[{$i}]", $i).$frm->label(constant('BANLAN_10'.$i), "ban_types[{$i}]", $i)." - (".constant('BANLAN_11'.$i).") - + + + ".$frm->checkbox("ban_types[{$i}]", $i).$frm->label($ipAdministrator->getBanTypeString($i, FALSE), "ban_types[{$i}]", $i)." + (".$ipAdministrator->getBanTypeString($i, TRUE).") + "; } @@ -621,6 +770,7 @@ switch($action)
".$frm->admin_button('ban_export', BANLAN_39, 'export', BANLAN_39)."
+ "; @@ -661,6 +811,7 @@ switch($action)
".$frm->admin_button('ban_import', BANLAN_45, 'import')." +
@@ -668,21 +819,23 @@ switch($action) "; - $e107->ns->tablerender(BANLAN_35, $emessage->render().$text); - break; + e107::getRender()->tablerender(BANLAN_35, $emessage->render().$text); + break; // End case 'transfer' - case 'list': - case 'white': - default: - if(($action != 'list') && ($action != 'white')) + case 'list' : + case 'white' : + default : + if (($action != 'list') && ($action != 'white')) $action = 'list'; $edit_action = ($action == 'list' ? 'edit' : 'whedit'); $del_action = ($action == 'list' ? 'remove' : 'whremove'); $col_widths = array('list' => array(10, 5, 35, 30, 10, 10), 'white' => array(15, 40, 35, 10)); - $col_titles = array('list' => array(BANLAN_17, BANLAN_20, BANLAN_10, BANLAN_19, BANLAN_18, LAN_OPTIONS), 'white' => array(BANLAN_55, BANLAN_56, BANLAN_19, LAN_OPTIONS)); + $col_titles = array('list' => array(BANLAN_17, BANLAN_20, BANLAN_10, BANLAN_19, BANLAN_18, LAN_OPTIONS), + 'white' => array(BANLAN_55, BANLAN_56, BANLAN_19, LAN_OPTIONS)); $no_values = array('list' => BANLAN_2, 'white' => BANLAN_54); - $col_defs = array('list' => array('banlist_datestamp' => 0, 'banlist_bantype' => 0, 'ip_reason' => BANLAN_7, 'banlist_notes' => 0, 'banlist_banexpires' => 0, 'ban_options' => 0), 'white' => array('banlist_datestamp' => 0, 'ip_reason' => BANLAN_57, 'banlist_notes' => 0, 'ban_options' => 0)); + $col_defs = array('list' => array('banlist_datestamp' => 0, 'banlist_bantype' => 0, 'ip_reason' => BANLAN_7, 'banlist_notes' => 0, 'banlist_banexpires' => 0, 'ban_options' => 0), + 'white' => array('banlist_datestamp' => 0, 'ip_reason' => BANLAN_57, 'banlist_notes' => 0, 'ban_options' => 0)); $text = "
@@ -691,7 +844,8 @@ switch($action) ".$frm->hidden("ban_secure", "1")." "; - $filter = ($action == 'white') ? 'banlist_bantype='.BAN_TYPE_WHITELIST : 'banlist_bantype!='.BAN_TYPE_WHITELIST; + $filter = ($action == 'white') ? 'banlist_bantype='.eIPHandler::BAN_TYPE_WHITELIST : 'banlist_bantype!='.eIPHandler::BAN_TYPE_WHITELIST; + if(!$ban_total = $sql->db_Select("banlist", "*", $filter." ORDER BY banlist_ip")) { @@ -725,8 +879,8 @@ switch($action) "; while($row = $sql->db_Fetch()) { - extract($row);//FIXME - kill extract() - $banlist_reason = str_replace("LAN_LOGIN_18", BANLAN_11, $banlist_reason); + //extract($row);//FIXME - kill extract() + $row['banlist_reason'] = str_replace('LAN_LOGIN_18', BANLAN_11, $row['banlist_reason']); $text .= ""; foreach($col_defs[$action] as $cd => $fv) { @@ -734,22 +888,22 @@ switch($action) switch($cd) { case 'banlist_datestamp': - $val = ($banlist_datestamp ? strftime(BAN_TIME_FORMAT, $banlist_datestamp) : BANLAN_22); + $val = ($row['banlist_datestamp'] ? strftime($pref['ban_date_format'], $row['banlist_datestamp']) : BANLAN_22); break; case 'banlist_bantype': - $val = "
".constant('BANLAN_10'.$banlist_bantype)."
"; + $val = "
".$ipAdministrator->getBanTypeString($row['banlist_bantype'], FALSE)."
"; break; case 'ip_reason': - $val = $e107->ipDecode($banlist_ip)."
".$fv.": ".$banlist_reason; + $val = e107::getIPHandler()->ipDecode($row['banlist_ip'])."
".$fv.": ".$row['banlist_reason']; break; case 'banlist_banexpires': - $val = ($banlist_banexpires ? strftime(BAN_TIME_FORMAT, $banlist_banexpires).(($banlist_banexpires < time()) ? ' ('.BANLAN_34.')' : '') : BANLAN_21)."
".ban_time_dropdown("onchange=\"e107Helper.urlJump('".e_SELF."?newtime-{$banlist_ip}-'+this.value)\""); + $val = ($row['banlist_banexpires'] ? strftime($pref['ban_date_format'], $row['banlist_banexpires']).(($row['banlist_banexpires'] < time()) ? ' ('.BANLAN_34.')' : '') : BANLAN_21)."
".ban_time_dropdown("onchange=\"e107Helper.urlJump('".e_SELF."?newtime-{$row['banlist_ip']}-'+this.value)\""); break; case 'ban_options': $row_class = ' class="center"'; $val = " - ".ADMIN_EDIT_ICON." -"; + ".ADMIN_EDIT_ICON." +"; break; case 'banlist_notes': default: @@ -784,9 +938,9 @@ switch($action)
"; - $e107->ns->tablerender(($action == 'list' ? BANLAN_3 : BANLAN_61), $emessage->render().$text); - // End of case 'list' and the default case -} // End switch ($action) + e107::getRender()->tablerender(($action == 'list' ? BANLAN_3 : BANLAN_61), $emessage->render().$text); + // End of case 'list' and the default case +} // End switch ($action) require_once(e_ADMIN.'footer.php'); @@ -799,19 +953,19 @@ function banlist_adminmenu() { $action = (e_QUERY) ? e_QUERY : 'list'; - $var['list']['text'] = BANLAN_14; // List existing bans + $var['list']['text'] = BANLAN_14; // List existing bans $var['list']['link'] = e_SELF.'?list'; $var['list']['perm'] = '4'; - $var['add']['text'] = BANLAN_25; // Add a new ban + $var['add']['text'] = BANLAN_25; // Add a new ban $var['add']['link'] = e_SELF.'?add'; $var['add']['perm'] = '4'; - $var['white']['text'] = BANLAN_52; // List existing whitelist entries + $var['white']['text'] = BANLAN_52; // List existing whitelist entries $var['white']['link'] = e_SELF.'?white'; $var['white']['perm'] = '4'; - $var['whadd']['text'] = BANLAN_53; // Add a new whitelist entry + $var['whadd']['text'] = BANLAN_53; // Add a new whitelist entry $var['whadd']['link'] = e_SELF.'?whadd'; $var['whadd']['perm'] = '4'; @@ -819,7 +973,7 @@ function banlist_adminmenu() $var['transfer']['link'] = e_SELF.'?transfer'; $var['transfer']['perm'] = '4'; - if(getperms('0')) + if (getperms('0')) { $var['times']['text'] = BANLAN_15; $var['times']['link'] = e_SELF.'?times'; @@ -828,6 +982,10 @@ function banlist_adminmenu() $var['options']['text'] = LAN_OPTIONS; $var['options']['link'] = e_SELF.'?options'; $var['options']['perm'] = '0'; + + $var['banlog']['text'] = BANLAN_81; + $var['banlog']['link'] = e_SELF.'?banlog'; + $var['banlog']['perm'] = '0'; } e_admin_menu(BANLAN_16, $action, $var); } @@ -837,7 +995,7 @@ function banlist_adminmenu() // Parse the date string used by the import/export - YYYYMMDD_HHMMSS function parse_date($instr) { - if(strlen($instr) != 15) + if (strlen($instr) != 15) return 0; return mktime(substr($instr, 9, 2), substr($instr, 11, 2), substr($instr, 13, 2), substr($instr, 4, 2), substr($instr, 6, 2), substr($instr, 0, 4)); } @@ -848,30 +1006,33 @@ function parse_date($instr) // Return a message function process_csv($filename, $override_imports, $override_expiry, $separator = ',', $quote = '"') { - global $sql, $pref, $e107, $emessage; + $sql = e107::getDb(); + $pref['ban_durations'] = e107::getPref('ban_durations'); // @todo - check this + $emessage = &eMessage::getInstance(); + // echo "Read CSV: {$filename} separator: {$separator}, quote: {$quote} override imports: {$override_imports} override expiry: {$override_expiry}
"; // Renumber imported bans - if($override_imports) - $sql->db_Update('banlist', "`banlist_bantype`=".BAN_TYPE_TEMPORARY." WHERE `banlist_bantype` = ".BAN_TYPE_IMPORTED); + if ($override_imports) + $sql->db_Update('banlist', "`banlist_bantype`=".eIPHandler::BAN_TYPE_TEMPORARY." WHERE `banlist_bantype` = ".eIPHandler::BAN_TYPE_IMPORTED); $temp = file($filename); $line_num = 0; - foreach($temp as $line) + foreach ($temp as $line) { // Process one entry $line = trim($line); - $line_num ++; - if($line) + $line_num++; + if ($line) { $fields = explode($separator, $line); $field_num = 0; - $field_list = array('banlist_bantype' => BAN_TYPE_IMPORTED); - foreach($fields as $f) + $field_list = array('banlist_bantype' => eIPHandler::BAN_TYPE_IMPORTED); + foreach ($fields as $f) { $f = trim($f); - if(substr($f, 0, 1) == $quote) + if (substr($f, 0, 1) == $quote) { - if(substr($f, - 1, 1) == $quote) + if (substr($f, -1, 1) == $quote) { // Strip quotes - $f = substr($f, 1, - 1); // Strip off the quotes + $f = substr($f, 1, -1); // Strip off the quotes } else { @@ -880,39 +1041,39 @@ function process_csv($filename, $override_imports, $override_expiry, $separator } } // Now handle the field - $field_num ++; - switch($field_num) + $field_num++; + switch ($field_num) { - case 1: // IP address - $field_list['banlist_ip'] = $e107->ipEncode($f); + case 1 : // IP address + $field_list['banlist_ip'] = e107::getIPHandler()->ipEncode($f); break; - case 2: // Original date of ban + case 2 : // Original date of ban $field_list['banlist_datestamp'] = parse_date($f); break; - case 3: // Expiry of ban - depends on $override_expiry - if($override_expiry) + case 3 : // Expiry of ban - depends on $override_expiry + if ($override_expiry) { $field_list['banlist_banexpires'] = parse_date($f); } else { // Use default ban time from now - $field_list['banlist_banexpires'] = $pref['ban_durations'][BAN_TYPE_IMPORTED] ? time() + (60 * 60 * $pref['ban_durations'][BAN_TYPE_IMPORTED]) : 0; + $field_list['banlist_banexpires'] = $pref['ban_durations'][eIPHandler::BAN_TYPE_IMPORTED] ? time() + (60*60*$pref['ban_durations'][eIPHandler::BAN_TYPE_IMPORTED]) : 0; } break; - case 4: // Original ban type - we always ignore this and force to 'imported' + case 4 : // Original ban type - we always ignore this and force to 'imported' break; - case 5: // Ban reason originally generated by E107 + case 5 : // Ban reason originally generated by E107 $field_list['banlist_reason'] = $f; break; - case 6: // Any user notes added + case 6 : // Any user notes added $field_list['banlist_notes'] = $f; break; - default: // Just ignore any others + default : // Just ignore any others } } $qry = "REPLACE INTO `#banlist` (".implode(',', array_keys($field_list)).") values ('".implode("', '", $field_list)."')"; // echo count($field_list)." elements, query: ".$qry."
"; - if(!$sql->db_Select_gen($qry)) + if (!$sql->db_Select_gen($qry)) { $emessage->add(BANLAN_50.$line_num, E_MESSAGE_ERROR); return BANLAN_50.$line_num; @@ -920,9 +1081,9 @@ function process_csv($filename, $override_imports, $override_expiry, $separator } } // Success here - may need to delete old imported bans - if($override_imports) - $sql->db_Delete('banlist', "`banlist_bantype` = ".BAN_TYPE_TEMPORARY); - @unlink($filename); // Delete file once done + if ($override_imports) + $sql->db_Delete('banlist', "`banlist_bantype` = ".eIPHandler::BAN_TYPE_TEMPORARY); + @unlink($filename); // Delete file once done $emessage->add(str_replace('--NUM--', $line_num, BANLAN_51).$filename, E_MESSAGE_SUCCESS); return str_replace('--NUM--', $line_num, BANLAN_51).$filename; } @@ -939,7 +1100,6 @@ function process_csv($filename, $override_imports, $override_expiry, $separator */ function banlist_adminlog($msg_num = '00', $woffle = '') { - // if (!varset($pref['admin_log_log']['admin_banlist'],0)) return; e107::getAdminLog()->log_event('BANLIST_'.$msg_num, $woffle, E_LOG_INFORMATIVE, ''); } @@ -972,4 +1132,4 @@ function headerjs() return $ret; } -?> \ No newline at end of file +?> diff --git a/e107_admin/sql/core_sql.php b/e107_admin/sql/core_sql.php index a08455690..0c0ca9ff2 100644 --- a/e107_admin/sql/core_sql.php +++ b/e107_admin/sql/core_sql.php @@ -73,7 +73,7 @@ CREATE TABLE audit_log ( CREATE TABLE banlist ( banlist_ip varchar(100) NOT NULL default '', - banlist_bantype tinyint(3) unsigned NOT NULL default '0', + banlist_bantype tinyint(3) signed NOT NULL default '0', banlist_datestamp int(10) unsigned NOT NULL default '0', banlist_banexpires int(10) unsigned NOT NULL default '0', banlist_admin smallint(5) unsigned NOT NULL default '0', diff --git a/e107_core/xml/default_install.xml b/e107_core/xml/default_install.xml index 690bd4cc4..d704cca08 100644 --- a/e107_core/xml/default_install.xml +++ b/e107_core/xml/default_install.xml @@ -17,6 +17,7 @@ 1 1 + %H:%M %d-%m-%y 100,200 0 diff --git a/e107_handlers/e107_class.php b/e107_handlers/e107_class.php index 35d9d6a4d..0da020768 100644 --- a/e107_handlers/e107_class.php +++ b/e107_handlers/e107_class.php @@ -200,6 +200,7 @@ class e107 'user_class' => '{e_HANDLER}userclass_class.php', 'userlogin' => '{e_HANDLER}login.php', 'xmlClass' => '{e_HANDLER}xml_class.php', + 'eIPHandler' => '{e_HANDLER}iphandler_class.php', ); @@ -224,7 +225,7 @@ class e107 * Constructor * * Use {@link getInstance()}, direct instantiating - * is not possible for signleton objects + * is not possible for singleton objects * * @return void */ @@ -668,7 +669,6 @@ class e107 //singleton object found - overload not possible if(self::getRegistry($id)) { - return self::getRegistry($id); } @@ -914,7 +914,7 @@ class e107 */ public static function getParser() { - return self::getSingleton('e_parse', e_HANDLER.'e_parse_class.php'); //WARNING - don't change this - inifinite loop!!! + return self::getSingleton('e_parse', e_HANDLER.'e_parse_class.php'); //WARNING - don't change this - infinite loop!!! } /** @@ -1155,6 +1155,16 @@ class e107 return self::getSingleton('language', true); } + /** + * Retrieve IP/ban handler singleton object + * + * @return language + */ + public static function getIPHandler() + { + return self::getSingleton('eIPHandler', true); + } + /** * Retrieve Xml handler singleton or new instance object * @param mixed $singleton false - new instance, true - singleton from default registry location, 'string' - registry path @@ -2558,6 +2568,7 @@ class e107 * FIXME - create eBanHelper, move it there * @return void */ + /* No longer required - moved to eIPHelper class public function ban() { $sql = e107::getDb(); @@ -2603,7 +2614,7 @@ class e107 $this->check_ban($match); } } - } + } */ /** * Check the banlist table. $query is used to determine the match. @@ -2618,6 +2629,7 @@ class e107 * @param boolean $do_return * @return boolean */ + /* No longer required - moved to eIPHelper class public function check_ban($query, $show_error = TRUE, $do_return = FALSE) { $sql = e107::getDb(); @@ -2665,7 +2677,7 @@ class e107 } //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","No ban found",$query,FALSE,LOG_TO_ROLLING); return TRUE; // Email address OK - } + } */ /** @@ -2684,6 +2696,9 @@ class e107 */ public function add_ban($bantype, $ban_message = '', $ban_ip = '', $ban_user = 0, $ban_notes = '') { + return e107::getIPHandler()->add_ban($bantype, $ban_message, $ban_ip, $ban_user, $ban_notes); + + /* global $sql, $pref, $e107, $admin_log; $sql = e107::getDb(); $pref = e107::getPref(); @@ -2697,7 +2712,9 @@ class e107 { $ban_ip = $this->getip(); } - $ban_ip = preg_replace('/[^\w@\.]*/', '', urldecode($ban_ip)); // Make sure no special characters + */ + //$ban_ip = preg_replace('/[^\w@\.]*/', '', urldecode($ban_ip)); // Make sure no special characters + /* if(!$ban_ip) { return FALSE; @@ -2714,17 +2731,19 @@ class e107 } // Add using an array - handles DB changes better $sql->db_Insert('banlist', array('banlist_ip' => $ban_ip , 'banlist_bantype' => $bantype , 'banlist_datestamp' => time() , 'banlist_banexpires' => (varsettrue($pref['ban_durations'][$bantype]) ? time()+($pref['ban_durations'][$bantype]*60*60) : 0) , 'banlist_admin' => $ban_user , 'banlist_reason' => $ban_message , 'banlist_notes' => $ban_notes)); - return TRUE; + return TRUE; */ } /** * Get the current user's IP address * returns the address in internal 'normalised' IPV6 format - so most code should continue to work provided the DB Field is big enougn - * FIXME - move to eHelper + * FIXME - call ipHandler directly * @return string */ public function getip() { + return e107::getIPHandler()->getIP(FALSE); + /* if(!$this->_ip_cache) { $ip=$_SERVER['REMOTE_ADDR']; @@ -2750,16 +2769,18 @@ class e107 $this->_ip_cache = $this->ipEncode($ip); // Normalise for storage } return $this->_ip_cache; + */ } /** * Encode an IP address to internal representation. Returns string if successful; FALSE on error * Default separates fields with ':'; set $div='' to produce a 32-char packed hex string - * FIXME - move to eHelper + * FIXME - moved to ipHandler - check for calls elsewhere * @param string $ip * @param string $div divider * @return string encoded IP */ + /* public function ipEncode($ip, $div = ':') { $ret = ''; @@ -2802,20 +2823,23 @@ class e107 return str_repeat('0000'.$div, 5).'ffff'.$div.$temp; } return FALSE; // Unknown - } + } */ /** * Takes an encoded IP address - returns a displayable one * Set $IP4Legacy TRUE to display 'old' (IPv4) addresses in the familiar dotted format, * FALSE to display in standard IPV6 format * Should handle most things that can be thrown at it. - * FIXME - move to eHelper + * FIXME - moved to ipHandler - check for calls elsewhere * @param string $ip encoded IP * @param boolean $IP4Legacy * @return string decoded IP */ - public function ipDecode($ip, $IP4Legacy = TRUE) + + public function ipdecode($ip, $IP4Legacy = TRUE) { + return e107::getIPHandler()->ipDecode($ip, $IP4Legacy); + /* if (strstr($ip,'.')) { if ($IP4Legacy) return $ip; // Assume its unencoded IPV4 @@ -2872,17 +2896,21 @@ class e107 $ret = implode('.',$z); } - return $ret; + return $ret; */ } /** * Given a string which may be IP address, email address etc, tries to work out what it is - * FIXME - move to eHelper + * Movet to eIPHandler class + * FIXME - moved to ipHandler - check for calls elsewhere * @param string $string * @return string ip|email|url|ftp|unknown */ + /* public function whatIsThis($string) { + //return e107::getIPHandler()->whatIsThis($string); + if (strstr($string,'@')) return 'email'; // Email address if (strstr($string,'http://')) return 'url'; if (strstr($string,'ftp://')) return 'ftp'; @@ -2892,14 +2920,16 @@ class e107 return 'ip'; } return 'unknown'; - } + } */ /** * Retrieve & cache host name * * @param string $ip_address * @return string host name + * FIXME - moved to ipHandler - check for calls elsewhere */ + /* public function get_host_name($ip_address) { if(!$this->_host_name_cache[$ip_address]) @@ -2907,7 +2937,7 @@ class e107 $this->_host_name_cache[$ip_address] = gethostbyaddr($ip_address); } return $this->_host_name_cache[$ip_address]; - } + } */ /** * MOVED TO eHelper::parseMemorySize() @@ -3181,6 +3211,10 @@ class e107 $ret = e107::getOnline(); break; + case 'eIPHandler': + $ret = e107::getIPHandler(); + break; + case 'user_class': $ret = e107::getUserClass(); break; diff --git a/e107_handlers/iphandler_class.php b/e107_handlers/iphandler_class.php new file mode 100644 index 000000000..0728f4ae3 --- /dev/null +++ b/e107_handlers/iphandler_class.php @@ -0,0 +1,1439 @@ +ourConfigDir = realpath($configDir); + } + else + { + $this->ourConfigDir = e_SYSTEM.eIPHandler::BAN_FILE_DIRECTORY; + } + $this->ourIP = $this->ipEncode($this->getCurrentIP()); + $this->makeUserToken(); + $ipStatus = $this->checkIP($this->ourIP); + if ($ipStatus != 0) + { + $this->logBanItem($ipStatus, 'result --> '.$ipStatus); + if ($ipStatus < 0) + { // Blacklisted + $this->banAction($ipStatus); // This will abort if appropriate + } + elseif ($ipStatus > 0) + { // Whitelisted - we may want to set a specific indicator + } + } + // Continue here - user not banned (so far) + } + + + + /** + * Add an entry to the banlist log file (which is a simple text file) + * A date/time string is prepended to the line + * + * @param int $reason - numeric reason code, usually in range -10..+10 + * @param string $message - additional text as required (length not checked, but should be less than 100 characters or so + * + * @return none + */ + private function logBanItem($reason, $message) + { + if ($tmp = fopen(e_SYSTEM.eIPHandler::BAN_LOG_DIRECTORY.eIPHandler::BAN_FILE_LOG_NAME, 'a')) + { + $logLine = time().' '.$this->ourIP.' '.$reason.' '.$message."\n"; + fwrite($tmp,$logLine); + fclose($tmp); + } + } + + + + /** + * Generate relatively unique user token from browser info + * (but don't believe that the browser info is accurate - can readily be spoofed) + * + * This supplements use of the IP address in some places; both to improve user identification, and to help deal with dynamic IP allocations + * + * May be replaced by a 'global' e107 token at some point + */ + private function makeUserToken() + { + $tmpStr = ''; + foreach (array('HTTP_USER_AGENT', 'HTTP_ACCEPT', 'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_ACCEPT_ENCODING') as $v) + { + if (isset($_SERVER[$v])) + { + $tmpStr .= $_SERVER[$v]; + } + else + { + $tmpStr .= 'dummy'.$v; + } + } + $this->accessID = md5($tmpStr); + } + + + + /** + * Return browser-characteristics token + */ + public function getUserToken() + { + return $this->accessID; // Should always be defined at this point + } + + + + /** + * Check whether an IP address is routable + * + * @param string $ip - IPV4 or IPV6 numeric address. + * + * @return boolean TRUE if routable, FALSE if not + + @todo handle IPV6 + */ + public function isAddressRoutable($ip) + { + if (preg_match(array( + '#^0\..*#' , '#^127\..*#' , // Local loopbacks + '#^192\.168\..*#' , // RFC1918 - Private Network + '#^172\.(?:1[6789]|2\d|3[01])\..*#' , // RFC1918 - Private network + '#^10\..*#' , // RFC1918 - Private Network + '#^169\.254\..*#' , // RFC3330 - Link-local, auto-DHCP + '#^2(?:2[456789]|[345][0-9])\..*#' // Single check for Class D and Class E + ), $ip)) + { + return FALSE; + } + if (strpos(':', $ip) === FALSE) return TRUE; + // Must be an IPV6 address here + // @todo need to handle IPV4 addresses in IPV6 format + $ip = strtolower($ip); + if ($ip == 'ff02::1') return FALSE; // link-local all nodes multicast group + if ($ip == 'ff02:0000:0000:0000:0000:0000:0000:0001') return FALSE; + if ($ip == '::1') return FALSE; // localhost + if ($ip == '0000:0000:0000:0000:0000:0000:0000:0001') return FALSE; + // @todo add: + // fc00::/7 - local addresses + // ::0 (all zero) - invalid + // ff02::1:ff00:0/104 - Solicited-Node multicast addresses - add? + // 2001:0000::/29 through 2001:01f8::/29 - special purpose addresses + // 2001:db8::/32 - used in documentation + } + + + + /** + * Get current user's IP address in 'normal' form. + * Likely to be very similar to existing e107::getIP() function + * May log X-FORWARDED-FOR cases - or could generate a special IPV6 address, maybe? + */ + private function getCurrentIP() + { + if(!$this->ourIP) + { + $ip = $_SERVER['REMOTE_ADDR']; + if ($ip4 = getenv('HTTP_X_FORWARDED_FOR')) + { + if (!$this->isAddressRoutable($ip)) + { + $ip3 = explode(',', $ip4); + $ip = trim($ip3[sizeof($ip3) - 1]); // If IP address is unroutable, replace with any forwarded_for address + $this->logBanItem(0, 'X_Forward '.$ip4.' --> '.$ip); // Just log for interest ATM + } + } + if($ip == '') + { + $ip = 'x.x.x.x'; + } + $this->ourIP = $this->ipEncode($ip); // Normalise for storage + } + return $this->ourIP; + } + + + + /** + * Return the user's IP address, in normal or display-friendly form as requested + * + * @param boolean $forDisplay - TRUE for minimum-length display-friendly format. FALSE for 'normal' form (to be used when storing into DB etc) + * + * @return string IP address + * + * Note: if we define USER_IP (and maybe USER_DISPLAY_IP) constant, this function is strictly unnecessary. But we still need a format conversion routine + */ + public function getIP($forDisplay = FALSE) + { + if ($forDisplay == FALSE) return $this->ourIP; + return $this->ipDecode($this->ourIP); + } + + + + /** + * Takes appropriate action for a blacklisted IP address + * + * @param int $code - integer value < 0 specifying the ban reason. + * + * @return none (may not even return) + * + * Looks up the reason code, and extracts the corresponding text. + * If this text begins with 'http://' or 'https://', assumed to be a link to a web page, and redirects. + * Otherwise displays an error message to the user (if configured) then aborts. + */ + private function banAction($code) + { + $search = '['.$code.']'; + $fileName = $this->ourConfigDir.eIPHandler::BAN_FILE_ACTION_NAME.eIPHandler::BAN_FILE_EXTENSION; + if (!is_readable($fileName)) return; // @todo should we just die if no file - we know the IP is in the ban list. + + $vals = file($fileName); + if ($vals === FALSE) return; + if (substr($vals[0], 0, 5) != 'logBanItem($code, 'Unmatched action: '.$search.' - no block implemented'); + } + + + + /** + * Get whitelist and blacklist + * + * @return array - each element is an array with elements 'ip', 'action, and 'time_limit' + * + * Note: Intentionally a single call, so the two lists can be split across files as convenient + * + * At present the list is a single file, one entry per line, whitelist entries first. Most precisely defined addresses before larger subnets + * + * Format of each line is: + * IP_address action expiry_time additional_parameters + * + * where action is: +1 = whitelisted, <0 blacklisted, value is 'reason code' + * expiry_time is zero for an indefinite ban, time stamp for a limited ban + * additional_parameters may be required for certain actions in the future + */ + private function getWhiteBlackList() + { + $ret = array(); + $fileName = $this->ourConfigDir.eIPHandler::BAN_FILE_IP_NAME.eIPHandler::BAN_FILE_EXTENSION; + if (!is_readable($fileName)) return $ret; + + $vals = file($fileName); + if ($vals === FALSE) return $ret; + if (substr($vals[0], 0, 5) != '= 2) + { + $ret[] = array('ip' => $tmp[0], 'action' => $tmp[1], 'time_limit' => intval(varset($tmp[2], 0))); + } + } + } + $this->actionCount = count($ret); // Note how many entries in list + return $ret; + } + + + + /** + * Checks whether IP address is in the whitelist or blacklist. + * + * @param string $addr - IP address in 'normal' form + * + * @return int - 1 = whitelisted, 0 = not listed (= 'OK'), <0 is 'reason code' for ban + * + * note: Could maybe combine this with getWhiteBlackList() for efficiency, but makes it less general + */ + private function checkIP($addr) + { + $now = time(); + $checkLists = $this->getWhiteBlackList(); + foreach ($checkLists as $val) + { + if (strpos($addr, $val['ip']) === 0) // See if our address begins with an entry - handles wildcards + { // Match found + if (($val['time_limit'] == 0) || ($val['time_limit'] > $now)) + { // Indefinite ban, or timed ban, not expired + return $val['action']; // OK to just return - PHP should release the memory used by $checkLists + } + // Time limit expired + $this->clearBan = $val['ip']; // Note what triggered the match - it could be a wildcard (although timed ban unlikely!) + return 0; // Can just return - shouldn't be another entry + } + } + return 0; + } + + + + + + /** + * Encode an IPv4 address into IPv6 + * Similar functionality to ipEncode + * + * @return string - the 'ip4' bit of an IPv6 address (i.e. last 32 bits) + */ + private function ip4Encode($ip, $wildCards = FALSE, $div = ':') + { + $ipa = explode('.', $ip); + $temp = ''; + for ($s = 0; $s < 4; $s++) + { + if (!isset($ipa[$s])) $ipa[$s] = '*'; + if ((($ipa[$s] == '*') || (strpos($ipa[$s], 'x') !== FALSE)) && $wildCards) + { + $temp .= 'xx'; + } + else + { // Put a zero in if wildcards not allowed + $temp .= sprintf('%02x', $ipa[$s]); + } + if ($s == 1) $temp .= $div; + } + return $temp; + } + + + /** + * Encode an IP address to internal representation. Returns string if successful; FALSE on error + * Default separates fields with ':'; set $div='' to produce a 32-char packed hex string + * + * @param string $ip - 'raw' IP address. May be IPv4, IPv6 + * @param boolean $wildCards - if TRUE, wildcard characters allowed at the end of an address: + * '*' replaces 2 hex characters (primarily for 8-bit subnets of IPv4 addresses) + * 'x' replaces a single hex character + * @param string $div separator between 4-character blocks of the IPv6 address + * + * @return bool|string encoded IP. Always exactly 32 characters plus separators if conversion successful + * FALSE if conversion unsuccessful + */ + public function ipEncode($ip, $wildCards = FALSE, $div = ':') + { + $ret = ''; + $divider = ''; + if(strpos($ip, ':')!==FALSE) + { // Its IPV6 (could have an IP4 'tail') + if(strpos($ip, '.')!==FALSE) + { // IPV4 'tail' to deal with + $temp = strrpos($ip, ':')+1; + $ip = substr($ip, 0, $temp).$this->ip4Encode(substr($ip, $temp), $wildCards, $div); + } + // Now 'normalise' the address + $temp = explode(':', $ip); + $s = 8-count($temp); // One element will of course be the blank + foreach($temp as $f) + { + if($f=='') + { + $ret .= $divider.'0000'; // Always put in one set of zeros for the blank + $divider = $div; + if($s>0) + { + $ret .= str_repeat($div.'0000', $s); + $s = 0; + } + } + else + { + $ret .= $divider.sprintf('%04x', hexdec($f)); + $divider = $div; + } + } + return $ret; + } + if(strpos($ip, '.')!==FALSE) + { // Its IPV4 + return str_repeat('0000'.$div, 5).'ffff'.$div.$this->ip4Encode($ip, $wildCards, $div); + } + return FALSE; // Unknown + } + + + + /** + * Takes an encoded IP address - returns a displayable one + * Set $IP4Legacy TRUE to display 'old' (IPv4) addresses in the familiar dotted format, + * FALSE to display in standard IPV6 format + * Should handle most things that can be thrown at it. + * If wildcard characters ('x' found, incorporated 'as is' + * + * @param string $ip encoded IP + * @param boolean $IP4Legacy + * @return string decoded IP + */ + public function ipDecode($ip, $IP4Legacy = TRUE) + { + if (strstr($ip,'.')) + { + if ($IP4Legacy) return $ip; // Assume its unencoded IPV4 + $ipa = explode('.', $ip); + $ip = '0:0:0:0:0:ffff:'.sprintf('%02x%02x:%02x%02x', $ipa[0], $ipa[1], $ipa[2], $ipa[3]); + $ip = str_repeat('0000'.':', 5).'ffff:'.$this->ip4Encode($ip, TRUE, ':'); + } + if (strstr($ip,'::')) return $ip; // Assume its a compressed IPV6 address already + if ((strlen($ip) == 8) && !strstr($ip,':')) + { // Assume a 'legacy' IPV4 encoding + $ip = '0:0:0:0:0:ffff:'.implode(':',str_split($ip,4)); // Turn it into standard IPV6 + } + elseif ((strlen($ip) == 32) && !strstr($ip,':')) + { // Assume a compressed hex IPV6 + $ip = implode(':',str_split($ip,4)); + } + if (!strstr($ip,':')) return FALSE; // Return on problem - no ':'! + $temp = explode(':',$ip); + $z = 0; // State of the 'zero manager' - 0 = not started, 1 = running, 2 = done + $ret = ''; + $zc = 0; // Count zero fields (not always required) + foreach ($temp as $t) + { + $v = hexdec($t); + if (($v != 0) || ($z == 2) || (strpos($t, 'x') !== FALSE)) + { + if ($z == 1) + { // Just finished a run of zeros + $z++; + $ret .= ':'; + } + if ($ret) $ret .= ':'; + if (strpos($t, 'x') !== FALSE) + { + $ret .= $t; + } + else + { + $ret .= sprintf('%x',$v); // Drop leading zeros + } + } + else + { // Zero field + $z = 1; + $zc++; + } + } + if ($z == 1) + { // Need to add trailing zeros, or double colon + if ($zc > 1) $ret .= '::'; else $ret .= ':0'; + } + if ($IP4Legacy && (substr($ret,0,7) == '::ffff:')) + { + $temp = str_replace(':', '', substr($ip,-9, 9)); + $tmp = str_split($temp, 2); // Four 2-character hex values + $z = array(); + foreach ($tmp as $t) + { + if ($t == 'xx') + { + $z[] = '*'; + } + else + { + $z[] = hexdec($t); + } + } + $ret = implode('.',$z); + } + return $ret; + } + + + + /** + * Given a string which may be IP address, email address etc, tries to work out what it is + * Uses a fairly simplistic (but quick) approach - does NOT check formatting etc + * + * @param string $string + * @return string ip|email|url|ftp|unknown + */ + public function whatIsThis($string) + { + $string = trim($string); + if (strpos($string, '@') !== FALSE) return 'email'; // Email address + if (strpos($string, 'http://') === 0) return 'url'; + if (strpos($string, 'https://') === 0) return 'url'; + if (strpos($string, 'ftp://') === 0) return 'ftp'; + if (strpos($string, ':') !== FALSE) return 'ip'; // Identify ipv6 + $string = strtolower($string); + if (str_replace(' ', '', strtr($string,'0123456789abcdef.*', ' ')) == '') // Delete all characters found in ipv4 addresses, plus wildcards + { + return 'ip'; + } + return 'unknown'; + } + + + /** + * Retrieve & cache host name + * + * @param string $ip_address + * @return string host name + */ + public function get_host_name($ip_address) + { + if(!isset($this->_host_name_cache[$ip_address])) + { + $this->_host_name_cache[$ip_address] = gethostbyaddr($ip_address); + } + return $this->_host_name_cache[$ip_address]; + } + + + + /** + * Generate DB query for domain name-related checks + * + * If an email address is passed, discards the individual's name + * + * @param string $email - an email address or domain name string + * @param string $fieldname - if non-empty, each array entry is a comparison with this field + * + * @return boolean|array + * false if invalid domain name format + * array of values to compare + */ + function makeDomainQuery($email, $fieldName = 'banlist_ip') + { + global $tp; + if (($tv = strrpos('@', $email)) !== FALSE) + { + $email = substr($email, $tv+1); + } + $tmp = strtolower($tp -> toDB(trim($email))); + if ($tmp == '') return FALSE; + if (strpos($tmp,'.') === FALSE) return FALSE; + $em = array_reverse(explode('.',$tmp)); + $line = ''; + $out = array('*@'.$tmp); // First element looks for domain as email address + foreach ($em as $e) + { + $line = '.'.$e.$line; + $out[] = '*'.$line; + } + if ($fieldName) + { + foreach ($out as $k => $v) + { + $out[$k] = '(`'.$fieldName."`='".$v."')"; + } + } + return $out; + } + + +/** + * Routines beyone here are to handle banlist-related tasks which involve the DB + * note: Most of these routines already existed; moved in from e107_class.php + */ + + + /** + * Check if current user is banned + * + * This is called soon after the DB is opened, to do checks which require it. + * Previous checks have already done IP-based bans. + * + * Starts by removing expired bans if $this->clearBan is set + * + * Generates the queries to interrogate the ban list, then calls $this->check_ban(). + * If the user is banned, $check_ban() never returns - so a return from this routine indicates a non-banned user. + * + * @return void + * + * @todo should be possible to simplify, since IP addresses already checked earlier + */ + public function ban() + { + $sql = e107::getDb(); + $pref = e107::getPref(); + + if ($this->clearBan !== FALSE) + { // Expired ban to clear - match exactly the address which triggered this action - could be a wildcard + if ($sql->db_Delete('banlist',"`banlist_ip`='{$this->clearBan}'")) + { + $this->actionCount--; // One less item on list + $this->logBanItem(0,'Ban cleared: '.$this->clearBan); + // Now regenerate the text files - so no further triggers from this entry + $this->regenerateFiles(); + } + } + + if($this->actionCount) + { + $ip = $this->getip(); // This will be in normalised IPV6 form + if ($ip != eIPHandler::LOCALHOST_IP) + { // Check host name, user email to see if banned + $vals = array(); + if(varsettrue($pref['enable_rdns'])) + { + $vals = array_merge($vals, $this->makeDomainQuery($this->get_host_name($ip), '')); + } + if ((defined('USEREMAIL') && USEREMAIL)) + { + // @todo is there point to this? Usually avoid a complete query if we skip it + $vals = array_merge($vals, $this->makeDomainQuery(USEREMAIL, '')); + } + if (count($vals)) + { + $vals = array_unique($vals); // Could get identical values from domain name check and email check + $match = "`banlist_ip`='".implode("' OR `banlist_ip`='", $vals)."'"; + $this->checkBan($match); + } + } + } + } + + + + /** + * Check the banlist table. $query is used to determine the match. + * If $do_return, will always return with ban status - TRUE for OK, FALSE for banned. + * If return permitted, will never display a message for a banned user; otherwise will display any message then exit + * @todo consider whether can be simplified + * + * @param string $query - the 'WHERE' part of the DB query to be executed + * @param boolean $show_error - if true, adds a '403 Forbidden' header for a banned user + * @param boolean $do_return - if TRUE, returns regardless without displaying anything. if FALSE, for a banned user displays any message and exits + * @return boolean TRUE for OK, FALSE for banned. + */ + public function checkBan($query, $show_error = TRUE, $do_return = FALSE) + { + $sql = e107::getDb(); + $pref = e107::getPref(); + $tp = e107::getParser(); + + //global $admin_log; + //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","Check for Ban",$query,FALSE,LOG_TO_ROLLING); + if ($sql->db_Select('banlist', '*', $query.' ORDER BY `banlist_bantype` DESC')) + { + // Any whitelist entries will be first, because they are large positive numbers - so we can answer based on the first DB record read + $row = $sql->db_Fetch(); + if ($row['banlist_bantype'] >= eIPHandler::BAN_TYPE_WHITELIST) + { + //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","Whitelist hit",$query,FALSE,LOG_TO_ROLLING); + return TRUE; // Whitelisted entry + } + // Found banlist entry in table here + if (($row['banlist_banexpires']>0) && ($row['banlist_banexpires']db_Delete('banlist', $query); + $this->regenerateFiles(); + return TRUE; + } + + // User is banned hereafter - just need to sort out the details. + if (varsettrue($pref['ban_retrigger']) && varsettrue($pref['ban_durations'][$row['banlist_bantype']])) + { // May need to retrigger ban period + $sql->db_Update('banlist', "`banlist_banexpires`=".intval(time()+($pref['ban_durations'][$row['banlist_bantype']]*60*60)), "WHERE `banlist_ip`='{$row['banlist_ip']}'"); + $this->regenerateFiles(); + //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","Retrigger Ban",$row['banlist_ip'],FALSE,LOG_TO_ROLLING); + } + //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","Active Ban",$query,FALSE,LOG_TO_ROLLING); + if ($show_error) + { + header('HTTP/1.1 403 Forbidden', true); + } + if (isset($pref['ban_messages'])) + { // May want to display a message + // Ban still current here + if($do_return) + { + return FALSE; + } + echo $tp->toHTML(varset($pref['ban_messages'][$row['banlist_bantype']])); // Show message if one set + } + //$admin_log->e_log_event(4, __FILE__."|".__FUNCTION__."@".__LINE__, 'BAN_03', 'LAN_AUDIT_LOG_003', $query, FALSE, LOG_TO_ROLLING); + exit(); + } + //$admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","No ban found",$query,FALSE,LOG_TO_ROLLING); + return TRUE; // Email address OK + } + + + + /** + * Add an entry to the banlist. $bantype = 1 for manual, 2 for flooding, 4 for multiple logins + * Returns TRUE if ban accepted. + * Returns FALSE if ban not accepted (e.g. because on whitelist, or invalid IP specified) + * + * @param integer $bantype - either one of the BAN_TYPE_xxx constants, or a legacy value as above + * @param string $ban_message + * @param string $ban_ip + * @param integer $ban_user + * @param string $ban_notes + * + * @return boolean check result - FALSE if ban rejected. TRUE if ban added. + */ + public function add_ban($bantype, $ban_message = '', $ban_ip = '', $ban_user = 0, $ban_notes = '') + { + $sql = e107::getDb(); + $pref = e107::getPref(); + + switch ($bantype) // Convert from 'internal' ban types to those used in the DB + { + case 1 : $bantype = eIPHandler::BAN_TYPE_MANUAL; break; + case 2 : $bantype = eIPHandler::BAN_TYPE_FLOOD; break; + case 4 : $bantype = eIPHandler::BAN_TYPE_LOGINS; break; + } + if (!$ban_message) + { + $ban_message = 'No explanation given'; + } + if (!$ban_ip) + { + $ban_ip = $this->getip(); + } + $ban_ip = preg_replace('/[^\w@\.:]*/', '', urldecode($ban_ip)); // Make sure no special characters + if (!$ban_ip) + { + return FALSE; + } + // See if the address is in the whitelist + if ($sql->db_Select('banlist', '*', "`banlist_ip`='{$ban_ip}' AND `banlist_bantype` >= ".eIPHandler::BAN_TYPE_WHITELIST)) + { // Got a whitelist entry for this + //$admin_log->e_log_event(4, __FILE__."|".__FUNCTION__."@".__LINE__, "BANLIST_11", 'LAN_AL_BANLIST_11', $ban_ip, FALSE, LOG_TO_ROLLING); + return FALSE; + } + if(varsettrue($pref['enable_rdns_on_ban'])) + { + $ban_message .= 'Host: '.$this->get_host_name($ban_ip); + } + // Add using an array - handles DB changes better + $sql->db_Insert('banlist', array('banlist_ip' => $ban_ip , 'banlist_bantype' => $bantype , 'banlist_datestamp' => time() , 'banlist_banexpires' => (varsettrue($pref['ban_durations'][$bantype]) ? time()+($pref['ban_durations'][$bantype]*60*60) : 0) , 'banlist_admin' => $ban_user , 'banlist_reason' => $ban_message , 'banlist_notes' => $ban_notes)); + + $this->regenerateFiles(); + return TRUE; + } + + + /** + * Regenerate the text-based banlist files (called after a banlist table mod) + */ + protected function regenerateFiles() + { + // Now regenerate the text files - so accesses of this IP address don't use the DB + $ipAdministrator = new banlistManager; + $ipAdministrator->writeBanListFiles('ip,htaccess'); + } + + + + public function getConfigDir() + { + return $this->ourConfigDir; + } + + + + /** + * Routine checks whether a file or directory has sufficient permissions + * + * ********** @todo this is in the wrong place! Move it to a more appropriate class! ************* + * + * @param string $name - file with path (if ends in anything other than '/' or '\') or directory (if ends in '/' or '\') + * @param string(?) $perms - required permissions as standard *nix 3-digit string + * @param boolean $message - if TRUE, and insufficient rights, a message is output (in 0.8, to the message handler) + * + * @return boolean TRUE if sufficient permissions, FALSE if not (or error) + * + * For each mode character: + * 1 - execute + * 2 - writable + * 4 - readable + */ + public function checkFilePerms($name, $perms, $message = TRUE) + { + $isDir = ((substr($name, -1,1) == '\\') || (substr($name, -1,1) == '/')); + $result = FALSE; + $msg = ''; + $dest = $isDir ? 'Directory' : 'File'; + $reqPerms = intval('0'.$perms) & 511; // We want an integer value to match the return from fileperms() + if (!file_exists($name)) + { + $msg = $dest.': '.$name.' does not exist'; + } + if ($msg == '') + { + $realPerms = fileperms($name); + $mgs = $name.' is not a '.$dest; // Assume an error to start; clear messsage if all OK + switch ($realPerms & 0xf000) + { + case 0x8000 : + if (!$isDir) + { + $msg = ''; + } + break; + case 0x4000 : + if ($isDir) + { + $msg = ''; + } + break; + } + } + if ($msg == '') + { + if (($reqPerms & $realPerms) == $reqPerms) + { + $result = TRUE; + } + else + { + $msg = $name.': Insufficient permissions. Required: '.$this->permsToString($reqPerms).' Actual: '.$this->permsToString($realPerms); + } + } + if ($message && $msg) + { // Do something with the error message + } + return $result; + } + + + /** + * Decode file/directory permissions into human-readable characters + * + * @param int $val representing permissions (LS 9 bits used) + * + * @return string exactly 9 characters, with blocks of 3 representing user, group and world permissions + */ + public function permsToString($val) + { + $perms = 'rwxrwxrwx'; + $mask = 0x100; + + for ($i = 0; $i < 9; $i++) + { + if (($mask & $val) == 0) $perms[$i] = '-'; + $mask = $mask >> 1; + } + return $perms; + } + + + /** + * Function to see whether a user is already logged as being online + * + * @todo - this is possibly in the wrong place! + * + * @param string $ip - in 'normalised' IPV6 form + * @param string $browser - browser token as logged + * + * @return boolean|array FALSE if DB error or not found. Best match table row if found + */ + public function isUserLogged($ip, $browser) + { + $ourDB = e107::getDb('olcheckDB'); // @todo is this OK, or should an existing one be used? + + $result = $ourDB->db_Select('online', '*', "`user_ip` = '{$ip}' OR `user_token` = '{$browser}'"); + if ($result === FALSE) return FALSE; + $gotIP = FALSE; + $gotBrowser = FALSE; + $bestRow = FALSE; + while (FALSE !== ($row = $ourDB->db_Fetch(MYSQL_ASSOC))) + { + if ($row['user_token'] == $browser) + { + if ($row['user_ip'] == $ip) + { // Perfect match + return $row; + } + // Just browser token match here + if ($bestRow === FALSE) + { + $bestRow = $row; + $gotBrowser = TRUE; + } + else + { // Problem - two or more rows with same browser token. What to do? + } + } + elseif ($row['user_ip'] == $ip) + { // Just IP match here + if ($bestRow === FALSE) + { + $bestRow = $row; + $gotIP = TRUE; + } + else + { // Problem - two or more rows with same IP address. Hopefully better offer later! + } + } + } + return $bestRow; + } +} + + + + + + +/** + * Routines involved with the management of the ban list and associated files + */ +class banlistManager +{ + private $ourConfigDir = ''; + + + public function __construct() + { + $this->ourConfigDir = e107::getIPHandler()->getConfigDir(); + } + + /** + * Return an array of valid ban types (for use as indices into array, generally) + */ + public function getValidReasonList() + { + return array( + eIPHandler::BAN_TYPE_LEGACY, + eIPHandler::BAN_TYPE_MANUAL, + eIPHandler::BAN_TYPE_FLOOD, + eIPHandler::BAN_TYPE_HITS, + eIPHandler::BAN_TYPE_LOGINS, + eIPHandler::BAN_TYPE_IMPORTED, + eIPHandler::BAN_TYPE_USER, + // Spare value + eIPHandler::BAN_TYPE_UNKNOWN + ); + } + + + /** + * Create banlist-related text files as requested: + * List of whitelisted and blacklisted IP addresses + * file for easy import into .htaccess file (allow from...., deny from....) + * Generic CSV-format export file + * + * @param string $options {ip|htaccess|csv} - comma separated list (no spaces) to select which files to write + * @param string $typeList - optional comma-separated list of ban types required (default is all) + * Uses constants: + * BAN_FILE_IP_NAME Saves list of banned and whitelisted IP addresses + * BAN_FILE_ACTION_NAME Details of actions for different ban types + * BAN_FILE_HTACCESS File in format for direct paste into .htaccess + * BAN_FILE_CSV_NAME + * BAN_FILE_EXTENSION File extension to append + * + */ + public function writeBanListFiles($options = 'ip', $typeList = '') + { + $sql = e107::getDb(); + $ipManager = e107::getIPHandler(); + + $optList = explode(',',$options); + $fileList = array(); // Array of file handles once we start + + $fileNameList = array('ip' => eIPHandler::BAN_FILE_IP_NAME, 'htaccess' => eIPHandler::BAN_FILE_HTACCESS, 'csv' => eIPHandler::BAN_FILE_CSV_NAME); + + $qry = "SELECT * FROM `#banlist` "; + if ($typeList != '') $qry .= " WHERE`banlist_bantype` IN ({$typeList})"; + $qry .= ' ORDER BY `banlist_bantype` DESC'; // Order ensures whitelisted addresses appear first + + // Create a temporary file for each type as demanded. Vet the options array on this pass, as well + foreach($optList as $k => $opt) + { + if (isset($fileNameList[$opt])) + { + if ($tmp = fopen($this->ourConfigDir.$fileNameList[$opt].'_tmp'.eIPHandler::BAN_FILE_EXTENSION, 'w')) + { + $fileList[$opt] = $tmp; // Save file handle + fwrite($fileList[$opt], "ourConfigDir.$fileNameList[$opt].'_tmp'.eIPHandler::BAN_FILE_EXTENSION.'
'; + } + else + { + unset($optList[$k]); + /// @todo - flag error? + } + } + else + { + unset($optList[$k]); + } + } + + if ($sql->db_Select_gen($qry)) + { + while ($row = $sql->db_Fetch(MYSQL_ASSOC)) + { + $row['banlist_ip'] = $this->trimWildcard($row['banlist_ip']); + if ($row['banlist_ip'] == '') continue; // Ignore empty IP addresses + if ($ipManager->whatIsThis($row['banlist_ip']) != 'ip') continue; // Ignore non-numeric IP Addresses + if ($row['banlist_bantype'] == eIPHandler::BAN_TYPE_LEGACY) $row['banlist_bantype'] = eIPHandler::BAN_TYPE_UNKNOWN; // Handle legacy bans + foreach ($optList as $opt) + { + $line = ''; + switch ($opt) + { + case 'ip' : + // IP_address action expiry_time additional_parameters + $line = $row['banlist_ip'].' '.$row['banlist_bantype'].' '.$row['banlist_banexpires']."\n"; + break; + case 'htaccess' : + $line = (($row['banlist_bantype'] > 0) ? 'allow from ' : 'deny from ').$row['banlist_ip']."\n"; + break; + case 'csv' : /// @todo - when PHP5.1 is minimum, can use fputcsv() function + $line = $row['banlist_ip'].','.$this->dateFormat($row['banlist_datestamp']).','.$this->dateFormat($row['banlist_expires']).','; + $line .= $row['banlist_bantype'].',"'.$row['banlist_reason'].'","'.$row['banlist_notes'].'"'."\n"; + break; + } + fwrite($fileList[$opt], $line); + } + } + } + + // Now close each file + foreach ($optList as $opt) + { + fclose($fileList[$opt]); + } + + // Finally, delete the working file, rename the temporary one + // Docs suggest that 'newname' is auto-deleted if it exists (as it usually should) + // - but didn't appear to work, hence copy then delete + foreach ($optList as $opt) + { + $oldName = $this->ourConfigDir.$fileNameList[$opt].'_tmp'.eIPHandler::BAN_FILE_EXTENSION; + $newName = $this->ourConfigDir.$fileNameList[$opt].eIPHandler::BAN_FILE_EXTENSION; + copy($oldName, $newName); + unlink($oldName); + } + } + + + + + /** + * Trim wildcards from IP addresses + * + * @param string $ip - IP address in any normal form + * + * Note - this removes all characters after (and including) the first '*' or 'x' found. So an '*' or 'x' in the middle of a string may + * cause unexpected results. + */ + private function trimWildcard($ip) + { + $ip = trim($ip); + $temp = strpos($ip, 'x'); + if ($temp !== FALSE) + { + return substr($ip, 0, $temp); + } + $temp = strpos($ip, '*'); + if ($temp !== FALSE) + { + return substr($ip, 0, $temp); + } + return $ip; + } + + + /** + * Format date and time for export into a text file. + * + * @param int $date - standard Unix time stamp + * + * @return string. '0' if date is zero, else formatted in consistent way. + */ + private function dateFormat($date) + { + if ($date == 0) return '0'; + return strftime('%Y%m%d_%H%M%S',$date); + } + + + + /** + * Converts a numeric ban type (as defined by a BAN_TYPE_xxxx constant) to the corresponding string + */ +// public function banTypeToText($banType) +// { +// if ($banType > 0) return ''; +// return constant('BANLAN_10'.(-$banType)); +// } + + /** + * Converts a numeric ban type (as defined by a BAN_TYPE_xxxx constant) to the corresponding mouseover (title)string + */ +// public function banTypeToTitle($banType) +// { +// if ($banType > 0) return ''; +// return constant('BANLAN_11'.(-$banType)); +// } + + + /** + * Return string corresponding to a ban type + * @param int $banType - constant representing the ban type + * @param bool $forMouseover - if true, its the (usually longer) explanatory string for a mouseover + * + * @return string + */ + public function getBanTypeString($banType, $forMouseover = FALSE) + { + switch ($banType) + { + case eIPHandler::BAN_TYPE_LEGACY : $listOffset = 0; break; + case eIPHandler::BAN_TYPE_MANUAL : $listOffset = 1; break; + case eIPHandler::BAN_TYPE_FLOOD : $listOffset = 2; break; + case eIPHandler::BAN_TYPE_HITS : $listOffset = 3; break; + case eIPHandler::BAN_TYPE_LOGINS : $listOffset = 4; break; + case eIPHandler::BAN_TYPE_IMPORTED : $listOffset = 5; break; + case eIPHandler::BAN_TYPE_USER : $listOffset = 6; break; + case eIPHandler::BAN_TYPE_TEMPORARY : $listOffset = 9; break; + + case eIPHandler::BAN_TYPE_WHITELIST : + return BANLAN_120; // Special case - may never occur + case eIPHandler::BAN_TYPE_UNKNOWN : + default : + if (($banType > 0) && ($banType < 9)) + { + $listOffset = $banType; // BC conversions + } + else + { + $listOffset = 8; + } + } + if ($forMouseover) return constant('BANLAN_11'.$listOffset); + return constant('BANLAN_10'.$listOffset); + } + + + /** + * Write a text file containing the ban messages related to each ban reason + */ + public function writeBanMessageFile() + { + $pref = e107::getPref(); + + $oldName = $this->ourConfigDir.eIPHandler::BAN_FILE_ACTION_NAME.'_tmp'.eIPHandler::BAN_FILE_EXTENSION; + if ($tmp = fopen($oldName, 'w')) + { + fwrite($tmp, "getValidReasonList() as $type) + { + fwrite($tmp,'['.$type.']'.$pref['ban_messages'][$type]."\n"); + } + fclose($tmp); + $newName = $this->ourConfigDir.eIPHandler::BAN_FILE_ACTION_NAME.eIPHandler::BAN_FILE_EXTENSION; + copy($oldName, $newName); + unlink($oldName); + } + } + + + public function doesMessageFileExist() + { + return is_readable($this->ourConfigDir.eIPHandler::BAN_FILE_ACTION_NAME.eIPHandler::BAN_FILE_EXTENSION); + } + + + + /** + * Get entries from the ban action log + * + * @param int $start - offset into list (zero is first entry) + * @param int $count - number of entries to return - zero is a special case + * @param int $numEntry - filled in on return with the total number of entries in the log file + * + * @return array of strings; each string is a single log entry, newest first. + * + * Returns an empty array if an error occurs (or if no entries) + * If $count is zero, all entries are returned, in ascending order. + */ + public function getLogEntries($start, $count, &$numEntry) + { + $ret = array(); + $numEntry = 0; + $fileName = e_SYSTEM.eIPHandler::BAN_LOG_DIRECTORY.eIPHandler::BAN_FILE_LOG_NAME; + if (!is_readable($fileName)) return $ret; + + $vals = file($fileName); + if ($vals === FALSE) return $ret; + if (substr($vals[0], 0, 5) == ' $numEntry) return $ret; // Empty return if beyond the end + if ($count == 0) return $vals; // Special case - return the lot in ascending date order + // Array is built up with newest last - but we want newest first. And we don't want to duplicate the array! + if (($start + $count) > $numEntry) $count = $numEntry - $start; // Last segment might not have enough entries + $ret = array_slice($vals, -$start - $count, $count); + return array_reverse($ret); + } + + + /** + * Converts one of the strings returned in a getLogEntries string into an array of values + * + * @param string $string - a text line, possibly including a 'newline' at the end + * + * @return array of up to $count entries + * ['banDate'] - time/date stamp + * ['banIP'] - IP address involved + * ['banReason'] - Numeric reason code for entry + * ['banNotes'] = any text appended + */ + public function splitLogEntry($string) + { + $temp = explode(' ',$string, 4); + while (count($temp) < 4) $temp[] = ''; + $ret['banDate'] = $temp[0]; + $ret['banIP'] = $temp[1]; + $ret['banReason'] = $temp[2]; + $ret['banNotes'] = str_replace("\n", '', $temp[3]); + return $ret; + } + + + /** + * Delete ban Log file + * + * @return boolean TRUE on success, FALSE on failure + */ + public function deleteLogFile() + { + $fileName = e_SYSTEM.eIPHandler::BAN_LOG_DIRECTORY.eIPHandler::BAN_FILE_LOG_NAME; + return unlink($fileName); + } + + + /** + * Update expiry time for IP addresses that have accessed the site while banned. + * + * @param int $start - date/timestamp for earliest time to consider (usually time previous cron call to this routine was run). + * + * @return int number of IP addresses updated + * + * @todo - finish this - DB access stuff to do + */ + public function banRetriggerAction($start = 0) + { + $numEntry = 0; // Make sure this variable declared before passing it - total number of log entries. + $ipAction = array(); // Array of IP addresses to action + $entries = $this->getLogEntries(0, 0, $numEntry); + while (count($entries) > 0) + { + // @todo Could do a binary search to find the first entry to use - Potentially much faster + $line = array_shift($entries); + $info = $this->splitLogEntry($line); + if (($info['banReason'] < 0) && ($info['banDate'] >= $start)) + { + $ipAction[$info['banIP']] = array('date' => $info['banDate'], 'reason' => $info['banReason']); // This will result in us gathering the most recent access from each IP address + } + } + + if (count($ipAction) == 0) return 0; // Nothing more to do + + // Now run through the database updating times + $numRet = 0; + $ourDb = new db; // @todo change for 0.8 + global $pref; + $writeDb = new db; + + foreach ($ipAction as $ipKey => $ipInfo) + { + // Have to search like this - may be a wildcard + if ($ourDb->db_Select('banlist', '*', "`banlist_ip` LIKE '".$ipKey."'") === 1) + { + if ($row = $ourDb->db_Fetch()) + { + // @todo check next line + $writeDb->db_Update('banlist', '`banlist_banexpires` = '.intval($row['banlist_banexpires'] + $pref['ban_durations'][$row['banlist_banreason']])); + } + } + } + return $numRet; + } +} + + +?> \ No newline at end of file diff --git a/e107_handlers/login.php b/e107_handlers/login.php index f42b8b611..1fa8ebd74 100644 --- a/e107_handlers/login.php +++ b/e107_handlers/login.php @@ -54,7 +54,7 @@ class userlogin public function __construct() { $this->e107 = e107::getInstance(); - $this->userIP = $this->e107->getip(); + $this->userIP = e107::getIPHandler()->getIP(); $this->userMethods = e107::getUserSession(); } @@ -92,7 +92,8 @@ class userlogin } // $this->e107->admin_log->e_log_event(4,__FILE__."|".__FUNCTION__."@".__LINE__,"DBG","User login",'IP: '.$fip,FALSE,LOG_TO_ROLLING); - $this->e107->check_ban("banlist_ip='{$this->userIP}' ",FALSE); // This will exit if a ban is in force +// $this->e107->check_ban("banlist_ip='{$this->userIP}' ",FALSE); // This will exit if a ban is in force + e107::getIPHandler()->checkBan("banlist_ip='{$this->userIP}' ",FALSE); // This will exit if a ban is in force $forceLogin = ($autologin == 'signup'); $autologin = intval($autologin); // Will decode to zero if forced login @@ -227,7 +228,7 @@ class userlogin /* restrict more than one person logging in using same us/pw */ if($pref['disallowMultiLogin']) { - if($this->e107->sql -> db_Select("online", "online_ip", "online_user_id='".$user_id.".".$user_name."'")) + if($this->e107->sql->db_Select("online", "online_ip", "online_user_id='".$user_id.".".$user_name."'")) { return $this->invalidLogin($username,LOGIN_MULTIPLE,$user_id); } @@ -342,7 +343,7 @@ class userlogin } // User is in DB here - $this->userData = $this->e107->sql -> db_Fetch(MYSQL_ASSOC); // Get user info + $this->userData = $this->e107->sql->db_Fetch(MYSQL_ASSOC); // Get user info $this->userData['user_perms'] = trim($this->userData['user_perms']); $this->lookEmail = $this->lookEmail && ($username == $this->userData['user_email']); // Know whether login name or email address used now @@ -510,11 +511,11 @@ class userlogin { // See if ban required (formerly the checkibr() function) if($pref['autoban'] == 1 || $pref['autoban'] == 3) { // Flood + Login or Login Only. - $fails = $this->e107->sql -> db_Count("generic", "(*)", "WHERE gen_ip='{$this->userIP}' AND gen_type='failed_login' "); + $fails = $this->e107->sql->db_Count("generic", "(*)", "WHERE gen_ip='{$this->userIP}' AND gen_type='failed_login' "); if($fails > 10) { $this->e107->add_ban(4,LAN_LOGIN_18,$this->userIP,1); - $this->e107->sql -> db_Insert("generic", "0, 'auto_banned', '".time()."', 0, '{$this->userIP}', '{$extra_text}', '".LAN_LOGIN_20.": ".$this->e107->tp -> toDB($username).", ".LAN_LOGIN_17.": ".md5($ouserpass)."' "); + $this->e107->sql->db_Insert("generic", "0, 'auto_banned', '".time()."', 0, '{$this->userIP}', '{$extra_text}', '".LAN_LOGIN_20.": ".$this->e107->tp -> toDB($username).", ".LAN_LOGIN_17.": ".md5($ouserpass)."' "); } } } diff --git a/e107_languages/English/admin/lan_banlist.php b/e107_languages/English/admin/lan_banlist.php index 4539e1b1e..8b12beea9 100644 --- a/e107_languages/English/admin/lan_banlist.php +++ b/e107_languages/English/admin/lan_banlist.php @@ -10,17 +10,17 @@ +----------------------------------------------------------------------------+ */ // define("BANLAN_1", "Ban removed."); -define("BANLAN_2", 'No bans in list.'); -define("BANLAN_3", "Existing Bans"); +define('BANLAN_2', 'No bans in list.'); +define('BANLAN_3', 'Existing Bans'); // define("BANLAN_4", "Remove ban"); -define("BANLAN_5", "Enter IP, email address, or host"); -define("BANLAN_7", "Reason"); -define("BANLAN_8", "Ban Address"); -define("BANLAN_9", "Ban users from site by email, IP or host address"); -define("BANLAN_10", "IP / Email / Reason"); -define("BANLAN_11", "Auto-ban: More than 10 failed login attempts"); -define("BANLAN_12", "Note: Reverse DNS is currently disabled; it must be enabled to allow banning by host. Banning by IP and email address will still function normally."); -define("BANLAN_13", "Note: To ban a user by user name, go to the users admin page: "); +define('BANLAN_5', 'Enter IP, email address, or host'); +define('BANLAN_7', 'Reason'); +define('BANLAN_8', 'Ban Address'); +define('BANLAN_9', 'Ban users from site by email, IP or host address'); +define('BANLAN_10', 'IP / Email / Reason'); +define('BANLAN_11', 'Auto-ban: More than 10 failed login attempts'); +define('BANLAN_12', 'Note: Reverse DNS is currently disabled; it must be enabled to allow banning by host. Banning by IP and email address will still function normally.'); +define('BANLAN_13', 'Note: To ban a user by user name, go to the users admin page: '); define('BANLAN_14','Ban List'); define('BANLAN_15','Messages/Ban Periods'); define('BANLAN_16','Banning'); @@ -36,7 +36,7 @@ define('BANLAN_25','Add to Banlist'); define('BANLAN_26','Currently '); define('BANLAN_27','Invalid characters in IP address stripped - now:'); define('BANLAN_28','Ban type'); -define('BANLAN_29','Message to show'); +define('BANLAN_29','Message to show to banned user'); define('BANLAN_30','Ban duration'); define('BANLAN_31','(Use an empty message if you wish the user to get a blank screen)'); define('BANLAN_32','Indefinite'); @@ -55,7 +55,7 @@ define('BANLAN_44','Use expiry date/time from import'); define('BANLAN_45','Import'); define('BANLAN_46','Import File:'); define('BANLAN_47','File upload error'); -//define('BANLAN_48','Error importing file'); +define('BANLAN_48','Deleted --NUM-- expired ban list entries'); define('BANLAN_49','CSV import: Unbalanced quotes in line '); define('BANLAN_50','CSV import: Error writing banlist record at line '); define('BANLAN_51','CSV import: Success, --NUM-- lines imported from file '); @@ -88,6 +88,19 @@ define('BANLAN_77','Messages/Ban Periods'); // define('BANLAN_78','Hit count exceeded (--HITS-- requests within allotted time)'); define('BANLAN_79','CSV Export format:'); define('BANLAN_80','CSV Import format:'); +define('BANLAN_81','Ban Action Log'); +define('BANLAN_82', 'No entries in Ban Action Log'); +define('BANLAN_83', 'Date/Time'); +define('BANLAN_84', 'IP Address'); +define('BANLAN_85', 'Additional information'); +define('BANLAN_86', 'Ban-related events'); +define('BANLAN_87', 'Total --NUM-- entries in list'); +define('BANLAN_88', 'Empty Ban Action Log'); +define('BANLAN_89', 'Log File Deleted'); +define('BANLAN_90', 'Error deleting log file'); +define('BANLAN_91', 'Date/time format for ban log'); +define('BANLAN_92', 'See the strftime function page at php.net'); +define('BANLAN_93', ''); // Ban types - block reserved 100-109 define('BANLAN_100', 'Unknown'); @@ -113,6 +126,6 @@ define('BANLAN_117', 'Spare reason'); define('BANLAN_118', 'Spare reason'); define('BANLAN_119', 'Indicates an import error - previously imported bans'); -define('BANLAN_120', 'Unknown'); +define('BANLAN_120', 'Whitelist entry'); ?> \ No newline at end of file diff --git a/e107_languages/English/admin/lan_log_messages.php b/e107_languages/English/admin/lan_log_messages.php index 6e4c7e66a..63b8b7410 100644 --- a/e107_languages/English/admin/lan_log_messages.php +++ b/e107_languages/English/admin/lan_log_messages.php @@ -88,6 +88,7 @@ define('LAN_AL_BANLIST_08','Banlist options updated'); define('LAN_AL_BANLIST_09','Banlist entry edited'); define('LAN_AL_BANLIST_10','Whitelist entry edited'); define('LAN_AL_BANLIST_11','Whitelist hit for ban entry'); +define('LAN_AL_BANLIST_12','Expired bans cleared'); // Comment-related events diff --git a/e107_languages/English/lan_userclass.php b/e107_languages/English/lan_userclass.php index f5c0309c6..7206eacf2 100644 --- a/e107_languages/English/lan_userclass.php +++ b/e107_languages/English/lan_userclass.php @@ -9,19 +9,19 @@ | $Author$ +----------------------------------------------------------------------------+ */ -define("UC_LAN_0", "Everyone (public)"); -define("UC_LAN_1", "Guests"); -define("UC_LAN_2", "No One (inactive)"); -define("UC_LAN_3", "Members"); -define("UC_LAN_4", "Read Only"); -define("UC_LAN_5", "Admin"); -define("UC_LAN_6", "Main Admin"); +define('UC_LAN_0', 'Everyone (public)'); +define('UC_LAN_1', 'Guests'); +define('UC_LAN_2', 'No One (inactive)'); +define('UC_LAN_3', 'Members'); +define('UC_LAN_4', 'Read Only'); +define('UC_LAN_5', 'Admin'); +define('UC_LAN_6', 'Main Admin'); define('UC_LAN_7', 'Forum Moderators'); define('UC_LAN_8','Admins and Mods'); define('UC_LAN_9','New Users'); define('UC_LAN_10', 'Search Bots'); -define('UC_LAN_INVERT', "Not --CLASS--"); -define('UC_LAN_INVERTLABEL', "Everyone but.."); +define('UC_LAN_INVERT', 'Not --CLASS--'); +define('UC_LAN_INVERTLABEL', 'Everyone but..'); ?> \ No newline at end of file