%s) to be viewed.'; $phrases['file_too_large'] = 'The requested file is too large. The maximum permitted filesize is %s MB.'; $phrases['server_busy'] = 'The server is currently busy and unable to process your request. Please try again in a few minutes. We apologise for any inconvenience.'; $phrases['http_error'] = 'The requested resource could not be loaded because the server returned an error:
  %s %s (?).'; $phrases['curl_error'] = 'The requested resource could not be loaded. libcurl returned the error:
%s'; $phrases['unknown_error'] = 'The script encountered an unknown error. Error id: %s.'; // If an HTTP error (status code >= 400) is encountered, the script will look here // for an additional "friendly" explanation of the problem. $httpErrors = array('404' => 'A 404 error occurs when the requested resource does not exist.'); /***************************************************************** * Load theme config ******************************************************************/ // Current version - no need to change this! $themeReplace['version'] = 'v1.0 Final'; // Look for a config.php in the /themes/themeName/ folder if ( ! defined('MULTIGLYPE') && file_exists($tmp = GLYPE_ROOT . '/themes/' . $CONFIG['theme'] . '/config.php') ) { // Load it include $tmp; } // NB if running multiple proxies off the same source files - with glype // manager or any other product - set the MULTIGLYPE constant to stop the // script automatically loading theme config files. /***************************************************************** * Start session ******************************************************************/ // Set name to the configured value - change if running multiple proxies in same // folder and experiencing session conflicts. session_name('s'); // Allow caching. We don't want PHP to send any cache-related headers automatically // (and by default it tries to stop all caching). Using this limiter sends the fewest // headers, which we override later. session_cache_limiter('private_no_expire'); // Don't call _start() if session.auto_start = 1 if ( session_id() == '' ) { session_start(); } // Copy certain session items (needed after session [may be] closed) $jsFlags = isset($_SESSION['js_flags']) ? $_SESSION['js_flags'] : false; /***************************************************************** * Check IP bans ******************************************************************/ // Only check once per session or if the IP address changes if ( empty($_SESSION['ip_verified']) || $_SESSION['ip_verified'] != $_SERVER['REMOTE_ADDR'] ) { // Current IP matches a banned IP? true/false $banned = false; // Examine all IP bans foreach ( $CONFIG['ip_bans'] as $ip ) { // Is this a range or single? if ( ($pos = strspn($ip, '0123456789.')) == strlen($ip) ) { // Just a single IP so check for a match if ( $_SERVER['REMOTE_ADDR'] == $ip ) { // Flag the match and break out the loop $banned = true; break; } // And try next IP continue; } // Must be some form of IP range if still here. Convert our own // IP address to int and binary. $ownLong = ip2long($_SERVER['REMOTE_ADDR']); $ownBin = decbin($ownLong); // What kind of range? if ( $ip[$pos] == '/' ) { // Slash notation - split by slash list($net, $mask) = explode('/', $ip); // Fill IP with .0 if shortened form if ( ( $tmp = substr_count($net, '.') ) < 3 ) { $net .= str_repeat('.0', 3-$tmp); } // Note: there MUST be a better way of doing the rest of this section // but couldn't understand and/or get anything else to work... // To do: improve! // Convert a subnet mask to a prefix length if ( strpos($mask, '.') ) { $mask = substr_count(decbin(ip2long($mask)), '1'); } // Produce a binary string of the network address of prefix length // and compare to the equivalent for own address if ( substr(decbin(ip2long($net)), 0, $mask) === substr($ownBin, 0, $mask) ) { // They match so must be banned $banned = true; break; } } else { // No slash so it should just be a pair of dotted quads $from = ip2long(substr($ip, 0, $pos)); $to = ip2long(substr($ip, $pos+1)); // Did we get valid ranges? if ( $from && $to ) { // Are we in the range? if ( $ownLong >= $from && $ownLong <= $to ) { // We're banned. Don't bother checking the rest of the bans. $banned = true; break; } } } } // Is the IP address banned? if ( $banned ) { // Send a Forbidden header header('HTTP/1.1 403 Forbidden', true, 403); // Print the banned page and exit! echo loadTemplate('banned.page'); exit; } // Still here? Must be OK so save IP in session to prevent rechecking next time $_SESSION['ip_verified'] = $_SERVER['REMOTE_ADDR']; } /***************************************************************** * Find bitfield to determine options from ******************************************************************/ // First, find the bitfield! if ( $CONFIG['path_info_urls'] && ! empty($_SERVER['PATH_INFO']) && preg_match('#/b([0-9]{1,5})(?:/f([a-z]{1,10}))?/?$#', $_SERVER['PATH_INFO'], $tmp) ) { // Found a /bXX/ value at end of path info $bitfield = $tmp[1]; // (And while we're here, grab the flag too) $flag = isset($tmp[2]) ? $tmp[2] : ''; } else if ( ! empty($_GET['b']) ) { // Found a b= value in the query string $bitfield = intval($_GET['b']); } else if ( ! empty($_SESSION['bitfield']) ) { // Use stored session bitfield - mid-browsing but somehow lost the bitfield $bitfield = $_SESSION['bitfield']; } else { // Could not find any bitfield, regenerate (later) $regenerate = true; $bitfield = 0; } // Get flag from query string while we're here if ( ! isset($flag) ) { $flag = isset($_GET['f']) ? $_GET['f'] : ''; } /***************************************************************** * Determine options / use defaults ******************************************************************/ $i = 0; // Loop through the possible options foreach ( $CONFIG['options'] as $name => $details ) { // Is the option forced? if ( ! empty($details['force']) ) { // Use default $options[$name] = $details['default']; // And move onto next option continue; } // Which bit does this option occupy in the bitfield? $bit = pow(2, $i); // Use value from bitfield if possible, if ( ! isset($regenerate) ) { // Use value from bitfield $options[$name] = checkBit($bitfield, $bit); } // No bitfield available - use defaults and regenerate else { // Use default value $options[$name] = $details['default']; // Set bit if ( $details['default'] ) { setBit($bitfield, $bit); } } // Increase index ++$i; } // Save new session value $_SESSION['bitfield'] = $bitfield; /***************************************************************** * Unique URLs ******************************************************************/ if ( $CONFIG['unique_urls'] ) { // First visit? Ensure we have a unique salt if ( ! isset($_SESSION['unique_salt']) ) { // Generate random string $_SESSION['unique_salt'] = substr(md5(uniqid(true)),rand(0,10),rand(11,20)); } // Session gets closed before all parsing complete so copy unique to globals $GLOBALS['unique_salt'] = $_SESSION['unique_salt']; } /***************************************************************** * Custom browser - set up defaults ******************************************************************/ if ( ! isset($_SESSION['custom_browser']) ) { $_SESSION['custom_browser'] = array( 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'referrer' => 'real', 'tunnel' => '', 'tunnel_port' => '', 'tunnel_type' => '', ); } /***************************************************************** * Global functions * NB: Some of these (e.g. templating) could make up a whole new class * that could be easily swapped out to completely change how it works. * In the interests of speed - but at the cost of convenience - all this * is stuck together in here as functions. ******************************************************************/ /***************************************************************** * URL encoding * There are 3 options that affect URL encodings - the path info setting, * the unique URLs setting and the users choice of to encode or not. ******************************************************************/ // Takes a normal URL and converts it to a URL that, when requested, // will load the resource through our proxy function proxifyURL($url, $flag='', $absolute=true) { global $CONFIG, $options, $bitfield; // First, validate the input if ( empty($url) ) { return ''; } // Is it an anchor? if ( $url[0] == '#' ) { return $url; } // Is it javascript? if ( stripos($url, 'javascript:') === 0 ) { return $url; } // Is it already proxified? if ( strpos($url, GLYPE_BROWSE) === 0 ) { return $url; } // URLs occur as values of HTML attributes so decode it $url = $url; // Convert to absolute url (if not already) $url = absoluteURL($url); // Extract an #anchor $jumpTo = ''; if ( $tmp = strpos($url, '#') ) { // Split into jumpTo (append it after proxifying) and $url $jumpTo = substr($url, $tmp); $url = substr($url, 0, $tmp); } // Add encoding if ( $options['encodeURL'] ) { // Part of our encoding is to remove HTTP (saves space and helps avoid detection) $url = substr($url, 4); // Add base64 $url = base64_encode($url); // Are we using unique URLs? if ( $CONFIG['unique_urls'] ) { // Add the salt $url = $GLOBALS['unique_salt'] . $url; } } // Protect chars that have other meaning in URLs $url = rawurlencode($url); // If generating absolute URL, add prefix $prefix = $absolute ? GLYPE_BROWSE : ''; // Return in path info format (only when encoding is on) if ( $CONFIG['path_info_urls'] && $options['encodeURL'] ) { return $prefix . '/' . str_replace('%', '_', chunk_split($url, 8, '/')) . 'b' . $bitfield . '/' . ( $flag ? 'f' . $flag : '') . $jumpTo; } // Otherwise, return in 'normal' (query string) format return $prefix . '?u=' . $url . '&b=' . $bitfield . ( $flag ? '&f=' . $flag : '' ) . $jumpTo; } // Takes a URL that has been "proxified" by the proxifyURL() function // and returns it to a normal, direct URL function deproxifyURL($url, $verifyUnique=false) { // Check we have URL to deproxify if ( empty($url) ) { return $url; } // Remove our prefix $url = str_replace(GLYPE_BROWSE, '', $url); // Take off flags and bitfield if ( $url[0] == '/' ) { // First char is slash, must be path info format $url = preg_replace('#/b[0-9]{1,5}(?:/f[a-z]{1,10})?/?$#', '', $url); // Return % and strip / $url = str_replace('_', '%', $url); $url = str_replace('/', '', $url); } else { // First char not / so must be the standard query string format if ( preg_match('#\bu=([^&]+)#', $url, $tmp) ) { $url = $tmp[1]; } } // Remove URL encoding (returns special chars such as /) $url = rawurldecode($url); // Is it encoded? Presence of :// means unencoded. if ( ! strpos($url, '://') ) { // Check for unique salt to remove if ( isset($GLOBALS['unique_salt']) ) { // Verify the salt if ( $verifyUnique && substr($url, 0, strlen($GLOBALS['unique_salt'])) != $GLOBALS['unique_salt'] ) { error('unique_mismatch', false); } // Remove the salt $url = substr($url, strlen($GLOBALS['unique_salt'])); } // Remove base64 $url = base64_decode($url); // Add http back $url = 'http' . $url; } // URLs were originally HTML attributes so *should* have had all // entities encoded. Decode it. $url = htmlspecialchars_decode($url); // Check for successful decoding if ( strpos($url, '://') === false ) { return false; } // Return decoded URL return $url; } // Take any type of URL (relative, absolute, with base, from root, etc.) // and return an absolute URL. function absoluteURL($input) { global $base, $URL; // No input? if ( ! $input ) { return $input; } // Ensure a complete URL if ( stripos($input, 'http://') !== 0 && stripos($input, 'https://') !== 0 ) { // No change if . if ( $input == '.' ) { $input = ''; } // Relative from root if ( $input && $input[0] == '/' ) { // "//domain.com" is also acceptable so check next char as well if ( isset($input[1]) && $input[1] == '/' ) { // Prefix the HTTP and we're done $input = 'http:' . $input; } else { // Relative path from root so add scheme+host as prefix and we're done $input = $URL['scheme_host'] . $input; } } else if ( isset($base) ) { // Relative path from base href $input = $base . $input; } else { // Relative from document $input = $URL['scheme_host'] . $URL['path'] . $input; } } // Simplify path // Strip ./ (refers to current directory) $input = str_replace('/./', '/', $input); // Strip double slash // if ( strlen($input) > 8 && strpos($input, '//', 8) ) { $input = preg_replace('#(? $level ) { // If this is the .., remove it and the previous directory // so in effect we've travelled up a directory if ( $level == '..' ) { unset($parts[$previous]); unset($parts[$key]); break; } // If we remove more than one .. part, keys will no // longer be consecutively ordered $previous = $key; } } // Return joined URI $input = implode('/',$parts); } return $input; } /***************************************************************** * Templating System ******************************************************************/ // Load a template function loadTemplate($file, $vars=array()) { // Extract passed vars extract($vars); // Start output buffer ob_start(); // Ensure file exists if ( $path = getTemplatePath($file) ) { // Load template into buffer include $path; // Get buffer into variable $template = ob_get_contents(); } // Dispose of output buffer ob_end_clean(); // Ensure template loaded properly if ( empty($template) ) { // Return an error message return 'ERROR: template failed to load. Please ensure you have correctly installed any custom themes and check you have not removed any files from the default theme.'; } // Apply theme replacements to template $template = replaceThemeTags($template); // Return HTML return $template; } // Take a template name and return absolute path function getTemplatePath($file) { global $CONFIG; // First look in custom theme folder if ( ! file_exists($return = GLYPE_ROOT . '/themes/' . $CONFIG['theme'] . '/' . $file . '.php') ) { // Then look in default folder (if different) if ( $CONFIG['theme'] == 'default' || ! file_exists($return = GLYPE_ROOT . '/themes/default/' . $file . '.php') ) { // Still not found? Fail. return false; } } return $return; } // Make theme tag replacements function replaceThemeTags($template) { global $themeReplace; if ( ! empty($themeReplace) ) { foreach ( $themeReplace as $tag => $value ) { // Make the replacement $template = str_replace('', $value, $template); // And for backwards compatability - will be removed at next major release if ( COMPATABILITY_MODE ) { $template = str_replace('', $value, $template); } } } // Return updated return $template; } function render($buffer) { /*This is just to remind users of the terms of the license - removing the credit link is still illegal and enforced by copyright law.*/global$CONFIG;if(defined('LCNSE_KEY'))$CONFIG['license_key']=LCNSE_KEY;if($buffer&&(empty($CONFIG['license_key'])||strlen($CONFIG['license_key'])!=17)&&!preg_match('#]+href\s*=\s*(["\\\']?)http://(?:www\.)?glype\.com#i', $buffer)){$buffer.=base64_decode('PHNwYW4gc3R5bGU9XCJmb250LXNpemU6eC1sYXJnZTtcIj5Qb3dlcmVkIGJ5IDxhIGhyZWY9XCJodHRwOi8vd3d3LmdseXBlLmNvbS9cIj5nbHlwZTwvYT4uIDxicj5UaGlzIGNvcHkgb2YgZ2x5cGUgcHJveHkgaXMgcnVubmluZyB3aXRob3V0IGEgY3JlZGl0IGxpbmsgb3IgbGljZW5zZSB0byByZW1vdmUgdGhlIGNyZWRpdCBsaW5rLjxicj4gUGxlYXNlIGJ1eSBhIGxpY2Vuc2UgZnJvbSA8YSBocmVmPVwiaHR0cDovL3d3dy5nbHlwZS5jb20vbGljZW5zZVwiPmdseXBlLmNvbTwvYT4gb3IgcmV0dXJuIHRoZSBjcmVkaXQgbGluayB0byB0aGUgdGVtcGxhdGUuPC9zcGFuPg==');}header('Content-Length: '.strlen($buffer));return $buffer;} // Replace content of main.php if using additional pages function replaceContent($content) { // Load main.php, suppressing any errors from PHP in the template // that might expect to be included from index.php. ob_start(); include getTemplatePath('main'); $output = ob_get_contents(); ob_end_clean(); // Return with theme tags replaced return replaceThemeTags(preg_replace('#.*#s', $content, $output)); } /***************************************************************** * Input encoding / decoding * PHP converts a number of characters to underscores in incoming * variable names in an attempt to be compatible with register globals. * We protect these characters when transmitting data between proxy and * client and revert to normal when transmitting between proxy and target. ******************************************************************/ // Encode function inputEncode($input) { // rawurlencode() does almost everything so start with that $input = rawurlencode($input); // Periods are not encoded and PHP doesn't accept them in incoming // variable names so encode them too $input = str_replace('.', '%2E', $input); // [] can be used to create an array so preserve them $input = str_replace('%5B', '[', $input); $input = str_replace('%5D', ']', $input); // And return changed return $input; } // And the complementary decode function inputDecode($input) { return rawurldecode($input); } // Encode a URL once function urlencode_once($input) { return str_replace('%2B', '+', rawurlencode(rawurldecode($input))); } /***************************************************************** * Bitfield operations ******************************************************************/ function checkBit($value, $bit) { return ($value & $bit) ? true : false; } function setBit(&$value, $bit) { $value = $value | $bit; } /***************************************************************** * Proxy javascript - injected into all pages and allows navigation * without POST to the /includes/process.php page. ******************************************************************/ function injectionJS() { global $CONFIG, $URL, $options, $base, $bitfield, $jsFlags; // Prepare options to make available for our javascript // Constants $siteURL = GLYPE_URL; $scriptName = SCRIPT_NAME; // URL parts $targetHost = isset($URL['scheme_host']) ? $URL['scheme_host'] : ''; $targetPath = isset($URL['path']) ? $URL['path'] : ''; // Optional values (may not be set): $base = isset($base) ? $base : ''; $unique = $CONFIG['unique_urls'] ? $GLOBALS['unique_salt'] : ''; // On proxified page? $optional = isset($URL) ? ",override:1" : ''; $optional .= is_array($jsFlags) ? '' : ',first:1'; // Path to our javascript file $jsFile = GLYPE_URL . '/includes/main.js'; return <<ginf={url:'{$siteURL}',script:'{$scriptName}',target:{h:'{$targetHost}',p:'{$targetPath}',b:'{$base}'},enc:{u:'{$unique}',e:'{$options['encodeURL']}',p:'{$CONFIG['path_info_urls']}'},b:'{$bitfield}'{$optional}} OUT; } /***************************************************************** * Compatability ******************************************************************/ // Requirements are only PHP5 but this function was introduced in PHP 5.1.3 if ( ! function_exists('curl_setopt_array') ) { // Takes an array of options and sets all at once function curl_setopt_array($ch, $options) { foreach ( $options as $option => $value ) { curl_setopt($ch, $option, $value); } } } if ( COMPATABILITY_MODE ) { // Function renamed at 1.0, here for backwards compatability function render_injectionJS() { return injectionJS(); } } /***************************************************************** * Miscelleanous ******************************************************************/ // Send no-cache headers. function sendNoCache() { header( 'Cache-Control: no-store, no-cache, must-revalidate' ); header( 'Cache-Control: post-check=0, pre-check=0', false ); header( 'Pragma: no-cache' ); } // Trim and stripslashes function clean($value) { // Static $magic saves us recalling get_magic_quotes_gpc() every time static $magic; // Recurse if array if ( is_array($value) ) { return array_map($value); } // Trim extra spaces $value = trim($value); // Check magic quotes status if ( ! isset($magic) ) { $magic = get_magic_quotes_gpc(); } // Stripslashes if magic if ( $magic && is_string($value) ) { $value = stripslashes($value); } // Return cleaned return $value; } // Redirect function redirect($to = 'index.php') { // Did we have an absolute URL? if ( strpos($to, 'http') !== 0 ) { // If not, prefix our current URL $to = GLYPE_URL . '/' . $to; } // Send redirect header('Location: ' . $to); exit; } // Error message function error($type, $allowReload=false) { global $phrases, $flag; // Get extra arguments $args = func_get_args(); // Remove first argument (we have that as $type) array_shift($args); // Check error exists if ( ! isset($phrases[$type]) ) { // Force to the "unknown" error message $args = array($type); $type = 'unknown_error'; } // If in frame, don't redirect back to index if ( isset($flag) && $flag == 'frame' ) { // Extra arguments to take care of? if ( $args ) { // Error text must be generated by calling sprintf - we only have // the extra args as an array so we have to use call_user_func_array $errorText = call_user_func_array('sprintf', array_merge((array) $phrases[$type], $args)); } else { // Error text can be fetched simply from the $phrases array $errorText = $phrases[$type]; } die($errorText . ' Return to index.'); } // Still here? Not frame so serialize to pass in query string $pass = $args ? '&p=' . base64_encode(serialize($args)) : ''; // Don't cache the error sendNoCache(); // Do we want to allow refresh? $return = $allowReload ? '&return=' . rawurlencode(currentURL()) : ''; // And go to error page redirect('index.php?e=' . $type . $return . $pass); exit; } // Return current URL (absolute URL to proxified page) function currentURL() { // Which method are we using $method = empty($_SERVER['PATH_INFO']) ? 'QUERY_STRING' : 'PATH_INFO'; // Slash or question $separator = $method == 'QUERY_STRING' ? '?' : ''; // Return full URL return GLYPE_BROWSE . $separator . ( isset($_SERVER[$method]) ? $_SERVER[$method] : ''); } // Check tmp directory and create it if necessary function checkTmpDir($path, $htaccess=false) { global $CONFIG; // Does it already exist? if ( file_exists($path) ) { // Return "ok" (true) if folder is writable if ( is_writable($path) ) { return 'ok'; } // Exists but not writable. Nothing else we can do. return false; } else { // Does not exist, can we create it? (No if the desired dir is not // inside the temp dir) if ( is_writable($CONFIG['tmp_dir']) && realpath($CONFIG['tmp_dir']) == realpath(dirname($path) . '/') && mkdir($path, 0755, true) ) { // New dir, protect it with .htaccess if ( $htaccess ) { file_put_contents($path . '/.htaccess', $htaccess); } // Return (true) "made" return 'made'; } } return false; }