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 ck_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 *)ck_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 = ck_calloc(env->used, sizeof(char **)); 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 = 211 (const char **)(cenv[j] = ck_calloc(k, sizeof(char *))); 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 = ck_malloc(sizeof(plugin_config_env)); 222 pcenv->env = env; 223 pcenv->cenv = (const char ***)cenv; 224 return pcenv; 225 } 226 227 228 static void 229 mod_maxminddb_merge_config_cpv(plugin_config * const pconf, 230 const config_plugin_value_t * const cpv) 231 { 232 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 233 case 0: /* maxminddb.activate */ 234 pconf->activate = (int)cpv->v.u; 235 break; 236 case 1: /* maxminddb.db */ 237 if (cpv->vtype != T_CONFIG_LOCAL) break; 238 pconf->mmdb = cpv->v.v; 239 break; 240 case 2: /* maxminddb.env */ 241 if (cpv->vtype == T_CONFIG_LOCAL) { 242 plugin_config_env * const pcenv = cpv->v.v; 243 pconf->env = pcenv->env; 244 pconf->cenv = pcenv->cenv; 245 } 246 break; 247 default:/* should not happen */ 248 return; 249 } 250 } 251 252 253 static void 254 mod_maxminddb_merge_config (plugin_config * const pconf, 255 const config_plugin_value_t *cpv) 256 { 257 do { 258 mod_maxminddb_merge_config_cpv(pconf, cpv); 259 } while ((++cpv)->k_id != -1); 260 } 261 262 263 static void 264 mod_maxmind_patch_config (request_st * const r, 265 const plugin_data * const p, 266 plugin_config * const pconf) 267 { 268 *pconf = p->defaults; /* copy small struct instead of memcpy() */ 269 /*memcpy(pconf, &p->defaults, sizeof(plugin_config));*/ 270 for (int i = 1, used = p->nconfig; i < used; ++i) { 271 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 272 mod_maxminddb_merge_config(pconf, p->cvlist + p->cvlist[i].v.u2[0]); 273 } 274 } 275 276 277 SETDEFAULTS_FUNC(mod_maxminddb_set_defaults) 278 { 279 static const config_plugin_keys_t cpk[] = { 280 { CONST_STR_LEN("maxminddb.activate"), 281 T_CONFIG_BOOL, 282 T_CONFIG_SCOPE_CONNECTION } 283 ,{ CONST_STR_LEN("maxminddb.db"), 284 T_CONFIG_STRING, 285 T_CONFIG_SCOPE_CONNECTION } 286 ,{ CONST_STR_LEN("maxminddb.env"), 287 T_CONFIG_ARRAY_KVSTRING, 288 T_CONFIG_SCOPE_CONNECTION } 289 ,{ NULL, 0, 290 T_CONFIG_UNSET, 291 T_CONFIG_SCOPE_UNSET } 292 }; 293 294 plugin_data * const p = p_d; 295 if (!config_plugin_values_init(srv, p, cpk, "mod_maxminddb")) 296 return HANDLER_ERROR; 297 298 /* process and validate config directives 299 * (init i to 0 if global context; to 1 to skip empty global context) */ 300 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 301 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 302 for (; -1 != cpv->k_id; ++cpv) { 303 switch (cpv->k_id) { 304 case 0: /* maxminddb.activate */ 305 break; 306 case 1: /* maxminddb.db */ 307 if (!buffer_is_blank(cpv->v.b)) { 308 cpv->v.v = mod_maxminddb_open_db(srv, cpv->v.b); 309 if (NULL == cpv->v.v) return HANDLER_ERROR; 310 cpv->vtype = T_CONFIG_LOCAL; 311 } 312 break; 313 case 2: /* maxminddb.env */ 314 if (cpv->v.a->used) { 315 cpv->v.v = mod_maxminddb_prep_cenv(srv, cpv->v.a); 316 if (NULL == cpv->v.v) return HANDLER_ERROR; 317 cpv->vtype = T_CONFIG_LOCAL; 318 } 319 break; 320 default:/* should not happen */ 321 break; 322 } 323 } 324 } 325 326 /* initialize p->defaults from global config context */ 327 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 328 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 329 if (-1 != cpv->k_id) 330 mod_maxminddb_merge_config(&p->defaults, cpv); 331 } 332 333 return HANDLER_GO_ON; 334 } 335 336 337 static void 338 geoip2_env_set (array * const env, const char * const k, 339 const size_t klen, MMDB_entry_data_s * const data) 340 { 341 /* GeoIP2 database interfaces return pointers directly into database, 342 * and these are valid until the database is closed. 343 * However, note that the strings *are not* '\0'-terminated */ 344 char buf[35]; 345 if (!data->has_data || 0 == data->offset) return; 346 const char *v = buf; 347 size_t vlen; 348 switch (data->type) { 349 case MMDB_DATA_TYPE_UTF8_STRING: 350 v = data->utf8_string; 351 vlen = data->data_size; 352 break; 353 case MMDB_DATA_TYPE_BOOLEAN: 354 v = data->boolean ? "1" : "0"; 355 vlen = 1; 356 break; 357 case MMDB_DATA_TYPE_BYTES: 358 v = (const char *)data->bytes; 359 vlen = data->data_size; 360 break; 361 case MMDB_DATA_TYPE_DOUBLE: 362 vlen = snprintf(buf, sizeof(buf), "%.5f", data->double_value); 363 break; 364 case MMDB_DATA_TYPE_FLOAT: 365 vlen = snprintf(buf, sizeof(buf), "%.5f", data->float_value); 366 break; 367 case MMDB_DATA_TYPE_INT32: 368 vlen = li_itostrn(buf, sizeof(buf), data->int32); 369 break; 370 case MMDB_DATA_TYPE_UINT32: 371 vlen = li_utostrn(buf, sizeof(buf), data->uint32); 372 break; 373 case MMDB_DATA_TYPE_UINT16: 374 vlen = li_utostrn(buf, sizeof(buf), data->uint16); 375 break; 376 case MMDB_DATA_TYPE_UINT64: 377 /* truncated value on 32-bit unless uintmax_t is 64-bit (long long) */ 378 vlen = li_utostrn(buf, sizeof(buf), data->uint64); 379 break; 380 case MMDB_DATA_TYPE_UINT128: 381 buf[0] = '0'; 382 buf[1] = 'x'; 383 #if MMDB_UINT128_IS_BYTE_ARRAY 384 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)data->uint128, 16); 385 #else 386 li_tohex_uc(buf+2, sizeof(buf)-2, (char *)&data->uint128, 16); 387 #endif 388 vlen = 34; 389 break; 390 default: /*(ignore unknown data type)*/ 391 return; 392 } 393 394 array_set_key_value(env, k, klen, v, vlen); 395 } 396 397 398 static void 399 mod_maxmind_geoip2 (array * const env, const struct sockaddr * const dst_addr, 400 plugin_config * const pconf) 401 { 402 MMDB_lookup_result_s res; 403 MMDB_entry_data_s data; 404 int rc; 405 406 res = MMDB_lookup_sockaddr(pconf->mmdb, dst_addr, &rc); 407 if (MMDB_SUCCESS != rc || !res.found_entry) return; 408 MMDB_entry_s * const entry = &res.entry; 409 410 const data_string ** const names = (const data_string **)pconf->env->data; 411 const char *** const cenv = pconf->cenv; 412 for (size_t i = 0, used = pconf->env->used; i < used; ++i) { 413 if (MMDB_SUCCESS == MMDB_aget_value(entry, &data, cenv[i]) 414 && data.has_data) { 415 geoip2_env_set(env, BUF_PTR_LEN(&names[i]->key), &data); 416 } 417 } 418 } 419 420 421 REQUEST_FUNC(mod_maxminddb_request_env_handler) 422 { 423 connection * const con = r->con; 424 const sock_addr * const dst_addr = &con->dst_addr; 425 const int sa_family = sock_addr_get_family(dst_addr); 426 if (sa_family != AF_INET && sa_family != AF_INET6) return HANDLER_GO_ON; 427 428 plugin_config pconf; 429 plugin_data *p = p_d; 430 mod_maxmind_patch_config(r, p, &pconf); 431 /* check that mod_maxmind is activated and env fields were requested */ 432 if (!pconf.activate || NULL == pconf.env) return HANDLER_GO_ON; 433 434 array *env = con->plugin_ctx[p->id]; 435 if (NULL == env) { 436 env = con->plugin_ctx[p->id] = array_init(pconf.env->used); 437 if (pconf.mmdb) 438 mod_maxmind_geoip2(env, (const struct sockaddr *)dst_addr, &pconf); 439 } 440 441 for (uint32_t i = 0; i < env->used; ++i) { 442 /* note: replaces values which may have been set by mod_openssl 443 * (when mod_extforward is listed after mod_openssl in server.modules)*/ 444 data_string *ds = (data_string *)env->data[i]; 445 http_header_env_set(r, BUF_PTR_LEN(&ds->key), BUF_PTR_LEN(&ds->value)); 446 } 447 448 return HANDLER_GO_ON; 449 } 450 451 452 CONNECTION_FUNC(mod_maxminddb_handle_con_close) 453 { 454 plugin_data *p = p_d; 455 array *env = con->plugin_ctx[p->id]; 456 if (NULL != env) { 457 array_free(env); 458 con->plugin_ctx[p->id] = NULL; 459 } 460 461 return HANDLER_GO_ON; 462 } 463