commit 58a6f5e3ccc02db2cdd7b8fcec24caa336cf2559 Author: Glenn Strauss Date: Sat Jun 25 19:29:45 2016 -0400 mod_geoip for 1.4.40-pre diff --git a/doc/geoip.txt b/doc/geoip.txt new file mode 100644 index 0000000..99b0ed0 --- /dev/null +++ b/doc/geoip.txt @@ -0,0 +1,148 @@ +{{{ +#!rst +============================== +ip based geographic lookups... +============================== + +----------------- +Module: mod_geoip +----------------- + + + +.. contents:: Table of Contents + +Requirements +============ + +:Packages: GeoIP C API & Library (http://www.maxmind.com/download/geoip/api/c/) + +Overview +======== + +mod_geoip is a module for fast ip/location lookups. It uses MaxMind GeoIP / +GeoCity databases. +If the ip was found in the database the module sets the appropriate +environments variables to the request, thus making other modules/fcgi be +informed. + +.. note:: + + Currently only country/city databases are supported because they have a free + version that i can test. + +Configuration Options +======================== + +mod_geoip uses two configuration options. + +1) geoip.db-filename = +2) geoip.memory-cache = : default disabled + +if enabled, mod_geoip will load the database binary file to +memory for very fast lookups. the only penalty is memory usage. + +.. note:: + + mod_geoip will determine the database type automatically so if you enter + GeoCity databse path it will load GeoCity Env. + +Environment +=========== + +Every database sets it's own ENV: + +GeoIP (Country): +---------------- + +:: + + GEOIP_COUNTRY_CODE + GEOIP_COUNTRY_CODE3 + GEOIP_COUNTRY_NAME + +GeoCity: +-------- + +:: + + GEOIP_COUNTRY_CODE + GEOIP_COUNTRY_CODE3 + GEOIP_COUNTRY_NAME + GEOIP_CITY_NAME + GEOIP_CITY_POSTAL_CODE + GEOIP_CITY_LATITUDE + GEOIP_CITY_LONG_LATITUDE + GEOIP_CITY_DMA_CODE + GEOIP_CITY_AREA_CODE + +Examples +======== + +mod_geoip + php +--------------- + +when using fastcgi (not only php) you can access mod_geoip env and do as you +please. this example just prints all mod_geoip envs to the client, html. + +Config-file :: + + geoip.db-filename = "/your-geoip-db-path/GeoCityLite.dat" + geoip.memory-cache = "enable" + +index.php :: + + \n\n\t
\n"; + echo 'Country Code: ' . $country_code . '
'; + echo 'Country Code 3: ' . $country_code3 . '
'; + echo 'Country Name: ' . $country_name . '
'; + echo '
'; + echo 'City Region: ' . $city_region . '
'; + echo 'City Name: ' . $city_name . '
'; + echo 'City Postal Code: ' . $city_postal_code . '
'; + echo 'City Latitude: ' . $city_latitude . '
'; + echo 'City Long Latitude: ' . $city_long_latitude . '
'; + echo 'City DMA Code: ' . $city_dma_code . '
'; + echo 'City Area Code: ' . $city_area_code . '
'; + echo "\n"; + ?> + +country based redirect +---------------------- + +Config-file :: + + $HTTP["host"] =~ "www.domain.com" { + url.rewrite = ( "" => "/redirect.php") + } + +redirect.php :: + + + +.. note:: + + Currently it is not possible to redirect based on mod_geoip directly in +lighttpd config file. But i believe with the relase of lighttpd mod_magnet +it would be. (mod_magnet will be available in lighttpd 1.4.12+) + +Downloads +========= +mod_geoip.c (http://trac.lighttpd.net/trac/attachment/wiki/Docs/ModGeoip/mod_geoip.c) +}}} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b89b2dd..084be2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -553,6 +553,7 @@ add_and_install_library(mod_expire mod_expire.c) add_and_install_library(mod_extforward mod_extforward.c) add_and_install_library(mod_fastcgi mod_fastcgi.c) add_and_install_library(mod_flv_streaming mod_flv_streaming.c) +add_and_install_library(mod_geoip mod_geoip.c) add_and_install_library(mod_indexfile mod_indexfile.c) add_and_install_library(mod_magnet "mod_magnet.c;mod_magnet_cache.c") add_and_install_library(mod_mysql_vhost mod_mysql_vhost.c) @@ -596,6 +597,8 @@ add_executable(test_configfile ) add_test(NAME test_configfile COMMAND test_configfile) +target_link_libraries(lighttpd GeoIP) + if(HAVE_PCRE_H) target_link_libraries(lighttpd ${PCRE_LDFLAGS}) add_target_properties(lighttpd COMPILE_FLAGS ${PCRE_CFLAGS}) diff --git a/src/Makefile.am b/src/Makefile.am index a0b2c30..b5785c1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -112,6 +112,11 @@ mod_flv_streaming_la_SOURCES = mod_flv_streaming.c mod_flv_streaming_la_LDFLAGS = $(common_module_ldflags) mod_flv_streaming_la_LIBADD = $(common_libadd) +lib_LTLIBRARIES += mod_geoip.la +mod_geoip_la_SOURCES = mod_geoip.c +mod_geoip_la_LDFLAGS = $(common_module_ldflags) +mod_geoip_la_LIBADD = $(common_libadd) -lGeoIP + lib_LTLIBRARIES += mod_evasive.la mod_evasive_la_SOURCES = mod_evasive.c mod_evasive_la_LDFLAGS = $(common_module_ldflags) @@ -314,6 +319,7 @@ lighttpd_SOURCES = \ mod_extforward.c \ mod_fastcgi.c \ mod_flv_streaming.c \ + mod_geoip.c \ mod_indexfile.c \ mod_magnet.c mod_magnet_cache.c \ mod_mysql_vhost.c \ diff --git a/src/SConscript b/src/SConscript index bf90793..33953d7 100644 --- a/src/SConscript +++ b/src/SConscript @@ -112,6 +112,7 @@ modules = { 'src' : [ 'mod_cml_lua.c', 'mod_cml.c', 'mod_cml_funcs.c' ], 'lib' : [ env['LIBPCRE'], env['LIBMEMCACHED'], env['LIBLUA'] ] }, + 'mod_geoip' : { 'src' : [ 'mod_geoip.c' ], 'lib' : [ 'GeoIP' ] }, } if env['with_memcached']: diff --git a/src/mod_geoip.c b/src/mod_geoip.c new file mode 100644 index 0000000..067133e --- /dev/null +++ b/src/mod_geoip.c @@ -0,0 +1,419 @@ +#include +#include + +#include +#include +#include + +#include "base.h" +#include "log.h" +#include "buffer.h" + +#include "plugin.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/** + * + * $mod_geoip.c (v2.0) (13.09.2006 00:29:11) + * + * Name: + * mod_geoip.c + * + * Description: + * GeoIP module (plugin) for lighttpd. + * the module loads a geoip database of type "country" or "city" and + * sets new ENV vars based on ip record lookups. + * + * country db env's: + * GEOIP_COUNTRY_CODE + * GEOIP_COUNTRY_CODE3 + * GEOIP_COUNTRY_NAME + * + * city db env's: + * GEOIP_COUNTRY_CODE + * GEOIP_COUNTRY_CODE3 + * GEOIP_COUNTRY_NAME + * GEOIP_CITY_NAME + * GEOIP_CITY_POSTAL_CODE + * GEOIP_CITY_LATITUDE + * GEOIP_CITY_LONG_LATITUDE + * GEOIP_CITY_DMA_CODE + * GEOIP_CITY_AREA_CODE + * + * Usage (configuration options): + * geoip.db-filename = + * geoip.memory-cache = : default disabled + * if enabled, mod_geoip will load the database binary file to + * memory for very fast lookups. the only penalty is memory usage. + * + * Author: + * Ami E. Bizamcher (amix) + * duke.amix@gmail.com + * + * Note: + * GeoIP Library and API must be installed! + */ + + +/* plugin config for all request/connections */ + +typedef struct { + unsigned short mem_cache; + buffer *db_name; + GeoIP *gi; +} plugin_config; + +typedef struct { + PLUGIN_DATA; + + plugin_config **config_storage; + + plugin_config conf; +} plugin_data; + +/* init the plugin data */ +INIT_FUNC(mod_geoip_init) { + plugin_data *p; + + p = calloc(1, sizeof(*p)); + + return p; +} + +/* destroy the plugin data */ +FREE_FUNC(mod_geoip_free) { + plugin_data *p = p_d; + + UNUSED(srv); + + if (!p) return HANDLER_GO_ON; + + if (p->config_storage) { + size_t i; + + for (i = 0; i < srv->config_context->used; i++) { + plugin_config *s = p->config_storage[i]; + + if (!s) continue; + + buffer_free(s->db_name); + + /* clean up */ + if (s->gi) GeoIP_delete(s->gi); + + free(s); + } + free(p->config_storage); + } + + free(p); + + return HANDLER_GO_ON; +} + +/* handle plugin config and check values */ + +SETDEFAULTS_FUNC(mod_geoip_set_defaults) { + plugin_data *p = p_d; + size_t i = 0; + + config_values_t cv[] = { + { "geoip.db-filename", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ + { "geoip.memory-cache", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } + }; + + if (!p) return HANDLER_ERROR; + + p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); + + for (i = 0; i < srv->config_context->used; i++) { + data_config const* config = (data_config const*)srv->config_context->data[i]; + plugin_config *s; + int mode; + + s = calloc(1, sizeof(plugin_config)); + + s->db_name = buffer_init(); + s->mem_cache = 0; /* default: do not load db to cache */ + s->gi = NULL; + + cv[0].destination = s->db_name; + cv[1].destination = &(s->mem_cache); + + p->config_storage[i] = s; + + if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { + return HANDLER_ERROR; + } + + mode = GEOIP_STANDARD | GEOIP_CHECK_CACHE; + + /* country db filename is requeried! */ + if (!buffer_is_empty(s->db_name)) { + + /* let's start cooking */ + if (s->mem_cache != 0) + mode = GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE; + + if (NULL == (s->gi = GeoIP_open(s->db_name->ptr, mode))) { + log_error_write(srv, __FILE__, __LINE__, "s", + "failed to open GeoIP database!!!"); + + return HANDLER_ERROR; + } + + /* is the db supported ? */ + if (s->gi->databaseType != GEOIP_COUNTRY_EDITION && + s->gi->databaseType != GEOIP_CITY_EDITION_REV0 && + s->gi->databaseType != GEOIP_CITY_EDITION_REV1) { + log_error_write(srv, __FILE__, __LINE__, "s", + "GeoIP database is of unsupported type!!!"); + } + } + } + + return HANDLER_GO_ON; +} + +#define PATCH(x) \ + p->conf.x = s->x; +static int mod_geoip_patch_connection(server *srv, connection *con, plugin_data *p) { + size_t i, j; + plugin_config *s = p->config_storage[0]; + + PATCH(db_name); + PATCH(mem_cache); + PATCH(gi); + + /* skip the first, the global context */ + for (i = 1; i < srv->config_context->used; i++) { + data_config *dc = (data_config *)srv->config_context->data[i]; + s = p->config_storage[i]; + + /* condition didn't match */ + if (!config_check_cond(srv, con, dc)) continue; + + /* merge config */ + for (j = 0; j < dc->value->used; j++) { + data_unset *du = dc->value->data[j]; + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("geoip.db-filename"))) { + PATCH(db_name); + } + + if (buffer_is_equal_string(du->key, CONST_STR_LEN("geoip.memory-cache"))) { + PATCH(mem_cache); + } + } + } + + return 0; +} +#undef PATCH + +URIHANDLER_FUNC(mod_geoip_subrequest) { + plugin_data *p = p_d; + + mod_geoip_patch_connection(srv, con, p); + + if (!buffer_is_empty(p->conf.db_name)) { + const char *remote_ip; + data_string *ds; + GeoIPRecord *gir; + const char *returnedCountry; + + remote_ip = con->dst_addr_buf->ptr; + + if (p->conf.gi->databaseType == GEOIP_COUNTRY_EDITION) { + /* get the country code 2 chars */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_CODE"))) { + if (NULL != (returnedCountry = GeoIP_country_code_by_addr(p->conf.gi, remote_ip))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_CODE"); + buffer_copy_string(ds->value, returnedCountry); + array_insert_unique(con->environment, (data_unset *)ds); + } + } + + /* get the country code 3 chars */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_CODE3"))) { + if (NULL != (returnedCountry = GeoIP_country_code3_by_addr(p->conf.gi, remote_ip))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_CODE3"); + buffer_copy_string(ds->value, returnedCountry); + array_insert_unique(con->environment, (data_unset *)ds); + } + } + + /* get the country name */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_NAME"))) { + if (NULL != (returnedCountry = GeoIP_country_name_by_addr(p->conf.gi, remote_ip))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_NAME"); + buffer_copy_string(ds->value, returnedCountry); + array_insert_unique(con->environment, (data_unset *)ds); + } + } + + /* go on... */ + return HANDLER_GO_ON; + } + + /* if we are here, geo city is in use */ + + if (NULL != (gir = GeoIP_record_by_addr(p->conf.gi, remote_ip))) { + /* get the country code 2 chars */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_CODE"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_CODE"); + buffer_copy_string(ds->value, gir->country_code); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the country code 3 chars */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_CODE3"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_CODE3"); + buffer_copy_string(ds->value, gir->country_code3); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the country name */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_COUNTRY_NAME"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_COUNTRY_NAME"); + buffer_copy_string(ds->value, gir->country_name); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the city region */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_REGION"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_CITY_REGION"); + buffer_copy_string(ds->value, gir->region); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the city */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_NAME"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_CITY_NAME"); + buffer_copy_string(ds->value, gir->city); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the postal code */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_POSTAL_CODE"))) { + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + buffer_copy_string(ds->key, "GEOIP_CITY_POSTAL_CODE"); + buffer_copy_string(ds->value, gir->postal_code); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the latitude */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_LATITUDE"))) { + char latitude[32]; + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + snprintf(latitude, sizeof(latitude), "%f", gir->latitude); + buffer_copy_string(ds->key, "GEOIP_CITY_LATITUDE"); + buffer_copy_string(ds->value, latitude); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the long latitude */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_LONG_LATITUDE"))) { + char long_latitude[32]; + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + snprintf(long_latitude, sizeof(long_latitude), "%f", gir->longitude); + buffer_copy_string(ds->key, "GEOIP_CITY_LONG_LATITUDE"); + buffer_copy_string(ds->value, long_latitude); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the dma code */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_DMA_CODE"))) { + char dc[5]; + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + snprintf(dc, sizeof(dc), "%i", gir->dma_code); + buffer_copy_string(ds->key, "GEOIP_CITY_DMA_CODE"); + buffer_copy_string(ds->value, dc); + array_insert_unique(con->environment, (data_unset *)ds); + } + + /* get the area code */ + if (NULL == (ds = (data_string *)array_get_element(con->environment, "GEOIP_CITY_AREA_CODE"))) { + char ac[5]; + if (NULL == (ds = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { + ds = data_string_init(); + } + + snprintf(ac, sizeof(ac), "%i", gir->area_code); + buffer_copy_string(ds->key, "GEOIP_CITY_AREA_CODE"); + buffer_copy_string(ds->value, ac); + array_insert_unique(con->environment, (data_unset *)ds); + } + + GeoIPRecord_delete(gir); + } + } + + /* keep walking... (johnnie walker style ;) */ + return HANDLER_GO_ON; +} + +/* this function is called at dlopen() time and inits the callbacks */ + +int mod_geoip_plugin_init(plugin *p); +int mod_geoip_plugin_init(plugin *p) { + p->version = LIGHTTPD_VERSION_ID; + p->name = buffer_init_string("geoip"); + + p->init = mod_geoip_init; + p->handle_subrequest_start = mod_geoip_subrequest; + p->set_defaults = mod_geoip_set_defaults; + p->cleanup = mod_geoip_free; + + p->data = NULL; + + return 0; +}