2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
5 * This product is released under the terms of the General Public Licence,
6 * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7 * distribution) which contains the complete text of the licence.
9 * There are special exceptions to the terms and conditions of the GPL
10 * as it is applied to this software. View the full text of the exception in
11 * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
14 * If any of the files related to licensing are missing or if you have any
15 * other questions related to licensing please contact Breach Security, Inc.
16 * directly using the email address support@breach.com.
22 /* -- Lookup Tables -- */
24 static const char *geo_country_code[GEO_COUNTRY_LAST + 1] = {
26 "AP","EU","AD","AE","AF","AG","AI","AL","AM","AN",
27 "AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB",
28 "BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO",
29 "BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD",
30 "CF","CG","CH","CI","CK","CL","CM","CN","CO","CR",
31 "CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO",
32 "DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ",
33 "FK","FM","FO","FR","FX","GA","GB","GD","GE","GF",
34 "GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT",
35 "GU","GW","GY","HK","HM","HN","HR","HT","HU","ID",
36 "IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO",
37 "JP","KE","KG","KH","KI","KM","KN","KP","KR","KW",
38 "KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT",
39 "LU","LV","LY","MA","MC","MD","MG","MH","MK","ML",
40 "MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV",
41 "MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI",
42 "NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF",
43 "PG","PH","PK","PL","PM","PN","PR","PS","PT","PW",
44 "PY","QA","RE","RO","RU","RW","SA","SB","SC","SD",
45 "SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO",
46 "SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH",
47 "TJ","TK","TM","TN","TO","TL","TR","TT","TV","TW",
48 "TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE",
49 "VG","VI","VN","VU","WF","WS","YE","YT","RS","ZA",
50 "ZM","ME","ZW","A1","A2","O1","AX","GG","IM","JE"
53 static const char *geo_country_code3[GEO_COUNTRY_LAST + 1] = {
55 "AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT",
56 "AGO","AQ","ARG","ASM","AUT","AUS","ABW","AZE","BIH","BRB",
57 "BGD","BEL","BFA","BGR","BHR","BDI","BEN","BMU","BRN","BOL",
58 "BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC","COD",
59 "CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI",
60 "CUB","CPV","CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM",
61 "DZA","ECU","EST","EGY","ESH","ERI","ESP","ETH","FIN","FJI",
62 "FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD","GEO","GUF",
63 "GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM",
64 "GUM","GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN",
65 "IRL","ISR","IND","IO","IRQ","IRN","ISL","ITA","JAM","JOR",
66 "JPN","KEN","KGZ","KHM","KIR","COM","KNA","PRK","KOR","KWT",
67 "CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
68 "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI",
69 "MMR","MNG","MAC","MNP","MTQ","MRT","MSR","MLT","MUS","MDV",
70 "MWI","MEX","MYS","MOZ","NAM","NCL","NER","NFK","NGA","NIC",
71 "NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER","PYF",
72 "PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW",
73 "PRY","QAT","REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN",
74 "SWE","SGP","SHN","SVN","SJM","SVK","SLE","SMR","SEN","SOM",
75 "SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF","TGO","THA",
76 "TJK","TKL","TKM","TUN","TON","TLS","TUR","TTO","TUV","TWN",
77 "TZA","UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN",
78 "VGB","VIR","VNM","VUT","WLF","WSM","YEM","YT","SRB","ZAF",
79 "ZMB","MNE","ZWE","A1","A2","O1","ALA","GGY","IMN","JEY"
82 static const char *geo_country_name[GEO_COUNTRY_LAST + 1] = {
84 "Asia/Pacific Region","Europe","Andorra","United Arab Emirates","Afghanistan","Antigua and Barbuda","Anguilla","Albania","Armenia","Netherlands Antilles",
85 "Angola","Antarctica","Argentina","American Samoa","Austria","Australia","Aruba","Azerbaijan","Bosnia and Herzegovina","Barbados",
86 "Bangladesh","Belgium","Burkina Faso","Bulgaria","Bahrain","Burundi","Benin","Bermuda","Brunei Darussalam","Bolivia",
87 "Brazil","Bahamas","Bhutan","Bouvet Island","Botswana","Belarus","Belize","Canada","Cocos (Keeling) Islands","Congo, The Democratic Republic of the",
88 "Central African Republic","Congo","Switzerland","Cote D'Ivoire","Cook Islands","Chile","Cameroon","China","Colombia","Costa Rica",
89 "Cuba","Cape Verde","Christmas Island","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominica","Dominican Republic",
90 "Algeria","Ecuador","Estonia","Egypt","Western Sahara","Eritrea","Spain","Ethiopia","Finland","Fiji",
91 "Falkland Islands (Malvinas)","Micronesia, Federated States of","Faroe Islands","France","France, Metropolitan","Gabon","United Kingdom","Grenada","Georgia","French Guiana",
92 "Ghana","Gibraltar","Greenland","Gambia","Guinea","Guadeloupe","Equatorial Guinea","Greece","South Georgia and the South Sandwich Islands","Guatemala",
93 "Guam","Guinea-Bissau","Guyana","Hong Kong","Heard Island and McDonald Islands","Honduras","Croatia","Haiti","Hungary","Indonesia",
94 "Ireland","Israel","India","British Indian Ocean Territory","Iraq","Iran, Islamic Republic of","Iceland","Italy","Jamaica","Jordan",
95 "Japan","Kenya","Kyrgyzstan","Cambodia","Kiribati","Comoros","Saint Kitts and Nevis","Korea, Democratic People's Republic of","Korea, Republic of","Kuwait",
96 "Cayman Islands","Kazakhstan","Lao People's Democratic Republic","Lebanon","Saint Lucia","Liechtenstein","Sri Lanka","Liberia","Lesotho","Lithuania",
97 "Luxembourg","Latvia","Libyan Arab Jamahiriya","Morocco","Monaco","Moldova, Republic of","Madagascar","Marshall Islands","Macedonia","Mali",
98 "Myanmar","Mongolia","Macau","Northern Mariana Islands","Martinique","Mauritania","Montserrat","Malta","Mauritius","Maldives",
99 "Malawi","Mexico","Malaysia","Mozambique","Namibia","New Caledonia","Niger","Norfolk Island","Nigeria","Nicaragua",
100 "Netherlands","Norway","Nepal","Nauru","Niue","New Zealand","Oman","Panama","Peru","French Polynesia",
101 "Papua New Guinea","Philippines","Pakistan","Poland","Saint Pierre and Miquelon","Pitcairn Islands","Puerto Rico","Palestinian Territory","Portugal","Palau",
102 "Paraguay","Qatar","Reunion","Romania","Russian Federation","Rwanda","Saudi Arabia","Solomon Islands","Seychelles","Sudan",
103 "Sweden","Singapore","Saint Helena","Slovenia","Svalbard and Jan Mayen","Slovakia","Sierra Leone","San Marino","Senegal","Somalia","Suriname",
104 "Sao Tome and Principe","El Salvador","Syrian Arab Republic","Swaziland","Turks and Caicos Islands","Chad","French Southern Territories","Togo","Thailand",
105 "Tajikistan","Tokelau","Turkmenistan","Tunisia","Tonga","Timor-Leste","Turkey","Trinidad and Tobago","Tuvalu","Taiwan",
106 "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",
107 "Virgin Islands, British","Virgin Islands, U.S.","Vietnam","Vanuatu","Wallis and Futuna","Samoa","Yemen","Mayotte","Serbia","South Africa",
108 "Zambia","Montenegro","Zimbabwe","Anonymous Proxy","Satellite Provider","Other","Aland Islands","Guernsey","Isle of Man","Jersey"
111 static const char *geo_country_continent[GEO_COUNTRY_LAST + 1] = {
113 "AS","EU","EU","AS","AS","SA","SA","EU","AS","SA",
114 "AF","AN","SA","OC","EU","OC","SA","AS","EU","SA",
115 "AS","EU","AF","EU","AS","AF","AF","SA","AS","SA",
116 "SA","SA","AS","AF","AF","EU","SA","NA","AS","AF",
117 "AF","AF","EU","AF","OC","SA","AF","AS","SA","SA",
118 "SA","AF","AS","AS","EU","EU","AF","EU","SA","SA",
119 "AF","SA","EU","AF","AF","AF","EU","AF","EU","OC",
120 "SA","OC","EU","EU","EU","AF","EU","SA","AS","SA",
121 "AF","EU","SA","AF","AF","SA","AF","EU","SA","SA",
122 "OC","AF","SA","AS","AF","SA","EU","SA","EU","AS",
123 "EU","AS","AS","AS","AS","AS","EU","EU","SA","AS",
124 "AS","AF","AS","AS","OC","AF","SA","AS","AS","AS",
125 "SA","AS","AS","AS","SA","EU","AS","AF","AF","EU",
126 "EU","EU","AF","AF","EU","EU","AF","OC","EU","AF",
127 "AS","AS","AS","OC","SA","AF","SA","EU","AF","AS",
128 "AF","NA","AS","AF","AF","OC","AF","OC","AF","SA",
129 "EU","EU","AS","OC","OC","OC","AS","SA","SA","OC",
130 "OC","AS","AS","EU","SA","OC","SA","AS","EU","OC",
131 "SA","AS","AF","EU","AS","AF","AS","OC","AF","AF",
132 "EU","AS","AF","EU","EU","EU","AF","EU","AF","AF",
133 "SA","AF","SA","AS","AF","SA","AF","AF","AF","AS",
134 "AS","OC","AS","AF","OC","AS","AS","SA","OC","AS",
135 "AF","EU","AF","OC","NA","SA","AS","EU","SA","SA",
136 "SA","SA","AS","OC","OC","OC","AS","AF","EU","AF",
137 "AF","EU","AF","--","--","--","EU","EU","EU","EU"
141 static int db_open(directory_config *dcfg, char **error_msg)
144 apr_pool_t *mp = dcfg->mp;
145 geo_db *geo = dcfg->geo;
149 unsigned char buf[3];
153 fprintf(stderr, "GEO: Initializing geo DB \"%s\".\n", geo->dbfn);
156 if ((rc = apr_file_open(&geo->db, geo->dbfn, APR_READ, APR_OS_DEFAULT, mp)) != APR_SUCCESS) {
157 *error_msg = apr_psprintf(mp, "Could not open geo database \"%s\": %s", geo->dbfn, apr_strerror(rc, errstr, 1024));
162 apr_file_seek(geo->db, APR_END, &offset);
163 /* TODO check offset */
166 geo->dbtype = GEO_COUNTRY_DATABASE;
167 geo->ctry_offset = GEO_COUNTRY_OFFSET;
169 for (i = 0; i < GEO_STRUCT_INFO_MAX_SIZE; i++) {
171 rc = apr_file_read_full(geo->db, &buf, 3, &nbytes);
173 fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
175 if ((rc != APR_SUCCESS) || (nbytes != 3)) {
176 *error_msg = apr_psprintf(mp, "Could not read from geo database \"%s\" (%" APR_SIZE_T_FMT "/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024));
179 if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)) {
181 fprintf(stderr, "GEO: Found DB info marker at offset 0x%08x\n", (unsigned int)offset);
184 rc = apr_file_read_full(geo->db, &buf, 1, &nbytes);
186 geo->dbtype = (int)buf[0];
188 /* Backwards compat */
189 if (geo->dbtype >= 106) {
193 fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
196 /* If a cities DB, then get country offset */
197 if ((geo->dbtype == GEO_CITY_DATABASE_0) || (geo->dbtype == GEO_CITY_DATABASE_1)) {
199 rc = apr_file_read_full(geo->db, &buf, 3, &nbytes);
200 if ((rc != APR_SUCCESS) || (nbytes != 3)) {
201 *error_msg = apr_psprintf(mp, "Could not read geo database \"%s\" country offset (%" APR_SIZE_T_FMT "/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024));
205 fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
207 geo->ctry_offset = 0;
208 for (j = 0; j < 3; j++) {
209 geo->ctry_offset += (buf[j] << (j * 8));
214 fprintf(stderr, "GEO: Country offset 0x%08x\n", geo->ctry_offset);
219 /* Backup a byte from where we started */
221 apr_file_seek(geo->db, APR_CUR, &offset);
223 fprintf(stderr, "GEO: DB offset 0x%08x\n", (unsigned int)offset);
227 if (geo->dbtype != GEO_COUNTRY_DATABASE) {
228 *error_msg = apr_psprintf(mp, "Unknown database format");
233 fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
239 static int field_length(const char *field, int maxlen)
247 for (i = 0; i < maxlen; i++) {
248 if (field[i] == '\0') {
257 * Initialise Geo data structure
259 int geo_init(directory_config *dcfg, const char *dbfn, char **error_msg)
263 if ((dcfg->geo == NULL) || (dcfg->geo == NOT_SET_P)) {
264 dcfg->geo = apr_pcalloc(dcfg->mp, sizeof(geo_db));
267 dcfg->geo->db = NULL;
268 dcfg->geo->dbfn = apr_pstrdup(dcfg->mp, dbfn);
269 dcfg->geo->dbtype = 0;
270 dcfg->geo->ctry_offset = 0;
272 return db_open(dcfg, error_msg);
276 * Perform geographical lookup on target.
278 int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **error_msg)
280 apr_sockaddr_t *addr;
282 char *targetip = NULL;
283 geo_db *geo = msr->txcfg->geo;
285 unsigned char buf[2* GEO_MAX_RECORD_LEN];
286 const int reclen = 3; /* Algorithm needs changed if this changes */
288 unsigned int rec_val = 0;
289 apr_off_t seekto = 0;
299 georec->country_code = geo_country_code[0];
300 georec->country_code3 = geo_country_code3[0];
301 georec->country_name = geo_country_name[0];
302 georec->country_continent = geo_country_continent[0];
305 georec->postal_code = "";
306 georec->latitude = 0;
307 georec->longitude = 0;
308 georec->dma_code = 0;
309 georec->area_code = 0;
311 if (msr->txcfg->debuglog_level >= 9) {
312 msr_log(msr, 9, "GEO: Looking up \"%s\".", log_escape(msr->mp, target));
315 /* NOTE: This only works with ipv4 */
316 if ((rc = apr_sockaddr_info_get(&addr, target, APR_INET, 0, 0, msr->mp)) != APR_SUCCESS) {
318 *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024));
319 msr_log(msr, 4, "%s", *error_msg);
322 if ((rc = apr_sockaddr_ip_get(&targetip, addr)) != APR_SUCCESS) {
323 *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024));
324 msr_log(msr, 4, "%s", *error_msg);
328 /* Why is this in host byte order? */
329 ipnum = ntohl(addr->sa.sin.sin_addr.s_addr);
331 if (msr->txcfg->debuglog_level >= 9) {
332 msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx).", targetip, ipnum);
335 for (level = 31; level >= 0; level--) {
336 /* Read the record */
337 seekto = 2 * reclen * rec_val;
338 apr_file_seek(geo->db, APR_SET, &seekto);
340 rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes);
342 /* NOTE: This is hard-coded for size 3 records */
344 if ((ipnum & (1 << level)) == 0) {
356 /* If we are past the country offset, then we are done */
357 if (rec_val >= geo->ctry_offset) {
362 if (geo->dbtype == GEO_COUNTRY_DATABASE) {
364 country -= geo->ctry_offset;
365 if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
366 *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
367 msr_log(msr, 4, "%s", *error_msg);
372 georec->country_code = geo_country_code[country];
373 georec->country_code3 = geo_country_code3[country];
374 georec->country_name = geo_country_name[country];
375 georec->country_continent = geo_country_continent[country];
380 int remaining = GEO_CITY_RECORD_LEN;
381 unsigned char cbuf[GEO_CITY_RECORD_LEN];
383 seekto = rec_val + (2 * reclen - 1) * geo->ctry_offset;
384 apr_file_seek(geo->db, APR_SET, &seekto);
386 rc = apr_file_read_full(geo->db, &cbuf, sizeof(cbuf), &nbytes);
389 if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
390 *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
391 msr_log(msr, 4, "%s", *error_msg);
394 if (msr->txcfg->debuglog_level >= 9) {
395 msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
399 if (msr->txcfg->debuglog_level >= 9) {
400 msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
402 georec->country_code = geo_country_code[country];
403 georec->country_code3 = geo_country_code3[country];
404 georec->country_name = geo_country_name[country];
405 georec->country_continent = geo_country_continent[country];
407 remaining -= rec_offset;
410 field_len = field_length((const char *)cbuf+rec_offset, remaining);
411 if (msr->txcfg->debuglog_level >= 9) {
412 msr_log(msr, 9, "GEO: region=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
414 georec->region = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
415 rec_offset += field_len + 1;
416 remaining -= field_len + 1;
419 field_len = field_length((const char *)cbuf+rec_offset, remaining);
420 if (msr->txcfg->debuglog_level >= 9) {
421 msr_log(msr, 9, "GEO: city=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
423 georec->city = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
424 rec_offset += field_len + 1;
425 remaining -= field_len + 1;
428 field_len = field_length((const char *)cbuf+rec_offset, remaining);
429 if (msr->txcfg->debuglog_level >= 9) {
430 msr_log(msr, 9, "GEO: postal_code=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
432 georec->postal_code = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
433 rec_offset += field_len + 1;
434 remaining -= field_len + 1;
437 if (msr->txcfg->debuglog_level >= 9) {
438 msr_log(msr, 9, "GEO: latitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
440 dtmp = cbuf[rec_offset] +
441 (cbuf[rec_offset+1] << 8) +
442 (cbuf[rec_offset+2] << 16);
443 georec->latitude = dtmp/10000 - 180;
449 if (msr->txcfg->debuglog_level >= 9) {
450 msr_log(msr, 9, "GEO: longitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
452 dtmp = cbuf[rec_offset] +
453 (cbuf[rec_offset+1] << 8) +
454 (cbuf[rec_offset+2] << 16);
455 georec->longitude = dtmp/10000 - 180;
459 /* dma/area codes are in city rev1 and US only */
460 if (msr->txcfg->debuglog_level >= 9) {
461 msr_log(msr, 9, "GEO: dma/area=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4));
463 if (geo->dbtype == GEO_CITY_DATABASE_1
464 && georec->country_code[0] == 'U'
465 && georec->country_code[1] == 'S')
468 itmp = cbuf[rec_offset] +
469 (cbuf[rec_offset+1] << 8) +
470 (cbuf[rec_offset+2] << 16);
471 georec->dma_code = itmp / 1000;
472 georec->area_code = itmp % 1000;
479 *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded.", log_escape(msr->mp, target));
484 * Frees the resources used for Geo lookups
486 apr_status_t geo_cleanup(modsec_rec *msr)