Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / msc_geo.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
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.
8  *
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
12  * distribution.
13  *
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.
17  *
18  */
19 #include "msc_geo.h"
20
21
22 /* -- Lookup Tables -- */
23
24 static const char *geo_country_code[GEO_COUNTRY_LAST + 1] = {
25     "--",
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"
51 };
52
53 static const char *geo_country_code3[GEO_COUNTRY_LAST + 1] = {
54     "--",
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"
80 };
81
82 static const char *geo_country_name[GEO_COUNTRY_LAST + 1] = {
83     "N/A",
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"
109 };
110
111 static const char *geo_country_continent[GEO_COUNTRY_LAST + 1] = {
112     "--",
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"
138 };
139
140
141 static int db_open(directory_config *dcfg, char **error_msg)
142 {
143     char errstr[1024];
144     apr_pool_t *mp = dcfg->mp;
145     geo_db *geo = dcfg->geo;
146     apr_status_t rc;
147     apr_size_t nbytes;
148     apr_off_t offset;
149     unsigned char buf[3];
150     int i, j;
151
152     #ifdef DEBUG_CONF
153     fprintf(stderr, "GEO: Initializing geo DB \"%s\".\n", geo->dbfn);
154     #endif
155
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));
158         return 0;
159     }
160
161     offset = -3;
162     apr_file_seek(geo->db, APR_END, &offset);
163     /* TODO check offset */
164
165     /* Defaults */
166     geo->dbtype = GEO_COUNTRY_DATABASE;
167     geo->ctry_offset = GEO_COUNTRY_OFFSET;
168
169     for (i = 0; i < GEO_STRUCT_INFO_MAX_SIZE; i++) {
170         memset(buf, 0, 3);
171         rc = apr_file_read_full(geo->db, &buf, 3, &nbytes);
172         #ifdef DEBUG_CONF
173         fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
174         #endif
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));
177             return -1;
178         }
179         if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)) {
180             #ifdef DEBUG_CONF
181             fprintf(stderr, "GEO: Found DB info marker at offset 0x%08x\n", (unsigned int)offset);
182             #endif
183             memset(buf, 0, 3);
184             rc = apr_file_read_full(geo->db, &buf, 1, &nbytes);
185             /* TODO: check rc */
186             geo->dbtype = (int)buf[0];
187
188             /* Backwards compat */
189             if (geo->dbtype >= 106) {
190                 geo->dbtype -= 105;
191             }
192             #ifdef DEBUG_CONF
193             fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
194             #endif
195
196             /* If a cities DB, then get country offset */
197             if ((geo->dbtype == GEO_CITY_DATABASE_0) || (geo->dbtype == GEO_CITY_DATABASE_1)) {
198                 memset(buf, 0, 3);
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));
202                     return -1;
203                 }
204                 #ifdef DEBUG_CONF
205                 fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]);
206                 #endif
207                 geo->ctry_offset = 0;
208                 for (j = 0; j < 3; j++) {
209                     geo->ctry_offset += (buf[j] << (j * 8));
210                 }
211             }
212
213             #ifdef DEBUG_CONF
214             fprintf(stderr, "GEO: Country offset 0x%08x\n", geo->ctry_offset);
215             #endif
216
217             return 1;
218         }
219         /* Backup a byte from where we started */
220         offset = -4;
221         apr_file_seek(geo->db, APR_CUR, &offset);
222         #ifdef DEBUG_CONF
223         fprintf(stderr, "GEO: DB offset 0x%08x\n", (unsigned int)offset);
224         #endif
225     }
226
227     if (geo->dbtype != GEO_COUNTRY_DATABASE) {
228         *error_msg = apr_psprintf(mp, "Unknown database format");
229         return 0;
230     }
231
232     #ifdef DEBUG_CONF
233     fprintf(stderr, "GEO: DB type %d\n", geo->dbtype);
234     #endif
235
236     return 1;
237 }
238
239 static int field_length(const char *field, int maxlen)
240 {
241     int i;
242
243     if (field == NULL) {
244         return 0;
245     }
246
247     for (i = 0; i < maxlen; i++) {
248         if (field[i] == '\0') {
249             break;
250         }
251     }
252
253     return i;
254 }
255
256 /**
257  * Initialise Geo data structure
258  */
259 int geo_init(directory_config *dcfg, const char *dbfn, char **error_msg)
260 {
261     *error_msg = NULL;
262
263     if ((dcfg->geo == NULL) || (dcfg->geo == NOT_SET_P)) {
264         dcfg->geo = apr_pcalloc(dcfg->mp, sizeof(geo_db));
265     }
266
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;
271
272     return db_open(dcfg, error_msg);
273 }
274
275 /**
276  * Perform geographical lookup on target.
277  */
278 int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **error_msg)
279 {
280     apr_sockaddr_t *addr;
281     long ipnum = 0;
282     char *targetip = NULL;
283     geo_db *geo = msr->txcfg->geo;
284     char errstr[1024];
285     unsigned char buf[2* GEO_MAX_RECORD_LEN];
286     const int reclen = 3; /* Algorithm needs changed if this changes */
287     apr_size_t nbytes;
288     unsigned int rec_val = 0;
289     apr_off_t seekto = 0;
290     int rc;
291     int country = 0;
292     int level;
293     double dtmp;
294     int itmp;
295
296     *error_msg = NULL;
297
298     /* init */
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];
303     georec->region = "";
304     georec->city = "";
305     georec->postal_code = "";
306     georec->latitude = 0;
307     georec->longitude = 0;
308     georec->dma_code = 0;
309     georec->area_code = 0;
310
311     if (msr->txcfg->debuglog_level >= 9) {
312         msr_log(msr, 9, "GEO: Looking up \"%s\".", log_escape(msr->mp, target));
313     }
314
315     /* NOTE: This only works with ipv4 */
316     if ((rc = apr_sockaddr_info_get(&addr, target, APR_INET, 0, 0, msr->mp)) != APR_SUCCESS) {
317
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);
320         return 0;
321     }
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);
325         return 0;
326     };
327
328     /* Why is this in host byte order? */
329     ipnum = ntohl(addr->sa.sin.sin_addr.s_addr);
330
331     if (msr->txcfg->debuglog_level >= 9) {
332         msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx).", targetip, ipnum);
333     }
334
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);
339         /* TODO: check rc */
340         rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes);
341
342         /* NOTE: This is hard-coded for size 3 records */
343         /* Left */
344         if ((ipnum & (1 << level)) == 0) {
345             rec_val = buf[0] +
346                      (buf[1] <<  8) +
347                      (buf[2] << 16);
348         }
349         /* Right */
350         else {
351             rec_val = buf[3] +
352                      (buf[4] <<  8) +
353                      (buf[5] << 16);
354         }
355
356         /* If we are past the country offset, then we are done */
357         if (rec_val >= geo->ctry_offset) {
358             break;
359         }
360     }
361
362     if (geo->dbtype == GEO_COUNTRY_DATABASE) {
363         country = rec_val;
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);
368             return 0;
369         }
370
371         /* Country */
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];
376     }
377     else {
378         int field_len = 0;
379         int rec_offset = 0;
380         int remaining = GEO_CITY_RECORD_LEN;
381         unsigned char cbuf[GEO_CITY_RECORD_LEN];
382
383         seekto = rec_val + (2 * reclen - 1) * geo->ctry_offset;
384         apr_file_seek(geo->db, APR_SET, &seekto);
385         /* TODO: check rc */
386         rc = apr_file_read_full(geo->db, &cbuf, sizeof(cbuf), &nbytes);
387
388         country = cbuf[0];
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);
392             return 0;
393         }
394         if (msr->txcfg->debuglog_level >= 9) {
395             msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf)));
396         }
397
398         /* Country */
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)));
401         }
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];
406         rec_offset++;
407         remaining -= rec_offset;
408
409         /* Region */
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));
413         }
414         georec->region = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
415         rec_offset += field_len + 1;
416         remaining -= field_len + 1;
417
418         /* City */
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));
422         }
423         georec->city = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining));
424         rec_offset += field_len + 1;
425         remaining -= field_len + 1;
426
427         /* Postal Code */
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));
431         }
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;
435
436         /* Latitude */
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));
439         }
440         dtmp = cbuf[rec_offset] +
441                (cbuf[rec_offset+1] << 8) +
442                (cbuf[rec_offset+2] << 16);
443         georec->latitude = dtmp/10000 - 180;
444         rec_offset += 3;
445         remaining -= 3;
446
447
448         /* Longitude */
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));
451         }
452         dtmp = cbuf[rec_offset] +
453               (cbuf[rec_offset+1] << 8) +
454               (cbuf[rec_offset+2] << 16);
455         georec->longitude = dtmp/10000 - 180;
456         rec_offset += 3;
457         remaining -= 3;
458
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));
462         }
463         if (geo->dbtype == GEO_CITY_DATABASE_1
464             && georec->country_code[0] == 'U'
465             && georec->country_code[1] == 'S')
466         {
467             /* DMA Code */
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;
473             rec_offset += 6;
474             remaining -= 6;
475         }
476
477     }
478
479     *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded.", log_escape(msr->mp, target));
480     return 1;
481 }
482
483 /**
484  * Frees the resources used for Geo lookups
485  */
486 apr_status_t geo_cleanup(modsec_rec *msr)
487 {
488     return APR_SUCCESS;
489 }
490
491