From 1f72396442647a6372f0691f3f419d8695978328 Mon Sep 17 00:00:00 2001 From: k4be <k4be@pirc.pl> Date: Thu, 26 Aug 2021 20:50:02 +0200 Subject: [PATCH] Initial version of geoip_csv --- Makefile.windows | 13 +- src/modules/Makefile.in | 6 +- src/modules/geoip_csv.c | 819 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 836 insertions(+), 2 deletions(-) create mode 100644 src/modules/geoip_csv.c diff --git a/Makefile.windows b/Makefile.windows index fba7ad5a5..02d595701 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -310,7 +310,12 @@ DLL_FILES=SRC/MODULES/OLDCLOAK.DLL \ SRC/MODULES/TARGETFLOODPROT.DLL \ SRC/MODULES/TYPING-INDICATOR.DLL \ SRC/MODULES/CLIENTTAGDENY.DLL \ - SRC/MODULES/WATCH-BACKEND.DLL + SRC/MODULES/WATCH-BACKEND.DLL \ + SRC/MODULES/MONITOR.DLL \ + SRC/MODULES/METADATA.DLL \ + SRC/MODULES/EXTENDED-MONITOR.DLL \ + SRC/MODULES/GEOIP_CSV.DLL \ + SRC/MODULES/GEOIP_BASE.DLL ALL: CONF UNREALSVC.EXE UnrealIRCd.exe MODULES @@ -1140,4 +1145,10 @@ src/modules/metadata.dll: src/modules/metadata.c $(INCLUDES) src/modules/extended-monitor.dll: src/modules/extended-monitor.c $(INCLUDES) $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/extended-monitor.c $(MODLFLAGS) +src/modules/geoip_base.dll: src/modules/geoip_base.c $(INCLUDES) + $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/geoip_base.c $(MODLFLAGS) + +src/modules/geoip_csv.dll: src/modules/geoip_csv.c $(INCLUDES) + $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/geoip_csv.c $(MODLFLAGS) + dummy: diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index f19e1e7cd..f6d68398d 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -77,7 +77,7 @@ R_MODULES= \ targetfloodprot.so clienttagdeny.so watch-backend.so \ monitor.so slog.so tls_cipher.so \ unreal_server_compat.so metadata.so \ - extended-monitor.so \ + extended-monitor.so geoip_csv.so \ geoip_base.so $(GEOIP_CLASSIC_OBJECTS) MODULES=oldcloak.so $(R_MODULES) @@ -697,6 +697,10 @@ extended-monitor.so: extended-monitor.c $(INCLUDES) $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ -o extended-monitor.so extended-monitor.c +geoip_csv.so: geoip_csv.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ + -o geoip_csv.so geoip_csv.c + ############################################################################# # capabilities ############################################################################# diff --git a/src/modules/geoip_csv.c b/src/modules/geoip_csv.c new file mode 100644 index 000000000..1452e2a3d --- /dev/null +++ b/src/modules/geoip_csv.c @@ -0,0 +1,819 @@ +/* + * 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; + } + char *filename = strdup(cep->value); + convert_to_absolute_path(&filename, PERMDATADIR); + if(access(filename, R_OK)){ + config_error("%s:%i: set::geoip-classic::%s: cannot open file \"%s\" for reading", cep->file->filename, cep->line_number, cep->name, cep->value); + errors++; + safe_free(filename); + continue; + } + safe_free(filename); + 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; + } + char *filename = strdup(cep->value); + convert_to_absolute_path(&filename, PERMDATADIR); + if(access(filename, R_OK)){ + config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s\" for reading", cep->file->filename, cep->line_number, cep->name, cep->value); + errors++; + safe_free(filename); + continue; + } + safe_free(filename); + 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; + } + char *filename = strdup(cep->value); + convert_to_absolute_path(&filename, PERMDATADIR); + if(access(filename, R_OK)){ + config_error("%s:%i: set::geoip-csv::%s: cannot open file \"%s\" for reading", cep->file->filename, cep->line_number, cep->name, cep->value); + errors++; + safe_free(filename); + continue; + } + safe_free(filename); + geoip_csv_config.have_ipv6_database = 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++; + } + } + + *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")) + safe_strdup(geoip_csv_config.v4_db_file, cep->value); + if (!strcmp(cep->name, "ipv6-blocks-file")) + 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 (!CallbackAddPVoidEx(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.have_config) + { + 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 (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"); + 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"); + 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"); + 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)) + { + *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)) + { + *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++; + if (length >= MEMBER_SIZE(struct geoip_csv_country, name)) + { + *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; + } + if (found) + 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; +} +