unrealircd

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

geoip_csv.c (20707B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/geoip_csv.c
      3  *   (C) 2021 The UnrealIRCd Team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers.
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 ModuleHeader MOD_HEADER
     26   = {
     27 	"geoip_csv",
     28 	"5.0",
     29 	"GEOIP using csv data files", 
     30 	"UnrealIRCd Team",
     31 	"unrealircd-6",
     32     };
     33 
     34 struct geoip_csv_config_s {
     35 	char *v4_db_file;
     36 	char *v6_db_file;
     37 	char *countries_db_file;
     38 /* for config reading only */
     39 	int have_config;
     40 	int have_ipv4_database;
     41 	int have_ipv6_database;
     42 	int have_countries;
     43 };
     44 
     45 struct geoip_csv_ip_range {
     46 	uint32_t addr;
     47 	uint32_t mask;
     48 	int geoid;
     49 	struct geoip_csv_ip_range *next;
     50 };
     51 
     52 struct geoip_csv_ip6_range {
     53 	uint16_t addr[8];
     54 	uint16_t mask[8];
     55 	int geoid;
     56 	struct geoip_csv_ip6_range *next;
     57 };
     58 
     59 struct geoip_csv_country {
     60 	char code[10];
     61 	char name[100];
     62 	char continent[25];
     63 	int id;
     64 	struct geoip_csv_country *next;
     65 };
     66 
     67 /* Variables */
     68 struct geoip_csv_config_s geoip_csv_config;
     69 struct geoip_csv_ip_range *geoip_csv_ip_range_list[256]; // we are keeping a separate list for each possible first octet to speed up searching
     70 struct geoip_csv_ip6_range *geoip_csv_ip6_range_list = NULL; // for ipv6 there would be too many separate lists so just use a single one
     71 struct geoip_csv_country *geoip_csv_country_list = NULL;
     72 
     73 /* Forward declarations */
     74 static void geoip_csv_free_ipv4(void);
     75 static void geoip_csv_free_ipv6(void);
     76 static void geoip_csv_free_ipv6(void);
     77 static void geoip_csv_free_countries(void);
     78 static void geoip_csv_free(void);
     79 static int geoip_csv_read_ipv4(char *file);
     80 static int geoip_csv_ip6_convert(char *ip, uint16_t out[8]);
     81 static int geoip_csv_read_ipv6(char *file);
     82 static int geoip_csv_read_countries(char *file);
     83 static struct geoip_csv_country *geoip_csv_get_country(int id);
     84 static int geoip_csv_get_v4_geoid(char *iip);
     85 static int geoip_csv_get_v6_geoid(char *iip);
     86 int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     87 int geoip_csv_configposttest(int *errs);
     88 int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
     89 void geoip_csv_free(void);
     90 GeoIPResult *geoip_lookup_csv(char *ip);
     91 
     92 int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
     93 {
     94 	ConfigEntry *cep;
     95 	int errors = 0;
     96 	int i;
     97 	
     98 	if (type != CONFIG_SET)
     99 		return 0;
    100 
    101 	if (!ce || !ce->name)
    102 		return 0;
    103 
    104 	if (strcmp(ce->name, "geoip-csv"))
    105 		return 0;
    106 
    107 	geoip_csv_config.have_config = 1;
    108 
    109 	for (cep = ce->items; cep; cep = cep->next)
    110 	{
    111 		if (!strcmp(cep->name, "ipv4-blocks-file"))
    112 		{
    113 			if (geoip_csv_config.have_ipv4_database)
    114 			{
    115 				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
    116 				continue;
    117 			}
    118 			if (!is_file_readable(cep->value, PERMDATADIR))
    119 			{
    120 				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
    121 				errors++;
    122 				continue;
    123 			}
    124 			geoip_csv_config.have_ipv4_database = 1;
    125 			continue;
    126 		}
    127 		if (!strcmp(cep->name, "ipv6-blocks-file"))
    128 		{
    129 			if (geoip_csv_config.have_ipv6_database)
    130 			{
    131 				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
    132 				continue;
    133 			}
    134 			if (!is_file_readable(cep->value, PERMDATADIR))
    135 			{
    136 				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
    137 				errors++;
    138 				continue;
    139 			}
    140 			geoip_csv_config.have_ipv6_database = 1;
    141 			continue;
    142 		}
    143 		if (!strcmp(cep->name, "countries-file"))
    144 		{
    145 			if (geoip_csv_config.have_countries)
    146 			{
    147 				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
    148 				continue;
    149 			}
    150 			if (!is_file_readable(cep->value, PERMDATADIR))
    151 			{
    152 				config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s/%s\" for reading (%s)", cep->file->filename, cep->line_number, cep->name, PERMDATADIR, cep->value, strerror(errno));
    153 				errors++;
    154 				continue;
    155 			}
    156 			geoip_csv_config.have_countries = 1;
    157 			continue;
    158 		}
    159 		config_warn("%s:%i: unknown item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
    160 	}
    161 	
    162 	*errs = errors;
    163 	return errors ? -1 : 1;
    164 }
    165 
    166 int geoip_csv_configposttest(int *errs)
    167 {
    168 	int errors = 0;
    169 	if (geoip_csv_config.have_config)
    170 	{
    171 		if (!geoip_csv_config.have_countries)
    172 		{
    173 			config_error("[geoip_csv] no countries file specified! Remove set::geoip-csv to use defaults");
    174 			errors++;
    175 		}
    176 		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
    177 		{
    178 			config_error("[geoip_csv] no database files specified! Remove set::geoip-csv to use defaults");
    179 			errors++;
    180 		}
    181 	} else
    182 	{
    183 		safe_strdup(geoip_csv_config.v4_db_file, "GeoLite2-Country-Blocks-IPv4.csv");
    184 		safe_strdup(geoip_csv_config.v6_db_file, "GeoLite2-Country-Blocks-IPv6.csv");
    185 		safe_strdup(geoip_csv_config.countries_db_file, "GeoLite2-Country-Locations-en.csv");
    186 
    187 		if (is_file_readable(geoip_csv_config.v4_db_file, PERMDATADIR))
    188 		{
    189 			geoip_csv_config.have_ipv4_database = 1;
    190 		} else
    191 		{
    192 			config_warn("[geoip_csv] cannot open IPv4 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v4_db_file, strerror(errno));
    193 			safe_free(geoip_csv_config.v4_db_file);
    194 		}
    195 		if (is_file_readable(geoip_csv_config.v6_db_file, PERMDATADIR))
    196 		{
    197 			geoip_csv_config.have_ipv6_database = 1;
    198 		} else
    199 		{
    200 			config_warn("[geoip_csv] cannot open IPv6 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v6_db_file, strerror(errno));
    201 			safe_free(geoip_csv_config.v6_db_file);
    202 		}
    203 		if (!is_file_readable(geoip_csv_config.countries_db_file, PERMDATADIR))
    204 		{
    205 			config_error("[geoip_csv] cannot open countries file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.countries_db_file, strerror(errno));
    206 			safe_free(geoip_csv_config.countries_db_file);
    207 			errors++;
    208 		}
    209 		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
    210 		{
    211 			config_error("[geoip_csv] couldn't read any blocks file! Either put these in %s location "
    212 					"or specify another in set::geoip-csv config block", PERMDATADIR);
    213 			errors++;
    214 		}
    215 	}
    216 
    217 	*errs = errors;
    218 	return errors ? -1 : 1;
    219 }
    220 
    221 int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
    222 {
    223 	ConfigEntry *cep;
    224 
    225 	if (type != CONFIG_SET)
    226 		return 0;
    227 
    228 	if (!ce || !ce->name)
    229 		return 0;
    230 
    231 	if (strcmp(ce->name, "geoip-csv"))
    232 		return 0;
    233 
    234 	for (cep = ce->items; cep; cep = cep->next)
    235 	{
    236 		if (!strcmp(cep->name, "ipv4-blocks-file") && geoip_csv_config.have_ipv4_database)
    237 			safe_strdup(geoip_csv_config.v4_db_file, cep->value);
    238 		if (!strcmp(cep->name, "ipv6-blocks-file") && geoip_csv_config.have_ipv6_database)
    239 			safe_strdup(geoip_csv_config.v6_db_file, cep->value);
    240 		if (!strcmp(cep->name, "countries-file"))
    241 			safe_strdup(geoip_csv_config.countries_db_file, cep->value);
    242 	}
    243 	return 1;
    244 }
    245 
    246 MOD_TEST()
    247 {
    248 	MARK_AS_OFFICIAL_MODULE(modinfo);
    249 	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_csv)))
    250 	{
    251 		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_ADD_CALLBACK_FAILED", NULL,
    252 		           "geoip_csv: Could not install GEOIP_LOOKUP callback. "
    253 		           "Most likely another geoip module is already loaded. "
    254 		           "You can only load one!");
    255 		return MOD_FAILED;
    256 	}
    257 
    258 	geoip_csv_config.have_config = 0;
    259 	geoip_csv_config.have_ipv4_database = 0;
    260 	geoip_csv_config.have_ipv6_database = 0;
    261 	geoip_csv_config.have_countries = 0;
    262 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_csv_configtest);
    263 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_csv_configposttest);
    264 	return MOD_SUCCESS;
    265 }
    266 
    267 MOD_INIT()
    268 {
    269 	MARK_AS_OFFICIAL_MODULE(modinfo);
    270 	geoip_csv_free();
    271 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_csv_configrun);
    272 	return MOD_SUCCESS;
    273 }
    274 
    275 MOD_LOAD()
    276 {
    277 	int found_good_file = 0;
    278 
    279 	if (geoip_csv_config.v4_db_file)
    280 	{
    281 		convert_to_absolute_path(&geoip_csv_config.v4_db_file, PERMDATADIR);
    282 		if (!geoip_csv_read_ipv4(geoip_csv_config.v4_db_file))
    283 		{
    284 			found_good_file = 1;
    285 		}
    286 	}
    287 	if (geoip_csv_config.v6_db_file)
    288 	{
    289 		convert_to_absolute_path(&geoip_csv_config.v6_db_file, PERMDATADIR);
    290 		if (!geoip_csv_read_ipv6(geoip_csv_config.v6_db_file))
    291 		{
    292 			found_good_file = 1;
    293 		}
    294 	}
    295 	if (!geoip_csv_config.countries_db_file)
    296 	{
    297 		unreal_log(ULOG_DEBUG, "geoip_csv", "GEOIP_NO_COUNTRIES", NULL,
    298 				"[BUG] No countries file specified");
    299 		geoip_csv_free();
    300 		return MOD_FAILED;
    301 	}
    302 	convert_to_absolute_path(&geoip_csv_config.countries_db_file, PERMDATADIR);
    303 	if (geoip_csv_read_countries(geoip_csv_config.countries_db_file))
    304 	{
    305 		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
    306 					"could not open required countries file!");
    307 		geoip_csv_free();
    308 		return MOD_FAILED;
    309 	}
    310 
    311 	if (!found_good_file)
    312 	{
    313 		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
    314 					"could not open any database!");
    315 		geoip_csv_free();
    316 		return MOD_FAILED;
    317 	}
    318 	return MOD_SUCCESS;
    319 }
    320 
    321 MOD_UNLOAD()
    322 {
    323 	geoip_csv_free();
    324 	return MOD_SUCCESS;
    325 }
    326 
    327 static void geoip_csv_free_ipv4(void)
    328 {
    329 	struct geoip_csv_ip_range *ptr, *oldptr;
    330 	int i;
    331 	for (i=0; i<256; i++)
    332 	{
    333 		ptr = geoip_csv_ip_range_list[i];
    334 		geoip_csv_ip_range_list[i] = NULL;
    335 		while (ptr)
    336 		{
    337 			oldptr = ptr;
    338 			ptr = ptr->next;
    339 			safe_free(oldptr);
    340 		}
    341 	}
    342 }
    343 
    344 static void geoip_csv_free_ipv6(void)
    345 {
    346 	struct geoip_csv_ip6_range *ptr, *oldptr;
    347 	ptr = geoip_csv_ip6_range_list;
    348 	geoip_csv_ip6_range_list = NULL;
    349 	while (ptr)
    350 	{
    351 		oldptr = ptr;
    352 		ptr = ptr->next;
    353 		safe_free(oldptr);
    354 	}
    355 }
    356 
    357 static void geoip_csv_free_countries(void)
    358 {
    359 	struct geoip_csv_country *ptr, *oldptr;
    360 	ptr = geoip_csv_country_list;
    361 	geoip_csv_country_list = NULL;
    362 	while (ptr)
    363 	{
    364 		oldptr = ptr;
    365 		ptr = ptr->next;
    366 		safe_free(oldptr);
    367 	}
    368 }
    369 
    370 static void geoip_csv_free(void)
    371 {
    372 	geoip_csv_free_ipv4();
    373 	geoip_csv_free_ipv6();
    374 	geoip_csv_free_countries();
    375 }
    376 
    377 /* reading data from files */
    378 
    379 #define STR_HELPER(x) #x
    380 #define STR(x) STR_HELPER(x)
    381 #define BUFLEN 8191
    382 
    383 static int geoip_csv_read_ipv4(char *file)
    384 {
    385 	FILE *u;
    386 	char buf[BUFLEN+1];
    387 	int cidr, geoid;
    388 	char ip[24];
    389 	char netmask[24];
    390 	uint32_t addr;
    391 	uint32_t mask;
    392 	struct geoip_csv_ip_range *curr[256];
    393 	struct geoip_csv_ip_range *ptr;
    394 	memset(curr, 0, sizeof(curr));
    395 	int i;
    396 	char *filename = NULL;
    397 	
    398 	safe_strdup(filename, file);
    399 	convert_to_absolute_path(&filename, CONFDIR);
    400 	u = fopen(filename, "r");
    401 	safe_free(filename);
    402 	if (!u)
    403 	{
    404 		config_warn("[geoip_csv] Cannot open IPv4 ranges list file");
    405 		return 1;
    406 	}
    407 	
    408 	if (!fgets(buf, BUFLEN, u))
    409 	{
    410 		config_warn("[geoip_csv] IPv4 list file is empty");
    411 		fclose(u);
    412 		return 1;
    413 	}
    414 	buf[BUFLEN] = '\0';
    415 	while (fscanf(u, "%23[^/\n]/%d,%" STR(BUFLEN) "[^\n]\n", ip, &cidr, buf) == 3)
    416 	{
    417 		if (sscanf(buf, "%d,", &geoid) != 1)
    418 		{
    419 			/* missing geoid: can happen with valid files */
    420 			continue;
    421 		}
    422 
    423 		if (cidr < 1 || cidr > 32)
    424 		{
    425 			config_warn("[geoip_csv] Invalid CIDR found! IP=%s CIDR=%d! Bad CSV file?", ip, cidr);
    426 			continue;
    427 		}
    428 
    429 		if (inet_pton(AF_INET, ip, &addr) < 1)
    430 		{
    431 			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
    432 			continue;
    433 		}
    434 		addr = htonl(addr);
    435 		
    436 		mask = 0;
    437 		while (cidr)
    438 		{ /* calculate netmask */
    439 			mask >>= 1;
    440 			mask |= (1<<31);
    441 			cidr--;
    442 		}
    443 		
    444 		i=0;
    445 		do
    446 		{ /* multiple iterations in case CIDR is <8 and we have multiple first octets matching */
    447 			uint8_t index = addr>>24;
    448 			if (!curr[index])
    449 			{
    450 				geoip_csv_ip_range_list[index] = safe_alloc(sizeof(struct geoip_csv_ip_range));
    451 				curr[index] = geoip_csv_ip_range_list[index];
    452 			} else
    453 			{
    454 				curr[index]->next = safe_alloc(sizeof(struct geoip_csv_ip_range));
    455 				curr[index] = curr[index]->next;
    456 			}
    457 			ptr = curr[index];
    458 			ptr->next = NULL;
    459 			ptr->addr = addr;
    460 			ptr->mask = mask;
    461 			ptr->geoid = geoid;
    462 			i++;
    463 			index++;
    464 		} while (i<=((~mask)>>24));
    465 	}
    466 	fclose(u);
    467 	return 0;
    468 }
    469 
    470 static int geoip_csv_ip6_convert(char *ip, uint16_t out[8])
    471 { /* convert text to binary form */
    472 	uint16_t tmp[8];
    473 	int i;
    474 	if (inet_pton(AF_INET6, ip, out) < 1)
    475 		return 0;
    476 	for (i=0; i<8; i++)
    477 	{
    478 		out[i] = htons(out[i]);
    479 	}
    480 	return 1;
    481 }
    482 
    483 #define IPV6_STRING_SIZE	40
    484 
    485 static int geoip_csv_read_ipv6(char *file)
    486 {
    487 	FILE *u;
    488 	char buf[BUFLEN+1];
    489 	char *bptr, *optr;
    490 	int cidr, geoid;
    491 	char ip[IPV6_STRING_SIZE];
    492 	uint16_t addr[8];
    493 	uint16_t mask[8];
    494 	struct geoip_csv_ip6_range *curr = NULL;
    495 	struct geoip_csv_ip6_range *ptr;
    496 	int error;
    497 	int length;
    498 	char *filename = NULL;
    499 
    500 	safe_strdup(filename, file);
    501 	convert_to_absolute_path(&filename, CONFDIR);
    502 	u = fopen(filename, "r");
    503 	safe_free(filename);
    504 	if (!u)
    505 	{
    506 		config_warn("[geoip_csv] Cannot open IPv6 ranges list file");
    507 		return 1;
    508 	}
    509 	if (!fgets(buf, BUFLEN, u))
    510 	{
    511 		config_warn("[geoip_csv] IPv6 list file is empty");
    512 		fclose(u);
    513 		return 1;
    514 	}
    515 	while (fgets(buf, BUFLEN, u))
    516 	{
    517 		error = 0;
    518 		bptr = buf;
    519 		optr = ip;
    520 		length = 0;
    521 		while (*bptr != '/')
    522 		{
    523 			if (!*bptr)
    524 			{
    525 				error = 1;
    526 				break;
    527 			}
    528 			if (++length >= IPV6_STRING_SIZE)
    529 			{
    530 				ip[IPV6_STRING_SIZE-1] = '\0';
    531 				config_warn("[geoip_csv] Too long IPv6 address found, starts with %s. Bad CSV file?", ip);
    532 				error = 1;
    533 				break;
    534 			}
    535 			*optr++ = *bptr++;
    536 		}
    537 		if (error)
    538 			continue;
    539 		*optr = '\0';
    540 		bptr++;
    541 		if (!geoip_csv_ip6_convert(ip, addr))
    542 		{
    543 			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
    544 			continue;
    545 		}
    546 		sscanf(bptr, "%d,%d,", &cidr, &geoid);
    547 		if (cidr < 1 || cidr > 128)
    548 		{
    549 			config_warn("[geoip_csv] Invalid CIDR found! CIDR=%d Bad CSV file?", cidr);
    550 			continue;
    551 		}
    552 
    553 		memset(mask, 0, 16);
    554 		
    555 		int mask_bit = 0;
    556 		while (cidr)
    557 		{ /* calculate netmask */
    558 			mask[mask_bit/16] |= 1<<(15-(mask_bit%16));
    559 			mask_bit++;
    560 			cidr--;
    561 		}
    562 
    563 		if (!curr)
    564 		{
    565 			geoip_csv_ip6_range_list = safe_alloc(sizeof(struct geoip_csv_ip6_range));
    566 			curr = geoip_csv_ip6_range_list;
    567 		} else
    568 		{
    569 			curr->next = safe_alloc(sizeof(struct geoip_csv_ip6_range));
    570 			curr = curr->next;
    571 		}
    572 		ptr = curr;
    573 		ptr->next = NULL;
    574 		memcpy(ptr->addr, addr, 16);
    575 		memcpy(ptr->mask, mask, 16);
    576 		ptr->geoid = geoid;
    577 	}
    578 	fclose(u);
    579 	return 0;
    580 }
    581 
    582 /* CSV fields; no STATE_GEONAME_ID because of using %d in fscanf */
    583 #define STATE_LOCALE_CODE	0
    584 #define STATE_CONTINENT_CODE	1
    585 #define STATE_CONTINENT_NAME	2
    586 #define STATE_COUNTRY_ISO_CODE	3
    587 #define STATE_COUNTRY_NAME	4
    588 #define STATE_IS_IN_EU	5
    589 
    590 #define MEMBER_SIZE(type,member) sizeof(((type *)0)->member)
    591 
    592 static int geoip_csv_read_countries(char *file)
    593 {
    594 	FILE *u;
    595 	char code[MEMBER_SIZE(struct geoip_csv_country, code)];
    596 	char continent[MEMBER_SIZE(struct geoip_csv_country, continent)];
    597 	char name[MEMBER_SIZE(struct geoip_csv_country, name)];
    598 	char buf[BUFLEN+1];
    599 	int state;
    600 	int id;
    601 	struct geoip_csv_country *curr = NULL;
    602 	char *filename = NULL;
    603 
    604 	safe_strdup(filename, file);
    605 	convert_to_absolute_path(&filename, CONFDIR);
    606 	u = fopen(filename, "r");
    607 	safe_free(filename);
    608 	if (!u)
    609 	{
    610 		config_warn("[geoip_csv] Cannot open countries list file");
    611 		return 1;
    612 	}
    613 	
    614 	if (!fgets(buf, BUFLEN, u))
    615 	{
    616 		config_warn("[geoip_csv] Countries list file is empty");
    617 		fclose(u);
    618 		return 1;
    619 	}
    620 	while (fscanf(u, "%d,%" STR(BUFLEN) "[^\n]", &id, buf) == 2)
    621 	{ /* getting country ID integer and all other data in string */
    622 		char *ptr = buf;
    623 		char *codeptr = code;
    624 		char *contptr = continent;
    625 		char *nptr = name;
    626 		int quote_open = 0;
    627 		int length = 0;
    628 		state = STATE_LOCALE_CODE;
    629 		while (*ptr)
    630 		{
    631 			switch (state)
    632 			{
    633 				case STATE_CONTINENT_NAME:
    634 					if (*ptr == ',')
    635 						goto next_line; /* no continent? */
    636 					if (length >= MEMBER_SIZE(struct geoip_csv_country, continent))
    637 					{
    638 						*contptr = '\0';
    639 						config_warn("[geoip_csv] Too long continent name found: `%s`. If you are sure your countries file is correct, please file a bug report.", continent);
    640 						goto next_line;
    641 					}
    642 					*contptr = *ptr; /* scan for continent name */
    643 					contptr++;
    644 					length++;
    645 					break;
    646 				case STATE_COUNTRY_ISO_CODE:
    647 					if (*ptr == ',')		/* country code is empty */
    648 						goto next_line;	/* -- that means only the continent is specified - we ignore it completely */
    649 					if (length >= MEMBER_SIZE(struct geoip_csv_country, code))
    650 					{
    651 						*codeptr = '\0';
    652 						config_warn("[geoip_csv] Too long country code found: `%s`. If you are sure your countries file is correct, please file a bug report.", code);
    653 						goto next_line;
    654 					}
    655 					*codeptr = *ptr; // scan for country code (DE, PL, US etc)
    656 					codeptr++;
    657 					length++;
    658 					break;
    659 				case STATE_COUNTRY_NAME:
    660 					goto read_country_name;
    661 				default:
    662 					break; // ignore this field and wait for next one
    663 			}
    664 			ptr++;
    665 			if (*ptr == ',')
    666 			{
    667 				length = 0;
    668 				ptr++;
    669 				state++;
    670 			}
    671 		}
    672 		read_country_name:
    673 		*codeptr = '\0';
    674 		*contptr = '\0';
    675 		length = 0;
    676 		while (*ptr)
    677 		{
    678 			switch (*ptr)
    679 			{
    680 				case '"':
    681 					quote_open = !quote_open;
    682 					ptr++;
    683 					continue;
    684 				case ',':
    685 					if (!quote_open)
    686 						goto end_country_name; /* we reached the end of current CSV field */
    687 				/* fall through */
    688 				default:
    689 					*nptr++ = *ptr++;
    690 					if (length >= MEMBER_SIZE(struct geoip_csv_country, name))
    691 					{
    692 						*nptr = '\0';
    693 						config_warn("[geoip_csv] Too long country name found: `%s`. If you are sure your countries file is correct, please file a bug report.", name);
    694 						goto next_line;
    695 					}
    696 					break; // scan for country name
    697 			}
    698 		}
    699 		end_country_name:
    700 		*nptr = '\0';
    701 		if (geoip_csv_country_list)
    702 		{
    703 			curr->next = safe_alloc(sizeof(struct geoip_csv_country));
    704 			curr = curr->next;
    705 		} else
    706 		{
    707 			geoip_csv_country_list = safe_alloc(sizeof(struct geoip_csv_country));
    708 			curr = geoip_csv_country_list;
    709 		}
    710 		curr->next = NULL;
    711 		strcpy(curr->code, code);
    712 		strcpy(curr->name, name);
    713 		strcpy(curr->continent, continent);
    714 		curr->id = id;
    715 		next_line: continue;
    716 	}
    717 	fclose(u);
    718 	return 0;
    719 }
    720 
    721 static struct geoip_csv_country *geoip_csv_get_country(int id)
    722 {
    723 	struct geoip_csv_country *curr = geoip_csv_country_list;
    724 	if (!curr)
    725 		return NULL;
    726 	int found = 0;
    727 	for (;curr;curr = curr->next)
    728 	{
    729 		if (curr->id == id)
    730 		{
    731 			found = 1;
    732 			break;
    733 		}
    734 	}
    735 	if (found)
    736 		return curr;
    737 	return NULL;
    738 }
    739 
    740 static int geoip_csv_get_v4_geoid(char *iip)
    741 {
    742 	uint32_t addr, tmp_addr;
    743 	struct geoip_csv_ip_range *curr;
    744 	int i;
    745 	int found = 0;
    746 	if (inet_pton(AF_INET, iip, &addr) < 1)
    747 	{
    748 		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
    749 		return 0;
    750 	}
    751 	addr = htonl(addr);
    752 	curr = geoip_csv_ip_range_list[addr>>24];
    753 	if (curr)
    754 	{
    755 		i = 0;
    756 		for (;curr;curr = curr->next)
    757 		{
    758 			tmp_addr = addr;
    759 			tmp_addr &= curr->mask; /* mask the address to filter out net prefix only */
    760 			if (tmp_addr == curr->addr)
    761 			{ /* ... and match it to the loaded data */
    762 				found = 1;
    763 				break;
    764 			}
    765 			i++;
    766 		}
    767 	}
    768 	if (found)
    769 		return curr->geoid;
    770 	return 0;
    771 }
    772 
    773 static int geoip_csv_get_v6_geoid(char *iip)
    774 {
    775 	uint16_t addr[8];
    776 	struct geoip_csv_ip6_range *curr;
    777 	int i;
    778 	int found = 0;
    779 	
    780 	if (!geoip_csv_ip6_convert(iip, addr))
    781 	{
    782 		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
    783 		return 0;
    784 	}
    785 	curr = geoip_csv_ip6_range_list;
    786 	if (curr)
    787 	{
    788 		for (;curr;curr = curr->next)
    789 		{
    790 			found = 1;
    791 			for (i=0; i<8; i++)
    792 			{
    793 				if (curr->addr[i] != (addr[i] & curr->mask[i]))
    794 				{ /* compare net address to loaded data */
    795 					found = 0;
    796 					break;
    797 				}
    798 			}
    799 			if(found)
    800 				break;
    801 		}
    802 	}
    803 	if (found)
    804 		return curr->geoid;
    805 	return 0;
    806 }
    807 
    808 GeoIPResult *geoip_lookup_csv(char *ip)
    809 {
    810 	int geoid;
    811 	struct geoip_csv_country *country;
    812 	GeoIPResult *r;
    813 
    814 	if (!ip)
    815 		return NULL;
    816 
    817 	if (strchr(ip, ':'))
    818 	{
    819 		geoid = geoip_csv_get_v6_geoid(ip);
    820 	} else
    821 	{
    822 		geoid = geoip_csv_get_v4_geoid(ip);
    823 	}
    824 
    825 	if (geoid == 0)
    826 		return NULL;
    827 
    828 	country = geoip_csv_get_country(geoid);
    829 
    830 	if (!country)
    831 		return NULL;
    832 
    833 	r = safe_alloc(sizeof(GeoIPResult));
    834 	safe_strdup(r->country_code, country->code);
    835 	safe_strdup(r->country_name, country->name);
    836 	return r;
    837 }
    838