Add geo lookup support. See #22.

This commit is contained in:
brectanus 2007-05-11 16:14:11 +00:00
parent d8abb48ad9
commit a68eb04884
14 changed files with 847 additions and 5 deletions

View File

@ -2,6 +2,9 @@
?? ??? 2007 - 2.2.0-trunk
-------------------------
* Add SecGeoLookupsDb, @geoLookups and GEO collection to support
geographical lookups by IP/host.
* Do not try to intercept a request after a failed rule. This fixes the
issue associated with an "Internal Error: Asked to intercept request
but was_intercepted is zero" error message.

View File

@ -23,7 +23,8 @@ LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.li
OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \
re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \
msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj msc_parsers.obj \
msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj pdf_protec.obj
msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj pdf_protect.obj \
msc_geo.obj
all: $(DLL)

View File

@ -91,6 +91,9 @@ void *create_directory_config(apr_pool_t *mp, char *path) {
dcfg->pdfp_token_name = NOT_SET_P;
dcfg->pdfp_only_get = NOT_SET;
/* Geo Lookups */
dcfg->geo = NOT_SET_P;
return dcfg;
}
@ -382,6 +385,10 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) {
merged->pdfp_only_get = (child->pdfp_only_get == NOT_SET
? parent->pdfp_only_get : child->pdfp_only_get);
/* Geo Lookup */
merged->geo = (child->geo == NOT_SET_P
? parent->geo : child->geo);
return merged;
}
@ -450,6 +457,9 @@ void init_directory_config(directory_config *dcfg) {
if (dcfg->pdfp_timeout == NOT_SET) dcfg->pdfp_timeout = 10;
if (dcfg->pdfp_token_name == NOT_SET_P) dcfg->pdfp_token_name = "PDFPTOKEN";
if (dcfg->pdfp_only_get == NOT_SET) dcfg->pdfp_only_get = 0;
/* Geo Lookup */
if (dcfg->geo == NOT_SET_P) dcfg->geo = NULL;
}
/**
@ -1185,6 +1195,22 @@ static const char *cmd_pdf_protect_intercept_get_only(cmd_parms *cmd, void *_dcf
return NULL;
}
/* -- Geo Lookup configuration -- */
static const char *cmd_geo_lookups_db(cmd_parms *cmd, void *_dcfg,
const char *p1)
{
char *error_msg;
directory_config *dcfg = (directory_config *)_dcfg;
if (dcfg == NULL) return NULL;
if (geo_init(dcfg, p1, &error_msg) <= 0) {
return error_msg;
}
return NULL;
}
/* -- Configuration directives definitions -- */
@ -1524,5 +1550,13 @@ const command_rec module_directives[] = {
"whether or not to intercept only GET requess."
),
AP_INIT_TAKE1 (
"SecGeoLookupsDb",
cmd_geo_lookups_db,
NULL,
RSRC_CONF,
"database for geographical lookups module."
),
{ NULL }
};

View File

@ -289,6 +289,9 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) {
msr->tx_vars = apr_table_make(msr->mp, 32);
if (msr->tx_vars == NULL) return -1;
msr->geo_vars = apr_table_make(msr->mp, 8);
if (msr->geo_vars == NULL) return -1;
msr->collections = apr_table_make(msr->mp, 8);
if (msr->collections == NULL) return -1;
msr->collections_dirty = apr_table_make(msr->mp, 8);

View File

@ -39,6 +39,7 @@ typedef struct msc_string msc_string;
#ifdef WITH_LIBXML2
#include "msc_xml.h"
#endif
#include "msc_geo.h"
#include "re.h"
#include "ap_config.h"
@ -243,6 +244,9 @@ struct modsec_rec {
apr_table_t *tx_vars;
/* ENH: refactor to allow arbitrary var tables */
apr_table_t *geo_vars;
/* response */
unsigned int response_status;
const char *status_line;
@ -402,6 +406,9 @@ struct directory_config {
int pdfp_timeout;
const char *pdfp_token_name;
int pdfp_only_get;
/* Geo Lookup */
geo_db *geo;
};
struct error_message {

View File

@ -2,10 +2,11 @@
MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \
re re_operators re_actions re_tfns re_variables \
msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \
persist_dbm msc_reqbody pdf_protect
persist_dbm msc_reqbody pdf_protect msc_geo
H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \
msc_geo.h
${MOD_SECURITY2:=.slo}: ${H}
${MOD_SECURITY2:=.lo}: ${H}

447
apache2/msc_geo.c Normal file
View File

@ -0,0 +1,447 @@
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
*
* You should have received a copy of the licence along with this
* program (stored in the file "LICENSE"). If the file is missing,
* or if you have any other questions related to the licence, please
* write to Thinking Stone at contact@thinkingstone.com.
*
*/
#include "msc_geo.h"
/* -- Lookup Tables -- */
static const char *geo_country_code[] = {
"--",
"AP","EU","AD","AE","AF","AG","AI","AL","AM","AN",
"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","FX","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"
};
static const char *geo_country_code3[] = {
"--",
"AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT",
"AGO","AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB",
"BGD","BEL","BFA","BGR","BHR","BDI","BEN","BMU","BRN","BOL",
"BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC","COD",
"CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI",
"CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM",
"DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI",
"FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF",
"GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM",
"GUM","GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN",
"IRL","ISR","IND","IO","IRQ","IRN","ISL","ITA","JAM","JOR",
"JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT",
"CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
"LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI",
"MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV",
"MWI","MEX","MYS","MOZ","NAM","NCL","NER","NFK","NGA","NIC",
"NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER","PYF",
"PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW",
"PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN",
"SWE","SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM",
"SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF","TGO","THA",
"TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN",
"TZA","UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN",
"VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","SRB","ZAF",
"ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY"
};
static const char *geo_country_name[] = {
"N/A",
"Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles",
"Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados",
"Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia",
"Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the",
"Central African Republic","Congo","Switzerland","Cote D'Ivoire","Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica",
"Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic",
"Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji",
"Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana",
"Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala",
"Guam","Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia",
"Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan",
"Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis","Korea, Democratic People's Republic of","Korea, Republic of","Kuwait",
"Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania",
"Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia","Mali",
"Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives",
"Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua",
"Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia",
"Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau",
"Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles","Sudan",
"Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname",
"Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo","Thailand",
"Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan",
"Tanzania, United Republic of","Ukraine","Uganda","United States Minor Outlying Islands","United States","Uruguay","Uzbekistan","Holy See (Vatican City State)","Saint Vincent and the Grenadines","Venezuela",
"Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa",
"Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey"
};
static const char *geo_country_continent[] = {
"--",
"AS","EU","EU","AS","AS","SA","SA","EU","AS","SA",
"AF","AN","SA","OC","EU","OC","SA","AS","EU","SA",
"AS","EU","AF","EU","AS","AF","AF","SA","AS","SA",
"SA","SA","AS","AF","AF","EU","SA","NA","AS","AF",
"AF","AF","EU","AF","OC","SA","AF","AS","SA","SA",
"SA","AF","AS","AS","EU","EU","AF","EU","SA","SA",
"AF","SA","EU","AF","AF","AF","EU","AF","EU","OC",
"SA","OC","EU","EU","EU","AF","EU","SA","AS","SA",
"AF","EU","SA","AF","AF","SA","AF","EU","SA","SA",
"OC","AF","SA","AS","AF","SA","EU","SA","EU","AS",
"EU","AS","AS","AS","AS","AS","EU","EU","SA","AS",
"AS","AF","AS","AS","OC","AF","SA","AS","AS","AS",
"SA","AS","AS","AS","SA","EU","AS","AF","AF","EU",
"EU","EU","AF","AF","EU","EU","AF","OC","EU","AF",
"AS","AS","AS","OC","SA","AF","SA","EU","AF","AS",
"AF","NA","AS","AF","AF","OC","AF","OC","AF","SA",
"EU","EU","AS","OC","OC","OC","AS","SA","SA","OC",
"OC","AS","AS","EU","SA","OC","SA","AS","EU","OC",
"SA","AS","AF","EU","AS","AF","AS","OC","AF","AF",
"EU","AS","AF","EU","EU","EU","AF","EU","AF","AF",
"SA","AF","SA","AS","AF","SA","AF","AF","AF","AS",
"AS","OC","AS","AF","OC","AS","AS","SA","OC","AS",
"AF","EU","AF","OC","NA","SA","AS","EU","SA","SA",
"SA","SA","AS","OC","OC","OC","AS","AF","EU","AF",
"AF","EU","AF","--","--","--","EU","EU","EU","EU"
};
static int db_open(directory_config *dcfg, char **error_msg)
{
char errstr[1024];
apr_pool_t *mp = dcfg->mp;
geo_db *geo = dcfg->geo;
apr_status_t rc;
apr_size_t nbytes;
apr_off_t offset;
unsigned char buf[3];
int i, j;
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: Initializing geo DB \"%s\".\n", geo->dbfn);
#endif
if ((rc = apr_file_open(&geo->db, geo->dbfn, APR_READ, APR_OS_DEFAULT, mp)) != APR_SUCCESS) {
*error_msg = apr_psprintf(mp, "Could not open geo database \"%s\": %s", geo->dbfn, apr_strerror(rc, errstr, 1024));
return 0;
}
offset = -3;
apr_file_seek(geo->db, APR_END, &offset);
/* TODO check offset */
/* Defaults */
geo->dbtype = GEO_COUNTRY_DATABASE;
geo->ctry_offset = GEO_COUNTRY_OFFSET;
for (i = 0; i < GEO_STRUCT_INFO_MAX_SIZE; i++) {
memset(buf, 0, 3);
rc = apr_file_read_full(geo->db, &buf, 3, &nbytes);
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
#endif
if ((rc != APR_SUCCESS) || (nbytes != 3)) {
*error_msg = apr_psprintf(mp, "Could not read from geo database \"%s\" (%d/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024));
return -1;
}
if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)) {
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: Found DB info marker at offset 0x%08x\n", (unsigned int)offset);
#endif
memset(buf, 0, 3);
rc = apr_file_read_full(geo->db, &buf, 1, &nbytes);
/* TODO: check rc */
geo->dbtype = (int)buf[0];
/* Backwards compat */
if (geo->dbtype >= 106) {
geo->dbtype -= 105;
}
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
#endif
/* If a cities DB, then get country offset */
if ((geo->dbtype == GEO_CITY_DATABASE_0) || (geo->dbtype == GEO_CITY_DATABASE_1)) {
memset(buf, 0, 3);
rc = apr_file_read_full(geo->db, &buf, 3, &nbytes);
if ((rc != APR_SUCCESS) || (nbytes != 3)) {
*error_msg = apr_psprintf(mp, "Could not read geo database \"%s\" country offset (%d/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024));
return -1;
}
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
#endif
geo->ctry_offset = 0;
for (j = 0; j < 3; j++) {
geo->ctry_offset += (buf[j] << (j * 8));
}
}
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: Country offset 0x%08x\n", geo->ctry_offset);
#endif
return 1;
}
/* Backup a byte from where we started */
offset = -4;
apr_file_seek(geo->db, APR_CUR, &offset);
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: DB offset 0x%08x\n", (unsigned int)offset);
#endif
}
if (geo->dbtype != GEO_COUNTRY_DATABASE) {
*error_msg = apr_psprintf(mp, "Unknown database format");
return 0;
}
#ifdef DEBUG_CONF
fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
#endif
return 1;
}
/**
* Initialise Geo data structure
*/
int geo_init(directory_config *dcfg, const char *dbfn, char **error_msg)
{
*error_msg = NULL;
if ((dcfg->geo == NULL) || (dcfg->geo == NOT_SET_P)) {
dcfg->geo = apr_pcalloc(dcfg->mp, sizeof(geo_db));
}
dcfg->geo->db = NULL;
dcfg->geo->dbfn = apr_pstrdup(dcfg->mp, dbfn);
dcfg->geo->dbtype = 0;
dcfg->geo->ctry_offset = 0;
return db_open(dcfg, error_msg);
}
/**
* Perform geographical lookup on target.
*/
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;
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;
msr_log(msr, 9, "GEO: Looking up \"%s\".", 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 of \"%s\" failed: %s", target, apr_strerror(rc, errstr, 1024));
return 0;
}
if ((rc = apr_sockaddr_ip_get(&targetip, addr)) != APR_SUCCESS) {
*error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" failed: %s", target, apr_strerror(rc, errstr, 1024));
return 0;
};
/* Why is this in host byte order? */
ipnum = ntohl(addr->sa.sin.sin_addr.s_addr);
msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08x).", targetip, ipnum);
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[0] +
(buf[1] << 8) +
(buf[2] << 16);
}
/* Right */
else {
rec_val = buf[3] +
(buf[4] << 8) +
(buf[5] << 16);
}
/* If we are past the country offset, then we are done */
if (rec_val >= geo->ctry_offset) {
break;
}
}
if (geo->dbtype == GEO_COUNTRY_DATABASE) {
country = rec_val;
country -= geo->ctry_offset;
if (country <= 0) {
*error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", target);
return 0;
}
msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, buf, sizeof(buf)));
/* Country */
msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, (unsigned char *)&rec_val, 1));
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) {
*error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", target);
return 0;
}
msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
/* Country */
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 = strnlen((const char *)cbuf+rec_offset,remaining);
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 = strnlen((const char *)cbuf+rec_offset,remaining);
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 = strnlen((const char *)cbuf+rec_offset,remaining);
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 */
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 */
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 */
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 of \"%s\" succeeded.", target);
return 1;
}
/**
* Frees the resources used for Geo lookups
*/
apr_status_t geo_cleanup(modsec_rec *msr)
{
return APR_SUCCESS;
}

62
apache2/msc_geo.h Normal file
View File

@ -0,0 +1,62 @@
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
*
* You should have received a copy of the licence along with this
* program (stored in the file "LICENSE"). If the file is missing,
* or if you have any other questions related to the licence, please
* write to Thinking Stone at contact@thinkingstone.com.
*
*/
#ifndef _MSC_GEO_H_
#define _MSC_GEO_H_
#define GEO_STRUCT_INFO_MAX_SIZE 20
#define GEO_DB_INFO_MAX_SIZE 100
#define GEO_COUNTRY_OFFSET 0xffff00
#define GEO_MAX_RECORD_LEN 4
#define GEO_COUNTRY_UNKNOWN "Unknown"
#define GEO_CITY_UNKNOWN "Unknown"
#define GEO_CITY_RECORD_LEN 50
#define GEO_COUNTRY_DATABASE 1
#define GEO_CITY_DATABASE_0 6
#define GEO_CITY_DATABASE_1 2
typedef struct geo_rec geo_rec;
typedef struct geo_db geo_db;
#include <apr_file_io.h>
#include "modsecurity.h"
/* Structures */
struct geo_rec {
const char *country_code;
const char *country_code3;
const char *country_name;
const char *country_continent;
const char *region;
const char *city;
const char *postal_code;
float latitude;
float longitude;
int dma_code;
int area_code;
};
struct geo_db {
apr_file_t *db;
const char *dbfn;
int dbtype;
unsigned int ctry_offset;
};
/* Functions */
int DSOLOCAL geo_init(directory_config *dcfg, const char *dbfn, char **error_msg);
int DSOLOCAL geo_lookup(modsec_rec *msr, geo_rec *rec, const char *target, char **error_msg);
apr_status_t DSOLOCAL geo_cleanup(modsec_rec *msr);
#endif

View File

@ -447,6 +447,18 @@ char *log_escape_header_name(apr_pool_t *mp, const char *text) {
return _log_escape(mp, (const unsigned char *)text, text ? strlen(text) : 0, 0, 1);
}
char *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length) {
unsigned char *ret = apr_palloc(mp, text_length * 4 + 1);
unsigned long int i, j;
for (i = 0, j = 0; i < text_length; i++, j += 4) {
apr_snprintf((char *)ret+j, 5, "\\x%02x", text[i]);
}
ret[text_length * 4] = '\0';
return (char *)ret;
}
/**
* Transform input into a form safe for logging.
*/

View File

@ -61,6 +61,8 @@ char DSOLOCAL *log_escape_nq_ex(apr_pool_t *p, const char *text, unsigned long i
char DSOLOCAL *log_escape_header_name(apr_pool_t *p, const char *text);
char *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length);
char DSOLOCAL *_log_escape(apr_pool_t *p, const unsigned char *input,
unsigned long int input_length, int escape_quotes, int escape_colon);

View File

@ -36,7 +36,6 @@ typedef struct msre_action msre_action;
#include "persist_dbm.h"
#include "apache2.h"
/* Actions, variables, functions and operator functions */
int DSOLOCAL expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *mptmp);

View File

@ -12,6 +12,7 @@
*/
#include "re.h"
#include "msc_pcre.h"
#include "msc_geo.h"
#include "apr_strmatch.h"
/**
@ -560,6 +561,102 @@ static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre
#endif
/**
* Perform geograpical lookups on an IP/Host.
*/
static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
geo_rec rec;
geo_db *geo = msr->txcfg->geo;
const char *geo_host = var->value;
msc_string *s = NULL;
int rc;
if (geo == NULL) {
msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database. Set SecGeoLookupDb.", geo_host);
return 0;
}
rc = geo_lookup(msr, &rec, geo_host, error_msg);
if (rc <= 0) {
return rc;
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "GEO: %s={country_code=%s, country_code3=%s, country_name=%s, country_continent=%s, region=%s, city=%s, postal_code=%s, latitude=%f, longitude=%f, dma_code=%d, area_code=%d}",
geo_host,
rec.country_code,
rec.country_code3,
rec.country_name,
rec.country_continent,
rec.region,
rec.city,
rec.postal_code,
rec.latitude,
rec.longitude,
rec.dma_code,
rec.area_code);
}
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "country_code");
s->value = apr_pstrdup(msr->mp, rec.country_code ? rec.country_code : "");
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "country_code3");
s->value = apr_pstrdup(msr->mp, rec.country_code3 ? rec.country_code3 : "");
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "region");
s->value = apr_pstrdup(msr->mp, rec.region ? rec.region : "");
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "city");
s->value = apr_pstrdup(msr->mp, rec.city ? rec.city : "");
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "postal_code");
s->value = apr_pstrdup(msr->mp, rec.postal_code ? rec.postal_code : "");
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "latitude");
s->value = apr_psprintf(msr->mp, "%f", rec.latitude);
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "longitude");
s->value = apr_psprintf(msr->mp, "%f", rec.longitude);
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "dma_code");
s->value = apr_psprintf(msr->mp, "%d", rec.dma_code);
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
s->name = apr_pstrdup(msr->mp, "area_code");
s->value = apr_psprintf(msr->mp, "%d", rec.area_code);
s->value_len = strlen(s->value);
apr_table_setn(msr->geo_vars, s->name, (void *)s);
return 1;
}
/* rbl */
static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
@ -1153,6 +1250,13 @@ void msre_engine_register_default_operators(msre_engine *engine) {
#endif
/* geoLookup */
msre_engine_op_register(engine,
"geoLookup",
NULL,
msre_op_geoLookup_execute
);
/* rbl */
msre_engine_op_register(engine,
"rbl",

View File

@ -548,6 +548,50 @@ static int var_tx_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
return count;
}
/* GEO */
static int var_geo_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
apr_table_t *vartab, apr_pool_t *mptmp)
{
const apr_array_header_t *arr = NULL;
const apr_table_entry_t *te = NULL;
int i, count = 0;
arr = apr_table_elts(msr->geo_vars);
te = (apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
msc_string *str = (msc_string *)te[i].val;
int match;
/* Figure out if we want to include this variable. */
match = 0;
if (var->param == NULL) match = 1; /* Unconditional inclusion. */
else {
if (var->param_data != NULL) { /* Regex. */
char *my_error_msg = NULL;
if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
} else { /* Simple comparison. */
if (strcasecmp(str->name, var->param) == 0) match = 1;
}
}
/* If we had a match add this argument to the collection. */
if (match) {
msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
rvar->value = str->value;
rvar->value_len = str->value_len;
rvar->name = apr_psprintf(mptmp, "GEO:%s", log_escape_nq(mptmp, str->name));
apr_table_addn(vartab, rvar->name, (void *)rvar);
count++;
}
}
return count;
}
/* IP */
static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
@ -1865,6 +1909,17 @@ void msre_engine_register_default_variables(msre_engine *engine) {
PHASE_REQUEST_HEADERS
);
/* GEO */
msre_engine_variable_register(engine,
"GEO",
VAR_LIST,
1, 1,
var_generic_list_validate,
var_geo_generate,
VAR_CACHE,
PHASE_REQUEST_HEADERS
);
/* IP */
msre_engine_variable_register(engine,
"IP",

View File

@ -1018,6 +1018,28 @@ SecAuditLogStorageDir logs/audit
phase.</para>
</section>
<section>
<title><literal>SecGeoLookupsDb</literal></title>
<para><emphasis role="bold">Description:</emphasis> Defines the path to
the geograpical database file.</para>
<para><emphasis role="bold">Syntax:</emphasis> <literal
moreinfo="none">SecGeoLookupsDb /path/to/db</literal></para>
<para><emphasis role="bold">Example Usage:</emphasis> <literal
moreinfo="none">SecGeoLookupsDb
/usr/local/geo/data/GeoLiteCity.dat</literal></para>
<para><emphasis role="bold">Processing Phase:</emphasis> N/A</para>
<para><emphasis role="bold"> <emphasis role="bold">Scope:</emphasis>
</emphasis>Any</para>
<para><emphasis role="bold">Dependencies/Notes:</emphasis> Check out
www.maxmind.com for free database files.</para>
</section>
<section>
<title><literal>SecGuardianLog</literal></title>
@ -2059,6 +2081,80 @@ SecRule <emphasis role="bold">ENV:tag</emphasis> "suspicious"</programlisting>
<programlisting format="linespecific">SecRule <emphasis role="bold">FILES_TMPNAMES</emphasis> "@inspectFile /path/to/inspect_script.pl"</programlisting>
</section>
<section>
<title><literal moreinfo="none">GEO</literal></title>
<para><literal>GEO</literal> is a collection populated by the <literal
moreinfo="none">@geoLookups</literal> operator. It can be used to match
geographical fields looked up by an IP address or hostname.</para>
<para>Available since 2.2.0.</para>
<para>Fields:</para>
<itemizedlist>
<listitem>
<para><emphasis role="bold">COUNTRY_CODE:</emphasis> Two character
country code. EX: US, UK, etc.</para>
</listitem>
<listitem>
<para><emphasis role="bold">COUNTRY_CODE3:</emphasis> Up to three
character country code.</para>
</listitem>
<listitem>
<para><emphasis role="bold">COUNTRY_NAME:</emphasis> The full
country name.</para>
</listitem>
<listitem>
<para><emphasis role="bold">COUNTRY_CONTINENT:</emphasis> The teo
character continent that the country is located. EX: EU</para>
</listitem>
<listitem>
<para><emphasis role="bold">REGION:</emphasis> The two character
region. For US, this is state. For Canada, providence, etc.</para>
</listitem>
<listitem>
<para><emphasis role="bold">CITY:</emphasis> The city name.</para>
</listitem>
<listitem>
<para><emphasis role="bold">POSTAL_CODE:</emphasis> The postal
code.</para>
</listitem>
<listitem>
<para><emphasis role="bold">LATITUDE:</emphasis> The
latitude.</para>
</listitem>
<listitem>
<para><emphasis role="bold">LONGITUDE:</emphasis> The
longitude.</para>
</listitem>
<listitem>
<para><emphasis role="bold">DMA_CODE:</emphasis> The metropoliton
area code. (US only)</para>
</listitem>
<listitem>
<para><emphasis role="bold">AREA_CODE:</emphasis> The phone system
area code. (US only)</para>
</listitem>
</itemizedlist>
<para>Example:</para>
<programlisting format="linespecific">SecRule REMOTE_ADDR "<emphasis
role="bold">@geoLookup</emphasis>" chain,drop,msg:'Non-UK IP address'
SecRule GEO:COUNTRY_CODE "!@streq UK"</programlisting>
</section>
<section>
<title><literal moreinfo="none">PATH_INFO</literal></title>
@ -4124,6 +4220,22 @@ SecRule ARGS:route "!<emphasis role="bold">@endsWith %{REQUEST_ADDR}</emphasis>"
role="bold">@ge</emphasis> 15"</programlisting>
</section>
<section>
<title><literal>geoLookup</literal></title>
<para><emphasis role="bold">Description:</emphasis> This operator looks
up various data fields from an IP address or hostname. The results will
be captured in the <literal moreinfo="none">GEO</literal>
collection.</para>
<para>You must provide a database via <literal
moreinfo="none">SecGeoLookupsDb</literal> before this operator can be
used.</para>
<para>See the <literal moreinfo="none">GEO</literal> variable for an
example and more information on various fields available.</para>
</section>
<section>
<title><literal>gt</literal></title>
@ -4383,4 +4495,4 @@ SecRule XML "<emphasis role="bold">@validateSchema /path/to/apache2/conf/xml.xsd
</itemizedlist>
</section>
</section>
</article>
</article>