<?
/*
 We have an index to speed up the search. The first line of
 the index file contains the step value used to create the
 index. The IP should be divided by this number, when an
 appropriate index entry is searched for. Then CSV formatted
 lines are placed in the file with the division results and 
 starting indexes (pointing to the data file).
 
 The data file has a fixed record size format to ease
 fseek()ing. The format of the database is:
     
  n records representing IP range and country relations
  the format of a record is:
      
   - starting IP number [10 bytes, padded with zeros from left]
   - ending IP number   [10 bytes, padded with zeros from left]
   - ISO country code   [3 bytes / letters]
   - newline            [1 byte: \n]
       
 IP numbers can be created with i2c_ip2num().
 */

// Try to find out the country of the user
function i2c_go() {
    global 
$COUNTRY;
    
// Get the real IP of the user
    
$ipnum i2c_realip();

    
// Convert the IP number to some useable form for searching
    
$ipn = (float) sprintf("%u"ip2long($ipnum));
    
    
// Find the index to start search from
    
$idx i2c_search_in_index($ipn);
    
    
// If we were unable to find any helpful entry
    // in the index do not search, as it would take 
    // a very long time. It is an error, if we have
    // not found anything in the index
    
if ($idx !== FALSE) {
        
$country i2c_search_in_db($ipn$idx);
    } else {
        
$country 'NA';
    }
    
// Set global variable for further usage
    
$COUNTRY $country;
    
    
// Set the country in a cookie for a week
    
return TRUE;
}

// Find nearest index entry for IP number 
function i2c_search_in_index($ip) {
    
// Indexed part and record number to jump to
    
$idxpart 0$recnum 0;
    
    
// Open the index file for reading
    
$dbidx fopen("/tmp/ip-to-country.idx","r");
    if (!
$dbidx) { return FALSE; }

    
// Read in granularity from index file and
    // convert current IP to something useful
    
$granularity intval(fgets($dbidx64));
    
$ip_chunk intval($ip $granularity);

    
// Loop till we can read the file
    
while (!feof($dbidx)) {
        
// Get CSV data from index file
        
$data fgetcsv($dbidx100);
        
// Compare current index part with our IP
        
if ($ip_chunk >= $idxpart && $ip_chunk < (int) $data[0]) {
            return array(
$recnum, (int) $data[1]);
        }
        
// Store for next compare
        
$idxpart = (int) $data[0];
        
$recnum  = (int) $data[1];
    }
    
// Return record number found
    
return array($recnum, -1);
}

// Find the country searching from record $idx
// $ip should be an IP number and not an IP address
function i2c_search_in_db($ip$idx) {
    
// Default range and country
    
$range_start 0$range_end 0;
    
$country "NA";

    
// Open DB for reading
    
$ipdb fopen("/tmp/ip-to-country.db","r");

    
// Return with "NA" in case of we cannot open the db
    
if (!$ipdb) { return $country; }

    
// Jump to record $idx
    
fseek($ipdb$idx[0]*24);
    
    
// Read records until we hit the end of the file,
    // or we find the range where this IP is, or we
    // reach the next indexed part [where the IP should
    // not be found, so there is no point in searching further]
    
while (!feof($ipdb) && !($range_start <= $ip && $range_end >= $ip)) {
            
        
// We had run out of the indexed region,
        // where we expected to find the IP
        
if ($idx[1] != -&& $idx[0] > $idx[1]) {
            
$country "NA"; break;
        }
        
        
// Try to read record
        
$record fread($ipdb24);
        
        
// Unable to read the record => error
        
if (strlen($record) != 24) { $country "NA"; break; }
        
// Split the record to it's parts
        
$range_start = (float) substr($record010);
        
$range_end   = (float) substr($record1010);
        
$country     substr($record203);
        
        
// Getting closer to the end of the indexed region
        
$idx[0] += 1;
    }

    
// Close datafile
    
fclose($ipdb);

    
// Return with the country found
    
return $country;
}

// Check if the current country is valid
function i2c_valid_country() {
    global 
$COUNTRY$COUNTRIES;
    return (!empty(
$COUNTRY) && $COUNTRY != "NA" && isset($COUNTRIES[$COUNTRY]));
}

// Returns the real IP address of the user
function i2c_realip() {
    
// No IP found (will be overwritten by for
    // if any IP is found behind a firewall)
    
$ip FALSE;
    
    
// User is behind a proxy and check that we discard RFC1918 IP addresses
    // if they are behind a proxy then only figure out which IP belongs to the
    // user.  Might not need any more hackin if there is a squid reverse proxy
    // infront of apache.
    
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        
// Put the IP's into an array which we shall work with shortly.
        
$ips explode (", "$_SERVER['HTTP_X_FORWARDED_FOR']);
        for (
$i 0$i count($ips); $i++) {
            
// Skip RFC 1918 IP's 10.0.0.0/8, 172.16.0.0/12 and
            // 192.168.0.0/16 -- jim kill me later with my regexp pattern
            // below.
            
if (!eregi ("^(10|172\.16|192\.168)\."$ips[$i])) {
                
$ip $ips[$i];
                break;
            }
        }
    }
    
// Return with the found IP or the remote address
    
return ($ip $ip $_SERVER['REMOTE_ADDR']);
}
?>