1 |
/** |
2 |
* clones.C: This file contains functionality implementing clone detection. |
3 |
* |
4 |
* Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team |
5 |
* Rights to this code are as documented in COPYING. |
6 |
* |
7 |
* |
8 |
* Portions of this file were derived from sources bearing the following license: |
9 |
* Copyright © 2006 Atheme Development Group |
10 |
* Rights to this code are documented in doc/LICENCE. |
11 |
* |
12 |
* $Id: clones.C,v 1.6 2007-09-16 18:54:43 pippijn Exp $ |
13 |
*/ |
14 |
|
15 |
#include <boost/foreach.hpp> |
16 |
|
17 |
#include "atheme.h" |
18 |
#include <libermyth.h> |
19 |
#include <ermyth/module.h> |
20 |
#include <util/predicates.h> |
21 |
|
22 |
static char const rcsid[] = "$Id: clones.C,v 1.6 2007-09-16 18:54:43 pippijn Exp $"; |
23 |
|
24 |
REGISTER_MODULE ("operserv/clones", false, "The Ermyth Team <http://ermyth.xinutec.org>"); |
25 |
|
26 |
#define DEFAULT_WARN_CLONES 3 /* IPs with more than this are warned about */ |
27 |
#define EXEMPT_GRACE 10 /* exempt IPs exceeding their allowance by this are banned */ |
28 |
|
29 |
static void os_cmd_clones (sourceinfo_t *si, int parc, char *parv[]); |
30 |
static void os_cmd_clones_kline (sourceinfo_t *si, int parc, char *parv[]); |
31 |
static void os_cmd_clones_list (sourceinfo_t *si, int parc, char *parv[]); |
32 |
static void os_cmd_clones_addexempt (sourceinfo_t *si, int parc, char *parv[]); |
33 |
static void os_cmd_clones_delexempt (sourceinfo_t *si, int parc, char *parv[]); |
34 |
static void os_cmd_clones_listexempt (sourceinfo_t *si, int parc, char *parv[]); |
35 |
|
36 |
static void write_exemptdb (void); |
37 |
static void load_exemptdb (void); |
38 |
|
39 |
E cmdvec os_cmdtree; |
40 |
E helpvec os_helptree; |
41 |
cmdvec os_clones_cmds; |
42 |
|
43 |
bool kline_enabled; |
44 |
|
45 |
struct cexcept_t |
46 |
{ |
47 |
typedef std::vector<cexcept_t *> vector_type; |
48 |
|
49 |
char *ip; |
50 |
int clones; |
51 |
char *reason; |
52 |
}; |
53 |
|
54 |
struct hostentry_t |
55 |
{ |
56 |
char ip[HOSTIPLEN]; |
57 |
user_t::vector_type clients; |
58 |
time_t lastaction; |
59 |
unsigned lastaction_clones; |
60 |
}; |
61 |
|
62 |
static cexcept_t::vector_type clone_exempts; |
63 |
|
64 |
typedef std::pair<char const * const, hostentry_t *> host_pair; |
65 |
typedef std::map<char const * const, hostentry_t *, str_lt> host_map; |
66 |
static host_map hostlist; |
67 |
|
68 |
command_t const os_clones = { "CLONES", N_("Manages network wide clones."), PRIV_AKILL, 4, os_cmd_clones }; |
69 |
|
70 |
command_t const os_clones_kline = { "KLINE", N_("Enables/disables klines for excessive clones."), AC_NONE, 1, os_cmd_clones_kline }; |
71 |
command_t const os_clones_list = { "LIST", N_("Lists clones on the network."), AC_NONE, 0, os_cmd_clones_list }; |
72 |
command_t const os_clones_addexempt = { "ADDEXEMPT", N_("Adds a clones exemption."), AC_NONE, 3, os_cmd_clones_addexempt }; |
73 |
command_t const os_clones_delexempt = { "DELEXEMPT", N_("Deletes a clones exemption."), AC_NONE, 1, os_cmd_clones_delexempt }; |
74 |
command_t const os_clones_listexempt = { "LISTEXEMPT", N_("Lists clones exemptions."), AC_NONE, 0, os_cmd_clones_listexempt }; |
75 |
|
76 |
static void |
77 |
write_exemptdb (void) |
78 |
{ |
79 |
FILE *f; |
80 |
|
81 |
if (!(f = fopen (DATADIR "/exempts.db.new", "w"))) |
82 |
{ |
83 |
slog (LG_ERROR, "write_exemptdb(): cannot write exempt database: %s", strerror (errno)); |
84 |
return; |
85 |
} |
86 |
|
87 |
fprintf (f, "CK %d\n", kline_enabled ? 1 : 0); |
88 |
foreach (cexcept_t *c, clone_exempts) |
89 |
fprintf (f, "EX %s %d %s\n", c->ip, c->clones, c->reason); |
90 |
|
91 |
fclose (f); |
92 |
|
93 |
if ((rename (DATADIR "/exempts.db.new", DATADIR "/exempts.db")) < 0) |
94 |
{ |
95 |
slog (LG_ERROR, "write_exemptdb(): couldn't rename exempts database."); |
96 |
return; |
97 |
} |
98 |
} |
99 |
|
100 |
static void |
101 |
load_exemptdb (void) |
102 |
{ |
103 |
FILE *f; |
104 |
char *item, rBuf[BUFSIZE * 2], *p; |
105 |
|
106 |
if (!(f = fopen (DATADIR "/exempts.db", "r"))) |
107 |
{ |
108 |
slog (LG_DEBUG, "load_exemptdb(): cannot open exempt database: %s", strerror (errno)); |
109 |
return; |
110 |
} |
111 |
|
112 |
while (fgets (rBuf, BUFSIZE * 2, f)) |
113 |
{ |
114 |
item = strtok (rBuf, " "); |
115 |
strip (item); |
116 |
|
117 |
if (!strcmp (item, "EX")) |
118 |
{ |
119 |
char *ip = strtok (NULL, " "); |
120 |
int clones = atoi (strtok (NULL, " ")); |
121 |
char *reason = strtok (NULL, ""); |
122 |
|
123 |
if (!ip || clones <= 0 || !reason) |
124 |
; /* erroneous, don't add */ |
125 |
else |
126 |
{ |
127 |
cexcept_t *c = new cexcept_t; |
128 |
|
129 |
c->ip = sstrdup (ip); |
130 |
c->clones = clones; |
131 |
p = strchr (reason, '\n'); |
132 |
if (p != NULL) |
133 |
*p = '\0'; |
134 |
c->reason = sstrdup (reason); |
135 |
clone_exempts.push_back (c); |
136 |
} |
137 |
} |
138 |
else if (!strcmp (item, "CK")) |
139 |
{ |
140 |
char *enable = strtok (NULL, " "); |
141 |
|
142 |
if (enable != NULL) |
143 |
kline_enabled = atoi (enable) != 0; |
144 |
} |
145 |
} |
146 |
|
147 |
fclose (f); |
148 |
} |
149 |
|
150 |
static int |
151 |
is_exempt (char const * const ip) |
152 |
{ |
153 |
/* first check for an exact match */ |
154 |
foreach (cexcept_t *c, clone_exempts) |
155 |
if (!strcmp (ip, c->ip)) |
156 |
return c->clones; |
157 |
|
158 |
/* then look for cidr */ |
159 |
foreach (cexcept_t *c, clone_exempts) |
160 |
if (!match_ips (c->ip, ip)) |
161 |
return c->clones; |
162 |
|
163 |
return 0; |
164 |
} |
165 |
|
166 |
static void |
167 |
os_cmd_clones (sourceinfo_t *si, int parc, char *parv[]) |
168 |
{ |
169 |
command_t const *c; |
170 |
char *cmd = parv[0]; |
171 |
|
172 |
/* Bad/missing arg */ |
173 |
if (!cmd) |
174 |
{ |
175 |
command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES"); |
176 |
command_fail (si, fault::needmoreparams, _("Syntax: CLONES KLINE|LIST|ADDEXEMPT|DELEXEMPT|LISTEXEMPT [parameters]")); |
177 |
return; |
178 |
} |
179 |
|
180 |
c = os_clones_cmds.find (cmd); |
181 |
if (c == NULL) |
182 |
{ |
183 |
command_fail (si, fault::badparams, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == false) ? "msg " : "", opersvs.me->disp); |
184 |
return; |
185 |
} |
186 |
|
187 |
c->exec (si->service, si, parc + 1, parv + 1); |
188 |
} |
189 |
|
190 |
static void |
191 |
os_cmd_clones_kline (sourceinfo_t *si, int parc, char *parv[]) |
192 |
{ |
193 |
char const *arg = parv[0]; |
194 |
|
195 |
if (arg == NULL) |
196 |
arg = ""; |
197 |
|
198 |
if (!strcasecmp (arg, "ON")) |
199 |
{ |
200 |
if (kline_enabled) |
201 |
{ |
202 |
command_fail (si, fault::nochange, _("CLONES klines are already enabled.")); |
203 |
return; |
204 |
} |
205 |
kline_enabled = true; |
206 |
command_success_nodata (si, _("Enabled CLONES klines.")); |
207 |
wallops ("\2%s\2 enabled CLONES klines", get_oper_name (si)); |
208 |
snoop ("CLONES:KLINE:ON: \2%s\2", get_oper_name (si)); |
209 |
logcommand (si, CMDLOG_ADMIN, "CLONES KLINE ON"); |
210 |
write_exemptdb (); |
211 |
} |
212 |
else if (!strcasecmp (arg, "OFF")) |
213 |
{ |
214 |
if (!kline_enabled) |
215 |
{ |
216 |
command_fail (si, fault::nochange, _("CLONES klines are already disabled.")); |
217 |
return; |
218 |
} |
219 |
kline_enabled = false; |
220 |
command_success_nodata (si, _("Disabled CLONES klines.")); |
221 |
wallops ("\2%s\2 disabled CLONES klines", get_oper_name (si)); |
222 |
snoop ("CLONES:KLINE:OFF: \2%s\2", get_oper_name (si)); |
223 |
logcommand (si, CMDLOG_ADMIN, "CLONES KLINE OFF"); |
224 |
write_exemptdb (); |
225 |
} |
226 |
else |
227 |
{ |
228 |
if (kline_enabled) |
229 |
command_success_string (si, "ON", _("CLONES klines are currently enabled.")); |
230 |
else |
231 |
command_success_string (si, "OFF", _("CLONES klines are currently disabled.")); |
232 |
} |
233 |
} |
234 |
|
235 |
static void |
236 |
os_cmd_clones_list (sourceinfo_t *si, int parc, char *parv[]) |
237 |
{ |
238 |
hostentry_t *he; |
239 |
int k = 0; |
240 |
int allowed = 0; |
241 |
|
242 |
foreach (host_pair &hp, hostlist) |
243 |
{ |
244 |
he = hp.second; |
245 |
k = he->clients.size (); |
246 |
|
247 |
if (k > 3) |
248 |
{ |
249 |
if ((allowed = is_exempt (he->ip))) |
250 |
command_success_nodata (si, _("%d from %s (\2EXEMPT\2; allowed %d)"), k, he->ip, allowed); |
251 |
else |
252 |
command_success_nodata (si, _("%d from %s"), k, he->ip); |
253 |
} |
254 |
} |
255 |
|
256 |
command_success_nodata (si, _("End of CLONES LIST")); |
257 |
logcommand (si, CMDLOG_ADMIN, "CLONES LIST"); |
258 |
} |
259 |
|
260 |
static void |
261 |
os_cmd_clones_addexempt (sourceinfo_t *si, int parc, char *parv[]) |
262 |
{ |
263 |
char *ip = parv[0]; |
264 |
char *clonesstr = parv[1]; |
265 |
int clones; |
266 |
char *reason = parv[2]; |
267 |
cexcept_t *c = NULL; |
268 |
|
269 |
if (!ip || !clonesstr) |
270 |
{ |
271 |
command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES ADDEXEMPT"); |
272 |
command_fail (si, fault::needmoreparams, _("Syntax: CLONES ADDEXEMPT <ip> <clones> <reason>")); |
273 |
return; |
274 |
} |
275 |
|
276 |
clones = atoi (clonesstr); |
277 |
if (clones <= DEFAULT_WARN_CLONES) |
278 |
{ |
279 |
command_fail (si, fault::badparams, _("Allowed clones count must be more than %d"), DEFAULT_WARN_CLONES); |
280 |
return; |
281 |
} |
282 |
|
283 |
foreach (cexcept_t *t, clone_exempts) |
284 |
if (!strcmp (ip, t->ip)) |
285 |
c = t; |
286 |
|
287 |
if (c == NULL) |
288 |
{ |
289 |
if (!reason) |
290 |
{ |
291 |
command_fail (si, fault::needmoreparams, STR_INSUFFICIENT_PARAMS, "CLONES ADDEXEMPT"); |
292 |
command_fail (si, fault::needmoreparams, _("Syntax: CLONES ADDEXEMPT <ip> <clones> <reason>")); |
293 |
return; |
294 |
} |
295 |
c = new cexcept_t; |
296 |
c->ip = sstrdup (ip); |
297 |
c->reason = sstrdup (reason); |
298 |
clone_exempts.push_back (c); |
299 |
command_success_nodata (si, _("Added \2%s\2 to clone exempt list."), ip); |
300 |
} |
301 |
else |
302 |
{ |
303 |
if (reason) |
304 |
{ |
305 |
sfree (c->reason); |
306 |
c->reason = sstrdup (reason); |
307 |
} |
308 |
command_success_nodata (si, _("Updated \2%s\2 in clone exempt list."), ip); |
309 |
} |
310 |
c->clones = clones; |
311 |
|
312 |
snoop ("CLONES:ADDEXEMPT: \2%s\2 \2%d\2 (%s) by \2%s\2", ip, clones, c->reason, get_oper_name (si)); |
313 |
logcommand (si, CMDLOG_ADMIN, "CLONES ADDEXEMPT %s %d %s", ip, clones, c->reason); |
314 |
write_exemptdb (); |
315 |
} |
316 |
|
317 |
static void |
318 |
os_cmd_clones_delexempt (sourceinfo_t *si, int parc, char *parv[]) |
319 |
{ |
320 |
cexcept_t::vector_type::iterator it = clone_exempts.begin (); |
321 |
cexcept_t::vector_type::iterator et = clone_exempts.end (); |
322 |
char *arg = parv[0]; |
323 |
|
324 |
if (!arg) |
325 |
return; |
326 |
|
327 |
while (it != et) |
328 |
{ |
329 |
cexcept_t *c = *it; |
330 |
|
331 |
if (!strcmp (c->ip, arg)) |
332 |
{ |
333 |
sfree (c->ip); |
334 |
sfree (c->reason); |
335 |
delete c; |
336 |
clone_exempts.erase (it); |
337 |
command_success_nodata (si, _("Removed \2%s\2 from clone exempt list."), arg); |
338 |
snoop ("CLONES:DELEXEMPT: \2%s\2 by \2%s\2", arg, get_oper_name (si)); |
339 |
logcommand (si, CMDLOG_ADMIN, "CLONES DELEXEMPT %s", arg); |
340 |
write_exemptdb (); |
341 |
return; |
342 |
} |
343 |
|
344 |
++it; |
345 |
} |
346 |
|
347 |
command_fail (si, fault::nosuch_target, _("\2%s\2 not found in clone exempt list."), arg); |
348 |
} |
349 |
|
350 |
static void |
351 |
os_cmd_clones_listexempt (sourceinfo_t *si, int parc, char *parv[]) |
352 |
{ |
353 |
foreach (cexcept_t *c, clone_exempts) |
354 |
command_success_nodata (si, "%s (%d, %s)", c->ip, c->clones, c->reason); |
355 |
|
356 |
command_success_nodata (si, _("End of CLONES LISTEXEMPT")); |
357 |
logcommand (si, CMDLOG_ADMIN, "CLONES LISTEXEMPT"); |
358 |
} |
359 |
|
360 |
static void |
361 |
clones_newuser (user_t *u) |
362 |
{ |
363 |
unsigned i = 0; |
364 |
hostentry_t *he; |
365 |
host_map::iterator heit; |
366 |
unsigned allowed = 0; |
367 |
|
368 |
/* User has no IP, ignore him */ |
369 |
if (is_internal_client (u) || *u->ip == '\0') |
370 |
return; |
371 |
|
372 |
heit = hostlist.find (u->ip); |
373 |
if (heit == hostlist.end ()) |
374 |
{ |
375 |
he = new hostentry_t; |
376 |
strlcpy (he->ip, u->ip, sizeof he->ip); |
377 |
hostlist[he->ip] = he; |
378 |
} |
379 |
else |
380 |
he = heit->second; |
381 |
he->clients.push_back (u); |
382 |
i = he->clients.size (); |
383 |
|
384 |
if (i > DEFAULT_WARN_CLONES) |
385 |
{ |
386 |
allowed = is_exempt (u->ip); |
387 |
|
388 |
if (allowed == 0 || i > allowed) |
389 |
{ |
390 |
if (he->lastaction + 300 < NOW) |
391 |
he->lastaction_clones = i; |
392 |
else if (i <= he->lastaction_clones) |
393 |
return; |
394 |
else |
395 |
he->lastaction_clones = i; |
396 |
he->lastaction = NOW; |
397 |
if (allowed == 0 && i < config_options.default_clone_limit) |
398 |
snoop ("CLONES: %d clones on %s (%s!%s@%s)", i, u->ip, u->nick, u->user, u->host); |
399 |
else if (allowed != 0 && i < allowed + EXEMPT_GRACE) |
400 |
snoop ("CLONES: %d clones on %s (%s!%s@%s) (%d allowed)", i, u->ip, u->nick, u->user, u->host, allowed); |
401 |
else if (!kline_enabled) |
402 |
snoop ("CLONES: %d clones on %s (%s!%s@%s) (TKLINE disabled)", i, u->ip, u->nick, u->user, u->host); |
403 |
else |
404 |
{ |
405 |
snoop ("CLONES: %d clones on %s (%s!%s@%s) (TKLINE due to excess clones)", i, u->ip, u->nick, u->user, u->host); |
406 |
slog (LG_INFO, "clones_newuser(): klining *@%s (user %s!%s@%s)", u->ip, u->nick, u->user, u->host); |
407 |
phandler->kline_sts ("*", "*", u->ip, 3600, "Excessive clones"); |
408 |
} |
409 |
} |
410 |
} |
411 |
} |
412 |
|
413 |
static void |
414 |
clones_userquit (user_t *u) |
415 |
{ |
416 |
host_map::iterator heit; |
417 |
hostentry_t *he; |
418 |
|
419 |
/* User has no IP, ignore him */ |
420 |
if (is_internal_client (u) || *u->ip == '\0') |
421 |
return; |
422 |
|
423 |
heit = hostlist.find (u->ip); |
424 |
if (heit == hostlist.end ()) |
425 |
{ |
426 |
slog (LG_DEBUG, "clones_userquit(): hostentry for %s not found??", u->ip); |
427 |
return; |
428 |
} |
429 |
he = heit->second; |
430 |
|
431 |
user_t::vector_type::iterator it = find_element (u, he->clients); |
432 |
if (it != he->clients.end ()) |
433 |
{ |
434 |
he->clients.erase (it); |
435 |
if (he->clients.empty ()) |
436 |
{ |
437 |
hostlist.erase (he->ip); |
438 |
delete he; |
439 |
} |
440 |
} |
441 |
} |
442 |
|
443 |
static inline void |
444 |
clones_newuser_pair (user_t::pair_type &up) |
445 |
{ |
446 |
clones_newuser (up.second); |
447 |
} |
448 |
|
449 |
bool |
450 |
_modinit (module *m) |
451 |
{ |
452 |
os_cmdtree << os_clones; |
453 |
|
454 |
os_clones_cmds << os_clones_kline; |
455 |
os_clones_cmds << os_clones_list; |
456 |
os_clones_cmds << os_clones_addexempt; |
457 |
os_clones_cmds << os_clones_delexempt; |
458 |
os_clones_cmds << os_clones_listexempt; |
459 |
|
460 |
help_addentry (os_helptree, "CLONES", "help/operserv/clones", NULL); |
461 |
|
462 |
user_t::callback.add.attach (clones_newuser); |
463 |
user_t::callback.remove.attach (clones_userquit); |
464 |
|
465 |
load_exemptdb (); |
466 |
|
467 |
/* add everyone to host hash */ |
468 |
std::for_each (user_t::map.begin (), user_t::map.end (), clones_newuser_pair); |
469 |
|
470 |
return true; |
471 |
} |
472 |
|
473 |
void |
474 |
_moddeinit (void) |
475 |
{ |
476 |
foreach (host_pair &hp, hostlist) |
477 |
delete hp.second; |
478 |
|
479 |
while (!clone_exempts.empty ()) |
480 |
{ |
481 |
cexcept_t *c = clone_exempts.back (); |
482 |
|
483 |
sfree (c->ip); |
484 |
sfree (c->reason); |
485 |
delete c; |
486 |
|
487 |
clone_exempts.pop_back (); |
488 |
} |
489 |
|
490 |
os_cmdtree >> os_clones; |
491 |
|
492 |
os_clones_cmds >> os_clones_kline; |
493 |
os_clones_cmds >> os_clones_list; |
494 |
os_clones_cmds >> os_clones_addexempt; |
495 |
os_clones_cmds >> os_clones_delexempt; |
496 |
os_clones_cmds >> os_clones_listexempt; |
497 |
|
498 |
help_delentry (os_helptree, "CLONES"); |
499 |
|
500 |
user_t::callback.add.detach (clones_newuser); |
501 |
user_t::callback.remove.detach (clones_userquit); |
502 |
} |