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