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