summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2019-05-20 00:53:20 -0400
committerGlenn Strauss <gstrauss@gluelogic.com>2019-05-26 10:21:57 -0400
commit4ac239c40193378ae09f28355530a3a04858065e (patch)
treea122691eb51b4001653f2ad92637ab7f17671e5e
parentef0a2117331ef9d2428f27fe208a5aafa3a4df72 (diff)
downloadlighttpd1.4-4ac239c40193378ae09f28355530a3a04858065e.tar.gz
lighttpd1.4-4ac239c40193378ae09f28355530a3a04858065e.zip
[mod_maxminddb] MaxMind GeoIP2 support
-rw-r--r--SConstruct9
-rw-r--r--configure.ac33
-rw-r--r--meson_options.txt5
-rw-r--r--src/CMakeLists.txt10
-rw-r--r--src/Makefile.am11
-rw-r--r--src/SConscript3
-rw-r--r--src/meson.build6
-rw-r--r--src/mod_maxminddb.c405
8 files changed, 482 insertions, 0 deletions
diff --git a/SConstruct b/SConstruct
index c286aed7..53a46cca 100644
--- a/SConstruct
+++ b/SConstruct
@@ -243,6 +243,7 @@ vars.AddVariables(
BoolVariable('with_fam', 'enable FAM/gamin support', 'no'),
BoolVariable('with_gdbm', 'enable gdbm support', 'no'),
BoolVariable('with_geoip', 'enable GeoIP support', 'no'),
+ BoolVariable('with_maxminddb', 'enable MaxMind GeoIP2 support', 'no'),
BoolVariable('with_krb5', 'enable krb5 auth support', 'no'),
BoolVariable('with_ldap', 'enable ldap auth support', 'no'),
# with_libev not supported
@@ -522,6 +523,14 @@ if 1:
LIBGEOIP = 'GeoIP',
)
+ if env['with_maxminddb']:
+ if not autoconf.CheckLibWithHeader('maxminddb', 'maxminddb.h', 'C'):
+ fail("Couldn't find maxminddb")
+ autoconf.env.Append(
+ CPPFLAGS = [ '-DHAVE_MAXMINDDB' ],
+ LIBMAXMINDDB = 'maxminddb',
+ )
+
if env['with_krb5']:
if not autoconf.CheckLibWithHeader('krb5', 'krb5.h', 'C'):
fail("Couldn't find krb5")
diff --git a/configure.ac b/configure.ac
index 46202791..27542890 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1041,6 +1041,36 @@ if test "$WITH_GEOIP" != no; then
fi
AM_CONDITIONAL([BUILD_WITH_GEOIP], [test "$WITH_GEOIP" != no])
+dnl Check for maxminddb
+AC_MSG_NOTICE([----------------------------------------])
+AC_MSG_CHECKING([for maxminddb])
+AC_ARG_WITH([maxminddb],
+ [AC_HELP_STRING([--with-maxminddb], [IP-based geolocation lookup])],
+ [WITH_MAXMINDDB=$withval],
+ [WITH_MAXMINDDB=no]
+)
+AC_MSG_RESULT([$WITH_MAXMINDDB])
+
+if test "$WITH_MAXMINDDB" != no; then
+ if test "$WITH_MAXMINDDB" != yes; then
+ MAXMINDDB_LIB="-L$WITH_MAXMINDDB -lmaxminddb"
+ CPPFLAGS="$CPPFLAGS -I$WITH_MAXMINDDB"
+ else
+ AC_CHECK_LIB([maxminddb], [MMDB_open],
+ [MAXMINDDB_LIB=-lmaxminddb],
+ [AC_MSG_ERROR([maxminddb lib not found, install it or build without --with-maxminddb])]
+ )
+ AC_CHECK_HEADERS([maxminddb.h], [],
+ [AC_MSG_ERROR([maxminddb headers not found, install them or build without --with-maxminddb])]
+ )
+ fi
+
+ AC_DEFINE([HAVE_MAXMINDDB], [1], [libmaxminddb])
+ AC_DEFINE([HAVE_MAXMINDDB_H], [1])
+ AC_SUBST([MAXMINDDB_LIB])
+fi
+AM_CONDITIONAL([BUILD_WITH_MAXMINDDB], [test "$WITH_MAXMINDDB" != no])
+
dnl Check for memcached
AC_MSG_NOTICE([----------------------------------------])
AC_MSG_CHECKING([for memcached])
@@ -1471,6 +1501,9 @@ lighty_track_feature "lua" "mod_cml mod_magnet" \
lighty_track_feature "geoip" "mod_geoip" \
'test "$WITH_GEOIP" != no'
+lighty_track_feature "maxminddb" "mod_maxminddb" \
+ 'test "$WITH_MAXMINDDB" != no'
+
lighty_track_feature "compress-gzip compress-deflate" "" \
'test "$WITH_ZLIB" != no'
diff --git a/meson_options.txt b/meson_options.txt
index 5da8b69a..bf1192d9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -48,6 +48,11 @@ option('with_lua',
value: false,
description: 'with lua 5.1 for mod_magnet [default: off]',
)
+option('with_maxminddb',
+ type: 'boolean',
+ value: false,
+ description: 'with MaxMind GeoIP2-support mod_maxminddb [default: off]',
+)
option('with_memcached',
type: 'boolean',
value: false,
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6026520f..7a38aa0b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -35,6 +35,7 @@ option(WITH_MEMCACHED "memcached storage for mod_trigger_b4_dl [default: off]")
option(WITH_LIBEV "libev support for fdevent handlers [default: off]")
option(WITH_LIBUNWIND "with libunwind to print backtraces in asserts [default: off]")
option(WITH_GEOIP "with GeoIP-support mod_geoip [default: off]")
+option(WITH_MAXMINDDB "with MaxMind GeoIP2-support mod_maxminddb [default: off]")
option(WITH_SASL "with SASL-support for mod_authn_sasl [default: off]")
if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
@@ -586,6 +587,10 @@ if(WITH_GEOIP)
check_library_exists(geoip GeoIP_country_name_by_addr "" HAVE_GEOIP)
endif()
+if(WITH_MAXMINDDB)
+ check_library_exists(maxminddb MMDB_open "" HAVE_MAXMINDDB)
+endif()
+
if(NOT BUILD_STATIC)
check_include_files(dlfcn.h HAVE_DLFCN_H)
else()
@@ -882,6 +887,11 @@ if(WITH_GEOIP)
target_link_libraries(mod_geoip GeoIP)
endif()
+if(WITH_MAXMINDDB)
+ add_and_install_library(mod_maxminddb mod_maxminddb.c)
+ target_link_libraries(mod_maxminddb maxminddb)
+endif()
+
if(HAVE_MYSQL_H AND HAVE_MYSQL)
add_and_install_library(mod_mysql_vhost "mod_mysql_vhost.c")
target_link_libraries(mod_mysql_vhost mysqlclient)
diff --git a/src/Makefile.am b/src/Makefile.am
index 2dccaabb..ac50f9c8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -135,6 +135,13 @@ mod_geoip_la_LDFLAGS = $(common_module_ldflags)
mod_geoip_la_LIBADD = $(common_libadd) $(GEOIP_LIB)
endif
+if BUILD_WITH_MAXMINDDB
+lib_LTLIBRARIES += mod_maxminddb.la
+mod_maxminddb_la_SOURCES = mod_maxminddb.c
+mod_maxminddb_la_LDFLAGS = $(common_module_ldflags)
+mod_maxminddb_la_LIBADD = $(common_libadd) $(MAXMINDDB_LIB)
+endif
+
lib_LTLIBRARIES += mod_evasive.la
mod_evasive_la_SOURCES = mod_evasive.c
mod_evasive_la_LDFLAGS = $(common_module_ldflags)
@@ -486,6 +493,10 @@ if BUILD_WITH_GEOIP
lighttpd_SOURCES += mod_geoip.c
lighttpd_LDADD += $(GEOIP_LIB)
endif
+if BUILD_WITH_MAXMINDDB
+lighttpd_SOURCES += mod_maxminddb.c
+lighttpd_LDADD += $(MAXMINDDB_LIB)
+endif
if BUILD_WITH_LUA
lighttpd_SOURCES += mod_cml.c mod_cml_lua.c mod_cml_funcs.c \
mod_magnet.c mod_magnet_cache.c
diff --git a/src/SConscript b/src/SConscript
index 003a377f..d181ce5a 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -136,6 +136,9 @@ modules = {
if env['with_geoip']:
modules['mod_geoip'] = { 'src' : [ 'mod_geoip.c' ], 'lib' : [ env['LIBGEOIP'] ] }
+if env['with_maxminddb']:
+ modules['mod_maxminddb'] = { 'src' : [ 'mod_maxminddb.c' ], 'lib' : [ env['LIBMAXMINDDB'] ] }
+
if env['with_krb5']:
modules['mod_authn_gssapi'] = { 'src' : [ 'mod_authn_gssapi.c' ], 'lib' : [ env['LIBKRB5'], env['LIBGSSAPI_KRB5'] ] }
diff --git a/src/meson.build b/src/meson.build
index 6a2e53d3..5c0fc607 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -910,6 +910,12 @@ if get_option('with_lua')
]
endif
+if get_option('with_maxminddb')
+ modules += [
+ [ 'mod_maxminddb', [ 'mod_maxminddb.c' ], libmaxminddb ],
+ ]
+endif
+
if get_option('with_geoip')
modules += [
[ 'mod_geoip', [ 'mod_geoip.c' ], libgeoip ],
diff --git a/src/mod_maxminddb.c b/src/mod_maxminddb.c
new file mode 100644
index 00000000..43134440
--- /dev/null
+++ b/src/mod_maxminddb.c
@@ -0,0 +1,405 @@
+/**
+ *
+ * Name:
+ * mod_maxminddb.c
+ *
+ * Description:
+ * MaxMind GeoIP2 module (plugin) for lighttpd.
+ *
+ * GeoIP2 country db env's:
+ * GEOIP_COUNTRY_CODE
+ * GEOIP_COUNTRY_NAME
+ *
+ * GeoIP2 city db env's:
+ * GEOIP_COUNTRY_CODE
+ * GEOIP_COUNTRY_NAME
+ * GEOIP_CITY_NAME
+ * GEOIP_CITY_LATITUDE
+ * GEOIP_CITY_LONGITUDE
+ *
+ * Usage (configuration options):
+ * maxminddb.db = <path to the geoip or geocity database>
+ * GeoLite2 database filenames end in ".mmdb"
+ * maxminddb.activate = <enable|disable> : default disabled
+ * maxminddb.env = (
+ * "GEOIP_COUNTRY_CODE" => "country/iso_code",
+ * "GEOIP_COUNTRY_NAME" => "country/names/en",
+ * "GEOIP_CITY_NAME" => "city/names/en",
+ * "GEOIP_CITY_LATITUDE" => "location/latitude",
+ * "GEOIP_CITY_LONGITUDE" => "location/longitude",
+ * )
+ *
+ * Installation Instructions:
+ * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
+ *
+ * References:
+ * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
+ * http://dev.maxmind.com/geoip/legacy/geolite/
+ * http://dev.maxmind.com/geoip/geoip2/geolite2/
+ * http://dev.maxmind.com/geoip/geoipupdate/
+ *
+ * GeoLite2 database format
+ * http://maxmind.github.io/MaxMind-DB/
+ * https://github.com/maxmind/libmaxminddb
+ *
+ * Note: GeoLite2 databases are free IP geolocation databases comparable to,
+ * but less accurate than, MaxMind’s GeoIP2 databases.
+ * If you are a commercial entity, please consider a subscription to the
+ * more accurate databases to support MaxMind.
+ * http://dev.maxmind.com/geoip/geoip2/downloadable/
+ */
+
+#include "first.h" /* first */
+#include "sys-socket.h" /* AF_INET AF_INET6 */
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+#include "buffer.h"
+#include "http_header.h"
+#include "log.h"
+#include "sock_addr.h"
+
+#include "plugin.h"
+
+#include <maxminddb.h>
+
+SETDEFAULTS_FUNC(mod_maxmind_set_defaults);
+INIT_FUNC(mod_maxmind_init);
+FREE_FUNC(mod_maxmind_free);
+CONNECTION_FUNC(mod_maxmind_request_env_handler);
+CONNECTION_FUNC(mod_maxmind_handle_con_close);
+
+int mod_maxminddb_plugin_init(plugin *p);
+int mod_maxminddb_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("maxminddb");
+
+ p->set_defaults = mod_maxmind_set_defaults;
+ p->init = mod_maxmind_init;
+ p->cleanup = mod_maxmind_free;
+ p->handle_request_env = mod_maxmind_request_env_handler;
+ p->handle_connection_close = mod_maxmind_handle_con_close;
+
+ p->data = NULL;
+
+ return 0;
+}
+
+typedef struct {
+ int activate;
+ array *env;
+ const char ***cenv;
+ struct MMDB_s *mmdb;
+ buffer *db_name;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ int nconfig;
+ plugin_config **config_storage;
+} plugin_data;
+
+
+INIT_FUNC(mod_maxmind_init)
+{
+ return calloc(1, sizeof(plugin_data));
+}
+
+
+FREE_FUNC(mod_maxmind_free)
+{
+ plugin_data *p = (plugin_data *)p_d;
+ if (!p) return HANDLER_GO_ON;
+
+ if (p->config_storage) {
+ for (int i = 0; i < p->nconfig; ++i) {
+ plugin_config * const s = p->config_storage[i];
+ if (!s) continue;
+ buffer_free(s->db_name);
+ if (s->mmdb) { MMDB_close(s->mmdb); free(s->mmdb); }
+ for (size_t k = 0, used = s->env->used; k < used; ++k)
+ free(s->cenv[k]);
+ free(s->cenv);
+ array_free(s->env);
+ }
+ free(p->config_storage);
+ }
+
+ free(p);
+
+ UNUSED(srv);
+ return HANDLER_GO_ON;
+}
+
+
+SETDEFAULTS_FUNC(mod_maxmind_set_defaults)
+{
+ static config_values_t cv[] = {
+ { "maxminddb.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
+ { "maxminddb.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
+ { "maxminddb.env", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+
+ plugin_data * const p = (plugin_data *)p_d;
+ const size_t n_context = p->nconfig = srv->config_context->used;
+ p->config_storage = calloc(p->nconfig, sizeof(plugin_config *));
+ force_assert(p->config_storage);
+
+ for (size_t i = 0; i < n_context; ++i) {
+ plugin_config * const s = calloc(1, sizeof(plugin_config));
+ force_assert(s);
+ p->config_storage[i] = s;
+ s->db_name = buffer_init();
+ s->env = array_init();
+
+ cv[0].destination = &s->activate;
+ cv[1].destination = s->db_name;
+ cv[2].destination = s->env;
+
+ array * const ca = ((data_config *)srv->config_context->data[i])->value;
+ if (0 != config_insert_values_global(srv, ca, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
+ return HANDLER_ERROR;
+ }
+
+ if (!buffer_is_empty(s->db_name)) {
+
+ if (s->db_name->used >= sizeof(".mmdb")
+ && 0 == memcmp(s->db_name->ptr+s->db_name->used-sizeof(".mmdb"),
+ CONST_STR_LEN(".mmdb"))) {
+ MMDB_s * const mmdb = (MMDB_s *)calloc(1, sizeof(MMDB_s));
+ int rc = MMDB_open(s->db_name->ptr, MMDB_MODE_MMAP, mmdb);
+ if (MMDB_SUCCESS != rc) {
+ if (MMDB_IO_ERROR == rc)
+ log_perror(srv->errh, __FILE__, __LINE__,
+ "failed to open GeoIP2 database (%.*s)",
+ BUFFER_INTLEN_PTR(s->db_name));
+ else
+ log_error(srv->errh, __FILE__, __LINE__,
+ "failed to open GeoIP2 database (%.*s): %s",
+ BUFFER_INTLEN_PTR(s->db_name),
+ MMDB_strerror(rc));
+ free(mmdb);
+ return HANDLER_ERROR;
+ }
+ s->mmdb = mmdb;
+ }
+ else {
+ log_error(srv->errh, __FILE__, __LINE__,
+ "GeoIP database is of unsupported type %.*s)",
+ BUFFER_INTLEN_PTR(s->db_name));
+ return HANDLER_ERROR;
+ }
+ }
+
+ if (s->env->used) {
+ data_string **data = (data_string **)s->env->data;
+ s->cenv = calloc(s->env->used, sizeof(char **));
+ force_assert(s->cenv);
+ for (size_t j = 0, used = s->env->used; j < used; ++j) {
+ if (data[j]->type != TYPE_STRING) {
+ log_error(srv->errh, __FILE__, __LINE__,
+ "maxminddb.env must be a list of strings");
+ return HANDLER_ERROR;
+ }
+ buffer *value = data[j]->value;
+ if (buffer_string_is_empty(value)
+ || '/' == value->ptr[0]
+ || '/' == value->ptr[buffer_string_length(value)-1]) {
+ log_error(srv->errh, __FILE__, __LINE__,
+ "maxminddb.env must be a list of non-empty "
+ "strings and must not begin or end with '/'");
+ return HANDLER_ERROR;
+ }
+ /* XXX: should strings be lowercased? */
+ unsigned int k = 2;
+ for (char *t = value->ptr; (t = strchr(t, '/')); ++t) ++k;
+ const char **keys = s->cenv[j] = calloc(k, sizeof(char *));
+ force_assert(keys);
+ k = 0;
+ keys[k] = value->ptr;
+ for (char *t = value->ptr; (t = strchr(t, '/')); ) {
+ *t = '\0';
+ keys[++k] = ++t;
+ }
+ keys[++k] = NULL;
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static void
+geoip2_env_set (array * const env, const char *k, size_t klen,
+ MMDB_entry_data_s *data)
+{
+ /* GeoIP2 database interfaces return pointers directly into database,
+ * and these are valid until the database is closed.
+ * However, note that the strings *are not* '\0'-terminated */
+ char buf[35];
+ if (!data->has_data || 0 == data->offset) return;
+ switch (data->type) {
+ case MMDB_DATA_TYPE_UTF8_STRING:
+ array_set_key_value(env, k, klen, data->utf8_string, data->data_size);
+ return;
+ case MMDB_DATA_TYPE_BOOLEAN:
+ array_set_key_value(env, k, klen, data->boolean ? "1" : "0", 1);
+ return;
+ case MMDB_DATA_TYPE_BYTES:
+ array_set_key_value(env, k, klen,
+ (const char *) data->bytes, data->data_size);
+ return;
+ case MMDB_DATA_TYPE_DOUBLE:
+ array_set_key_value(env, k, klen,
+ buf, snprintf(buf, sizeof(buf), "%.5f",
+ data->double_value));
+ return;
+ case MMDB_DATA_TYPE_FLOAT:
+ array_set_key_value(env, k, klen,
+ buf, snprintf(buf, sizeof(buf), "%.5f",
+ data->float_value));
+ return;
+ case MMDB_DATA_TYPE_INT32:
+ li_itostrn(buf, sizeof(buf), data->int32);
+ break;
+ case MMDB_DATA_TYPE_UINT32:
+ li_utostrn(buf, sizeof(buf), data->uint32);
+ break;
+ case MMDB_DATA_TYPE_UINT16:
+ li_utostrn(buf, sizeof(buf), data->uint16);
+ break;
+ case MMDB_DATA_TYPE_UINT64:
+ /* truncated value on 32-bit unless uintmax_t is 64-bit (long long) */
+ li_utostrn(buf, sizeof(buf), data->uint64);
+ break;
+ case MMDB_DATA_TYPE_UINT128:
+ buf[0] = '0';
+ buf[1] = 'x';
+ #if MMDB_UINT128_IS_BYTE_ARRAY
+ li_tohex_uc(buf+2, sizeof(buf)-2, (char *)data->uint128, 16);
+ #else
+ li_tohex_uc(buf+2, sizeof(buf)-2, (char *)&data->uint128, 16);
+ #endif
+ array_set_key_value(env, k, klen, buf, 34);
+ return;
+ default: /*(ignore unknown data type)*/
+ return;
+ }
+
+ array_set_key_value(env, k, klen, buf, strlen(buf)); /*(numerical types)*/
+}
+
+
+static void
+mod_maxmind_geoip2 (array * const env, sock_addr *dst_addr,
+ plugin_config *pconf)
+{
+ MMDB_lookup_result_s res;
+ MMDB_entry_data_s data;
+ int rc;
+
+ res = MMDB_lookup_sockaddr(pconf->mmdb, (struct sockaddr *)dst_addr, &rc);
+ if (MMDB_SUCCESS != rc || !res.found_entry) return;
+ MMDB_entry_s * const entry = &res.entry;
+
+ const data_string ** const names = (const data_string **)pconf->env->data;
+ const char *** const cenv = pconf->cenv;
+ for (size_t i = 0, used = pconf->env->used; i < used; ++i) {
+ if (MMDB_SUCCESS == MMDB_aget_value(entry, &data, cenv[i])
+ && data.has_data) {
+ geoip2_env_set(env, CONST_BUF_LEN(names[i]->key), &data);
+ }
+ }
+}
+
+
+static void
+mod_maxmind_patch_connection (server * const srv,
+ connection * const con,
+ const plugin_data * const p,
+ plugin_config * const pconf)
+{
+ const plugin_config *s = p->config_storage[0];
+ memcpy(pconf, s, sizeof(*s));
+ if (1 == p->nconfig)
+ return;
+
+ data_config ** const context_data =
+ (data_config **)srv->config_context->data;
+
+ s = p->config_storage[1]; /* base config (global context) copied above */
+ for (size_t i = 1; i < srv->config_context->used; ++i) {
+ data_config *dc = context_data[i];
+ if (!config_check_cond(srv, con, dc))
+ continue; /* condition did not match */
+
+ s = p->config_storage[i];
+
+ /* merge config */
+ #define PATCH(x) pconf->x = s->x;
+ for (size_t j = 0; j < dc->value->used; ++j) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.activate"))) {
+ PATCH(activate);
+ }
+ else if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.db"))) {
+ /*PATCH(db_name);*//*(not used)*/
+ PATCH(mmdb);
+ }
+ else if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.env"))) {
+ PATCH(env);
+ PATCH(cenv);
+ }
+ }
+ #undef PATCH
+ }
+}
+
+
+CONNECTION_FUNC(mod_maxmind_request_env_handler)
+{
+ const int sa_family = con->dst_addr.plain.sa_family;
+ if (sa_family != AF_INET && sa_family != AF_INET6) return HANDLER_GO_ON;
+
+ plugin_config pconf;
+ plugin_data *p = p_d;
+ mod_maxmind_patch_connection(srv, con, p, &pconf);
+ /* check that mod_maxmind is activated and env fields were requested */
+ if (!pconf.activate || 0 == pconf.env->used) return HANDLER_GO_ON;
+
+ array *env = con->plugin_ctx[p->id];
+ if (NULL == env) {
+ env = con->plugin_ctx[p->id] = array_init();
+ if (pconf.mmdb)
+ mod_maxmind_geoip2(env, &con->dst_addr, &pconf);
+ }
+
+ for (size_t i = 0; i < env->used; ++i) {
+ /* note: replaces values which may have been set by mod_openssl
+ * (when mod_extforward is listed after mod_openssl in server.modules)*/
+ data_string *ds = (data_string *)env->data[i];
+ http_header_env_set(con,
+ CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+CONNECTION_FUNC(mod_maxmind_handle_con_close)
+{
+ plugin_data *p = p_d;
+ array *env = con->plugin_ctx[p->id];
+ UNUSED(srv);
+ if (NULL != env) {
+ array_free(env);
+ con->plugin_ctx[p->id] = NULL;
+ }
+
+ return HANDLER_GO_ON;
+}