1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-06 20:23:13 +02:00
Files
unrealircd/src/modules/geoip_base.c
T
Bram Matthys 5860172780 Free previous GeoIP result upon IP change. Otherwise if the new geoip
lookup fails the old result stays there which is confusing.

Reported on IRC where 10.x.x.x was shown as "Poland" which was a
leftover from the "real IP" before WEBIRC spoofing was used to set
the IP to 10.x.x.x. Reported by Jellis.
2024-09-23 19:10:33 +02:00

438 lines
10 KiB
C

/*
* GEOIP Base module, needed for all geoip functions
* as this stores the geo information in ModData.
* (C) Copyright 2021-.. Syzop and The UnrealIRCd Team
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"geoip_base",
"5.0",
"Base module for geoip",
"UnrealIRCd Team",
"unrealircd-6",
};
struct geoip_base_config_s {
int check_on_load;
};
/* Forward declarations */
void geoip_base_free(ModData *m);
const char *geoip_base_serialize(ModData *m);
void geoip_base_unserialize(const char *str, ModData *m);
int geoip_base_handshake(Client *client);
int geoip_base_ip_change(Client *client, const char *oldip);
int geoip_base_whois_country(Client *client, Client *target, NameValuePrioList **list);
int geoip_base_whois_asn(Client *client, Client *target, NameValuePrioList **list);
int geoip_connect_extinfo(Client *client, NameValuePrioList **list);
int geoip_json_expand_client(Client *client, int detail, json_t *j);
int geoip_base_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int geoip_base_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
EVENT(geoip_base_set_existing_users_evt);
CMD_FUNC(cmd_geoip);
ModDataInfo *geoip_md; /* Module Data structure which we acquire */
struct geoip_base_config_s geoip_base_config;
/* We can use GEOIPDATA() and GEOIPDATARAW() for fast access.
* People wanting to get this information from outside this module
* should use geoip_client(client) !
*/
#define GEOIPDATARAW(x) (moddata_client((x), geoip_md).ptr)
#define GEOIPDATA(x) ((GeoIPResult *)moddata_client((x), geoip_md).ptr)
int geoip_base_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"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "check-on-load"))
{
CheckNull(cep);
continue;
}
config_warn("%s:%i: unknown item geoip::%s", cep->file->filename, cep->line_number, cep->name);
}
*errs = errors;
return errors ? -1 : 1;
}
int geoip_base_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"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "check-on-load"))
geoip_base_config.check_on_load = config_checkval(cep->value, CFG_YESNO);
}
return 1;
}
MOD_TEST()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, geoip_base_configtest);
return MOD_SUCCESS;
}
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&mreq, 0, sizeof(mreq));
mreq.name = "geoip";
mreq.free = geoip_base_free;
mreq.serialize = geoip_base_serialize;
mreq.unserialize = geoip_base_unserialize;
mreq.sync = MODDATA_SYNC_EARLY;
mreq.type = MODDATATYPE_CLIENT;
geoip_md = ModDataAdd(modinfo->handle, mreq);
if (!geoip_md)
abort();
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, geoip_base_configrun);
HookAdd(modinfo->handle, HOOKTYPE_HANDSHAKE, 0, geoip_base_handshake);
HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 0, geoip_base_ip_change);
HookAdd(modinfo->handle, HOOKTYPE_SERVER_HANDSHAKE_OUT, 0, geoip_base_handshake);
HookAdd(modinfo->handle, HOOKTYPE_CONNECT_EXTINFO, 1, geoip_connect_extinfo); /* (prio: near-first) */
HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0,geoip_base_handshake); /* in case the IP changed in registration phase (WEBIRC, HTTP Forwarded) */
HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois_country);
HookAdd(modinfo->handle, HOOKTYPE_WHOIS, 0, geoip_base_whois_asn);
HookAdd(modinfo->handle, HOOKTYPE_JSON_EXPAND_CLIENT, 0, geoip_json_expand_client);
CommandAdd(modinfo->handle, "GEOIP", cmd_geoip, MAXPARA, CMD_USER);
/* set defaults */
geoip_base_config.check_on_load = 1;
return MOD_SUCCESS;
}
MOD_LOAD()
{
/* add info for all users upon module loading if enabled, but delay it a bit for data provider module to load */
if (geoip_base_config.check_on_load)
{
EventAdd(modinfo->handle, "geoip_base_set_existing_users", geoip_base_set_existing_users_evt, NULL, 1000, 1);
}
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
int geoip_base_handshake(Client *client)
{
if (!client->ip)
return 0;
GeoIPResult *res = geoip_lookup(client->ip);
if (!res)
return 0;
if (GEOIPDATA(client))
{
free_geoip_result(GEOIPDATA(client));
GEOIPDATARAW(client) = NULL;
}
GEOIPDATARAW(client) = res;
return 0;
}
int geoip_base_ip_change(Client *client, const char *oldip)
{
if (GEOIPDATA(client))
{
free_geoip_result(GEOIPDATA(client));
GEOIPDATARAW(client) = NULL;
}
geoip_base_handshake(client);
return 0;
}
void geoip_base_free(ModData *m)
{
if (m->ptr)
{
free_geoip_result((GeoIPResult *)m->ptr);
m->ptr = NULL;
}
}
/* This should be unnecessary, but yeah.. you never know... it is data after all. */
const char *geoip_sanitized_asname(const char *str)
{
static char buf[512];
char *p;
/* The most likely scenario: quick search and return */
if (!strchr(str, '|'))
return str;
/* This converts '|' into '/' */
strlcpy(buf, str, sizeof(buf));
for (p = buf; *p; p++)
if (*p == '|')
*p = '/';
return buf;
}
const char *geoip_base_serialize(ModData *m)
{
static char buf[512];
GeoIPResult *geo;
if (!m->ptr)
return NULL;
geo = m->ptr;
if (geo->asname)
{
snprintf(buf, sizeof(buf), "cc=%s|cd=%s|asn=%u|asname=%s",
geo->country_code,
geo->country_name,
geo->asn,
geoip_sanitized_asname(geo->asname));
} else {
snprintf(buf, sizeof(buf), "cc=%s|cd=%s|asn=%u",
geo->country_code,
geo->country_name,
geo->asn);
}
return buf;
}
void geoip_base_unserialize(const char *str, ModData *m)
{
char buf[512], *p=NULL, *varname, *value;
char *country_name = NULL;
char *country_code = NULL;
long asn = 0;
char *asname = NULL;
GeoIPResult *res;
if (m->ptr)
{
free_geoip_result((GeoIPResult *)m->ptr);
m->ptr = NULL;
}
if (str == NULL)
return;
strlcpy(buf, str, sizeof(buf));
for (varname = strtoken(&p, buf, "|"); varname; varname = strtoken(&p, NULL, "|"))
{
value = strchr(varname, '=');
if (!value)
continue;
*value++ = '\0';
if (!strcmp(varname, "cc"))
country_code = value;
else if (!strcmp(varname, "cd"))
country_name = value;
else if (!strcmp(varname, "asn"))
asn = strtoul(value, NULL, 10);
else if (!strcmp(varname, "asname"))
asname = value;
}
if (!country_code || !country_name)
return; /* does not meet minimum criteria */
res = safe_alloc(sizeof(GeoIPResult));
safe_strdup(res->country_name, country_name);
safe_strdup(res->country_code, country_code);
safe_strdup(res->asname, asname);
res->asn = asn;
m->ptr = res;
}
EVENT(geoip_base_set_existing_users_evt)
{
Client *client;
list_for_each_entry(client, &client_list, client_node)
{
if (MyUser(client))
geoip_base_handshake(client);
}
}
int geoip_connect_extinfo(Client *client, NameValuePrioList **list)
{
GeoIPResult *geo = GEOIPDATA(client);
if (geo)
{
if (geo->asname)
add_nvplist(list, 0, "asname", geo->asname);
if (geo->asn)
{
char buf[64];
snprintf(buf, sizeof(buf), "%u", geo->asn);
add_nvplist(list, 0, "asn", buf);
}
add_nvplist(list, 0, "country", geo->country_code);
}
return 0;
}
int geoip_json_expand_client(Client *client, int detail, json_t *j)
{
GeoIPResult *geo = GEOIPDATA(client);
json_t *geoip;
if (!geo)
return 0;
geoip = json_object();
json_object_set_new(j, "geoip", geoip);
json_object_set_new(geoip, "country_code", json_string_unreal(geo->country_code));
if (geo->asn)
json_object_set_new(geoip, "asn", json_integer(geo->asn));
if (geo->asname)
json_object_set_new(geoip, "asname", json_string_unreal(geo->asname));
return 0;
}
int geoip_base_whois_country(Client *client, Client *target, NameValuePrioList **list)
{
GeoIPResult *geo;
char buf[512];
int policy = whois_get_policy(client, target, "geo");
if (policy == WHOIS_CONFIG_DETAILS_NONE)
return 0;
geo = GEOIPDATA(target);
if (!geo)
return 0;
// we only have country atm, but if we add city then city goes in 'full' and
// country goes in 'limited'
// if policy == WHOIS_CONFIG_DETAILS_LIMITED ...
add_nvplist_numeric_fmt(list, 0, "geo", client, RPL_WHOISCOUNTRY,
"%s %s :is connecting from %s",
target->name,
geo->country_code,
geo->country_name);
return 0;
}
int geoip_base_whois_asn(Client *client, Client *target, NameValuePrioList **list)
{
GeoIPResult *geo;
char buf[512];
int policy = whois_get_policy(client, target, "asn");
if (policy == WHOIS_CONFIG_DETAILS_NONE)
return 0;
geo = GEOIPDATA(target);
if (!geo || !geo->asn)
return 0;
// WHOIS_CONFIG_DETAILS_LIMITED / WHOIS_CONFIG_DETAILS_FULL distinction makes no sense here
add_nvplist_numeric_fmt(list, 0, "asn", client, RPL_WHOISASN,
"%s %u :is connecting from AS%u [%s]",
target->name,
geo->asn,
geo->asn,
geo->asname ? geo->asname : "UNKNOWN");
return 0;
}
CMD_FUNC(cmd_geoip)
{
const char *ip = NULL;
Client *target;
GeoIPResult *res;
if (!IsOper(client))
{
sendnumeric(client, ERR_NOPRIVILEGES);
return;
}
if ((parc < 2) || BadPtr(parv[1]))
{
/* Maybe some report */
return;
}
if (strchr(parv[1], '.') || strchr(parv[1], ':'))
{
ip = parv[1];
} else {
target = find_user(parv[1], NULL);
if (!target)
{
sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
return;
}
ip = target->ip;
if (!ip)
{
sendnotice(client, "User %s has no known IP address", client->name); // (eg: services bot)
return;
}
}
res = geoip_lookup(ip);
sendnotice(client, "*** GEOIP information for IP %s ***", ip);
if (!res)
{
sendnotice(client, "- No information available");
return;
} else {
if (res->country_code)
sendnotice(client, "- Country code: %s", res->country_code);
if (res->country_name)
sendnotice(client, "- Country name: %s", res->country_name);
if (res->asn)
sendnotice(client, "- AS Number: %u", res->asn);
if (res->asname)
sendnotice(client, "- AS Name: %s", res->asname);
}
free_geoip_result(res);
sendnotice(client, "*** End of information ***");
}