]>
Commit | Line | Data |
---|---|---|
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 | 18 | MODULE_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 |
24 | struct country { |
25 | int total; | |
26 | char code[3]; | |
27 | char name[COUNTRY_NAME_LEN + 1]; | |
28 | }; | |
cc3768c4 | 29 | |
d0fea13b CP |
30 | static struct country countries[COUNTRY_MAX] = { [UNKNOWN_COUNTRY] = { 0, "??", "Unknown" } }; |
31 | static struct country *unknown_country = &countries[UNKNOWN_COUNTRY]; | |
cc3768c4 | 32 | |
d0fea13b CP |
33 | static int nickext = -1; |
34 | static MMDB_s db; | |
cc3768c4 | 35 | |
d0fea13b CP |
36 | static 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 |
79 | static 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 |
103 | static void nick_new(int hook, void *args) { |
104 | nick_setup(args); | |
cc3768c4 CP |
105 | } |
106 | ||
d0fea13b CP |
107 | static 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 |
118 | static 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 |
134 | struct 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 |
146 | const char *geoip_code(struct country *c) { |
147 | return c->code; | |
148 | } | |
cc3768c4 | 149 | |
d0fea13b CP |
150 | const char *geoip_name(struct country *c) { |
151 | return c->name; | |
152 | } | |
153 | ||
154 | int geoip_total(struct country *c) { | |
155 | return c->total; | |
156 | } | |
157 | ||
158 | struct 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 |
178 | struct country *geoip_lookup_nick(nick *np) { |
179 | if (nickext == -1) { | |
180 | return NULL; | |
181 | } | |
182 | ||
183 | return np->exts[nickext]; | |
184 | } | |
185 | ||
d0fea13b CP |
186 | void _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 |
211 | void _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 | } |