int geo_lookup()

in apache2/msc_geo.c [261:505]


int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **error_msg)
{
    apr_sockaddr_t *addr;
    long ipnum = 0;
    char *targetip = NULL;
    geo_db *geo = msr->txcfg->geo;
    char errstr[1024];
    unsigned char buf[2* GEO_MAX_RECORD_LEN];
    const int reclen = 3; /* Algorithm needs changed if this changes */
    apr_size_t nbytes;
    unsigned int rec_val = 0;
    apr_off_t seekto = 0;
    apr_status_t ret;
    int rc;
    int country = 0;
    int level;
    double dtmp;
    int itmp;

    *error_msg = NULL;

    /* init */
    georec->country_code = geo_country_code[0];
    georec->country_code3 = geo_country_code3[0];
    georec->country_name = geo_country_name[0];
    georec->country_continent = geo_country_continent[0];
    georec->region = "";
    georec->city = "";
    georec->postal_code = "";
    georec->latitude = 0;
    georec->longitude = 0;
    georec->dma_code = 0;
    georec->area_code = 0;

    if (msr->txcfg->debuglog_level >= 9) {
        msr_log(msr, 9, "GEO: Looking up \"%s\".", log_escape(msr->mp, target));
    }

    /* NOTE: This only works with ipv4 */
    if ((rc = apr_sockaddr_info_get(&addr, target, APR_INET, 0, 0, msr->mp)) != APR_SUCCESS) {

        *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024));
        msr_log(msr, 4, "%s", *error_msg);
        return 0;
    }
    if ((rc = apr_sockaddr_ip_get(&targetip, addr)) != APR_SUCCESS) {
        *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024));
        msr_log(msr, 4, "%s", *error_msg);
        return 0;
    };

    /* Why is this in host byte order? */
    ipnum = ntohl(addr->sa.sin.sin_addr.s_addr);

    if (msr->txcfg->debuglog_level >= 9) {
        msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx). %lu", targetip, ipnum, ipnum);
    }

    ret = waf_get_exclusive_lock(msr->modsecurity->geo_lock);
    if (waf_lock_is_error(ret)) {
        msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
                get_apr_error(msr->mp, ret));
    }

    for (level = 31; level >= 0; level--) {
        /* Read the record */
        seekto = 2 * reclen * rec_val;
        apr_file_seek(geo->db, APR_SET, &seekto);
        /* TODO: check rc */
        rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes);

        /* NOTE: This is hard-coded for size 3 records */
        /* Left */
        if ((ipnum & (1 << level)) == 0) {
            rec_val =   (buf[3*0 + 0] << (0*8)) +
                        (buf[3*0 + 1] << (1*8)) +
                        (buf[3*0 + 2] << (2*8));
        }
        /* Right */
        else {
            rec_val =   (buf[3*1 + 0] << (0*8)) +
                        (buf[3*1 + 1] << (1*8)) +
                        (buf[3*1 + 2] << (2*8));
        }

        /* If we are past the country offset, then we are done */
        if (rec_val >= geo->ctry_offset) {
            break;
        }
    }

    if (rec_val == geo->ctry_offset) {
        *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\").", log_escape(msr->mp, target));
        msr_log(msr, 4, "%s", *error_msg);

        ret = waf_free_exclusive_lock(msr->modsecurity->geo_lock);
        if (waf_lock_is_error(ret)) {
            msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
                    get_apr_error(msr->mp, ret));
        }

        return 0;
    }

    if (geo->dbtype == GEO_COUNTRY_DATABASE) {
        country = rec_val;
        country -= geo->ctry_offset;
        if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
            *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
            msr_log(msr, 4, "%s", *error_msg);

            ret = waf_free_exclusive_lock(msr->modsecurity->geo_lock);
            if (waf_lock_is_error(ret)) {
                msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
                        get_apr_error(msr->mp, ret));
            }

            return 0;
        }

        /* Country */
        georec->country_code = geo_country_code[country];
        georec->country_code3 = geo_country_code3[country];
        georec->country_name = geo_country_name[country];
        georec->country_continent = geo_country_continent[country];
    }
    else {
        int field_len = 0;
        int rec_offset = 0;
        int remaining = GEO_CITY_RECORD_LEN;
        unsigned char cbuf[GEO_CITY_RECORD_LEN];

        seekto = rec_val + (2 * reclen - 1) * geo->ctry_offset;
        apr_file_seek(geo->db, APR_SET, &seekto);
        /* TODO: check rc */
        rc = apr_file_read_full(geo->db, &cbuf, sizeof(cbuf), &nbytes);

        country = cbuf[0];
        if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
            *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
            msr_log(msr, 4, "%s", *error_msg);

            ret = waf_free_exclusive_lock(msr->modsecurity->geo_lock);
            if (waf_lock_is_error(ret)) {
                msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
                        get_apr_error(msr->mp, ret));
            }

            return 0;
        }
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
        }

        /* Country */
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
        }
        georec->country_code = geo_country_code[country];
        georec->country_code3 = geo_country_code3[country];
        georec->country_name = geo_country_name[country];
        georec->country_continent = geo_country_continent[country];
        rec_offset++;
        remaining -= rec_offset;

        /* Region */
        field_len = field_length((const char *)cbuf+rec_offset, remaining);
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: region=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        georec->region = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
        rec_offset += field_len + 1;
        remaining -= field_len + 1;

        /* City */
        field_len = field_length((const char *)cbuf+rec_offset, remaining);
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: city=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        georec->city = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
        rec_offset += field_len + 1;
        remaining -= field_len + 1;

        /* Postal Code */
        field_len = field_length((const char *)cbuf+rec_offset, remaining);
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: postal_code=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        georec->postal_code = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
        rec_offset += field_len + 1;
        remaining -= field_len + 1;

        /* Latitude */
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: latitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        dtmp = cbuf[rec_offset] +
               (cbuf[rec_offset+1] << 8) +
               (cbuf[rec_offset+2] << 16);
        georec->latitude = dtmp/10000 - 180;
        rec_offset += 3;
        remaining -= 3;


        /* Longitude */
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: longitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        dtmp = cbuf[rec_offset] +
              (cbuf[rec_offset+1] << 8) +
              (cbuf[rec_offset+2] << 16);
        georec->longitude = dtmp/10000 - 180;
        rec_offset += 3;
        remaining -= 3;

        /* dma/area codes are in city rev1 and US only */
        if (msr->txcfg->debuglog_level >= 9) {
            msr_log(msr, 9, "GEO: dma/area=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
        }
        if (geo->dbtype == GEO_CITY_DATABASE_1
            && georec->country_code[0] == 'U'
            && georec->country_code[1] == 'S')
        {
            /* DMA Code */
            itmp = cbuf[rec_offset] +
                  (cbuf[rec_offset+1] << 8) +
                  (cbuf[rec_offset+2] << 16);
            georec->dma_code = itmp / 1000;
            georec->area_code = itmp % 1000;
            rec_offset += 6;
            remaining -= 6;
        }

    }

    *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded.", log_escape(msr->mp, target));

    ret = waf_free_exclusive_lock(msr->modsecurity->geo_lock);
    if (waf_lock_is_error(ret)) {
        msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
                get_apr_error(msr->mp, ret));
    }

    return 1;
}