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