/** * Private constructor. * It parses PHP server variables, and initializes its variables. */ private function __construct() { // This is the pattern of a local (or unknown) IP address in both IPv4 and IPv6 $local_ip_pattern = '((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)'; // Client IP: REMOTE_ADDR, unless missing if (!isset($_SERVER['REMOTE_ADDR'])) { // Command line, or else. $this->_client_ip = ''; } elseif (!isValidIPv6($_SERVER['REMOTE_ADDR']) || preg_match('~::ffff:\\d+\\.\\d+\\.\\d+\\.\\d+~', $_SERVER['REMOTE_ADDR']) !== 0) { $this->_client_ip = preg_replace('~^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)~', '\\1', $_SERVER['REMOTE_ADDR']); // Just incase we have a legacy IPv4 address. // @ TODO: Convert to IPv6. if (filter_var($this->_client_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) { $this->_client_ip = 'unknown'; } } else { $this->_client_ip = $_SERVER['REMOTE_ADDR']; } // Second IP, guesswork it is, try to get the best IP we can, when using proxies or such $this->_ban_ip = $this->_client_ip; // Forwarded, maybe? if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^' . $local_ip_pattern . '~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^' . $local_ip_pattern . '~', $this->_client_ip) != 0)) { // check the first forwarded for as the block - only switch if it's better that way. if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.') && '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.') && (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown)~', $this->_client_ip) != 0)) { $this->_ban_ip = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); } else { $this->_ban_ip = $_SERVER['HTTP_CLIENT_IP']; } } if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^' . $local_ip_pattern . '~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^' . $local_ip_pattern . '~', $this->_client_ip) != 0)) { // Since they are in different blocks, it's probably reversed. if (strtok($this->_client_ip, '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.')) { $this->_ban_ip = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); } else { $this->_ban_ip = $_SERVER['HTTP_CLIENT_IP']; } } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { // If there are commas, get the last one.. probably. if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) { $ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR'])); // Go through each IP... foreach ($ips as $i => $ip) { // Make sure it's in a valid range... if (preg_match('~^' . $local_ip_pattern . '~', $ip) != 0 && preg_match('~^' . $local_ip_pattern . '~', $this->_client_ip) == 0) { continue; } // Otherwise, we've got an IP! $this->_ban_ip = trim($ip); break; } } elseif (preg_match('~^' . $local_ip_pattern . '~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^' . $local_ip_pattern . '~', $this->_client_ip) != 0) { $this->_ban_ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } } // Some final checking. if (filter_var($this->_ban_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && !isValidIPv6($this->_ban_ip)) { $this->_ban_ip = ''; } if ($this->_client_ip == 'unknown') { $this->_client_ip = ''; } // Keep compatibility with the uses of $_SERVER['REMOTE_ADDR']... $_SERVER['REMOTE_ADDR'] = $this->_client_ip; // Set the scheme, for later use $this->_scheme = !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https' : 'http'; // Make sure we know everything about you... HTTP_USER_AGENT! $this->_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? htmlspecialchars($_SERVER['HTTP_USER_AGENT'], ENT_QUOTES, 'UTF-8') : ''; // Keep compatibility with the uses of $_SERVER['HTTP_USER_AGENT']... $_SERVER['HTTP_USER_AGENT'] = $this->_user_agent; // We want to know who we are, too :P $this->_server_software = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ''; }
/** * @todo needs a description * * @param int $memID = 0 id_member */ function TrackIP($memID = 0) { global $user_profile, $scripturl, $txt, $user_info, $modSettings, $sourcedir; global $context, $smcFunc; // Can the user do this? isAllowedTo('moderate_forum'); if ($memID == 0) { $context['ip'] = $user_info['ip']; loadTemplate('Profile'); loadLanguage('Profile'); $context['sub_template'] = 'trackIP'; $context['page_title'] = $txt['profile']; $context['base_url'] = $scripturl . '?action=trackip'; } else { $context['ip'] = $user_profile[$memID]['member_ip']; $context['base_url'] = $scripturl . '?action=profile;area=tracking;sa=ip;u=' . $memID; } // Searching? if (isset($_REQUEST['searchip'])) { $context['ip'] = trim($_REQUEST['searchip']); } if (preg_match('/^\\d{1,3}\\.(\\d{1,3}|\\*)\\.(\\d{1,3}|\\*)\\.(\\d{1,3}|\\*)$/', $context['ip']) == 0 && isValidIPv6($context['ip']) === false) { fatal_lang_error('invalid_tracking_ip', false); } $ip_var = str_replace('*', '%', $context['ip']); $ip_string = strpos($ip_var, '%') === false ? '= {string:ip_address}' : 'LIKE {string:ip_address}'; if (empty($context['tracking_area'])) { $context['page_title'] = $txt['trackIP'] . ' - ' . $context['ip']; } $request = $smcFunc['db_query']('', ' SELECT id_member, real_name AS display_name, member_ip FROM {db_prefix}members WHERE member_ip ' . $ip_string, array('ip_address' => $ip_var)); $context['ips'] = array(); while ($row = $smcFunc['db_fetch_assoc']($request)) { $context['ips'][$row['member_ip']][] = '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['display_name'] . '</a>'; } $smcFunc['db_free_result']($request); ksort($context['ips']); // Gonna want this for the list. require_once $sourcedir . '/Subs-List.php'; // Start with the user messages. $listOptions = array('id' => 'track_message_list', 'title' => $txt['messages_from_ip'] . ' ' . $context['ip'], 'start_var_name' => 'messageStart', 'items_per_page' => $modSettings['defaultMaxMessages'], 'no_items_label' => $txt['no_messages_from_ip'], 'base_href' => $context['base_url'] . ';searchip=' . $context['ip'], 'default_sort_col' => 'date', 'get_items' => array('function' => 'list_getIPMessages', 'params' => array('m.poster_ip ' . $ip_string, array('ip_address' => $ip_var))), 'get_count' => array('function' => 'list_getIPMessageCount', 'params' => array('m.poster_ip ' . $ip_string, array('ip_address' => $ip_var))), 'columns' => array('ip_address' => array('header' => array('value' => $txt['ip_address']), 'data' => array('sprintf' => array('format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>', 'params' => array('ip' => false))), 'sort' => array('default' => 'INET_ATON(m.poster_ip)', 'reverse' => 'INET_ATON(m.poster_ip) DESC')), 'poster' => array('header' => array('value' => $txt['poster']), 'data' => array('db' => 'member_link')), 'subject' => array('header' => array('value' => $txt['subject']), 'data' => array('sprintf' => array('format' => '<a href="' . $scripturl . '?topic=%1$s.msg%2$s#msg%2$s" rel="nofollow">%3$s</a>', 'params' => array('topic' => false, 'id' => false, 'subject' => false)))), 'date' => array('header' => array('value' => $txt['date']), 'data' => array('db' => 'time'), 'sort' => array('default' => 'm.id_msg DESC', 'reverse' => 'm.id_msg'))), 'additional_rows' => array(array('position' => 'after_title', 'value' => $txt['messages_from_ip_desc'], 'class' => 'windowbg2', 'style' => 'padding: 1ex 2ex;'))); // Create the messages list. createList($listOptions); // Set the options for the error lists. $listOptions = array('id' => 'track_user_list', 'title' => $txt['errors_from_ip'] . ' ' . $context['ip'], 'start_var_name' => 'errorStart', 'items_per_page' => $modSettings['defaultMaxMessages'], 'no_items_label' => $txt['no_errors_from_ip'], 'base_href' => $context['base_url'] . ';searchip=' . $context['ip'], 'default_sort_col' => 'date2', 'get_items' => array('function' => 'list_getUserErrors', 'params' => array('le.ip ' . $ip_string, array('ip_address' => $ip_var))), 'get_count' => array('function' => 'list_getUserErrorCount', 'params' => array('ip ' . $ip_string, array('ip_address' => $ip_var))), 'columns' => array('ip_address2' => array('header' => array('value' => $txt['ip_address']), 'data' => array('sprintf' => array('format' => '<a href="' . $context['base_url'] . ';searchip=%1$s">%1$s</a>', 'params' => array('ip' => false))), 'sort' => array('default' => 'INET_ATON(le.ip)', 'reverse' => 'INET_ATON(le.ip) DESC')), 'display_name' => array('header' => array('value' => $txt['display_name']), 'data' => array('db' => 'member_link')), 'message' => array('header' => array('value' => $txt['message']), 'data' => array('sprintf' => array('format' => '%1$s<br /><a href="%2$s">%2$s</a>', 'params' => array('message' => false, 'url' => false)))), 'date2' => array('header' => array('value' => $txt['date']), 'data' => array('db' => 'time'), 'sort' => array('default' => 'le.id_error DESC', 'reverse' => 'le.id_error'))), 'additional_rows' => array(array('position' => 'after_title', 'value' => $txt['errors_from_ip_desc'], 'class' => 'windowbg2', 'style' => 'padding: 1ex 2ex;'))); // Create the error list. createList($listOptions); $context['single_ip'] = strpos($context['ip'], '*') === false; if ($context['single_ip']) { $context['whois_servers'] = array('afrinic' => array('name' => $txt['whois_afrinic'], 'url' => 'http://www.afrinic.net/cgi-bin/whois?searchtext=' . $context['ip'], 'range' => array(41, 154, 196)), 'apnic' => array('name' => $txt['whois_apnic'], 'url' => 'http://wq.apnic.net/apnic-bin/whois.pl?searchtext=' . $context['ip'], 'range' => array(58, 59, 60, 61, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 133, 150, 153, 163, 171, 202, 203, 210, 211, 218, 219, 220, 221, 222)), 'arin' => array('name' => $txt['whois_arin'], 'url' => 'http://whois.arin.net/rest/ip/' . $context['ip'], 'range' => array(7, 24, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 96, 97, 98, 99, 128, 129, 130, 131, 132, 134, 135, 136, 137, 138, 139, 140, 142, 143, 144, 146, 147, 148, 149, 152, 155, 156, 157, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174, 192, 198, 199, 204, 205, 206, 207, 208, 209, 216)), 'lacnic' => array('name' => $txt['whois_lacnic'], 'url' => 'http://lacnic.net/cgi-bin/lacnic/whois?query=' . $context['ip'], 'range' => array(186, 187, 189, 190, 191, 200, 201)), 'ripe' => array('name' => $txt['whois_ripe'], 'url' => 'http://www.db.ripe.net/whois?searchtext=' . $context['ip'], 'range' => array(62, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 141, 145, 151, 188, 193, 194, 195, 212, 213, 217))); foreach ($context['whois_servers'] as $whois) { // Strip off the "decimal point" and anything following... if (in_array((int) $context['ip'], $whois['range'])) { $context['auto_whois_server'] = $whois; } } } }
/** * Another helper function that put together the * @param string $fullip An IP address either IPv6 or not * @return string A SQL condition */ function constructBanQueryIP($fullip) { // First attempt a IPv6 address. if (isValidIPv6($fullip)) { $ip_parts = convertIPv6toInts($fullip); $ban_query = '((' . $ip_parts[0] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) AND (' . $ip_parts[1] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low4 AND bi.ip_high4) AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low5 AND bi.ip_high5) AND (' . $ip_parts[5] . ' BETWEEN bi.ip_low6 AND bi.ip_high6) AND (' . $ip_parts[6] . ' BETWEEN bi.ip_low7 AND bi.ip_high7) AND (' . $ip_parts[7] . ' BETWEEN bi.ip_low8 AND bi.ip_high8))'; } elseif (preg_match('/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/', $fullip, $ip_parts) == 1) { $ban_query = '((' . $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1) AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2) AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3) AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4))'; } else { $ban_query = '(bi.ip_low1 = 255 AND bi.ip_high1 = 255 AND bi.ip_low2 = 255 AND bi.ip_high2 = 255 AND bi.ip_low3 = 255 AND bi.ip_high3 = 255 AND bi.ip_low4 = 255 AND bi.ip_high4 = 255)'; } return $ban_query; }
function cleanRequest() { global $board, $topic, $boardurl, $scripturl, $modSettings, $smcFunc; // Makes it easier to refer to things this way. $scripturl = $boardurl . '/index.php'; // What function to use to reverse magic quotes - if sybase is on we assume that the database sensibly has the right unescape function! $removeMagicQuoteFunction = ini_get('magic_quotes_sybase') || strtolower(ini_get('magic_quotes_sybase')) == 'on' ? 'unescapestring__recursive' : 'stripslashes__recursive'; // Save some memory.. (since we don't use these anyway.) unset($GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_POST_VARS']); unset($GLOBALS['HTTP_POST_FILES'], $GLOBALS['HTTP_POST_FILES']); // These keys shouldn't be set...ever. if (isset($_REQUEST['GLOBALS']) || isset($_COOKIE['GLOBALS'])) { die('Invalid request variable.'); } // Same goes for numeric keys. foreach (array_merge(array_keys($_POST), array_keys($_GET), array_keys($_FILES)) as $key) { if (is_numeric($key)) { die('Numeric request keys are invalid.'); } } // Numeric keys in cookies are less of a problem. Just unset those. foreach ($_COOKIE as $key => $value) { if (is_numeric($key)) { unset($_COOKIE[$key]); } } // Get the correct query string. It may be in an environment variable... if (!isset($_SERVER['QUERY_STRING'])) { $_SERVER['QUERY_STRING'] = getenv('QUERY_STRING'); } // It seems that sticking a URL after the query string is mighty common, well, it's evil - don't. if (strpos($_SERVER['QUERY_STRING'], 'http') === 0) { header('HTTP/1.1 400 Bad Request'); die; } // Are we going to need to parse the ; out? if (strpos(ini_get('arg_separator.input'), ';') === false && !empty($_SERVER['QUERY_STRING'])) { // Get rid of the old one! You don't know where it's been! $_GET = array(); // Was this redirected? If so, get the REDIRECT_QUERY_STRING. // Do not urldecode() the querystring, unless you so much wish to break OpenID implementation. :) $_SERVER['QUERY_STRING'] = substr($_SERVER['QUERY_STRING'], 0, 5) === 'url=/' ? $_SERVER['REDIRECT_QUERY_STRING'] : $_SERVER['QUERY_STRING']; // Replace ';' with '&' and '&something&' with '&something=&'. (this is done for compatibility...) // @todo smflib parse_str(preg_replace('/&(\\w+)(?=&|$)/', '&$1=', strtr($_SERVER['QUERY_STRING'], array(';?' => '&', ';' => '&', '%00' => '', "" => ''))), $_GET); // Magic quotes still applies with parse_str - so clean it up. if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) { $_GET = $removeMagicQuoteFunction($_GET); } } elseif (strpos(ini_get('arg_separator.input'), ';') !== false) { if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) { $_GET = $removeMagicQuoteFunction($_GET); } // Search engines will send action=profile%3Bu=1, which confuses PHP. foreach ($_GET as $k => $v) { if ((string) $v === $v && strpos($k, ';') !== false) { $temp = explode(';', $v); $_GET[$k] = $temp[0]; for ($i = 1, $n = count($temp); $i < $n; $i++) { @(list($key, $val) = @explode('=', $temp[$i], 2)); if (!isset($_GET[$key])) { $_GET[$key] = $val; } } } // This helps a lot with integration! if (strpos($k, '?') === 0) { $_GET[substr($k, 1)] = $v; unset($_GET[$k]); } } } // There's no query string, but there is a URL... try to get the data from there. if (!empty($_SERVER['REQUEST_URI'])) { // Remove the .html, assuming there is one. if (substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], '.'), 4) == '.htm') { $request = substr($_SERVER['REQUEST_URI'], 0, strrpos($_SERVER['REQUEST_URI'], '.')); } else { $request = $_SERVER['REQUEST_URI']; } // @todo smflib. // Replace 'index.php/a,b,c/d/e,f' with 'a=b,c&d=&e=f' and parse it into $_GET. if (strpos($request, basename($scripturl) . '/') !== false) { parse_str(substr(preg_replace('/&(\\w+)(?=&|$)/', '&$1=', strtr(preg_replace('~/([^,/]+),~', '/$1=', substr($request, strpos($request, basename($scripturl)) + strlen(basename($scripturl)))), '/', '&')), 1), $temp); if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0 && empty($modSettings['integrate_magic_quotes'])) { $temp = $removeMagicQuoteFunction($temp); } $_GET += $temp; } } // If magic quotes is on we have some work... if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc() != 0) { $_ENV = $removeMagicQuoteFunction($_ENV); $_POST = $removeMagicQuoteFunction($_POST); $_COOKIE = $removeMagicQuoteFunction($_COOKIE); foreach ($_FILES as $k => $dummy) { if (isset($_FILES[$k]['name'])) { $_FILES[$k]['name'] = $removeMagicQuoteFunction($_FILES[$k]['name']); } } } // Add entities to GET. This is kinda like the slashes on everything else. $_GET = htmlspecialchars__recursive($_GET); // Let's not depend on the ini settings... why even have COOKIE in there, anyway? $_REQUEST = $_POST + $_GET; // Make sure $board and $topic are numbers. if (isset($_REQUEST['board'])) { // Make sure its a string and not something else like an array $_REQUEST['board'] = (string) $_REQUEST['board']; // If there's a slash in it, we've got a start value! (old, compatible links.) if (strpos($_REQUEST['board'], '/') !== false) { list($_REQUEST['board'], $_REQUEST['start']) = explode('/', $_REQUEST['board']); } elseif (strpos($_REQUEST['board'], '.') !== false) { list($_REQUEST['board'], $_REQUEST['start']) = explode('.', $_REQUEST['board']); } // Now make absolutely sure it's a number. $board = (int) $_REQUEST['board']; $_REQUEST['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0; // This is for "Who's Online" because it might come via POST - and it should be an int here. $_GET['board'] = $board; } else { $board = 0; } // If there's a threadid, it's probably an old YaBB SE link. Flow with it. if (isset($_REQUEST['threadid']) && !isset($_REQUEST['topic'])) { $_REQUEST['topic'] = $_REQUEST['threadid']; } // We've got topic! if (isset($_REQUEST['topic'])) { // Make sure its a string and not something else like an array $_REQUEST['topic'] = (string) $_REQUEST['topic']; // Slash means old, beta style, formatting. That's okay though, the link should still work. if (strpos($_REQUEST['topic'], '/') !== false) { list($_REQUEST['topic'], $_REQUEST['start']) = explode('/', $_REQUEST['topic']); } elseif (strpos($_REQUEST['topic'], '.') !== false) { list($_REQUEST['topic'], $_REQUEST['start']) = explode('.', $_REQUEST['topic']); } $topic = (int) $_REQUEST['topic']; // Now make sure the online log gets the right number. $_GET['topic'] = $topic; } else { $topic = 0; } // There should be a $_REQUEST['start'], some at least. If you need to default to other than 0, use $_GET['start']. if (empty($_REQUEST['start']) || $_REQUEST['start'] < 0 || (int) $_REQUEST['start'] > 2147473647) { $_REQUEST['start'] = 0; } // The action needs to be a string and not an array or anything else if (isset($_REQUEST['action'])) { $_REQUEST['action'] = (string) $_REQUEST['action']; } if (isset($_GET['action'])) { $_GET['action'] = (string) $_GET['action']; } // Make sure we have a valid REMOTE_ADDR. if (!isset($_SERVER['REMOTE_ADDR'])) { $_SERVER['REMOTE_ADDR'] = ''; // A new magic variable to indicate we think this is command line. $_SERVER['is_cli'] = true; } elseif (!isValidIPv6($_SERVER['REMOTE_ADDR']) || preg_match('~::ffff:\\d+\\.\\d+\\.\\d+\\.\\d+~', $_SERVER['REMOTE_ADDR']) !== 0) { $_SERVER['REMOTE_ADDR'] = preg_replace('~^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)~', '\\1', $_SERVER['REMOTE_ADDR']); // Just incase we have a legacy IPv4 address. // @ TODO: Convert to IPv6. if (preg_match('~^((([1]?\\d)?\\d|2[0-4]\\d|25[0-5])\\.){3}(([1]?\\d)?\\d|2[0-4]\\d|25[0-5])$~', $_SERVER['REMOTE_ADDR']) === 0) { $_SERVER['REMOTE_ADDR'] = 'unknown'; } } // Try to calculate their most likely IP for those people behind proxies (And the like). $_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR']; // Find the user's IP address. (but don't let it give you 'unknown'!) // @ TODO: IPv6 really doesn't need this. if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0)) { // We have both forwarded for AND client IP... check the first forwarded for as the block - only switch if it's better that way. if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.') && '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.') && (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0)) { $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); } else { $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP']; } } if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0)) { // Since they are in different blocks, it's probably reversed. if (strtok($_SERVER['REMOTE_ADDR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.')) { $_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP']))); } else { $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP']; } } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { // If there are commas, get the last one.. probably. if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) { $ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR'])); // Go through each IP... foreach ($ips as $i => $ip) { // Make sure it's in a valid range... if (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $ip) != 0 && preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) == 0) { continue; } // Otherwise, we've got an IP! $_SERVER['BAN_CHECK_IP'] = trim($ip); break; } } elseif (preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\\.(1[6-9]|2[0-9]|3[01])|192\\.168|255|127)\\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0) { $_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_X_FORWARDED_FOR']; } } // Make sure we know the URL of the current request. if (empty($_SERVER['REQUEST_URI'])) { $_SERVER['REQUEST_URL'] = $scripturl . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); } elseif (preg_match('~^([^/]+//[^/]+)~', $scripturl, $match) == 1) { $_SERVER['REQUEST_URL'] = $match[1] . $_SERVER['REQUEST_URI']; } else { $_SERVER['REQUEST_URL'] = $_SERVER['REQUEST_URI']; } // And make sure HTTP_USER_AGENT is set. $_SERVER['HTTP_USER_AGENT'] = isset($_SERVER['HTTP_USER_AGENT']) ? htmlspecialchars(stripslashes($_SERVER['HTTP_USER_AGENT']), ENT_QUOTES) : ''; // Some final checking. if (preg_match('~^((([1]?\\d)?\\d|2[0-4]\\d|25[0-5])\\.){3}(([1]?\\d)?\\d|2[0-4]\\d|25[0-5])$~', $_SERVER['BAN_CHECK_IP']) === 0 || !isValidIPv6($_SERVER['BAN_CHECK_IP'])) { $_SERVER['BAN_CHECK_IP'] = ''; } if ($_SERVER['REMOTE_ADDR'] == 'unknown') { $_SERVER['REMOTE_ADDR'] = ''; } }
/** * This function is behind the screen for adding new bans and modifying existing ones. * * Adding new bans: * - is accesssed by ?action=admin;area=ban;sa=add. * - uses the ban_edit sub template of the ManageBans template. * * Modifying existing bans: * - is accesssed by ?action=admin;area=ban;sa=edit;bg=x * - uses the ban_edit sub template of the ManageBans template. * - shows a list of ban triggers for the specified ban. */ public function action_edit() { global $txt, $modSettings, $context, $scripturl; require_once SUBSDIR . '/Bans.subs.php'; $ban_errors = Error_Context::context('ban', 1); // Saving a new or edited ban? if ((isset($_POST['add_ban']) || isset($_POST['modify_ban']) || isset($_POST['remove_selection'])) && !$ban_errors->hasErrors()) { $this->action_edit2(); } $ban_group_id = isset($context['ban']['id']) ? $context['ban']['id'] : (isset($_REQUEST['bg']) ? (int) $_REQUEST['bg'] : 0); // Template needs this to show errors using javascript loadLanguage('Errors'); createToken('admin-bet'); $context['form_url'] = $scripturl . '?action=admin;area=ban;sa=edit'; // Prepare any errors found to the template to show $context['ban_errors'] = array('errors' => $ban_errors->prepareErrors(), 'type' => $ban_errors->getErrorType() == 0 ? 'minor' : 'serious', 'title' => $txt['ban_errors_detected']); if (!$ban_errors->hasErrors()) { // If we're editing an existing ban, get it from the database. if (!empty($ban_group_id)) { $context['ban_group_id'] = $ban_group_id; // We're going to want this for making our list. require_once SUBSDIR . '/GenericList.class.php'; // Setup for a createlist $listOptions = array('id' => 'ban_items', 'base_href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id, 'no_items_label' => $txt['ban_no_triggers'], 'items_per_page' => $modSettings['defaultMaxMessages'], 'get_items' => array('function' => 'list_getBanItems', 'params' => array('ban_group_id' => $ban_group_id)), 'get_count' => array('function' => 'list_getNumBanItems', 'params' => array('ban_group_id' => $ban_group_id)), 'columns' => array('type' => array('header' => array('value' => $txt['ban_banned_entity'], 'style' => 'width: 60%;'), 'data' => array('function' => create_function('$ban_item', ' global $txt; if (in_array($ban_item[\'type\'], array(\'ip\', \'hostname\', \'email\'))) return \'<strong>\' . $txt[$ban_item[\'type\']] . \':</strong> \' . $ban_item[$ban_item[\'type\']]; elseif ($ban_item[\'type\'] == \'user\') return \'<strong>\' . $txt[\'username\'] . \':</strong> \' . $ban_item[\'user\'][\'link\']; else return \'<strong>\' . $txt[\'unknown\'] . \':</strong> \' . $ban_item[\'no_bantype_selected\']; '))), 'hits' => array('header' => array('value' => $txt['ban_hits'], 'style' => 'width: 15%;text-align: center'), 'data' => array('db' => 'hits', 'class' => 'centertext')), 'id' => array('header' => array('value' => $txt['ban_actions'], 'style' => 'width: 15%;'), 'data' => array('function' => create_function('$ban_item', ' global $txt, $context, $scripturl; return \'<a href="\' . $scripturl . \'?action=admin;area=ban;sa=edittrigger;bg=\' . $context[\'ban\'][\'id\'] . \';bi=\' . $ban_item[\'id\'] . \'">\' . $txt[\'ban_edit_trigger\'] . \'</a>\'; '))), 'checkboxes' => array('header' => array('value' => '<input type="checkbox" onclick="invertAll(this, this.form, \'ban_items\');" class="input_check" />', 'style' => 'width: 5%;'), 'data' => array('sprintf' => array('format' => '<input type="checkbox" name="ban_items[]" value="%1$d" class="input_check" />', 'params' => array('id' => false))))), 'form' => array('href' => $scripturl . '?action=admin;area=ban;sa=edit;bg=' . $ban_group_id), 'additional_rows' => array(array('position' => 'below_table_data', 'class' => 'submitbutton', 'value' => ' <input type="submit" name="remove_selection" value="' . $txt['ban_remove_selected_triggers'] . '" class="right_submit" /> <a class="linkbutton" href="' . $scripturl . '?action=admin;area=ban;sa=edittrigger;bg=' . $ban_group_id . '">' . $txt['ban_add_trigger'] . '</a> <input type="hidden" name="bg" value="' . $ban_group_id . '" /> <input type="hidden" name="' . $context['session_var'] . '" value="' . $context['session_id'] . '" /> <input type="hidden" name="' . $context['admin-bet_token_var'] . '" value="' . $context['admin-bet_token'] . '" />'))); createList($listOptions); } else { $context['ban'] = array('id' => 0, 'name' => '', 'expiration' => array('status' => 'never', 'days' => 0), 'reason' => '', 'notes' => '', 'ban_days' => 0, 'cannot' => array('access' => true, 'post' => false, 'register' => false, 'login' => false), 'is_new' => true); $context['ban_suggestions'] = array('main_ip' => '', 'hostname' => '', 'email' => '', 'member' => array('id' => 0)); // Overwrite some of the default form values if a user ID was given. if (!empty($_REQUEST['u'])) { $context['ban_suggestions'] = array_merge($context['ban_suggestions'], getMemberData((int) $_REQUEST['u'])); if (!empty($context['ban_suggestions']['member']['id'])) { $context['ban_suggestions']['href'] = $scripturl . '?action=profile;u=' . $context['ban_suggestions']['member']['id']; $context['ban_suggestions']['member']['link'] = '<a href="' . $context['ban_suggestions']['href'] . '">' . $context['ban_suggestions']['member']['name'] . '</a>'; // Default the ban name to the name of the banned member. $context['ban']['name'] = $context['ban_suggestions']['member']['name']; // @todo: there should be a better solution... // used to lock the "Ban on Username" input when banning from profile $context['ban']['from_user'] = true; // Would be nice if we could also ban the hostname. if ((preg_match('/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/', $context['ban_suggestions']['main_ip']) == 1 || isValidIPv6($context['ban_suggestions']['main_ip'])) && empty($modSettings['disableHostnameLookup'])) { $context['ban_suggestions']['hostname'] = host_from_ip($context['ban_suggestions']['main_ip']); } $context['ban_suggestions']['other_ips'] = banLoadAdditionalIPs($context['ban_suggestions']['member']['id']); } } else { $context['use_autosuggest'] = true; loadJavascriptFile('suggest.js'); } } } // Set the right template $context['sub_template'] = 'ban_edit'; // A couple of text strings we *may* need addJavascriptVar(array('txt_ban_name_empty' => $txt['ban_name_empty'], 'txt_ban_restriction_empty' => $txt['ban_restriction_empty']), true); // And a bit of javascript to enable/disable some fields addInlineJavascript('addLoadEvent(fUpdateStatus);', true); }
/** * Convert a single IP to a ranged IP. * * - internal function used to convert a user-readable format to a format suitable for the database. * * @param string $fullip * @return array|string 'unknown' if the ip in the input was '255.255.255.255' */ function ip2range($fullip) { // If its IPv6, validate it first. if (isValidIPv6($fullip) !== false) { $ip_parts = explode(':', expandIPv6($fullip, false)); $ip_array = array(); if (count($ip_parts) != 8) { return array(); } for ($i = 0; $i < 8; $i++) { if ($ip_parts[$i] == '*') { $ip_array[$i] = array('low' => '0', 'high' => hexdec('ffff')); } elseif (preg_match('/^([0-9A-Fa-f]{1,4})\\-([0-9A-Fa-f]{1,4})$/', $ip_parts[$i], $range) == 1) { $ip_array[$i] = array('low' => hexdec($range[1]), 'high' => hexdec($range[2])); } elseif (is_numeric(hexdec($ip_parts[$i]))) { $ip_array[$i] = array('low' => hexdec($ip_parts[$i]), 'high' => hexdec($ip_parts[$i])); } } return $ip_array; } // Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.) if ($fullip == 'unknown') { $fullip = '255.255.255.255'; } $ip_parts = explode('.', $fullip); $ip_array = array(); if (count($ip_parts) != 4) { return array(); } for ($i = 0; $i < 4; $i++) { if ($ip_parts[$i] == '*') { $ip_array[$i] = array('low' => '0', 'high' => '255'); } elseif (preg_match('/^(\\d{1,3})\\-(\\d{1,3})$/', $ip_parts[$i], $range) == 1) { $ip_array[$i] = array('low' => $range[1], 'high' => $range[2]); } elseif (is_numeric($ip_parts[$i])) { $ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]); } } // Makes it simpiler to work with. $ip_array[4] = array('low' => 0, 'high' => 0); $ip_array[5] = array('low' => 0, 'high' => 0); $ip_array[6] = array('low' => 0, 'high' => 0); $ip_array[7] = array('low' => 0, 'high' => 0); return $ip_array; }