]> jfr.im git - irc/quakenet/newserv.git/blame - geoip/geoip.c
Merge pull request #132 from retropc/lua_country
[irc/quakenet/newserv.git] / geoip / geoip.c
CommitLineData
cc3768c4
CP
1/*
2 Geoip module
d0fea13b 3 Copyright (C) 2004-2023 Chris Porter.
cc3768c4
CP
4*/
5
6#include "../nick/nick.h"
7#include "../core/error.h"
8#include "../core/config.h"
9#include "../core/hooks.h"
10#include "../control/control.h"
87698d77 11#include "../lib/version.h"
cc3768c4 12
d0fea13b 13#include <string.h>
4e4920a4 14
d0fea13b 15#include <maxminddb.h>
cc3768c4
CP
16#include "geoip.h"
17
70b0a4e5 18MODULE_VERSION("");
87698d77 19
d0fea13b
CP
20#define COUNTRY_MAX (26 * 26 + 1)
21#define UNKNOWN_COUNTRY (COUNTRY_MAX - 1)
22#define COUNTRY_NAME_LEN 40
cc3768c4 23
d0fea13b
CP
24struct country {
25 int total;
26 char code[3];
27 char name[COUNTRY_NAME_LEN + 1];
28};
cc3768c4 29
d0fea13b
CP
30static struct country countries[COUNTRY_MAX] = { [UNKNOWN_COUNTRY] = { 0, "??", "Unknown" } };
31static struct country *unknown_country = &countries[UNKNOWN_COUNTRY];
cc3768c4 32
d0fea13b
CP
33static int nickext = -1;
34static MMDB_s db;
cc3768c4 35
d0fea13b
CP
36static struct country *lookup_country(struct sockaddr *addr) {
37 int mmdb_error;
38 MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&db, addr, &mmdb_error);
39 if (mmdb_error != MMDB_SUCCESS || !result.found_entry) {
40 return NULL;
41 }
cc3768c4 42
d0fea13b
CP
43 MMDB_entry_data_s entry_data;
44 int status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL);
45 if (status != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING || entry_data.data_size != 2) {
46 return NULL;
1edc8522 47 }
cc3768c4 48
d0fea13b
CP
49 /* not null terminated, sad */
50 const char *code_u = entry_data.utf8_string;
51 const char code[3] = { code_u[0], code_u[1], '\0' };
cc3768c4 52
d0fea13b
CP
53 struct country *c = geoip_lookup_code(code);
54 if (!c) {
55 return NULL;
56 }
cc3768c4 57
d0fea13b
CP
58 if (c->code[0] == '\0') {
59 status = MMDB_get_value(&result.entry, &entry_data, "country", "names", "en", NULL);
60 if (status != MMDB_SUCCESS || !entry_data.has_data || entry_data.type != MMDB_DATA_TYPE_UTF8_STRING) {
61 return NULL;
62 }
63
64 /* not null terminated, sad */
65 const char *name = entry_data.utf8_string;
66 size_t name_len = entry_data.data_size;
67 if (name_len > COUNTRY_NAME_LEN) {
68 name_len = COUNTRY_NAME_LEN;
69 }
70
71 memcpy(c->code, code, 3);
72 memcpy(c->name, name, name_len);
73 c->name[name_len] = '\0';
74 }
cc3768c4 75
d0fea13b 76 return c;
cc3768c4
CP
77}
78
d0fea13b
CP
79static void nick_setup(nick *np) {
80 union {
81 struct sockaddr_in sin;
82 struct sockaddr_in6 sin6;
83 } u = {};
84
85 struct irc_in_addr *ip = &np->ipaddress;
86 if (irc_in_addr_is_ipv4(ip)) {
87 u.sin.sin_family = AF_INET;
88 u.sin.sin_addr.s_addr = htonl(irc_in_addr_v4_to_int(ip));
89 } else {
90 u.sin6.sin6_family = AF_INET6;
91 memcpy(&u.sin6.sin6_addr.s6_addr, ip->in6_16, sizeof(ip->in6_16));
92 }
cc3768c4 93
d0fea13b
CP
94 struct country *c = lookup_country((struct sockaddr *)&u);
95 if (!c) {
96 c = unknown_country;
97 }
cc3768c4 98
d0fea13b
CP
99 c->total++;
100 np->exts[nickext] = c;
101}
cc3768c4 102
d0fea13b
CP
103static void nick_new(int hook, void *args) {
104 nick_setup(args);
cc3768c4
CP
105}
106
d0fea13b
CP
107static void nick_lost(int hook, void *args) {
108 nick *np = args;
20554f73 109
d0fea13b
CP
110 struct country *c = geoip_lookup_nick(np);
111 if (!c) {
cc3768c4 112 return;
d0fea13b 113 }
cc3768c4 114
d0fea13b 115 c->total--;
cc3768c4
CP
116}
117
d0fea13b
CP
118static void whois_handler(int hooknum, void *arg) {
119 nick *np = arg;
120 if (!np) {
121 return;
122 }
123
124 struct country *c = geoip_lookup_nick(np);
125 if (!c) {
126 return;
127 }
128
129 char buf[512];
130 snprintf(buf, sizeof(buf), "Country : %s (%s)", geoip_code(c), geoip_name(c));
131 triggerhook(HOOK_CONTROL_WHOISREPLY, buf);
cc3768c4
CP
132}
133
d0fea13b
CP
134struct country *geoip_lookup_code(const char *code) {
135 if (code[0] < 'A' || code[0] > 'Z' || code[1] < 'A' || code[1] > 'Z' || code[2] != '\0') {
136 if (!strcmp("??", code)) {
137 return unknown_country;
138 }
cc3768c4 139
d0fea13b
CP
140 return NULL;
141 }
cc3768c4 142
d0fea13b 143 return &countries[(code[0] - 'A') * 26 + (code[1] - 'A')];
cc3768c4
CP
144}
145
d0fea13b
CP
146const char *geoip_code(struct country *c) {
147 return c->code;
148}
cc3768c4 149
d0fea13b
CP
150const char *geoip_name(struct country *c) {
151 return c->name;
152}
153
154int geoip_total(struct country *c) {
155 return c->total;
156}
157
158struct country *geoip_next(struct country *c) {
159 int pos;
160 if (c == NULL) {
161 pos = 0;
162 } else {
163 pos = c - countries + 1;
164 }
165
166 for (; pos < COUNTRY_MAX; pos++) {
167 c = &countries[pos];
168 if (!c->total) {
169 continue;
170 }
cc3768c4 171
d0fea13b
CP
172 return c;
173 }
174
175 return NULL;
176}
177
c47c34ad
CP
178struct country *geoip_lookup_nick(nick *np) {
179 if (nickext == -1) {
180 return NULL;
181 }
182
183 return np->exts[nickext];
184}
185
d0fea13b
CP
186void _init(void) {
187 nickext = registernickext("geoip");
188 if (nickext == -1) {
cc3768c4 189 return;
d0fea13b 190 }
cc3768c4 191
d0fea13b
CP
192 sstring *filename = getcopyconfigitem("geoip", "db", "GeoLite2-Country.mmdb", 256);
193 int result = MMDB_open(filename->content, MMDB_MODE_MMAP, &db);
194 freesstring(filename);
195 if (result != MMDB_SUCCESS) {
196 Error("geoip", ERR_WARNING, "Unable to load geoip database [filename: %s] [code: %d]", filename->content, result);
197 return;
198 }
69793602 199
d0fea13b
CP
200 for (int i = 0; i < NICKHASHSIZE; i++) {
201 for (nick *np = nicktable[i]; np; np=np->next) {
202 nick_setup(np);
203 }
cc3768c4 204 }
d0fea13b
CP
205
206 registerhook(HOOK_NICK_NEWNICK, nick_new);
207 registerhook(HOOK_NICK_LOSTNICK, nick_lost);
208 registerhook(HOOK_CONTROL_WHOISREQUEST, whois_handler);
cc3768c4
CP
209}
210
d0fea13b
CP
211void _fini(void) {
212 if (nickext != -1) {
213 releasenickext(nickext);
214 }
215
216 MMDB_close(&db);
217
218 deregisterhook(HOOK_NICK_NEWNICK, nick_new);
219 deregisterhook(HOOK_NICK_LOSTNICK, nick_lost);
220 deregisterhook(HOOK_CONTROL_WHOISREQUEST, whois_handler);
221}