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); 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 110 INIT_FUNC(mod_maxminddb_init) 111 { 112 return calloc(1, sizeof(plugin_data)); 113 } 114 115 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 * 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 * 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 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 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 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 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 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 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 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 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