/*
 *   IRC - Internet Relay Chat, src/modules/geoip_csv.c
 *   (C) 2021 The UnrealIRCd Team
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "unrealircd.h"

ModuleHeader MOD_HEADER
  = {
	"geoip_csv",
	"5.0",
	"GEOIP using csv data files", 
	"UnrealIRCd Team",
	"unrealircd-6",
    };

struct geoip_csv_config_s {
	char *v4_db_file;
	char *v6_db_file;
	char *countries_db_file;
/* for config reading only */
	int have_config;
	int have_ipv4_database;
	int have_ipv6_database;
	int have_countries;
};

struct geoip_csv_ip_range {
	uint32_t addr;
	uint32_t mask;
	int geoid;
	struct geoip_csv_ip_range *next;
};

struct geoip_csv_ip6_range {
	uint16_t addr[8];
	uint16_t mask[8];
	int geoid;
	struct geoip_csv_ip6_range *next;
};

struct geoip_csv_country {
	char code[10];
	char name[100];
	char continent[25];
	int id;
	struct geoip_csv_country *next;
};

/* Variables */
struct geoip_csv_config_s geoip_csv_config;
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
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
struct geoip_csv_country *geoip_csv_country_list = NULL;

/* Forward declarations */
static void geoip_csv_free_ipv4(void);
static void geoip_csv_free_ipv6(void);
static void geoip_csv_free_ipv6(void);
static void geoip_csv_free_countries(void);
static void geoip_csv_free(void);
static int geoip_csv_read_ipv4(char *file);
static int geoip_csv_ip6_convert(char *ip, uint16_t out[8]);
static int geoip_csv_read_ipv6(char *file);
static int geoip_csv_read_countries(char *file);
static struct geoip_csv_country *geoip_csv_get_country(int id);
static int geoip_csv_get_v4_geoid(char *iip);
static int geoip_csv_get_v6_geoid(char *iip);
int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int geoip_csv_configposttest(int *errs);
int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
void geoip_csv_free(void);
GeoIPResult *geoip_lookup_csv(char *ip);

int geoip_csv_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	ConfigEntry *cep;
	int errors = 0;
	int i;
	
	if (type != CONFIG_SET)
		return 0;

	if (!ce || !ce->name)
		return 0;

	if (strcmp(ce->name, "geoip-csv"))
		return 0;

	geoip_csv_config.have_config = 1;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "ipv4-blocks-file"))
		{
			if (geoip_csv_config.have_ipv4_database)
			{
				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
				continue;
			}
			if (!is_file_readable(cep->value, PERMDATADIR))
			{
				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));
				errors++;
				continue;
			}
			geoip_csv_config.have_ipv4_database = 1;
			continue;
		}
		if (!strcmp(cep->name, "ipv6-blocks-file"))
		{
			if (geoip_csv_config.have_ipv6_database)
			{
				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
				continue;
			}
			if (!is_file_readable(cep->value, PERMDATADIR))
			{
				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));
				errors++;
				continue;
			}
			geoip_csv_config.have_ipv6_database = 1;
			continue;
		}
		if (!strcmp(cep->name, "countries-file"))
		{
			if (geoip_csv_config.have_countries)
			{
				config_error("%s:%i: duplicate item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
				continue;
			}
			if (!is_file_readable(cep->value, PERMDATADIR))
			{
				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));
				errors++;
				continue;
			}
			geoip_csv_config.have_countries = 1;
			continue;
		}
		config_warn("%s:%i: unknown item set::geoip-csv::%s", cep->file->filename, cep->line_number, cep->name);
	}
	
	*errs = errors;
	return errors ? -1 : 1;
}

int geoip_csv_configposttest(int *errs)
{
	int errors = 0;
	if (geoip_csv_config.have_config)
	{
		if (!geoip_csv_config.have_countries)
		{
			config_error("[geoip_csv] no countries file specified! Remove set::geoip-csv to use defaults");
			errors++;
		}
		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
		{
			config_error("[geoip_csv] no database files specified! Remove set::geoip-csv to use defaults");
			errors++;
		}
	} else
	{
		safe_strdup(geoip_csv_config.v4_db_file, "GeoLite2-Country-Blocks-IPv4.csv");
		safe_strdup(geoip_csv_config.v6_db_file, "GeoLite2-Country-Blocks-IPv6.csv");
		safe_strdup(geoip_csv_config.countries_db_file, "GeoLite2-Country-Locations-en.csv");

		if (is_file_readable(geoip_csv_config.v4_db_file, PERMDATADIR))
		{
			geoip_csv_config.have_ipv4_database = 1;
		} else
		{
			config_warn("[geoip_csv] cannot open IPv4 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v4_db_file, strerror(errno));
			safe_free(geoip_csv_config.v4_db_file);
		}
		if (is_file_readable(geoip_csv_config.v6_db_file, PERMDATADIR))
		{
			geoip_csv_config.have_ipv6_database = 1;
		} else
		{
			config_warn("[geoip_csv] cannot open IPv6 blocks file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.v6_db_file, strerror(errno));
			safe_free(geoip_csv_config.v6_db_file);
		}
		if (!is_file_readable(geoip_csv_config.countries_db_file, PERMDATADIR))
		{
			config_error("[geoip_csv] cannot open countries file \"%s/%s\" for reading (%s)", PERMDATADIR, geoip_csv_config.countries_db_file, strerror(errno));
			safe_free(geoip_csv_config.countries_db_file);
			errors++;
		}
		if (!geoip_csv_config.have_ipv4_database && !geoip_csv_config.have_ipv6_database)
		{
			config_error("[geoip_csv] couldn't read any blocks file! Either put these in %s location "
					"or specify another in set::geoip-csv config block", PERMDATADIR);
			errors++;
		}
	}

	*errs = errors;
	return errors ? -1 : 1;
}

int geoip_csv_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
{
	ConfigEntry *cep;

	if (type != CONFIG_SET)
		return 0;

	if (!ce || !ce->name)
		return 0;

	if (strcmp(ce->name, "geoip-csv"))
		return 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "ipv4-blocks-file") && geoip_csv_config.have_ipv4_database)
			safe_strdup(geoip_csv_config.v4_db_file, cep->value);
		if (!strcmp(cep->name, "ipv6-blocks-file") && geoip_csv_config.have_ipv6_database)
			safe_strdup(geoip_csv_config.v6_db_file, cep->value);
		if (!strcmp(cep->name, "countries-file"))
			safe_strdup(geoip_csv_config.countries_db_file, cep->value);
	}
	return 1;
}

MOD_TEST()
{
	MARK_AS_OFFICIAL_MODULE(modinfo);
	if (!CallbackAddPVoid(modinfo->handle, CALLBACKTYPE_GEOIP_LOOKUP, TO_PVOIDFUNC(geoip_lookup_csv)))
	{
		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_ADD_CALLBACK_FAILED", NULL,
		           "geoip_csv: Could not install GEOIP_LOOKUP callback. "
		           "Most likely another geoip module is already loaded. "
		           "You can only load one!");
		return MOD_FAILED;
	}

	geoip_csv_config.have_config = 0;
	geoip_csv_config.have_ipv4_database = 0;
	geoip_csv_config.have_ipv6_database = 0;
	geoip_csv_config.have_countries = 0;
	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_csv_configtest);
	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, geoip_csv_configposttest);
	return MOD_SUCCESS;
}

MOD_INIT()
{
	MARK_AS_OFFICIAL_MODULE(modinfo);
	geoip_csv_free();
	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_csv_configrun);
	return MOD_SUCCESS;
}

MOD_LOAD()
{
	int found_good_file = 0;

	if (geoip_csv_config.v4_db_file)
	{
		convert_to_absolute_path(&geoip_csv_config.v4_db_file, PERMDATADIR);
		if (!geoip_csv_read_ipv4(geoip_csv_config.v4_db_file))
		{
			found_good_file = 1;
		}
	}
	if (geoip_csv_config.v6_db_file)
	{
		convert_to_absolute_path(&geoip_csv_config.v6_db_file, PERMDATADIR);
		if (!geoip_csv_read_ipv6(geoip_csv_config.v6_db_file))
		{
			found_good_file = 1;
		}
	}
	if (!geoip_csv_config.countries_db_file)
	{
		unreal_log(ULOG_DEBUG, "geoip_csv", "GEOIP_NO_COUNTRIES", NULL,
				"[BUG] No countries file specified");
		geoip_csv_free();
		return MOD_FAILED;
	}
	convert_to_absolute_path(&geoip_csv_config.countries_db_file, PERMDATADIR);
	if (geoip_csv_read_countries(geoip_csv_config.countries_db_file))
	{
		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
					"could not open required countries file!");
		geoip_csv_free();
		return MOD_FAILED;
	}

	if (!found_good_file)
	{
		unreal_log(ULOG_ERROR, "geoip_csv", "GEOIP_CANNOT_OPEN_DB", NULL,
					"could not open any database!");
		geoip_csv_free();
		return MOD_FAILED;
	}
	return MOD_SUCCESS;
}

MOD_UNLOAD()
{
	geoip_csv_free();
	return MOD_SUCCESS;
}

static void geoip_csv_free_ipv4(void)
{
	struct geoip_csv_ip_range *ptr, *oldptr;
	int i;
	for (i=0; i<256; i++)
	{
		ptr = geoip_csv_ip_range_list[i];
		geoip_csv_ip_range_list[i] = NULL;
		while (ptr)
		{
			oldptr = ptr;
			ptr = ptr->next;
			safe_free(oldptr);
		}
	}
}

static void geoip_csv_free_ipv6(void)
{
	struct geoip_csv_ip6_range *ptr, *oldptr;
	ptr = geoip_csv_ip6_range_list;
	geoip_csv_ip6_range_list = NULL;
	while (ptr)
	{
		oldptr = ptr;
		ptr = ptr->next;
		safe_free(oldptr);
	}
}

static void geoip_csv_free_countries(void)
{
	struct geoip_csv_country *ptr, *oldptr;
	ptr = geoip_csv_country_list;
	geoip_csv_country_list = NULL;
	while (ptr)
	{
		oldptr = ptr;
		ptr = ptr->next;
		safe_free(oldptr);
	}
}

static void geoip_csv_free(void)
{
	geoip_csv_free_ipv4();
	geoip_csv_free_ipv6();
	geoip_csv_free_countries();
}

/* reading data from files */

#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define BUFLEN 8191

static int geoip_csv_read_ipv4(char *file)
{
	FILE *u;
	char buf[BUFLEN+1];
	int cidr, geoid;
	char ip[24];
	char netmask[24];
	uint32_t addr;
	uint32_t mask;
	struct geoip_csv_ip_range *curr[256];
	struct geoip_csv_ip_range *ptr;
	memset(curr, 0, sizeof(curr));
	int i;
	char *filename = NULL;
	
	safe_strdup(filename, file);
	convert_to_absolute_path(&filename, CONFDIR);
	u = fopen(filename, "r");
	safe_free(filename);
	if (!u)
	{
		config_warn("[geoip_csv] Cannot open IPv4 ranges list file");
		return 1;
	}
	
	if (!fgets(buf, BUFLEN, u))
	{
		config_warn("[geoip_csv] IPv4 list file is empty");
		fclose(u);
		return 1;
	}
	buf[BUFLEN] = '\0';
	while (fscanf(u, "%23[^/\n]/%d,%" STR(BUFLEN) "[^\n]\n", ip, &cidr, buf) == 3)
	{
		if (sscanf(buf, "%d,", &geoid) != 1)
		{
			/* missing geoid: can happen with valid files */
			continue;
		}

		if (cidr < 1 || cidr > 32)
		{
			config_warn("[geoip_csv] Invalid CIDR found! IP=%s CIDR=%d! Bad CSV file?", ip, cidr);
			continue;
		}

		if (inet_pton(AF_INET, ip, &addr) < 1)
		{
			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
			continue;
		}
		addr = htonl(addr);
		
		mask = 0;
		while (cidr)
		{ /* calculate netmask */
			mask >>= 1;
			mask |= (1<<31);
			cidr--;
		}
		
		i=0;
		do
		{ /* multiple iterations in case CIDR is <8 and we have multiple first octets matching */
			uint8_t index = addr>>24;
			if (!curr[index])
			{
				geoip_csv_ip_range_list[index] = safe_alloc(sizeof(struct geoip_csv_ip_range));
				curr[index] = geoip_csv_ip_range_list[index];
			} else
			{
				curr[index]->next = safe_alloc(sizeof(struct geoip_csv_ip_range));
				curr[index] = curr[index]->next;
			}
			ptr = curr[index];
			ptr->next = NULL;
			ptr->addr = addr;
			ptr->mask = mask;
			ptr->geoid = geoid;
			i++;
			index++;
		} while (i<=((~mask)>>24));
	}
	fclose(u);
	return 0;
}

static int geoip_csv_ip6_convert(char *ip, uint16_t out[8])
{ /* convert text to binary form */
	uint16_t tmp[8];
	int i;
	if (inet_pton(AF_INET6, ip, out) < 1)
		return 0;
	for (i=0; i<8; i++)
	{
		out[i] = htons(out[i]);
	}
	return 1;
}

#define IPV6_STRING_SIZE	40

static int geoip_csv_read_ipv6(char *file)
{
	FILE *u;
	char buf[BUFLEN+1];
	char *bptr, *optr;
	int cidr, geoid;
	char ip[IPV6_STRING_SIZE];
	uint16_t addr[8];
	uint16_t mask[8];
	struct geoip_csv_ip6_range *curr = NULL;
	struct geoip_csv_ip6_range *ptr;
	int error;
	int length;
	char *filename = NULL;

	safe_strdup(filename, file);
	convert_to_absolute_path(&filename, CONFDIR);
	u = fopen(filename, "r");
	safe_free(filename);
	if (!u)
	{
		config_warn("[geoip_csv] Cannot open IPv6 ranges list file");
		return 1;
	}
	if (!fgets(buf, BUFLEN, u))
	{
		config_warn("[geoip_csv] IPv6 list file is empty");
		fclose(u);
		return 1;
	}
	while (fgets(buf, BUFLEN, u))
	{
		error = 0;
		bptr = buf;
		optr = ip;
		length = 0;
		while (*bptr != '/')
		{
			if (!*bptr)
			{
				error = 1;
				break;
			}
			if (++length >= IPV6_STRING_SIZE)
			{
				ip[IPV6_STRING_SIZE-1] = '\0';
				config_warn("[geoip_csv] Too long IPv6 address found, starts with %s. Bad CSV file?", ip);
				error = 1;
				break;
			}
			*optr++ = *bptr++;
		}
		if (error)
			continue;
		*optr = '\0';
		bptr++;
		if (!geoip_csv_ip6_convert(ip, addr))
		{
			config_warn("[geoip_csv] Invalid IP found! \"%s\" Bad CSV file?", ip);
			continue;
		}
		sscanf(bptr, "%d,%d,", &cidr, &geoid);
		if (cidr < 1 || cidr > 128)
		{
			config_warn("[geoip_csv] Invalid CIDR found! CIDR=%d Bad CSV file?", cidr);
			continue;
		}

		memset(mask, 0, 16);
		
		int mask_bit = 0;
		while (cidr)
		{ /* calculate netmask */
			mask[mask_bit/16] |= 1<<(15-(mask_bit%16));
			mask_bit++;
			cidr--;
		}

		if (!curr)
		{
			geoip_csv_ip6_range_list = safe_alloc(sizeof(struct geoip_csv_ip6_range));
			curr = geoip_csv_ip6_range_list;
		} else
		{
			curr->next = safe_alloc(sizeof(struct geoip_csv_ip6_range));
			curr = curr->next;
		}
		ptr = curr;
		ptr->next = NULL;
		memcpy(ptr->addr, addr, 16);
		memcpy(ptr->mask, mask, 16);
		ptr->geoid = geoid;
	}
	fclose(u);
	return 0;
}

/* CSV fields; no STATE_GEONAME_ID because of using %d in fscanf */
#define STATE_LOCALE_CODE	0
#define STATE_CONTINENT_CODE	1
#define STATE_CONTINENT_NAME	2
#define STATE_COUNTRY_ISO_CODE	3
#define STATE_COUNTRY_NAME	4
#define STATE_IS_IN_EU	5

#define MEMBER_SIZE(type,member) sizeof(((type *)0)->member)

static int geoip_csv_read_countries(char *file)
{
	FILE *u;
	char code[MEMBER_SIZE(struct geoip_csv_country, code)];
	char continent[MEMBER_SIZE(struct geoip_csv_country, continent)];
	char name[MEMBER_SIZE(struct geoip_csv_country, name)];
	char buf[BUFLEN+1];
	int state;
	int id;
	struct geoip_csv_country *curr = NULL;
	char *filename = NULL;

	safe_strdup(filename, file);
	convert_to_absolute_path(&filename, CONFDIR);
	u = fopen(filename, "r");
	safe_free(filename);
	if (!u)
	{
		config_warn("[geoip_csv] Cannot open countries list file");
		return 1;
	}
	
	if (!fgets(buf, BUFLEN, u))
	{
		config_warn("[geoip_csv] Countries list file is empty");
		fclose(u);
		return 1;
	}
	while (fscanf(u, "%d,%" STR(BUFLEN) "[^\n]", &id, buf) == 2)
	{ /* getting country ID integer and all other data in string */
		char *ptr = buf;
		char *codeptr = code;
		char *contptr = continent;
		char *nptr = name;
		int quote_open = 0;
		int length = 0;
		state = STATE_LOCALE_CODE;
		while (*ptr)
		{
			switch (state)
			{
				case STATE_CONTINENT_NAME:
					if (*ptr == ',')
						goto next_line; /* no continent? */
					if (length >= MEMBER_SIZE(struct geoip_csv_country, continent) - 1)
					{
						*contptr = '\0';
						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);
						goto next_line;
					}
					*contptr = *ptr; /* scan for continent name */
					contptr++;
					length++;
					break;
				case STATE_COUNTRY_ISO_CODE:
					if (*ptr == ',')		/* country code is empty */
						goto next_line;	/* -- that means only the continent is specified - we ignore it completely */
					if (length >= MEMBER_SIZE(struct geoip_csv_country, code) - 1)
					{
						*codeptr = '\0';
						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);
						goto next_line;
					}
					*codeptr = *ptr; // scan for country code (DE, PL, US etc)
					codeptr++;
					length++;
					break;
				case STATE_COUNTRY_NAME:
					goto read_country_name;
				default:
					break; // ignore this field and wait for next one
			}
			ptr++;
			if (*ptr == ',')
			{
				length = 0;
				ptr++;
				state++;
			}
		}
		read_country_name:
		*codeptr = '\0';
		*contptr = '\0';
		length = 0;
		while (*ptr)
		{
			switch (*ptr)
			{
				case '"':
					quote_open = !quote_open;
					ptr++;
					continue;
				case ',':
					if (!quote_open)
						goto end_country_name; /* we reached the end of current CSV field */
				/* fall through */
				default:
					*nptr++ = *ptr++;
					length++;
					if (length >= MEMBER_SIZE(struct geoip_csv_country, name) - 1)
					{
						*nptr = '\0';
						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);
						goto next_line;
					}
					break; // scan for country name
			}
		}
		end_country_name:
		*nptr = '\0';
		if (geoip_csv_country_list)
		{
			curr->next = safe_alloc(sizeof(struct geoip_csv_country));
			curr = curr->next;
		} else
		{
			geoip_csv_country_list = safe_alloc(sizeof(struct geoip_csv_country));
			curr = geoip_csv_country_list;
		}
		curr->next = NULL;
		strcpy(curr->code, code);
		strcpy(curr->name, name);
		strcpy(curr->continent, continent);
		curr->id = id;
		next_line: continue;
	}
	fclose(u);
	return 0;
}

static struct geoip_csv_country *geoip_csv_get_country(int id)
{
	struct geoip_csv_country *curr = geoip_csv_country_list;
	if (!curr)
		return NULL;
	int found = 0;
	for (;curr;curr = curr->next)
	{
		if (curr->id == id)
		{
			found = 1;
			break;
		}
	}
	if (found)
		return curr;
	return NULL;
}

static int geoip_csv_get_v4_geoid(char *iip)
{
	uint32_t addr, tmp_addr;
	struct geoip_csv_ip_range *curr;
	int i;
	int found = 0;
	if (inet_pton(AF_INET, iip, &addr) < 1)
	{
		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
		return 0;
	}
	addr = htonl(addr);
	curr = geoip_csv_ip_range_list[addr>>24];
	if (curr)
	{
		i = 0;
		for (;curr;curr = curr->next)
		{
			tmp_addr = addr;
			tmp_addr &= curr->mask; /* mask the address to filter out net prefix only */
			if (tmp_addr == curr->addr)
			{ /* ... and match it to the loaded data */
				found = 1;
				break;
			}
			i++;
		}
	}
	if (found)
		return curr->geoid;
	return 0;
}

static int geoip_csv_get_v6_geoid(char *iip)
{
	uint16_t addr[8];
	struct geoip_csv_ip6_range *curr;
	int i;
	int found = 0;
	
	if (!geoip_csv_ip6_convert(iip, addr))
	{
		unreal_log(ULOG_WARNING, "geoip_csv", "UNSUPPORTED_IP", NULL, "Invalid or unsupported client IP $ip", log_data_string("ip", iip));
		return 0;
	}
	curr = geoip_csv_ip6_range_list;
	if (curr)
	{
		for (;curr;curr = curr->next)
		{
			found = 1;
			for (i=0; i<8; i++)
			{
				if (curr->addr[i] != (addr[i] & curr->mask[i]))
				{ /* compare net address to loaded data */
					found = 0;
					break;
				}
			}
			if(found)
				break;
		}
	}
	if (found)
		return curr->geoid;
	return 0;
}

GeoIPResult *geoip_lookup_csv(char *ip)
{
	int geoid;
	struct geoip_csv_country *country;
	GeoIPResult *r;

	if (!ip)
		return NULL;

	if (strchr(ip, ':'))
	{
		geoid = geoip_csv_get_v6_geoid(ip);
	} else
	{
		geoid = geoip_csv_get_v4_geoid(ip);
	}

	if (geoid == 0)
		return NULL;

	country = geoip_csv_get_country(geoid);

	if (!country)
		return NULL;

	r = safe_alloc(sizeof(GeoIPResult));
	safe_strdup(r->country_code, country->code);
	safe_strdup(r->country_name, country->name);
	return r;
}

