7 * MaxMind GeoIP2 module (plugin) for lighttpd.
9 * GeoIP2 country db env's:
13 * GeoIP2 city db env's:
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
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",
32 * Installation Instructions:
33 * https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModGeoip
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 */
59 #include "http_header.h"
61 #include "sock_addr.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
;
100 plugin_config
**config_storage
;
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
];
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
)
126 free(p
->config_storage
);
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
));
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
));
180 log_error(srv
->errh
, __FILE__
, __LINE__
,
181 "failed to open GeoIP2 database (%.*s): %s",
182 BUFFER_INTLEN_PTR(s
->db_name
),
185 return HANDLER_ERROR
;
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
;
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? */
218 for (char *t
= value
->ptr
; (t
= strchr(t
, '/')); ++t
) ++k
;
219 const char **keys
= s
->cenv
[j
] = calloc(k
, sizeof(char *));
222 keys
[k
] = value
->ptr
;
223 for (char *t
= value
->ptr
; (t
= strchr(t
, '/')); ) {
232 return HANDLER_GO_ON
;
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 */
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
);
249 case MMDB_DATA_TYPE_BOOLEAN
:
250 array_set_key_value(env
, k
, klen
, data
->boolean
? "1" : "0", 1);
252 case MMDB_DATA_TYPE_BYTES
:
253 array_set_key_value(env
, k
, klen
,
254 (const char *) data
->bytes
, data
->data_size
);
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
));
261 case MMDB_DATA_TYPE_FLOAT
:
262 array_set_key_value(env
, k
, klen
,
263 buf
, snprintf(buf
, sizeof(buf
), "%.5f",
266 case MMDB_DATA_TYPE_INT32
:
267 li_itostrn(buf
, sizeof(buf
), data
->int32
);
269 case MMDB_DATA_TYPE_UINT32
:
270 li_utostrn(buf
, sizeof(buf
), data
->uint32
);
272 case MMDB_DATA_TYPE_UINT16
:
273 li_utostrn(buf
, sizeof(buf
), data
->uint16
);
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
);
279 case MMDB_DATA_TYPE_UINT128
:
282 #if MMDB_UINT128_IS_BYTE_ARRAY
283 li_tohex_uc(buf
+2, sizeof(buf
)-2, (char *)data
->uint128
, 16);
285 li_tohex_uc(buf
+2, sizeof(buf
)-2, (char *)&data
->uint128
, 16);
287 array_set_key_value(env
, k
, klen
, buf
, 34);
289 default: /*(ignore unknown data type)*/
293 array_set_key_value(env
, k
, klen
, buf
, strlen(buf
)); /*(numerical types)*/
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
;
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
])
314 geoip2_env_set(env
, CONST_BUF_LEN(names
[i
]->key
), &data
);
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
));
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
];
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"))) {
350 else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("maxminddb.db"))) {
351 /*PATCH(db_name);*//*(not used)*/
354 else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("maxminddb.env"))) {
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
;
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
];
377 env
= con
->plugin_ctx
[p
->id
] = array_init();
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
];
401 con
->plugin_ctx
[p
->id
] = NULL
;
404 return HANDLER_GO_ON
;