1 /*
2 * mod_maxminddb - MaxMind GeoIP2 support for lighttpd
3 *
4 * Copyright(c) 2019 Glenn Strauss gstrauss()gluelogic.com All rights reserved
5 * License: BSD 3-clause (same as lighttpd)
6 */
7 /**
8 *
9 * Name:
10 * mod_maxminddb.c
11 *
12 * Description:
13 * MaxMind GeoIP2 module (plugin) for lighttpd.
14 *
15 * GeoIP2 country db env's:
16 * GEOIP_COUNTRY_CODE
17 * GEOIP_COUNTRY_NAME
18 *
19 * GeoIP2 city db env's:
20 * GEOIP_COUNTRY_CODE
21 * GEOIP_COUNTRY_NAME
22 * GEOIP_CITY_NAME
23 * GEOIP_CITY_LATITUDE
24 * GEOIP_CITY_LONGITUDE
25 *
26 * Usage (configuration options):
27 * maxminddb.db = <path to the geoip or geocity database>
28 * GeoLite2 database filenames end in ".mmdb"
29 * maxminddb.activate = <enable|disable> : default disabled
30 * maxminddb.env = (
31 * "GEOIP_COUNTRY_CODE" => "country/iso_code",
32 * "GEOIP_COUNTRY_NAME" => "country/names/en",
33 * "GEOIP_CITY_NAME" => "city/names/en",
34 * "GEOIP_CITY_LATITUDE" => "location/latitude",
35 * "GEOIP_CITY_LONGITUDE" => "location/longitude",
36 * )
37 *
38 * Installation Instructions:
39 * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
40 *
41 * References:
42 * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
43 * http://dev.maxmind.com/geoip/legacy/geolite/
44 * http://dev.maxmind.com/geoip/geoip2/geolite2/
45 * http://dev.maxmind.com/geoip/geoipupdate/
46 *
47 * GeoLite2 database format
48 * http://maxmind.github.io/MaxMind-DB/
49 * https://github.com/maxmind/libmaxminddb
50 *
51 * Note: GeoLite2 databases are free IP geolocation databases comparable to,
52 * but less accurate than, MaxMind’s GeoIP2 databases.
53 * If you are a commercial entity, please consider a subscription to the
54 * more accurate databases to support MaxMind.
55 * http://dev.maxmind.com/geoip/geoip2/downloadable/
56 */
57
58 #include "first.h" /* first */
59 #include "sys-socket.h" /* AF_INET AF_INET6 */
60 #include <stdlib.h>
61 #include <string.h>
62
63 #include "base.h"
64 #include "buffer.h"
65 #include "http_header.h"
66 #include "log.h"
67 #include "sock_addr.h"
68
69 #include "plugin.h"
70
71 #include <maxminddb.h>
72
73 SETDEFAULTS_FUNC(mod_maxminddb_set_defaults);
74 INIT_FUNC(mod_maxminddb_init);
75 FREE_FUNC(mod_maxminddb_free);
76 REQUEST_FUNC(mod_maxminddb_request_env_handler);
77 CONNECTION_FUNC(mod_maxminddb_handle_con_close);
78
79 int mod_maxminddb_plugin_init(plugin *p);
mod_maxminddb_plugin_init(plugin * p)80 int mod_maxminddb_plugin_init(plugin *p) {
81 p->version = LIGHTTPD_VERSION_ID;
82 p->name = "maxminddb";
83
84 p->set_defaults = mod_maxminddb_set_defaults;
85 p->init = mod_maxminddb_init;
86 p->cleanup = mod_maxminddb_free;
87 p->handle_request_env = mod_maxminddb_request_env_handler;
88 p->handle_connection_close = mod_maxminddb_handle_con_close;
89
90 return 0;
91 }
92
93 typedef struct {
94 int activate;
95 const array *env;
96 const char ***cenv;
97 struct MMDB_s *mmdb;
98 } plugin_config;
99
100 typedef struct {
101 PLUGIN_DATA;
102 plugin_config defaults;
103 } plugin_data;
104
105 typedef struct {
106 const array *env;
107 const char ***cenv;
108 } plugin_config_env;
109
INIT_FUNC(mod_maxminddb_init)110 INIT_FUNC(mod_maxminddb_init)
111 {
112 return calloc(1, sizeof(plugin_data));
113 }
114
115
FREE_FUNC(mod_maxminddb_free)116 FREE_FUNC(mod_maxminddb_free)
117 {
118 plugin_data * const p = p_d;
119 if (NULL == p->cvlist) return;
120 /* (init i to 0 if global context; to 1 to skip empty global context) */
121 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
122 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
123 for (; -1 != cpv->k_id; ++cpv) {
124 switch (cpv->k_id) {
125 case 1: /* maxminddb.db */
126 if (cpv->vtype == T_CONFIG_LOCAL && NULL != cpv->v.v) {
127 struct MMDB_s *mmdb;
128 *(struct MMDB_s **)&mmdb = cpv->v.v;
129 MMDB_close(mmdb);
130 free(mmdb);
131 }
132 break;
133 case 2: /* maxminddb.env */
134 if (cpv->vtype == T_CONFIG_LOCAL && NULL != cpv->v.v) {
135 plugin_config_env * const pcenv = cpv->v.v;
136 const array * const env = pcenv->env;
137 char ***cenv;
138 *(const char ****)&cenv = pcenv->cenv;
139 for (uint32_t k = 0, cused = env->used; k < cused; ++k)
140 free(cenv[k]);
141 free(cenv);
142 }
143 break;
144 default:
145 break;
146 }
147 }
148 }
149 }
150
151
152 static MMDB_s *
mod_maxminddb_open_db(server * srv,const buffer * db_name)153 mod_maxminddb_open_db (server *srv, const buffer *db_name)
154 {
155 if (db_name->used < sizeof(".mmdb")
156 || 0 != memcmp(db_name->ptr+db_name->used-sizeof(".mmdb"),
157 CONST_STR_LEN(".mmdb"))) {
158 log_error(srv->errh, __FILE__, __LINE__,
159 "GeoIP database is of unsupported type %s)",
160 db_name->ptr);
161 return NULL;
162 }
163
164 MMDB_s * const mmdb = (MMDB_s *)calloc(1, sizeof(MMDB_s));
165 int rc = MMDB_open(db_name->ptr, MMDB_MODE_MMAP, mmdb);
166 if (MMDB_SUCCESS == rc)
167 return mmdb;
168
169 if (MMDB_IO_ERROR == rc)
170 log_perror(srv->errh, __FILE__, __LINE__,
171 "failed to open GeoIP2 database (%s)",
172 db_name->ptr);
173 else
174 log_error(srv->errh, __FILE__, __LINE__,
175 "failed to open GeoIP2 database (%s): %s",
176 db_name->ptr, MMDB_strerror(rc));
177 free(mmdb);
178 return NULL;
179 }
180
181
182 static plugin_config_env *
mod_maxminddb_prep_cenv(server * srv,const array * const env)183 mod_maxminddb_prep_cenv (server *srv, const array * const env)
184 {
185 data_string ** const data = (data_string **)env->data;
186 char *** const cenv = calloc(env->used, sizeof(char **));
187 force_assert(cenv);
188 for (uint32_t j = 0, used = env->used; j < used; ++j) {
189 if (data[j]->type != TYPE_STRING) {
190 log_error(srv->errh, __FILE__, __LINE__,
191 "maxminddb.env must be a list of strings");
192 for (uint32_t k = 0; k < j; ++k) free(cenv[k]);
193 free(cenv);
194 return NULL;
195 }
196 buffer *value = &data[j]->value;
197 if (buffer_is_blank(value)
198 || '/' == value->ptr[0]
199 || '/' == value->ptr[buffer_clen(value)-1]) {
200 log_error(srv->errh, __FILE__, __LINE__,
201 "maxminddb.env must be a list of non-empty "
202 "strings and must not begin or end with '/'");
203 for (uint32_t k = 0; k < j; ++k) free(cenv[k]);
204 free(cenv);
205 return NULL;
206 }
207 /* XXX: should strings be lowercased? */
208 unsigned int k = 2;
209 for (char *t = value->ptr; (t = strchr(t, '/')); ++t) ++k;
210 const char **keys = (const char **)(cenv[j] = calloc(k,sizeof(char *)));
211 force_assert(keys);
212 k = 0;
213 keys[k] = value->ptr;
214 for (char *t = value->ptr; (t = strchr(t, '/')); ) {
215 *t = '\0';
216 keys[++k] = ++t;
217 }
218 keys[++k] = NULL;
219 }
220
221 plugin_config_env * const pcenv = malloc(sizeof(plugin_config_env));
222 force_assert(pcenv);
223 pcenv->env = env;
224 pcenv->cenv = (const char ***)cenv;
225 return pcenv;
226 }
227
228
229 static void
mod_maxminddb_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)230 mod_maxminddb_merge_config_cpv(plugin_config * const pconf,
231 const config_plugin_value_t * const cpv)
232 {
233 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
234 case 0: /* maxminddb.activate */
235 pconf->activate = (int)cpv->v.u;
236 break;
237 case 1: /* maxminddb.db */
238 if (cpv->vtype != T_CONFIG_LOCAL) break;
239 pconf->mmdb = cpv->v.v;
240 break;
241 case 2: /* maxminddb.env */
242 if (cpv->vtype == T_CONFIG_LOCAL) {
243 plugin_config_env * const pcenv = cpv->v.v;
244 pconf->env = pcenv->env;
245 pconf->cenv = pcenv->cenv;
246 }
247 break;
248 default:/* should not happen */
249 return;
250 }
251 }
252
253
254 static void
mod_maxminddb_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)255 mod_maxminddb_merge_config (plugin_config * const pconf,
256 const config_plugin_value_t *cpv)
257 {
258 do {
259 mod_maxminddb_merge_config_cpv(pconf, cpv);
260 } while ((++cpv)->k_id != -1);
261 }
262
263
264 static void
mod_maxmind_patch_config(request_st * const r,const plugin_data * const p,plugin_config * const pconf)265 mod_maxmind_patch_config (request_st * const r,
266 const plugin_data * const p,
267 plugin_config * const pconf)
268 {
269 *pconf = p->defaults; /* copy small struct instead of memcpy() */
270 /*memcpy(pconf, &p->defaults, sizeof(plugin_config));*/
271 for (int i = 1, used = p->nconfig; i < used; ++i) {
272 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
273 mod_maxminddb_merge_config(pconf, p->cvlist + p->cvlist[i].v.u2[0]);
274 }
275 }
276
277
SETDEFAULTS_FUNC(mod_maxminddb_set_defaults)278 SETDEFAULTS_FUNC(mod_maxminddb_set_defaults)
279 {
280 static const config_plugin_keys_t cpk[] = {
281 { CONST_STR_LEN("maxminddb.activate"),
282 T_CONFIG_BOOL,
283 T_CONFIG_SCOPE_CONNECTION }
284 ,{ CONST_STR_LEN("maxminddb.db"),
285 T_CONFIG_STRING,
286 T_CONFIG_SCOPE_CONNECTION }
287 ,{ CONST_STR_LEN("maxminddb.env"),
288 T_CONFIG_ARRAY_KVSTRING,
289 T_CONFIG_SCOPE_CONNECTION }
290 ,{ NULL, 0,
291 T_CONFIG_UNSET,
292 T_CONFIG_SCOPE_UNSET }
293 };
294
295 plugin_data * const p = p_d;
296 if (!config_plugin_values_init(srv, p, cpk, "mod_maxminddb"))
297 return HANDLER_ERROR;
298
299 /* process and validate config directives
300 * (init i to 0 if global context; to 1 to skip empty global context) */
301 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
302 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
303 for (; -1 != cpv->k_id; ++cpv) {
304 switch (cpv->k_id) {
305 case 0: /* maxminddb.activate */
306 break;
307 case 1: /* maxminddb.db */
308 if (!buffer_is_blank(cpv->v.b)) {
309 cpv->v.v = mod_maxminddb_open_db(srv, cpv->v.b);
310 if (NULL == cpv->v.v) return HANDLER_ERROR;
311 cpv->vtype = T_CONFIG_LOCAL;
312 }
313 break;
314 case 2: /* maxminddb.env */
315 if (cpv->v.a->used) {
316 cpv->v.v = mod_maxminddb_prep_cenv(srv, cpv->v.a);
317 if (NULL == cpv->v.v) return HANDLER_ERROR;
318 cpv->vtype = T_CONFIG_LOCAL;
319 }
320 break;
321 default:/* should not happen */
322 break;
323 }
324 }
325 }
326
327 /* initialize p->defaults from global config context */
328 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
329 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
330 if (-1 != cpv->k_id)
331 mod_maxminddb_merge_config(&p->defaults, cpv);
332 }
333
334 return HANDLER_GO_ON;
335 }
336
337
338 static void
geoip2_env_set(array * const env,const char * const k,const size_t klen,MMDB_entry_data_s * const data)339 geoip2_env_set (array * const env, const char * const k,
340 const size_t klen, MMDB_entry_data_s * const data)
341 {
342 /* GeoIP2 database interfaces return pointers directly into database,
343 * and these are valid until the database is closed.
344 * However, note that the strings *are not* '\0'-terminated */
345 char buf[35];
346 if (!data->has_data || 0 == data->offset) return;
347 const char *v = buf;
348 size_t vlen;
349 switch (data->type) {
350 case MMDB_DATA_TYPE_UTF8_STRING:
351 v = data->utf8_string;
352 vlen = data->data_size;
353 break;
354 case MMDB_DATA_TYPE_BOOLEAN:
355 v = data->boolean ? "1" : "0";
356 vlen = 1;
357 break;
358 case MMDB_DATA_TYPE_BYTES:
359 v = (const char *)data->bytes;
360 vlen = data->data_size;
361 break;
362 case MMDB_DATA_TYPE_DOUBLE:
363 vlen = snprintf(buf, sizeof(buf), "%.5f", data->double_value);
364 break;
365 case MMDB_DATA_TYPE_FLOAT:
366 vlen = snprintf(buf, sizeof(buf), "%.5f", data->float_value);
367 break;
368 case MMDB_DATA_TYPE_INT32:
369 vlen = li_itostrn(buf, sizeof(buf), data->int32);
370 break;
371 case MMDB_DATA_TYPE_UINT32:
372 vlen = li_utostrn(buf, sizeof(buf), data->uint32);
373 break;
374 case MMDB_DATA_TYPE_UINT16:
375 vlen = li_utostrn(buf, sizeof(buf), data->uint16);
376 break;
377 case MMDB_DATA_TYPE_UINT64:
378 /* truncated value on 32-bit unless uintmax_t is 64-bit (long long) */
379 vlen = li_utostrn(buf, sizeof(buf), data->uint64);
380 break;
381 case MMDB_DATA_TYPE_UINT128:
382 buf[0] = '0';
383 buf[1] = 'x';
384 #if MMDB_UINT128_IS_BYTE_ARRAY
385 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)data->uint128, 16);
386 #else
387 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)&data->uint128, 16);
388 #endif
389 vlen = 34;
390 break;
391 default: /*(ignore unknown data type)*/
392 return;
393 }
394
395 array_set_key_value(env, k, klen, v, vlen);
396 }
397
398
399 static void
mod_maxmind_geoip2(array * const env,const struct sockaddr * const dst_addr,plugin_config * const pconf)400 mod_maxmind_geoip2 (array * const env, const struct sockaddr * const dst_addr,
401 plugin_config * const pconf)
402 {
403 MMDB_lookup_result_s res;
404 MMDB_entry_data_s data;
405 int rc;
406
407 res = MMDB_lookup_sockaddr(pconf->mmdb, dst_addr, &rc);
408 if (MMDB_SUCCESS != rc || !res.found_entry) return;
409 MMDB_entry_s * const entry = &res.entry;
410
411 const data_string ** const names = (const data_string **)pconf->env->data;
412 const char *** const cenv = pconf->cenv;
413 for (size_t i = 0, used = pconf->env->used; i < used; ++i) {
414 if (MMDB_SUCCESS == MMDB_aget_value(entry, &data, cenv[i])
415 && data.has_data) {
416 geoip2_env_set(env, BUF_PTR_LEN(&names[i]->key), &data);
417 }
418 }
419 }
420
421
REQUEST_FUNC(mod_maxminddb_request_env_handler)422 REQUEST_FUNC(mod_maxminddb_request_env_handler)
423 {
424 connection * const con = r->con;
425 const sock_addr * const dst_addr = &con->dst_addr;
426 const int sa_family = sock_addr_get_family(dst_addr);
427 if (sa_family != AF_INET && sa_family != AF_INET6) return HANDLER_GO_ON;
428
429 plugin_config pconf;
430 plugin_data *p = p_d;
431 mod_maxmind_patch_config(r, p, &pconf);
432 /* check that mod_maxmind is activated and env fields were requested */
433 if (!pconf.activate || NULL == pconf.env) return HANDLER_GO_ON;
434
435 array *env = con->plugin_ctx[p->id];
436 if (NULL == env) {
437 env = con->plugin_ctx[p->id] = array_init(pconf.env->used);
438 if (pconf.mmdb)
439 mod_maxmind_geoip2(env, (const struct sockaddr *)dst_addr, &pconf);
440 }
441
442 for (uint32_t i = 0; i < env->used; ++i) {
443 /* note: replaces values which may have been set by mod_openssl
444 * (when mod_extforward is listed after mod_openssl in server.modules)*/
445 data_string *ds = (data_string *)env->data[i];
446 http_header_env_set(r, BUF_PTR_LEN(&ds->key), BUF_PTR_LEN(&ds->value));
447 }
448
449 return HANDLER_GO_ON;
450 }
451
452
CONNECTION_FUNC(mod_maxminddb_handle_con_close)453 CONNECTION_FUNC(mod_maxminddb_handle_con_close)
454 {
455 plugin_data *p = p_d;
456 array *env = con->plugin_ctx[p->id];
457 if (NULL != env) {
458 array_free(env);
459 con->plugin_ctx[p->id] = NULL;
460 }
461
462 return HANDLER_GO_ON;
463 }
464