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