<?php
/* This file is modified for IP Geo Block based on pre 1.15 (15 Feb, 2014) */
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 2 -*- */
/* geoip.inc
 *
 * Copyright (C) 2007 MaxMind LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

define("GEOIP_COUNTRY_BEGIN", 16776960);
define("GEOIP_STATE_BEGIN_REV0", 16700000);
define("GEOIP_STATE_BEGIN_REV1", 16000000);
define("GEOIP_STANDARD", 0);
define("GEOIP_MEMORY_CACHE", 1);
define("GEOIP_SHARED_MEMORY", 2);
define("STRUCTURE_INFO_MAX_SIZE", 20);
define("DATABASE_INFO_MAX_SIZE", 100);
define("GEOIP_COUNTRY_EDITION", 1);
define("GEOIP_PROXY_EDITION", 8);
define("GEOIP_ASNUM_EDITION", 9);
define("GEOIP_NETSPEED_EDITION", 10);
define("GEOIP_REGION_EDITION_REV0", 7);
define("GEOIP_REGION_EDITION_REV1", 3);
define("GEOIP_CITY_EDITION_REV0", 6);
define("GEOIP_CITY_EDITION_REV1", 2);
define("GEOIP_ORG_EDITION", 5);
define("GEOIP_ISP_EDITION", 4);
define("SEGMENT_RECORD_LENGTH", 3);
define("STANDARD_RECORD_LENGTH", 3);
define("ORG_RECORD_LENGTH", 4);
define("MAX_RECORD_LENGTH", 4);
define("MAX_ORG_RECORD_LENGTH", 300);
define("GEOIP_SHM_KEY", 0x4f415401);
define("US_OFFSET", 1);
define("CANADA_OFFSET", 677);
define("WORLD_OFFSET", 1353);
define("FIPS_RANGE", 360);
define("GEOIP_UNKNOWN_SPEED", 0);
define("GEOIP_DIALUP_SPEED", 1);
define("GEOIP_CABLEDSL_SPEED", 2);
define("GEOIP_CORPORATE_SPEED", 3);
define("GEOIP_DOMAIN_EDITION", 11);
define("GEOIP_COUNTRY_EDITION_V6", 12);
define("GEOIP_LOCATIONA_EDITION", 13);
define("GEOIP_ACCURACYRADIUS_EDITION", 14);
define("GEOIP_CITYCOMBINED_EDITION", 15);
define("GEOIP_CITY_EDITION_REV1_V6", 30);
define("GEOIP_CITY_EDITION_REV0_V6", 31);
define("GEOIP_NETSPEED_EDITION_REV1", 32);
define("GEOIP_NETSPEED_EDITION_REV1_V6", 33);
define("GEOIP_USERTYPE_EDITION", 28);
define("GEOIP_USERTYPE_EDITION_V6", 29);
define("GEOIP_ASNUM_EDITION_V6", 21);
define("GEOIP_ISP_EDITION_V6", 22);
define("GEOIP_ORG_EDITION_V6", 23);
define("GEOIP_DOMAIN_EDITION_V6", 24);

define("CITYCOMBINED_FIXED_RECORD", 7);

class GeoIP
{
    public $flags;
    public $filehandle;
    public $memory_buffer;
    public $databaseType;
    public $databaseSegments;
    public $record_length;
    public $shmid;
    public $GEOIP_COUNTRY_CODES = array(
        "",   "AP", "EU", "AD", "AE", "AF", "AG", "AI",
        "AL", "AM", "CW", "AO", "AQ", "AR", "AS", "AT",
        "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF",
        "BG", "BH", "BI", "BJ", "BM", "BN", "BO", "BR",
        "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC",
        "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM",
        "CN", "CO", "CR", "CU", "CV", "CX", "CY", "CZ",
        "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE",
        "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK",
        "FM", "FO", "FR", "SX", "GA", "GB", "GD", "GE",
        "GF", "GH", "GI", "GL", "GM", "GN", "GP", "GQ",
        "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM",
        "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN",
        "IO", "IQ", "IR", "IS", "IT", "JM", "JO", "JP",
        "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR",
        "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK",
        "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC",
        "MD", "MG", "MH", "MK", "ML", "MM", "MN", "MO",
        "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW",
        "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG",
        "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM",
        "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM",
        "PN", "PR", "PS", "PT", "PW", "PY", "QA", "RE",
        "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE",
        "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN",
        "SO", "SR", "ST", "SV", "SY", "SZ", "TC", "TD",
        "TF", "TG", "TH", "TJ", "TK", "TM", "TN", "TO",
        "TL", "TR", "TT", "TV", "TW", "TZ", "UA", "UG",
        "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG",
        "VI", "VN", "VU", "WF", "WS", "YE", "YT", "RS",
        "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1", "AX",
        "GG", "IM", "JE", "BL", "MF", "BQ", "SS", "O1"
    );
}

function _setup_segments($gi)
{
    $gi->databaseType = GEOIP_COUNTRY_EDITION;
    $gi->record_length = STANDARD_RECORD_LENGTH;
    if ($gi->flags & GEOIP_SHARED_MEMORY) {
        $offset = @shmop_size($gi->shmid) - 3;
        for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
            $delim = @shmop_read($gi->shmid, $offset, 3);
            $offset += 3;
            if ($delim == (chr(255) . chr(255) . chr(255))) {
                $gi->databaseType = ord(@shmop_read($gi->shmid, $offset, 1));
                if ($gi->databaseType >= 106) {
                    $gi->databaseType -= 105;
                }
                $offset++;

                if ($gi->databaseType == GEOIP_REGION_EDITION_REV0) {
                    $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
                } elseif ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
                    $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
                } elseif (($gi->databaseType == GEOIP_CITY_EDITION_REV0)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV1)
                    || ($gi->databaseType == GEOIP_ORG_EDITION)
                    || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
                    || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
                    || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
                    || ($gi->databaseType == GEOIP_ISP_EDITION)
                    || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
                    || ($gi->databaseType == GEOIP_USERTYPE_EDITION)
                    || ($gi->databaseType == GEOIP_USERTYPE_EDITION_V6)
                    || ($gi->databaseType == GEOIP_LOCATIONA_EDITION)
                    || ($gi->databaseType == GEOIP_ACCURACYRADIUS_EDITION)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
                    || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1)
                    || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1_V6)
                    || ($gi->databaseType == GEOIP_ASNUM_EDITION)
                    || ($gi->databaseType == GEOIP_ASNUM_EDITION_V6)
                ) {
                    $gi->databaseSegments = 0;
                    $buf = @shmop_read($gi->shmid, $offset, SEGMENT_RECORD_LENGTH);
                    for ($j = 0; $j < SEGMENT_RECORD_LENGTH; $j++) {
                        $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
                    }
                    if (($gi->databaseType == GEOIP_ORG_EDITION)
                        || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
                        || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
                        || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
                        || ($gi->databaseType == GEOIP_ISP_EDITION)
                        || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
                    ) {
                        $gi->record_length = ORG_RECORD_LENGTH;
                    }
                }
                break;
            } else {
                $offset -= 4;
            }
        }
        if (($gi->databaseType == GEOIP_COUNTRY_EDITION) ||
            ($gi->databaseType == GEOIP_COUNTRY_EDITION_V6) ||
            ($gi->databaseType == GEOIP_PROXY_EDITION) ||
            ($gi->databaseType == GEOIP_NETSPEED_EDITION)
        ) {
            $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
        }
    } else {
        $filepos = ftell($gi->filehandle);
        fseek($gi->filehandle, -3, SEEK_END);
        for ($i = 0; $i < STRUCTURE_INFO_MAX_SIZE; $i++) {
            $delim = fread($gi->filehandle, 3);
            if ($delim == (chr(255) . chr(255) . chr(255))) {
                $gi->databaseType = ord(fread($gi->filehandle, 1));
                if ($gi->databaseType >= 106) {
                    $gi->databaseType -= 105;
                }
                if ($gi->databaseType == GEOIP_REGION_EDITION_REV0) {
                    $gi->databaseSegments = GEOIP_STATE_BEGIN_REV0;
                } elseif ($gi->databaseType == GEOIP_REGION_EDITION_REV1) {
                    $gi->databaseSegments = GEOIP_STATE_BEGIN_REV1;
                } elseif (($gi->databaseType == GEOIP_CITY_EDITION_REV0)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV1)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
                    || ($gi->databaseType == GEOIP_ORG_EDITION)
                    || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
                    || ($gi->databaseType == GEOIP_ISP_EDITION)
                    || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
                    || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
                    || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
                    || ($gi->databaseType == GEOIP_LOCATIONA_EDITION)
                    || ($gi->databaseType == GEOIP_ACCURACYRADIUS_EDITION)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV0_V6)
                    || ($gi->databaseType == GEOIP_CITY_EDITION_REV1_V6)
                    || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1)
                    || ($gi->databaseType == GEOIP_NETSPEED_EDITION_REV1_V6)
                    || ($gi->databaseType == GEOIP_USERTYPE_EDITION)
                    || ($gi->databaseType == GEOIP_USERTYPE_EDITION_V6)
                    || ($gi->databaseType == GEOIP_ASNUM_EDITION)
                    || ($gi->databaseType == GEOIP_ASNUM_EDITION_V6)
                ) {
                    $gi->databaseSegments = 0;
                    $buf = fread($gi->filehandle, SEGMENT_RECORD_LENGTH);
                    for ($j = 0; $j < SEGMENT_RECORD_LENGTH; $j++) {
                        $gi->databaseSegments += (ord($buf[$j]) << ($j * 8));
                    }
                    if (($gi->databaseType == GEOIP_ORG_EDITION)
                        || ($gi->databaseType == GEOIP_DOMAIN_EDITION)
                        || ($gi->databaseType == GEOIP_ISP_EDITION)
                        || ($gi->databaseType == GEOIP_ORG_EDITION_V6)
                        || ($gi->databaseType == GEOIP_DOMAIN_EDITION_V6)
                        || ($gi->databaseType == GEOIP_ISP_EDITION_V6)
                    ) {
                        $gi->record_length = ORG_RECORD_LENGTH;
                    }
                }
                break;
            } else {
                fseek($gi->filehandle, -4, SEEK_CUR);
            }
        }
        if (($gi->databaseType == GEOIP_COUNTRY_EDITION) ||
            ($gi->databaseType == GEOIP_COUNTRY_EDITION_V6) ||
            ($gi->databaseType == GEOIP_PROXY_EDITION) ||
            ($gi->databaseType == GEOIP_NETSPEED_EDITION)
        ) {
            $gi->databaseSegments = GEOIP_COUNTRY_BEGIN;
        }
        fseek($gi->filehandle, $filepos, SEEK_SET);
    }
    return $gi;
}

function geoip_open($filename, $flags)
{
    $gi = new GeoIP;
    $gi->flags = $flags;
    if ($gi->flags & GEOIP_SHARED_MEMORY) {
        $gi->shmid = @shmop_open(GEOIP_SHM_KEY, "a", 0, 0);
    } else {
        $gi->filehandle = fopen($filename, "rb"); if (!$gi->filehandle) return FALSE; // or die("Can not open $filename\n");
        flock($gi->filehandle, LOCK_SH); // @since 1.1.6
        if ($gi->flags & GEOIP_MEMORY_CACHE) {
            $s_array = fstat($gi->filehandle);
            $gi->memory_buffer = fread($gi->filehandle, $s_array['size']);
        }
    }

    $gi = _setup_segments($gi);
    return $gi;
}

function geoip_close($gi)
{
    if ($gi->flags & GEOIP_SHARED_MEMORY) {
        return true;
    }

    return flock($gi->filehandle, LOCK_UN) and fclose($gi->filehandle); // @since 1.1.6
}

function geoip_country_id_by_addr_v6($gi, $addr)
{
    $ipnum = inet_pton($addr);
    return _geoip_seek_country_v6($gi, $ipnum) - GEOIP_COUNTRY_BEGIN;
}

function geoip_country_id_by_addr($gi, $addr)
{
    $ipnum = ip2long($addr);
    return _geoip_seek_country($gi, $ipnum) - GEOIP_COUNTRY_BEGIN;
}

function geoip_country_code_by_addr_v6($gi, $addr)
{
    $country_id = geoip_country_id_by_addr_v6($gi, $addr);
    if ($country_id !== false) {
        return $gi->GEOIP_COUNTRY_CODES[$country_id];
    }
    return false;
}

function geoip_country_code_by_addr($gi, $addr)
{
    if ($gi->databaseType == GEOIP_CITY_EDITION_REV1) {
        $record = geoip_record_by_addr($gi, $addr);
        if ($record !== false) {
            return $record->country_code;
        }
    } else {
        $country_id = geoip_country_id_by_addr($gi, $addr);
        if ($country_id !== false) {
            return $gi->GEOIP_COUNTRY_CODES[$country_id];
        }
    }
    return false;
}

function _geoip_seek_country_v6($gi, $ipnum)
{
    # arrays from unpack start with offset 1
    # yet another php mystery. array_merge work around
    # this broken behaviour
    $v6vec = array_merge(unpack("C16", $ipnum));

    $offset = 0;
    for ($depth = 127; $depth >= 0; --$depth) {
        if ($gi->flags & GEOIP_MEMORY_CACHE) {
            $buf = _safe_substr(
                $gi->memory_buffer,
                2 * $gi->record_length * $offset,
                2 * $gi->record_length
            );
        } elseif ($gi->flags & GEOIP_SHARED_MEMORY) {
            $buf = @shmop_read(
                $gi->shmid,
                2 * $gi->record_length * $offset,
                2 * $gi->record_length
            );
        } else {
            fseek($gi->filehandle, 2 * $gi->record_length * $offset, SEEK_SET) == 0
            or die("fseek failed");
            $buf = fread($gi->filehandle, 2 * $gi->record_length);
        }
        $x = array(0, 0);
        for ($i = 0; $i < 2; ++$i) {
            for ($j = 0; $j < $gi->record_length; ++$j) {
                $x[$i] += ord($buf[$gi->record_length * $i + $j]) << ($j * 8);
            }
        }

        $bnum = 127 - $depth;
        $idx = $bnum >> 3;
        $b_mask = 1 << ($bnum & 7 ^ 7);
        if (($v6vec[$idx] & $b_mask) > 0) {
            if ($x[1] >= $gi->databaseSegments) {
                return $x[1];
            }
            $offset = $x[1];
        } else {
            if ($x[0] >= $gi->databaseSegments) {
                return $x[0];
            }
            $offset = $x[0];
        }
    }
    trigger_error("error traversing database - perhaps it is corrupt?", E_USER_ERROR);
    return false;
}

function _geoip_seek_country($gi, $ipnum)
{
    $offset = 0;
    for ($depth = 31; $depth >= 0; --$depth) {
        if ($gi->flags & GEOIP_MEMORY_CACHE) {
            $buf = _safe_substr(
                $gi->memory_buffer,
                2 * $gi->record_length * $offset,
                2 * $gi->record_length
            );
        } elseif ($gi->flags & GEOIP_SHARED_MEMORY) {
            $buf = @shmop_read(
                $gi->shmid,
                2 * $gi->record_length * $offset,
                2 * $gi->record_length
            );
        } else {
            fseek($gi->filehandle, 2 * $gi->record_length * $offset, SEEK_SET) == 0
            or die("fseek failed");
            $buf = fread($gi->filehandle, 2 * $gi->record_length);
        }
        $x = array(0, 0);
        for ($i = 0; $i < 2; ++$i) {
            for ($j = 0; $j < $gi->record_length; ++$j) {
                $x[$i] += ord($buf[$gi->record_length * $i + $j]) << ($j * 8);
            }
        }
        if ($ipnum & (1 << $depth)) {
            if ($x[1] >= $gi->databaseSegments) {
                return $x[1];
            }
            $offset = $x[1];
        } else {
            if ($x[0] >= $gi->databaseSegments) {
                return $x[0];
            }
            $offset = $x[0];
        }
    }
    trigger_error("error traversing database - perhaps it is corrupt?", E_USER_ERROR);
    return false;
}
function _common_get_org($gi, $seek_org)
{
    $record_pointer = $seek_org + (2 * $gi->record_length - 1) * $gi->databaseSegments;
    if ($gi->flags & GEOIP_SHARED_MEMORY) {
        $org_buf = _sharedMemRead($gi, $record_pointer, MAX_ORG_RECORD_LENGTH);
    } else {
        fseek($gi->filehandle, $record_pointer, SEEK_SET);
        $org_buf = fread($gi->filehandle, MAX_ORG_RECORD_LENGTH);
    }
    $org_buf = _safe_substr($org_buf, 0, strpos($org_buf, "\0"));
    return $org_buf;
}

function _get_org_v6($gi, $ipnum)
{
    $seek_org = _geoip_seek_country_v6($gi, $ipnum);
    if ($seek_org == $gi->databaseSegments) {
        return null;
    }
    return _common_get_org($gi, $seek_org);
}

function _get_org($gi, $ipnum)
{
    $seek_org = _geoip_seek_country($gi, $ipnum);
    if ($seek_org == $gi->databaseSegments) {
        return null;
    }
    return _common_get_org($gi, $seek_org);
}


function geoip_name_by_addr_v6($gi, $addr)
{
    if ($addr == null) {
        return 0;
    }
    $ipnum = inet_pton($addr);
    return _get_org_v6($gi, $ipnum);
}

function geoip_name_by_addr($gi, $addr)
{
    if ($addr == null) {
        return 0;
    }
    $ipnum = ip2long($addr);
    return _get_org($gi, $ipnum);
}

function _safe_substr($string, $start, $length)
{
    // workaround php's broken substr, strpos, etc handling with
    // mbstring.func_overload and mbstring.internal_encoding
    $mbExists = extension_loaded('mbstring');

    if ($mbExists) {
        $enc = mb_internal_encoding();
        mb_internal_encoding('ISO-8859-1');
    }

    $buf = substr($string, $start, $length);

    if ($mbExists) {
        mb_internal_encoding($enc);
    }

    return $buf;
}
