[mod_auth] require digest uri= match original URI
[lighttpd.git] / src / mod_maxminddb.c
blob431344409ccbb170c032ef80beb1f8deff23e3f8
1 /**
3 * Name:
4 * mod_maxminddb.c
6 * Description:
7 * MaxMind GeoIP2 module (plugin) for lighttpd.
9 * GeoIP2 country db env's:
10 * GEOIP_COUNTRY_CODE
11 * GEOIP_COUNTRY_NAME
13 * GeoIP2 city db env's:
14 * GEOIP_COUNTRY_CODE
15 * GEOIP_COUNTRY_NAME
16 * GEOIP_CITY_NAME
17 * GEOIP_CITY_LATITUDE
18 * GEOIP_CITY_LONGITUDE
20 * Usage (configuration options):
21 * maxminddb.db = <path to the geoip or geocity database>
22 * GeoLite2 database filenames end in ".mmdb"
23 * maxminddb.activate = <enable|disable> : default disabled
24 * maxminddb.env = (
25 * "GEOIP_COUNTRY_CODE" => "country/iso_code",
26 * "GEOIP_COUNTRY_NAME" => "country/names/en",
27 * "GEOIP_CITY_NAME" => "city/names/en",
28 * "GEOIP_CITY_LATITUDE" => "location/latitude",
29 * "GEOIP_CITY_LONGITUDE" => "location/longitude",
30 * )
32 * Installation Instructions:
33 * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
35 * References:
36 * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
37 * http://dev.maxmind.com/geoip/legacy/geolite/
38 * http://dev.maxmind.com/geoip/geoip2/geolite2/
39 * http://dev.maxmind.com/geoip/geoipupdate/
41 * GeoLite2 database format
42 * http://maxmind.github.io/MaxMind-DB/
43 * https://github.com/maxmind/libmaxminddb
45 * Note: GeoLite2 databases are free IP geolocation databases comparable to,
46 * but less accurate than, MaxMind’s GeoIP2 databases.
47 * If you are a commercial entity, please consider a subscription to the
48 * more accurate databases to support MaxMind.
49 * http://dev.maxmind.com/geoip/geoip2/downloadable/
52 #include "first.h" /* first */
53 #include "sys-socket.h" /* AF_INET AF_INET6 */
54 #include <stdlib.h>
55 #include <string.h>
57 #include "base.h"
58 #include "buffer.h"
59 #include "http_header.h"
60 #include "log.h"
61 #include "sock_addr.h"
63 #include "plugin.h"
65 #include <maxminddb.h>
67 SETDEFAULTS_FUNC(mod_maxmind_set_defaults);
68 INIT_FUNC(mod_maxmind_init);
69 FREE_FUNC(mod_maxmind_free);
70 CONNECTION_FUNC(mod_maxmind_request_env_handler);
71 CONNECTION_FUNC(mod_maxmind_handle_con_close);
73 int mod_maxminddb_plugin_init(plugin *p);
74 int mod_maxminddb_plugin_init(plugin *p) {
75 p->version = LIGHTTPD_VERSION_ID;
76 p->name = buffer_init_string("maxminddb");
78 p->set_defaults = mod_maxmind_set_defaults;
79 p->init = mod_maxmind_init;
80 p->cleanup = mod_maxmind_free;
81 p->handle_request_env = mod_maxmind_request_env_handler;
82 p->handle_connection_close = mod_maxmind_handle_con_close;
84 p->data = NULL;
86 return 0;
89 typedef struct {
90 int activate;
91 array *env;
92 const char ***cenv;
93 struct MMDB_s *mmdb;
94 buffer *db_name;
95 } plugin_config;
97 typedef struct {
98 PLUGIN_DATA;
99 int nconfig;
100 plugin_config **config_storage;
101 } plugin_data;
104 INIT_FUNC(mod_maxmind_init)
106 return calloc(1, sizeof(plugin_data));
110 FREE_FUNC(mod_maxmind_free)
112 plugin_data *p = (plugin_data *)p_d;
113 if (!p) return HANDLER_GO_ON;
115 if (p->config_storage) {
116 for (int i = 0; i < p->nconfig; ++i) {
117 plugin_config * const s = p->config_storage[i];
118 if (!s) continue;
119 buffer_free(s->db_name);
120 if (s->mmdb) { MMDB_close(s->mmdb); free(s->mmdb); }
121 for (size_t k = 0, used = s->env->used; k < used; ++k)
122 free(s->cenv[k]);
123 free(s->cenv);
124 array_free(s->env);
126 free(p->config_storage);
129 free(p);
131 UNUSED(srv);
132 return HANDLER_GO_ON;
136 SETDEFAULTS_FUNC(mod_maxmind_set_defaults)
138 static config_values_t cv[] = {
139 { "maxminddb.activate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
140 { "maxminddb.db", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
141 { "maxminddb.env", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
143 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
146 plugin_data * const p = (plugin_data *)p_d;
147 const size_t n_context = p->nconfig = srv->config_context->used;
148 p->config_storage = calloc(p->nconfig, sizeof(plugin_config *));
149 force_assert(p->config_storage);
151 for (size_t i = 0; i < n_context; ++i) {
152 plugin_config * const s = calloc(1, sizeof(plugin_config));
153 force_assert(s);
154 p->config_storage[i] = s;
155 s->db_name = buffer_init();
156 s->env = array_init();
158 cv[0].destination = &s->activate;
159 cv[1].destination = s->db_name;
160 cv[2].destination = s->env;
162 array * const ca = ((data_config *)srv->config_context->data[i])->value;
163 if (0 != config_insert_values_global(srv, ca, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
164 return HANDLER_ERROR;
167 if (!buffer_is_empty(s->db_name)) {
169 if (s->db_name->used >= sizeof(".mmdb")
170 && 0 == memcmp(s->db_name->ptr+s->db_name->used-sizeof(".mmdb"),
171 CONST_STR_LEN(".mmdb"))) {
172 MMDB_s * const mmdb = (MMDB_s *)calloc(1, sizeof(MMDB_s));
173 int rc = MMDB_open(s->db_name->ptr, MMDB_MODE_MMAP, mmdb);
174 if (MMDB_SUCCESS != rc) {
175 if (MMDB_IO_ERROR == rc)
176 log_perror(srv->errh, __FILE__, __LINE__,
177 "failed to open GeoIP2 database (%.*s)",
178 BUFFER_INTLEN_PTR(s->db_name));
179 else
180 log_error(srv->errh, __FILE__, __LINE__,
181 "failed to open GeoIP2 database (%.*s): %s",
182 BUFFER_INTLEN_PTR(s->db_name),
183 MMDB_strerror(rc));
184 free(mmdb);
185 return HANDLER_ERROR;
187 s->mmdb = mmdb;
189 else {
190 log_error(srv->errh, __FILE__, __LINE__,
191 "GeoIP database is of unsupported type %.*s)",
192 BUFFER_INTLEN_PTR(s->db_name));
193 return HANDLER_ERROR;
197 if (s->env->used) {
198 data_string **data = (data_string **)s->env->data;
199 s->cenv = calloc(s->env->used, sizeof(char **));
200 force_assert(s->cenv);
201 for (size_t j = 0, used = s->env->used; j < used; ++j) {
202 if (data[j]->type != TYPE_STRING) {
203 log_error(srv->errh, __FILE__, __LINE__,
204 "maxminddb.env must be a list of strings");
205 return HANDLER_ERROR;
207 buffer *value = data[j]->value;
208 if (buffer_string_is_empty(value)
209 || '/' == value->ptr[0]
210 || '/' == value->ptr[buffer_string_length(value)-1]) {
211 log_error(srv->errh, __FILE__, __LINE__,
212 "maxminddb.env must be a list of non-empty "
213 "strings and must not begin or end with '/'");
214 return HANDLER_ERROR;
216 /* XXX: should strings be lowercased? */
217 unsigned int k = 2;
218 for (char *t = value->ptr; (t = strchr(t, '/')); ++t) ++k;
219 const char **keys = s->cenv[j] = calloc(k, sizeof(char *));
220 force_assert(keys);
221 k = 0;
222 keys[k] = value->ptr;
223 for (char *t = value->ptr; (t = strchr(t, '/')); ) {
224 *t = '\0';
225 keys[++k] = ++t;
227 keys[++k] = NULL;
232 return HANDLER_GO_ON;
236 static void
237 geoip2_env_set (array * const env, const char *k, size_t klen,
238 MMDB_entry_data_s *data)
240 /* GeoIP2 database interfaces return pointers directly into database,
241 * and these are valid until the database is closed.
242 * However, note that the strings *are not* '\0'-terminated */
243 char buf[35];
244 if (!data->has_data || 0 == data->offset) return;
245 switch (data->type) {
246 case MMDB_DATA_TYPE_UTF8_STRING:
247 array_set_key_value(env, k, klen, data->utf8_string, data->data_size);
248 return;
249 case MMDB_DATA_TYPE_BOOLEAN:
250 array_set_key_value(env, k, klen, data->boolean ? "1" : "0", 1);
251 return;
252 case MMDB_DATA_TYPE_BYTES:
253 array_set_key_value(env, k, klen,
254 (const char *) data->bytes, data->data_size);
255 return;
256 case MMDB_DATA_TYPE_DOUBLE:
257 array_set_key_value(env, k, klen,
258 buf, snprintf(buf, sizeof(buf), "%.5f",
259 data->double_value));
260 return;
261 case MMDB_DATA_TYPE_FLOAT:
262 array_set_key_value(env, k, klen,
263 buf, snprintf(buf, sizeof(buf), "%.5f",
264 data->float_value));
265 return;
266 case MMDB_DATA_TYPE_INT32:
267 li_itostrn(buf, sizeof(buf), data->int32);
268 break;
269 case MMDB_DATA_TYPE_UINT32:
270 li_utostrn(buf, sizeof(buf), data->uint32);
271 break;
272 case MMDB_DATA_TYPE_UINT16:
273 li_utostrn(buf, sizeof(buf), data->uint16);
274 break;
275 case MMDB_DATA_TYPE_UINT64:
276 /* truncated value on 32-bit unless uintmax_t is 64-bit (long long) */
277 li_utostrn(buf, sizeof(buf), data->uint64);
278 break;
279 case MMDB_DATA_TYPE_UINT128:
280 buf[0] = '0';
281 buf[1] = 'x';
282 #if MMDB_UINT128_IS_BYTE_ARRAY
283 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)data->uint128, 16);
284 #else
285 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)&data->uint128, 16);
286 #endif
287 array_set_key_value(env, k, klen, buf, 34);
288 return;
289 default: /*(ignore unknown data type)*/
290 return;
293 array_set_key_value(env, k, klen, buf, strlen(buf)); /*(numerical types)*/
297 static void
298 mod_maxmind_geoip2 (array * const env, sock_addr *dst_addr,
299 plugin_config *pconf)
301 MMDB_lookup_result_s res;
302 MMDB_entry_data_s data;
303 int rc;
305 res = MMDB_lookup_sockaddr(pconf->mmdb, (struct sockaddr *)dst_addr, &rc);
306 if (MMDB_SUCCESS != rc || !res.found_entry) return;
307 MMDB_entry_s * const entry = &res.entry;
309 const data_string ** const names = (const data_string **)pconf->env->data;
310 const char *** const cenv = pconf->cenv;
311 for (size_t i = 0, used = pconf->env->used; i < used; ++i) {
312 if (MMDB_SUCCESS == MMDB_aget_value(entry, &data, cenv[i])
313 && data.has_data) {
314 geoip2_env_set(env, CONST_BUF_LEN(names[i]->key), &data);
320 static void
321 mod_maxmind_patch_connection (server * const srv,
322 connection * const con,
323 const plugin_data * const p,
324 plugin_config * const pconf)
326 const plugin_config *s = p->config_storage[0];
327 memcpy(pconf, s, sizeof(*s));
328 if (1 == p->nconfig)
329 return;
331 data_config ** const context_data =
332 (data_config **)srv->config_context->data;
334 s = p->config_storage[1]; /* base config (global context) copied above */
335 for (size_t i = 1; i < srv->config_context->used; ++i) {
336 data_config *dc = context_data[i];
337 if (!config_check_cond(srv, con, dc))
338 continue; /* condition did not match */
340 s = p->config_storage[i];
342 /* merge config */
343 #define PATCH(x) pconf->x = s->x;
344 for (size_t j = 0; j < dc->value->used; ++j) {
345 data_unset *du = dc->value->data[j];
347 if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.activate"))) {
348 PATCH(activate);
350 else if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.db"))) {
351 /*PATCH(db_name);*//*(not used)*/
352 PATCH(mmdb);
354 else if (buffer_is_equal_string(du->key, CONST_STR_LEN("maxminddb.env"))) {
355 PATCH(env);
356 PATCH(cenv);
359 #undef PATCH
364 CONNECTION_FUNC(mod_maxmind_request_env_handler)
366 const int sa_family = con->dst_addr.plain.sa_family;
367 if (sa_family != AF_INET && sa_family != AF_INET6) return HANDLER_GO_ON;
369 plugin_config pconf;
370 plugin_data *p = p_d;
371 mod_maxmind_patch_connection(srv, con, p, &pconf);
372 /* check that mod_maxmind is activated and env fields were requested */
373 if (!pconf.activate || 0 == pconf.env->used) return HANDLER_GO_ON;
375 array *env = con->plugin_ctx[p->id];
376 if (NULL == env) {
377 env = con->plugin_ctx[p->id] = array_init();
378 if (pconf.mmdb)
379 mod_maxmind_geoip2(env, &con->dst_addr, &pconf);
382 for (size_t i = 0; i < env->used; ++i) {
383 /* note: replaces values which may have been set by mod_openssl
384 * (when mod_extforward is listed after mod_openssl in server.modules)*/
385 data_string *ds = (data_string *)env->data[i];
386 http_header_env_set(con,
387 CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
390 return HANDLER_GO_ON;
394 CONNECTION_FUNC(mod_maxmind_handle_con_close)
396 plugin_data *p = p_d;
397 array *env = con->plugin_ctx[p->id];
398 UNUSED(srv);
399 if (NULL != env) {
400 array_free(env);
401 con->plugin_ctx[p->id] = NULL;
404 return HANDLER_GO_ON;