unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

geoip_maxmind.c (6503B)

      1 /* GEOIP maxmind module
      2  * (C) Copyright 2021 Bram Matthys and the UnrealIRCd team
      3  * License: GPLv2 or later
      4  */
      5 
      6 #include "unrealircd.h"
      7 #include <maxminddb.h>
      8 
      9 ModuleHeader MOD_HEADER
     10   = {
     11 	"geoip_maxmind",
     12 	"5.0",
     13 	"GEOIP using maxmind databases", 
     14 	"UnrealIRCd Team",
     15 	"unrealircd-6",
     16 	};
     17 
     18 struct geoip_maxmind_config_s {
     19 	char *db_file;
     20 /* for config reading only */
     21 	int have_config;
     22 	int have_database;
     23 };
     24 
     25 /* Variables */
     26 
     27 struct geoip_maxmind_config_s geoip_maxmind_config;
     28 MMDB_s mmdb;
     29 
     30 /* Forward declarations */
     31 int geoip_maxmind_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     32 int geoip_maxmind_configposttest(int *errs);
     33 int geoip_maxmind_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
     34 void geoip_maxmind_free(void);
     35 GeoIPResult *geoip_lookup_maxmind(char *ip);
     36 
     37 int geoip_maxmind_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
     38 {
     39 	ConfigEntry *cep;
     40 	int errors = 0;
     41 	int i;
     42 	
     43 	if (type != CONFIG_SET)
     44 		return 0;
     45 
     46 	if (!ce || !ce->name)
     47 		return 0;
     48 
     49 	if (strcmp(ce->name, "geoip-maxmind"))
     50 		return 0;
     51 
     52 	geoip_maxmind_config.have_config = 1;
     53 
     54 	for (cep = ce->items; cep; cep = cep->next)
     55 	{
     56 		if (!strcmp(cep->name, "database"))
     57 		{
     58 			if (geoip_maxmind_config.have_database)
     59 			{
     60 				config_error("%s:%i: duplicate item set::geoip-maxmind::%s", cep->file->filename, cep->line_number, cep->name);
     61 				continue;
     62 			}
     63 			if (!is_file_readable(cep->value, PERMDATADIR))
     64 			{
     65 				config_error("%s:%i: set::geoip-maxmind::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
     66 				errors++;
     67 				continue;
     68 			}
     69 			geoip_maxmind_config.have_database = 1;
     70 			continue;
     71 		}
     72 		config_warn("%s:%i: unknown item set::geoip-maxmind::%s", cep->file->filename, cep->line_number, cep->name);
     73 	}
     74 	
     75 	*errs = errors;
     76 	return errors ? -1 : 1;
     77 }
     78 
     79 int geoip_maxmind_configposttest(int *errs)
     80 {
     81 	int errors = 0;
     82 	if (geoip_maxmind_config.have_config)
     83 	{
     84 		if (!geoip_maxmind_config.have_database)
     85 		{
     86 			config_error("geoip_maxmind: no database file specified! Remove set::geoip-maxmind to use defaults");
     87 			errors++;
     88 		}
     89 	} else
     90 	{
     91 		safe_strdup(geoip_maxmind_config.db_file, "GeoLite2-Country.mmdb");
     92 
     93 		if (is_file_readable(geoip_maxmind_config.db_file, PERMDATADIR))
     94 		{
     95 			geoip_maxmind_config.have_database = 1;
     96 		} else
     97 		{
     98 			config_error("[geoip_maxmind] cannot open database file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_maxmind_config.db_file, strerror(errno));
     99 			safe_free(geoip_maxmind_config.db_file);
    100 			errors++;
    101 		}
    102 	}
    103 
    104 	*errs = errors;
    105 	return errors ? -1 : 1;
    106 }
    107 
    108 int geoip_maxmind_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
    109 {
    110 	ConfigEntry *cep;
    111 
    112 	if (type != CONFIG_SET)
    113 		return 0;
    114 
    115 	if (!ce || !ce->name)
    116 		return 0;
    117 
    118 	if (strcmp(ce->name, "geoip-maxmind"))
    119 		return 0;
    120 
    121 	for (cep = ce->items; cep; cep = cep->next)
    122 	{
    123 		if (!strcmp(cep->name, "database") && geoip_maxmind_config.have_database)
    124 			safe_strdup(geoip_maxmind_config.db_file, cep->value);
    125 	}
    126 	return 1;
    127 }
    128 
    129 MOD_TEST()
    130 {
    131 	MARK_AS_OFFICIAL_MODULE(modinfo);
    132 	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_maxmind)))
    133 	{
    134 		unreal_log(ULOG_ERROR, "geoip_maxmind", "GEOIP_ADD_CALLBACK_FAILED", NULL,
    135 				   "geoip_maxmind: Could not install GEOIP_LOOKUP callback. "
    136 				   "Most likely another geoip module is already loaded. "
    137 				   "You can only load one!");
    138 		return MOD_FAILED;
    139 	}
    140 
    141 	geoip_maxmind_config.have_config = 0;
    142 	geoip_maxmind_config.have_database = 0;
    143 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_maxmind_configtest);
    144 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_maxmind_configposttest);
    145 	return MOD_SUCCESS;
    146 }
    147 
    148 MOD_INIT()
    149 {
    150 	MARK_AS_OFFICIAL_MODULE(modinfo);
    151 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_maxmind_configrun);
    152 	return MOD_SUCCESS;
    153 }
    154 
    155 MOD_LOAD()
    156 {
    157 	geoip_maxmind_free();
    158 	convert_to_absolute_path(&geoip_maxmind_config.db_file, PERMDATADIR);
    159 	
    160 	int status = MMDB_open(geoip_maxmind_config.db_file, MMDB_MODE_MMAP, &mmdb);
    161 
    162 	if (status != MMDB_SUCCESS) {
    163 		int save_err = errno;
    164 		unreal_log(ULOG_WARNING, "geoip_maxmind", "GEOIP_CANNOT_OPEN_DB", NULL,
    165 				   "Could not open '$filename' - $maxmind_error; IO error: $io_error",
    166 				   log_data_string("filename", geoip_maxmind_config.db_file),
    167 				   log_data_string("maxmind_error", MMDB_strerror(status)),
    168 				   log_data_string("io_error", (status == MMDB_IO_ERROR)?strerror(save_err):"none"));
    169 		return MOD_FAILED;
    170 	}
    171 	return MOD_SUCCESS;
    172 }
    173 
    174 MOD_UNLOAD()
    175 {
    176 	geoip_maxmind_free();
    177 	return MOD_SUCCESS;
    178 }
    179 
    180 void geoip_maxmind_free(void)
    181 {
    182 	MMDB_close(&mmdb);
    183 }
    184 
    185 GeoIPResult *geoip_lookup_maxmind(char *ip)
    186 {
    187 	int gai_error, mmdb_error, status;
    188 	MMDB_lookup_result_s result;
    189 	MMDB_entry_data_s country_code;
    190 	MMDB_entry_data_s country_name;
    191 	char *country_code_str, *country_name_str;
    192 	GeoIPResult *r;
    193 
    194 	if (!ip)
    195 		return NULL;
    196 
    197 	result = MMDB_lookup_string(&mmdb, ip, &gai_error, &mmdb_error);
    198 	if (gai_error)
    199 	{
    200 		unreal_log(ULOG_DEBUG, "geoip_maxmind", "GEOIP_DB_ERROR", NULL,
    201 				"libmaxminddb: getaddrinfo error for $ip: $error",
    202 				log_data_string("ip", ip),
    203 				log_data_string("error", gai_strerror(gai_error)));
    204 		return NULL;
    205 	}
    206 	
    207 	if (mmdb_error != MMDB_SUCCESS)
    208 	{
    209 		unreal_log(ULOG_DEBUG, "geoip_maxmind", "GEOIP_DB_ERROR", NULL,
    210 				"libmaxminddb: library error for $ip: $error",
    211 				log_data_string("ip", ip),
    212 				log_data_string("error", MMDB_strerror(mmdb_error)));
    213 		return NULL;
    214 	}
    215 
    216 	if (!result.found_entry) /* no result */
    217 		return NULL;
    218 
    219 	status = MMDB_get_value(&result.entry, &country_code, "country", "iso_code", NULL);
    220 	if (status != MMDB_SUCCESS || !country_code.has_data || country_code.type != MMDB_DATA_TYPE_UTF8_STRING)
    221 		return NULL;
    222 	status = MMDB_get_value(&result.entry, &country_name, "country", "names", "en", NULL);
    223 	if (status != MMDB_SUCCESS || !country_name.has_data || country_name.type != MMDB_DATA_TYPE_UTF8_STRING)
    224 		return NULL;
    225 
    226 	/* these results are not null-terminated */
    227 	country_code_str = safe_alloc(country_code.data_size + 1);
    228 	country_name_str = safe_alloc(country_name.data_size + 1);
    229 	memcpy(country_code_str, country_code.utf8_string, country_code.data_size);
    230 	country_code_str[country_code.data_size] = '\0';
    231 	memcpy(country_name_str, country_name.utf8_string, country_name.data_size);
    232 	country_name_str[country_name.data_size] = '\0';
    233 
    234 	r = safe_alloc(sizeof(GeoIPResult));
    235 	r->country_code = country_code_str;
    236 	r->country_name = country_name_str;
    237 	return r;
    238 }
    239