ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/operserv/clones.C
Revision: 1.7
Committed: Sat Sep 22 14:27:28 2007 UTC (16 years, 8 months ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.6: +63 -85 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# Content
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 }