xref: /lighttpd1.4/src/mod_maxminddb.c (revision e2de4e58)
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_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);
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_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;
83 
84     return 0;
85 }
86 
87 typedef struct {
88     int activate;
89     array *env;
90     const char ***cenv;
91     struct MMDB_s *mmdb;
92     buffer *db_name;
93 } plugin_config;
94 
95 typedef struct {
96     PLUGIN_DATA;
97     plugin_config **config_storage;
98 } plugin_data;
99 
100 
101 INIT_FUNC(mod_maxmind_init)
102 {
103     return calloc(1, sizeof(plugin_data));
104 }
105 
106 
107 FREE_FUNC(mod_maxmind_free)
108 {
109     plugin_data *p = (plugin_data *)p_d;
110     if (!p) return HANDLER_GO_ON;
111 
112     if (p->config_storage) {
113         for (int i = 0; i < p->nconfig; ++i) {
114             plugin_config * const s = p->config_storage[i];
115             if (!s) continue;
116             buffer_free(s->db_name);
117             if (s->mmdb) { MMDB_close(s->mmdb); free(s->mmdb); }
118             for (size_t k = 0, used = s->env->used; k < used; ++k)
119                 free(s->cenv[k]);
120             free(s->cenv);
121             array_free(s->env);
122         }
123         free(p->config_storage);
124     }
125 
126     free(p);
127 
128     UNUSED(srv);
129     return HANDLER_GO_ON;
130 }
131 
132 
133 SETDEFAULTS_FUNC(mod_maxmind_set_defaults)
134 {
135     static config_values_t cv[] = {
136       { "maxminddb.activate",  NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },
137       { "maxminddb.db",        NULL, T_CONFIG_STRING,  T_CONFIG_SCOPE_CONNECTION },
138       { "maxminddb.env",       NULL, T_CONFIG_ARRAY,   T_CONFIG_SCOPE_CONNECTION },
139 
140       { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
141     };
142 
143     plugin_data * const p = (plugin_data *)p_d;
144     const size_t n_context = p->nconfig = srv->config_context->used;
145     p->config_storage = calloc(p->nconfig, sizeof(plugin_config *));
146     force_assert(p->config_storage);
147 
148     for (size_t i = 0; i < n_context; ++i) {
149         plugin_config * const s = calloc(1, sizeof(plugin_config));
150         force_assert(s);
151         p->config_storage[i] = s;
152         s->db_name = buffer_init();
153         s->env = array_init();
154 
155         cv[0].destination = &s->activate;
156         cv[1].destination = s->db_name;
157         cv[2].destination = s->env;
158 
159         array * const ca = ((data_config *)srv->config_context->data[i])->value;
160         if (0 != config_insert_values_global(srv, ca, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
161             return HANDLER_ERROR;
162         }
163 
164         if (!buffer_is_empty(s->db_name)) {
165 
166             if (s->db_name->used >= sizeof(".mmdb")
167                 && 0 == memcmp(s->db_name->ptr+s->db_name->used-sizeof(".mmdb"),
168                                CONST_STR_LEN(".mmdb"))) {
169                 MMDB_s * const mmdb = (MMDB_s *)calloc(1, sizeof(MMDB_s));
170                 int rc = MMDB_open(s->db_name->ptr, MMDB_MODE_MMAP, mmdb);
171                 if (MMDB_SUCCESS != rc) {
172                     if (MMDB_IO_ERROR == rc)
173                         log_perror(srv->errh, __FILE__, __LINE__,
174                                    "failed to open GeoIP2 database (%.*s)",
175                                    BUFFER_INTLEN_PTR(s->db_name));
176                     else
177                         log_error(srv->errh, __FILE__, __LINE__,
178                                   "failed to open GeoIP2 database (%.*s): %s",
179                                   BUFFER_INTLEN_PTR(s->db_name),
180                                   MMDB_strerror(rc));
181                     free(mmdb);
182                     return HANDLER_ERROR;
183                 }
184                 s->mmdb = mmdb;
185             }
186             else {
187                 log_error(srv->errh, __FILE__, __LINE__,
188                           "GeoIP database is of unsupported type %.*s)",
189                           BUFFER_INTLEN_PTR(s->db_name));
190                 return HANDLER_ERROR;
191             }
192         }
193 
194         if (s->env->used) {
195             data_string **data = (data_string **)s->env->data;
196             s->cenv = calloc(s->env->used, sizeof(char **));
197             force_assert(s->cenv);
198             for (size_t j = 0, used = s->env->used; j < used; ++j) {
199                 if (data[j]->type != TYPE_STRING) {
200                     log_error(srv->errh, __FILE__, __LINE__,
201                               "maxminddb.env must be a list of strings");
202                     return HANDLER_ERROR;
203                 }
204                 buffer *value = &data[j]->value;
205                 if (buffer_string_is_empty(value)
206                     || '/' == value->ptr[0]
207                     || '/' == value->ptr[buffer_string_length(value)-1]) {
208                     log_error(srv->errh, __FILE__, __LINE__,
209                               "maxminddb.env must be a list of non-empty "
210                               "strings and must not begin or end with '/'");
211                     return HANDLER_ERROR;
212                 }
213                 /* XXX: should strings be lowercased? */
214                 unsigned int k = 2;
215                 for (char *t = value->ptr; (t = strchr(t, '/')); ++t) ++k;
216                 const char **keys = s->cenv[j] = calloc(k, sizeof(char *));
217                 force_assert(keys);
218                 k = 0;
219                 keys[k] = value->ptr;
220                 for (char *t = value->ptr; (t = strchr(t, '/')); ) {
221                     *t = '\0';
222                     keys[++k] = ++t;
223                 }
224                 keys[++k] = NULL;
225             }
226         }
227     }
228 
229     return HANDLER_GO_ON;
230 }
231 
232 
233 static void
234 geoip2_env_set (array * const env, const char *k, size_t klen,
235                 MMDB_entry_data_s *data)
236 {
237     /* GeoIP2 database interfaces return pointers directly into database,
238      * and these are valid until the database is closed.
239      * However, note that the strings *are not* '\0'-terminated */
240     char buf[35];
241     if (!data->has_data || 0 == data->offset) return;
242     switch (data->type) {
243       case MMDB_DATA_TYPE_UTF8_STRING:
244         array_set_key_value(env, k, klen, data->utf8_string, data->data_size);
245         return;
246       case MMDB_DATA_TYPE_BOOLEAN:
247         array_set_key_value(env, k, klen, data->boolean ? "1" : "0", 1);
248         return;
249       case MMDB_DATA_TYPE_BYTES:
250         array_set_key_value(env, k, klen,
251                             (const char *) data->bytes, data->data_size);
252         return;
253       case MMDB_DATA_TYPE_DOUBLE:
254         array_set_key_value(env, k, klen,
255                             buf, snprintf(buf, sizeof(buf), "%.5f",
256                                           data->double_value));
257         return;
258       case MMDB_DATA_TYPE_FLOAT:
259         array_set_key_value(env, k, klen,
260                             buf, snprintf(buf, sizeof(buf), "%.5f",
261                                           data->float_value));
262         return;
263       case MMDB_DATA_TYPE_INT32:
264         li_itostrn(buf, sizeof(buf), data->int32);
265         break;
266       case MMDB_DATA_TYPE_UINT32:
267         li_utostrn(buf, sizeof(buf), data->uint32);
268         break;
269       case MMDB_DATA_TYPE_UINT16:
270         li_utostrn(buf, sizeof(buf), data->uint16);
271         break;
272       case MMDB_DATA_TYPE_UINT64:
273         /* truncated value on 32-bit unless uintmax_t is 64-bit (long long) */
274         li_utostrn(buf, sizeof(buf), data->uint64);
275         break;
276       case MMDB_DATA_TYPE_UINT128:
277         buf[0] = '0';
278         buf[1] = 'x';
279        #if MMDB_UINT128_IS_BYTE_ARRAY
280         li_tohex_uc(buf+2, sizeof(buf)-2, (char *)data->uint128, 16);
281        #else
282         li_tohex_uc(buf+2, sizeof(buf)-2, (char *)&data->uint128, 16);
283        #endif
284         array_set_key_value(env, k, klen, buf, 34);
285         return;
286       default: /*(ignore unknown data type)*/
287         return;
288     }
289 
290     array_set_key_value(env, k, klen, buf, strlen(buf)); /*(numerical types)*/
291 }
292 
293 
294 static void
295 mod_maxmind_geoip2 (array * const env, sock_addr *dst_addr,
296                     plugin_config *pconf)
297 {
298     MMDB_lookup_result_s res;
299     MMDB_entry_data_s data;
300     int rc;
301 
302     res = MMDB_lookup_sockaddr(pconf->mmdb, (struct sockaddr *)dst_addr, &rc);
303     if (MMDB_SUCCESS != rc || !res.found_entry) return;
304     MMDB_entry_s * const entry = &res.entry;
305 
306     const data_string ** const names = (const data_string **)pconf->env->data;
307     const char *** const cenv = pconf->cenv;
308     for (size_t i = 0, used = pconf->env->used; i < used; ++i) {
309         if (MMDB_SUCCESS == MMDB_aget_value(entry, &data, cenv[i])
310             && data.has_data) {
311             geoip2_env_set(env, CONST_BUF_LEN(&names[i]->key), &data);
312         }
313     }
314 }
315 
316 
317 static void
318 mod_maxmind_patch_connection (server * const srv,
319                               connection * const con,
320                               const plugin_data * const p,
321                               plugin_config * const pconf)
322 {
323     const plugin_config *s = p->config_storage[0];
324     memcpy(pconf, s, sizeof(*s));
325     if (1 == p->nconfig)
326         return;
327 
328     data_config ** const context_data =
329       (data_config **)srv->config_context->data;
330 
331     s = p->config_storage[1]; /* base config (global context) copied above */
332     for (size_t i = 1; i < srv->config_context->used; ++i) {
333         if (!config_check_cond(con, i)) continue; /* condition not matched */
334 
335         data_config *dc = context_data[i];
336         s = p->config_storage[i];
337 
338         /* merge config */
339         #define PATCH(x) pconf->x = s->x;
340         for (size_t j = 0; j < dc->value->used; ++j) {
341             data_unset *du = dc->value->data[j];
342 
343             if (buffer_is_equal_string(&du->key, CONST_STR_LEN("maxminddb.activate"))) {
344                 PATCH(activate);
345             }
346             else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("maxminddb.db"))) {
347                 /*PATCH(db_name);*//*(not used)*/
348                 PATCH(mmdb);
349             }
350             else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("maxminddb.env"))) {
351                 PATCH(env);
352                 PATCH(cenv);
353             }
354         }
355         #undef PATCH
356     }
357 }
358 
359 
360 CONNECTION_FUNC(mod_maxmind_request_env_handler)
361 {
362     const int sa_family = con->dst_addr.plain.sa_family;
363     if (sa_family != AF_INET && sa_family != AF_INET6) return HANDLER_GO_ON;
364 
365     plugin_config pconf;
366     plugin_data *p = p_d;
367     mod_maxmind_patch_connection(srv, con, p, &pconf);
368     /* check that mod_maxmind is activated and env fields were requested */
369     if (!pconf.activate || 0 == pconf.env->used) return HANDLER_GO_ON;
370 
371     array *env = con->plugin_ctx[p->id];
372     if (NULL == env) {
373         env = con->plugin_ctx[p->id] = array_init();
374         if (pconf.mmdb)
375             mod_maxmind_geoip2(env, &con->dst_addr, &pconf);
376     }
377 
378     for (size_t i = 0; i < env->used; ++i) {
379         /* note: replaces values which may have been set by mod_openssl
380          * (when mod_extforward is listed after mod_openssl in server.modules)*/
381         data_string *ds = (data_string *)env->data[i];
382         http_header_env_set(con,
383                             CONST_BUF_LEN(&ds->key), CONST_BUF_LEN(&ds->value));
384     }
385 
386     return HANDLER_GO_ON;
387 }
388 
389 
390 CONNECTION_FUNC(mod_maxmind_handle_con_close)
391 {
392     plugin_data *p = p_d;
393     array *env = con->plugin_ctx[p->id];
394     UNUSED(srv);
395     if (NULL != env) {
396         array_free(env);
397         con->plugin_ctx[p->id] = NULL;
398     }
399 
400     return HANDLER_GO_ON;
401 }
402