]> jfr.im git - irc/quakenet/newserv.git/blame - chanfix/chanfix.c
gline->glines2, trusts_search->trusts2_search, trusts_newsearch->trusts2_newsearch
[irc/quakenet/newserv.git] / chanfix / chanfix.c
CommitLineData
8a25ddc6
P
1/* shroud's chanfix */
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <unistd.h>
6#include <string.h>
7#include <time.h>
8#include <sys/time.h>
9#include "chanfix.h"
10#include "../splitlist/splitlist.h"
11#include "../localuser/localuserchannel.h"
12#include "../core/schedule.h"
13#include "../core/error.h"
14#include "../nick/nick.h"
15#include "../lib/irc_string.h"
16#include "../control/control.h"
5857b2db
P
17#include "../lib/version.h"
18
19MODULE_VERSION("")
8a25ddc6
P
20
21/* control's nick */
22extern nick *mynick;
23
24int cfext;
25int cfnext;
e8966eea 26int cffailedinit;
8a25ddc6
P
27
28/* user accessible commands */
29int cfcmd_debug(void *source, int cargc, char **cargv);
30int cfcmd_debughistogram(void *source, int cargc, char **cargv);
31int cfcmd_debugsample(void *source, int cargc, char **cargv);
32int cfcmd_debugexpire(void *source, int cargc, char **cargv);
33int cfcmd_chanopstat(void *source, int cargc, char **cargv);
34int cfcmd_chanoplist(void *source, int cargc, char **cargv);
35int cfcmd_chanfix(void *source, int cargc, char **cargv);
36int cfcmd_showregs(void *source, int cargc, char **cargv);
37int cfcmd_requestop(void *source, int cargc, char **cargv);
38int cfcmd_save(void *source, int cargc, char **cargv);
39int cfcmd_load(void *source, int cargc, char **cargv);
40
41/* scheduled events */
42void cfsched_dosample(void *arg);
43void cfsched_doexpire(void *arg);
44void cfsched_dosave(void *arg);
45
46/* hooks */
47void cfhook_autofix(int hook, void *arg);
48void cfhook_statsreport(int hook, void *arg);
49void cfhook_auth(int hook, void *arg);
50void cfhook_lostnick(int hook, void *arg);
51
52/* helper functions */
53regop *cf_createregop(nick *np, chanindex *cip);
54void cf_deleteregop(chanindex *cip, regop *ro);
55unsigned long cf_gethash(nick *np, int type);
56
57int cf_storechanfix(void);
58int cf_loadchanfix(void);
59void cf_free(void);
60
61#define min(a,b) ((a > b) ? b : a)
62
63void _init() {
64 cfext = registerchanext("chanfix");
65 cfnext = registernickext("chanfix");
66
67 if (cfext < 0 || cfnext < 0) {
68 Error("chanfix", ERR_ERROR, "Couldn't register channel and/or nick extension");
e8966eea 69 cffailedinit = 1;
8a25ddc6
P
70 } else {
71 schedulerecurring(time(NULL), 0, CFSAMPLEINTERVAL, &cfsched_dosample, NULL);
72 schedulerecurring(time(NULL), 0, CFEXPIREINTERVAL, &cfsched_doexpire, NULL);
73 schedulerecurring(time(NULL), 0, CFAUTOSAVEINTERVAL, &cfsched_dosave, NULL);
74
95ff6b03
P
75 registercontrolhelpcmd("cfdebug", NO_DEVELOPER, 1, &cfcmd_debug, "Display Debug Information on chanfix data for channel");
76 registercontrolhelpcmd("cfhistogram", NO_DEVELOPER, 1, &cfcmd_debughistogram, "Display Debug Histogram of chanfix data for channel");
8a25ddc6 77#if CFDEBUG
95ff6b03
P
78 registercontrolhelpcmd("cfsample", NO_DEVELOPER, &cfcmd_debugsample, "DEBUG Command - must not be loaded on live instances");
79 registercontrolhelpcmd("cfexpire", NO_DEVELOPER, 1, &cfcmd_debugexpire, "DEBUG Command - must not be loaded on live instances");
8a25ddc6 80#endif
95ff6b03
P
81 registercontrolhelpcmd("chanopstat", NO_OPER, 1, &cfcmd_chanopstat, "Shows chanop statistics for a given channel");
82 registercontrolhelpcmd("chanoplist", NO_OPER, 1, &cfcmd_chanoplist, "Shows lists of known chanops, including scores");
8a25ddc6 83
95ff6b03
P
84 registercontrolhelpcmd("chanfix", NO_OPER, 1, &cfcmd_chanfix, "Perform a chanfix on a channel to op known users only");
85 registercontrolhelpcmd("showregs", NO_OPER, 1, &cfcmd_showregs, "Show regular ops known on a channel (including services)");
8a25ddc6
P
86#if CFDEBUG
87 /* should we disable this in the 'final' build? */
95ff6b03 88 /* registercontrolcmd("requestop", 0, 2, &cfcmd_requestop); */
8a25ddc6 89#endif
95ff6b03
P
90 registercontrolhelpcmd("cfsave", NO_DEVELOPER, 0, &cfcmd_save, "Force save of chanfix data");
91 registercontrolhelpcmd("cfload", NO_DEVELOPER, 0, &cfcmd_load, "Force load of chanfix data");
8a25ddc6
P
92
93#if CFAUTOFIX
94 registerhook(HOOK_CHANNEL_DEOPPED, &cfhook_autofix);
95 registerhook(HOOK_CHANNEL_PART, &cfhook_autofix);
96 registerhook(HOOK_CHANNEL_KICK, &cfhook_autofix);
97 registerhook(HOOK_CHANNEL_JOIN, &cfhook_autofix);
98#endif
99
100 registerhook(HOOK_CORE_STATSREQUEST, &cfhook_statsreport);
101 registerhook(HOOK_NICK_ACCOUNT, &cfhook_auth);
102 registerhook(HOOK_NICK_LOSTNICK, &cfhook_lostnick);
103
104 cf_loadchanfix();
105
e8966eea 106 cffailedinit = 0;
8a25ddc6
P
107 }
108}
109
110void _fini() {
111 int i;
112 nick *nip;
113
e8966eea 114 if (cffailedinit == 0) {
8a25ddc6
P
115 deleteschedule(NULL, &cfsched_dosample, NULL);
116 deleteschedule(NULL, &cfsched_doexpire, NULL);
117 deleteschedule(NULL, &cfsched_dosave, NULL);
118
119 cf_storechanfix();
120
121 cf_free();
122
123 for (i=0; i<NICKHASHSIZE; i++)
124 for (nip=nicktable[i]; nip; nip=nip->next)
125 free(nip->exts[cfnext]);
126
127 releasechanext(cfext);
128 releasenickext(cfnext);
129
130 deregistercontrolcmd("cfdebug", &cfcmd_debug);
131 deregistercontrolcmd("cfhistogram", &cfcmd_debughistogram);
132#if CFDEBUG
133 deregistercontrolcmd("cfsample", &cfcmd_debugsample);
134 deregistercontrolcmd("cfexpire", &cfcmd_debugexpire);
135#endif
136 deregistercontrolcmd("chanopstat", &cfcmd_chanopstat);
137 deregistercontrolcmd("chanoplist", &cfcmd_chanoplist);
1b0a2e39 138 deregistercontrolcmd("chanfix", &cfcmd_chanfix);
8a25ddc6
P
139 deregistercontrolcmd("showregs", &cfcmd_showregs);
140#if CFDEBUG
141// deregistercontrolcmd("requestop", &cfcmd_requestop);
142#endif
143 deregistercontrolcmd("cfsave", &cfcmd_save);
144 deregistercontrolcmd("cfload", &cfcmd_load);
145
146#if CFAUTOFIX
147 deregisterhook(HOOK_CHANNEL_DEOPPED, &cfhook_autofix);
148 deregisterhook(HOOK_CHANNEL_PART, &cfhook_autofix);
149 deregisterhook(HOOK_CHANNEL_KICK, &cfhook_autofix);
150 deregisterhook(HOOK_CHANNEL_JOIN, &cfhook_autofix);
151#endif
152
153 deregisterhook(HOOK_CORE_STATSREQUEST, &cfhook_statsreport);
154 deregisterhook(HOOK_NICK_ACCOUNT, &cfhook_auth);
155 deregisterhook(HOOK_NICK_LOSTNICK, &cfhook_lostnick);
156 }
157}
158
159int cfcmd_debug(void *source, int cargc, char **cargv) {
160 nick *np = (nick*)source;
161 chanindex *cip;
162 chanfix *cf;
163 regop *ro;
164 int i;
165
166 if (cargc < 1) {
167 controlreply(np, "Syntax: cfdebug <#channel>");
168
169 return CMD_ERROR;
170 }
171
172 cip = findchanindex(cargv[0]);
173
174 if (cip == NULL) {
175 controlreply(np, "No such channel.");
176
177 return CMD_ERROR;
178 } else
179 controlreply(np, "Found channel %s. Retrieving chanfix information...", cargv[0]);
180
181 cf = cip->exts[cfext];
182
183 if (cf == NULL) {
184 controlreply(np, "No chanfix information for %s", cargv[0]);
185
186 return CMD_ERROR;
187 } else
188 controlreply(np, "Found chanfix information. Dumping...");
189
190 for (i=0;i<cf->regops.cursi;i++) {
191 ro = ((regop**)cf->regops.content)[i];
192
193 controlreply(np, "%d. type: %s hash: 0x%x lastopped: %d uh: %s score: %d",
194 i + 1, ro->type == CFACCOUNT ? "CFACCOUNT" : "CFHOST", ro->hash,
195 ro->lastopped, ro->uh ? ro->uh->content : "(unknown)", ro->score);
196 }
197
198 controlreply(np, "Done.");
199
200 return CMD_OK;
201}
202
203int cfcmd_debughistogram(void *source, int cargc, char **cargv) {
204 nick *np = (nick*)source;
205 int i,a,score;
206 chanindex* cip;
207 chanfix *cf;
208 char buf[400];
209 int histogram[10001]; /* 625 (lines) * 16 (columns/line) + 1 (for histogram[0]) */
210
211 for (i = 0; i < 10001; i++)
212 histogram[i] = 0;
213
214 for (i=0; i<CHANNELHASHSIZE; i++) {
215 for (cip=chantable[i]; cip; cip=cip->next) {
216 if ((cf = cip->exts[cfext]) != NULL) {
217 for (a=0;a<cf->regops.cursi;a++) {
218 score = ((regop**)cf->regops.content)[a]->score;
219
220 if (score < 10001)
221 histogram[score]++;
222 }
223 }
224 }
225 }
226
227 controlreply(np, "--- Histogram of chanfix scores");
228
229 for (i = 1; i < 10001; i += 16) {
230 buf[0] = '\0';
231
232 for (a = 0; a < 16; a++) {
233 if (a != 0)
234 strcat(buf, " ");
235
236 sprintf(buf+strlen(buf),"%d", histogram[i+a]);
237 }
238
239 controlreply(np, "%6d: %s", i, buf);
240 }
241
242 controlreply(np, "--- End of histogram");
243
244 return CMD_OK;
245}
246
247int cfcmd_debugsample(void *source, int cargc, char **cargv) {
248 cfsched_dosample(NULL);
249
250 controlreply((nick*)source, "Done.");
251
252 return CMD_OK;
253}
254
255int cfcmd_debugexpire(void *source, int cargc, char **cargv) {
256 cfsched_doexpire(NULL);
257
258 controlreply((nick*)source, "Done.");
259
260 return CMD_OK;
261}
262
263/* used for qsorting int arrays */
264int cmpint(const void *a, const void *b) {
265 int p = *(int*)a;
266 int q = *(int*)b;
267
268 if (p > q)
269 return -1;
270 else if (p < q)
271 return 1;
272 else
273 return 0;
274}
275
276/* used for qsorting regop* arrays */
277int cmpregop(const void *a, const void *b) {
278 regop *p = *(regop**)a;
279 regop *q = *(regop**)b;
280
281 if (p->score > q->score)
282 return -1;
283 else if (p->score < q->score)
284 return 1;
285 else
286 return 0;
287}
288
289int cf_getsortedregops(chanfix *cf, int max, regop **list) {
290 int i;
291
292 if (cf == NULL)
293 return 0;
294
295 qsort(cf->regops.content, cf->regops.cursi, sizeof(regop*), cmpregop);
296
297 for (i = 0; i < min(max, cf->regops.cursi); i++) {
298 list[i] = ((regop**)cf->regops.content)[i];
299 }
300
301 return i;
302}
303
304int cfcmd_chanopstat(void *source, int cargc, char **cargv) {
305 nick *np = (nick*)source;
306 nick *np2;
307 channel *cp;
308 chanfix *cf;
309 regop *ro;
310 regop *rolist[10];
311 int i, a, count;
312 int *scores;
313 char buf[400];
314
315 if (cargc < 1) {
316 controlreply(np, "Syntax: chanopstat <#channel>");
317
318 return CMD_ERROR;
319 }
320
321 cp = findchannel(cargv[0]);
322
323 if (cp == NULL) {
324 controlreply(np, "No such channel.");
325
326 return CMD_ERROR;
327 }
328
329 cf = cp->index->exts[cfext];
330
331 if (cf == NULL) {
332 controlreply(np, "No chanfix information for %s", cargv[0]);
333
334 return CMD_ERROR;
335 }
336
337 /* known ops */
338 count = cf_getsortedregops(cf, 10, rolist);
339
340 buf[0] = '\0';
341
342 for (i=0;i<count;i++) {
343 if (i != 0)
344 strcat(buf, " ");
345
346 sprintf(buf+strlen(buf),"%d", rolist[i]->score);
347 }
348
349 controlreply(np, "Scores of \"best ops\" on %s are: %s", cargv[0], buf);
350
351 /* current ops */
352 scores = (int*)malloc(sizeof(int) * cp->users->hashsize);
353
354 i = 0;
355
356 for (a=0;a<cp->users->hashsize;a++) {
357 if ((cp->users->content[a] != nouser) && (cp->users->content[a] & CUMODE_OP)) {
358 np2 = getnickbynumeric(cp->users->content[a]);
359
360 ro = cf_findregop(np2, cp->index, CFACCOUNT | CFHOST);
361
362 if (ro)
363 scores[i++] = ro->score;
364 }
365 }
366
367 qsort(scores, i, sizeof(int), &cmpint);
368
369 buf[0] = '\0';
370
371 for (a=0;a<min(i,20);a++) {
372 if (scores[a] == 0)
373 break;
374
375 if (a != 0)
376 strcat(buf, " ");
377
378 sprintf(buf+strlen(buf),"%d", scores[a]);
379 }
380
381 free(scores);
382
383 controlreply(np, "Scores of current ops on %s are: %s", cargv[0], buf);
384
385 controlreply(np, "Done.");
386
387 return CMD_OK;
388}
389
390int cfcmd_chanoplist(void *source, int cargc, char **cargv) {
391 nick *np = (nick*)source;
392 nick *np2;
393 chanindex *cip;
394 chanfix *cf;
395 regop *rolist[50];
396 int i,a,count;
397 time_t ct;
398 const char* cdate;
399 char* date;
400 unsigned long *hand;
401
402 if (cargc < 1) {
403 controlreply(np, "Syntax: chanoplist <#channel>");
404
405 return CMD_ERROR;
406 }
407
408 cip = findchanindex(cargv[0]);
409
410 if (cip == NULL) {
411 controlreply(np, "No such channel.");
412
413 return CMD_ERROR;
414 }
415
416 cf = cip->exts[cfext];
417
418 if (cf == NULL) {
419 controlreply(np, "No chanfix information for %s", cargv[0]);
420
421 return CMD_ERROR;
422 }
423
424 count = cf_getsortedregops(cf, 50, rolist);
425
426 controlreply(np, "Pos Score Type User/Last seen");
427
428 for (i=0;i<count;i++) {
429 np2 = cf_findnick(rolist[i], cip);
430
431 if (np2 != NULL) {
432 hand = getnumerichandlefromchanhash(cip->channel->users, np2->numeric);
433
434 /* hand should be non-null in all cases */
435 if (hand != NULL)
436 controlreply(np, "%3d %5d %-7s %1s%-16s %s@%s (%s)", i + 1, rolist[i]->score,
437 (rolist[i]->type == CFACCOUNT) ? "account" : "host",
438 (*hand & CUMODE_OP) ? "@" : "", np2->nick, np2->ident,
439 np2->host->name->content, np2->realname->name->content);
440 } else {
441 ct = rolist[i]->lastopped;
442
443 cdate = ctime(&ct);
444
445 date = (char*)malloc(strlen(cdate) + 1);
446 strcpy(date, cdate);
447
448 for (a=0;a<strlen(date);a++) {
449 if (date[a] == '\n') {
450 date[a] = '\0';
451 break;
452 }
453 }
454
455 controlreply(np, "%3d %5d %-7s %1s%-16s %s Last seen: %s", i + 1, rolist[i]->score,
456 (rolist[i]->type == CFACCOUNT) ? "account" : "host",
457 "", "!NONE!",
458 rolist[i]->uh ? rolist[i]->uh->content : "!UNKNOWN!", date);
459
460 free(date);
461 }
462 }
463
464 controlreply(np, "Done.");
465
466 return CMD_OK;
467}
468
469int cfcmd_chanfix(void *source, int cargc, char **cargv) {
470 nick *np = (nick*)source;
471 channel *cp;
472 int ret;
473
474 if (cargc < 1) {
475 controlreply(np, "Syntax: chanfix <#channel>");
476
477 return CMD_ERROR;
478 }
479
480 cp = findchannel(cargv[0]);
481
482 if (cp == NULL) {
483 controlreply(np, "No such channel.");
484
485 return CMD_ERROR;
486 }
487
488 if (sp_countsplitservers() > 0) {
489 controlreply(np, "Chanfix cannot be used during a netsplit.");
490
491 return CMD_ERROR;
492 }
493
494 ret = cf_fixchannel(cp);
495
496 switch (ret) {
497 case CFX_FIXED:
498 controlreply(np, "Channel was fixed.");
499 break;
500 case CFX_NOCHANFIX:
501 controlreply(np, "Channel cannot be fixed: no chanfix information");
502 break;
503 case CFX_FIXEDFEWOPS:
504 controlreply(np, "Channel was fixed but only a few ops could be found and reopped.");
505 break;
506 default:
507 /* oops */
508 break;
509 }
510
511 return CMD_OK;
512}
513
514int cfcmd_showregs(void *source, int cargc, char **cargv) {
515 nick *np = (nick*)source;
516 nick *np2;
517 chanfix *cf;
518 channel *cp;
519 int a, i, count, ops;
520 regop *rolist[50];
521
522 if (cargc < 1) {
523 controlreply(np, "Syntax: showregs <#channel>");
524
525 return CMD_ERROR;
526 }
527
528 cp = findchannel(cargv[0]);
529
530 if (cp == NULL) {
531 controlreply(np, "No such channel.");
532
533 return CMD_ERROR;
534 }
535
536 cf = cp->index->exts[cfext];
537
538 if (cf == NULL) {
539 controlreply(np, "No chanfix information for %s", cargv[0]);
540
541 return CMD_ERROR;
542 }
543
544 count = 0;
545
546 for(a=0;a<cp->users->hashsize;a++) {
547 if(cp->users->content[a] != nouser) {
548 np2 = getnickbynumeric(cp->users->content[a]);
549
550 if (IsService(np2)) {
551 controlreply(np, "%s (service)", np2->nick);
552 count++;
553 }
554 }
555 }
556
557 /* now get a sorted list of regops */
558 ops = cf_getsortedregops(cf, 50, rolist);
559
560 /* and show some of them */
561 for (i=0;i<ops;i++) {
562 if (count >= CFMAXOPS || rolist[i]->score < rolist[0]->score / 2 || rolist[i]->score < CFMINSCORE)
563 break;
564
565 np2 = cf_findnick(rolist[i], cp->index);
566
567 if (np2) {
568 controlreply(np, "%s (%s)", np2->nick, rolist[i]->type == CFACCOUNT ? "account" : "host");
569
570 count++;
571 }
572 }
573
574 controlreply((nick*)source, "--- End of list: %d users listed", count);
575
576 return CMD_OK;
577}
578
579int cfcmd_requestop(void *source, int cargc, char **cargv) {
580 nick *np = (nick*)source;
581 nick *user = np;
582 channel *cp;
583 int ret, a;
584 unsigned long *hand;
585 modechanges changes;
586
587 if (cargc < 1) {
588 controlreply(np, "Syntax: requestop <#channel> [nick]");
589
590 return CMD_ERROR;
591 }
592
593 cp = findchannel(cargv[0]);
594
595 if (cp == NULL) {
596 controlreply(np, "No such channel.");
597
598 return CMD_ERROR;
599 }
600
601 if (cargc > 1) {
602 user = getnickbynick(cargv[1]);
603
604 if (!user) {
605 controlreply(np, "No such user.");
606
607 return CMD_ERROR;
608 }
609 }
610
611 hand = getnumerichandlefromchanhash(cp->users, user->numeric);
612
613 if (hand == NULL) {
614 controlreply(np, "User %s is not on channel %s.", user->nick, cargv[0]);
615
616 return CMD_ERROR;
617 }
618
619 for (a=0;a<cp->users->hashsize;a++) {
620 if ((cp->users->content[a] != nouser) && (cp->users->content[a] & CUMODE_OP)) {
621 controlreply(np, "There are ops on channel %s. This command can only be"
622 " used if there are no ops.", cargv[0]);
623
624 return CMD_ERROR;
625 }
626 }
627
628 if (sp_countsplitservers() > 0) {
629 controlreply(np, "One or more servers are currently split. Wait until the"
630 " netsplit is over and try again.");
631
632 return CMD_ERROR;
633 }
634
635 if (cf_wouldreop(user, cp)) {
636 localsetmodeinit(&changes, cp, mynick);
637 localdosetmode_nick(&changes, user, MC_OP);
638 localsetmodeflush(&changes, 1);
639
640 controlreply(np, "Chanfix opped you on the specified channel.");
641 } else {
642 ret = cf_fixchannel(cp);
643
644 if (ret == CFX_NOUSERSAVAILABLE)
645 controlreply(np, "Chanfix knows regular ops for that channel. They will"
646 " be opped when they return.");
647 else
648 controlreply(np, "Chanfix has opped known ops for that channel.");
649 }
650
651 return CMD_OK;
652}
653
654int cfcmd_save(void *source, int cargc, char **cargv) {
655 nick *np = (nick*)source;
656 int count;
657
658 count = cf_storechanfix();
659
660 controlreply(np, "%d chanfix records saved.", count);
661
662 return CMD_OK;
663}
664
665void cf_free(void) {
666 int a, i;
667 chanfix *cf;
668 chanindex *cip;
669
670 /* free old stuff */
671 for (i=0; i<CHANNELHASHSIZE; i++) {
672 for (cip=chantable[i]; cip; cip=cip->next) {
673 if ((cf = cip->exts[cfext]) != NULL) {
674 for (a=0;a<cf->regops.cursi;a++) {
675 freesstring(((regop**)cf->regops.content)[a]->uh);
676 free(((regop**)cf->regops.content)[a]);
677 }
678
679 array_free(&(((chanfix*)cip->exts[cfext])->regops));
680 }
681
682 free(cip->exts[cfext]);
683 cip->exts[cfext] = NULL;
684 }
685 }
686}
687
688int cfcmd_load(void *source, int cargc, char **cargv) {
689 nick *np = (nick*)source;
690 int count;
691
692 count = cf_loadchanfix();
693
694 controlreply(np, "%d chanfix records loaded.", count);
695
696 return CMD_OK;
697}
698
699int cf_hasauthedcloneonchan(nick *np, channel *cp) {
700 nick *jp;
701 unsigned long *hand;
702
703 for (jp = np->host->nicks; jp; jp = jp->nextbyhost) {
704 if (!IsAccount(jp) || jp->numeric == np->numeric)
705 continue;
706
707 hand = getnumerichandlefromchanhash(cp->users, jp->numeric);
708
709 if (hand && (*hand & CUMODE_OP) && strcmp(np->ident, jp->ident) == 0)
710 return 1;
711 }
712
713 return 0;
714}
715
716void cfsched_dosample(void *arg) {
717 int i,a,now,cfscore,cfnewro,cfuhost,diff;
718 channel *cp;
719 chanindex *cip;
720 nick *np;
721 chanfix *cf;
722 regop *ro, *roh;
723 struct timeval start;
724 struct timeval end;
725 char buf[USERLEN+1+HOSTLEN+1];
726
727 now = getnettime();
728
729 cfuhost = cfscore = cfnewro = 0;
730
731 if (sp_countsplitservers() > CFMAXSPLITSERVERS)
732 return;
733
734 gettimeofday(&start, NULL);
735
736 for (i=0; i<CHANNELHASHSIZE; i++) {
737 for (cip=chantable[i]; cip; cip=cip->next) {
738 cf = (chanfix*)cip->exts[cfext];
739 cp = cip->channel;
740
741 if (!cp || cp->users->totalusers < CFMINUSERS)
742 continue;
743
744 for (a=0;a<cp->users->hashsize;a++) {
745 if ((cp->users->content[a] != nouser) && (cp->users->content[a] & CUMODE_OP)) {
746 np = getnickbynumeric(cp->users->content[a]);
747
748#if !CFDEBUG
749 if (IsService(np))
750 continue;
751#endif
752
753 roh = ro = cf_findregop(np, cip, CFACCOUNT | CFHOST);
754
755 if ((ro == NULL || (IsAccount(np) && ro->type == CFHOST)) &&
756 !cf_hasauthedcloneonchan(np, cp)) {
757 ro = cf_createregop(np, cip);
758 cfnewro++;
759 }
760
761 /* lastopped == now if the user has clones, we obviously
762 * don't want to give them points in this case */
763 if (ro && ro->lastopped != now) {
764 if (ro->type != CFHOST || !cf_hasauthedcloneonchan(np, cp)) {
765 ro->score++;
766 cfscore++;
767 }
768
769 /* merge any matching CFHOST records */
770 if (roh && roh->type == CFHOST && ro->type == CFACCOUNT) {
771 /* hmm */
772 ro->score += roh->score;
773
774 cf_deleteregop(cip, roh);
775 }
776
777 /* store the user's account/host if we have to */
778 if (ro->uh == NULL && ro->score >= CFMINSCOREUH) {
779 if (ro->type == CFACCOUNT)
780 ro->uh = getsstring(np->authname, ACCOUNTLEN);
781 else {
782 strcpy(buf, np->ident);
783 strcat(buf, "@");
784 strcat(buf, np->host->name->content);
785
786 roh->uh = getsstring(buf, USERLEN+1+HOSTLEN);
787 }
788
789 cfuhost++;
790 }
791
792 ro->lastopped = now;
793 }
794 }
795 }
796 }
797 }
798
799 cp = findchannel("#qnet.chanfix");
800
801 if (cp) {
802 gettimeofday(&end, NULL);
803
804 diff = (end.tv_sec * 1000 + end.tv_usec / 1000) -
805 (start.tv_sec * 1000 + start.tv_usec / 1000);
806
807 sendmessagetochannel(mynick, cp, "sampled chanfix scores, assigned %d new"
808 " points, %d new regops, %d user@hosts added, deltaT: %dms", cfscore,
809 cfnewro, cfuhost, diff);
810 }
811}
812
813void cfsched_doexpire(void *arg) {
814 channel *cp;
815 chanindex *cip;
816 chanindex *ncip;
817 chanfix *cf;
818 int i,a,cfscore,cfregop,cffreeuh,diff;
819 regop **rolist;
820 regop *ro;
821 struct timeval start;
822 struct timeval end;
9b145387 823 time_t currenttime;
8a25ddc6
P
824
825 cffreeuh = cfscore = cfregop = 0;
826
827 gettimeofday(&start, NULL);
9b145387 828 currenttime=getnettime();
8a25ddc6
P
829
830 for (i=0; i<CHANNELHASHSIZE; i++) {
831 for (cip=chantable[i]; cip; cip=cip->next) {
832 cf = (chanfix*)cip->exts[cfext];
833
834 if (cf) {
835 rolist = (regop**)cf->regops.content;
836
837 for (a=0;a<cf->regops.cursi;a++) {
838 ro = rolist[a];
839
9b145387 840 if ((currenttime - ro->lastopped > 2 * CFSAMPLEINTERVAL) && ro->score) {
8a25ddc6
P
841 ro->score--;
842 cfscore++;
843 }
844
845 if (ro->score < CFMINSCOREUH && ro->uh) {
846 freesstring(ro->uh);
847 ro->uh = NULL;
848
849 cffreeuh++;
850 }
851
9b145387 852 if (ro->score == 0 || ro->lastopped < currenttime - CFREMEMBEROPS) {
8a25ddc6
P
853 cf_deleteregop(cip, ro);
854 cfregop++;
855 }
856 }
857 }
858 }
859 }
860
861 /* stolen from channel/channelindex.c */
862 for (i=0;i<CHANNELHASHSIZE;i++) {
863 for (cip=chantable[i];cip;cip=ncip) {
864 /* CAREFUL: deleting items from chains you're walking is bad */
865 ncip=cip->next;
866
867 /* try to delete it if there's no chanfix* pointer */
868 if (cip->exts[cfext] == NULL)
869 releasechanindex(cip);
870 }
871 }
872
873 cp = findchannel("#qnet.chanfix");
874
875 if (cp) {
876 gettimeofday(&end, NULL);
877
878 diff = (end.tv_sec * 1000 + end.tv_usec / 1000) -
879 (start.tv_sec * 1000 + start.tv_usec / 1000);
880
881 sendmessagetochannel(mynick, cp, "expired chanfix scores, purged %d points,"
882 " scrapped %6d regops, %d user@hosts freed, deltaT: %dms", cfscore,
883 cfregop, cffreeuh, diff);
884 }
885
886}
887
888void cfsched_dosave(void *arg) {
889 cf_storechanfix();
890}
891
892#if CFAUTOFIX
893void cfhook_autofix(int hook, void *arg) {
894 int a, count;
895 void **args = (void**)arg;
896 channel *cp;
897
898 if (hook == HOOK_CHANNEL_DEOPPED || hook == HOOK_CHANNEL_PART ||
899 hook == HOOK_CHANNEL_KICK || hook == HOOK_CHANNEL_JOIN) {
900 cp = args[0];
901
902 if (sp_countsplitservers() > 0)
903 return;
904
905 for(a=0;a<cp->users->hashsize;a++) {
906 if (cp->users->content[a] != nouser) {
907 count++;
908
909 if (cp->users->content[a] & CUMODE_OP)
910 return;
911 }
912 }
913
914 /* don't fix small channels.. it's inaccurate and
915 * they could just cycle the channel */
916 if (count < 4)
917 return;
918
919 cf_fixchannel((channel*)args[0]);
920 }
921}
922#endif
923
924void cfhook_statsreport(int hook, void *arg) {
925 char buf[300];
926 int i,a,rc,mc,memory;
927 chanindex *cip;
928 nick *nip;
929 chanfix *cf;
930
c3db6f7e 931 if ((long)arg > 2) {
8a25ddc6
P
932 memory = rc = mc = 0;
933
934 for (i=0; i<CHANNELHASHSIZE; i++) {
935 for (cip=chantable[i]; cip; cip=cip->next) {
936 if ((cf = cip->exts[cfext]) != NULL) {
937 for (a=0;a<cf->regops.cursi;a++) {
938 memory += sizeof(regop) + sizeof(regop*);
939
940 if (((regop**)cf->regops.content)[a]->uh)
941 memory += sizeof(sstring) + strlen(((regop**)cf->regops.content)[a]->uh->content) + 1;
942
943 rc++;
944 }
945
946 memory += sizeof(chanfix);
947
948 mc++;
949 }
950 }
951 }
952
953 for (i=0; i<NICKHASHSIZE; i++) {
954 for (nip=nicktable[i]; nip; nip=nip->next) {
955 if (nip->exts[cfnext])
956 memory += sizeof(int);
957 }
958 }
959
960 sprintf(buf, "Chanfix : %6d registered ops, %9d monitored channels. %9d"
961 " kbytes of memory used", rc, mc, memory / 1024);
962 triggerhook(HOOK_CORE_STATSREPLY, buf);
963 }
964}
965
966void cfhook_auth(int hook, void *arg) {
967 nick *np = (nick*)arg;
968
969 /* Invalidate the user's hash */
970 free(np->exts[cfnext]);
971
972 np->exts[cfnext] = NULL;
973
974 /* Calculate the new hash */
975 cf_gethash(np, CFACCOUNT);
976}
977
978void cfhook_lostnick(int hook, void *arg) {
979 nick *np = (nick*)arg;
980
981 free(np->exts[cfnext]);
982}
983
984/* Returns the hash of a specific user (np), type can be either CFACCOUNT,
985 CFHOST or both (binary or'd values). cf_gethash will also cache the user's
986 hash in a nick extension */
987unsigned long cf_gethash(nick *np, int type) {
988 char buf[USERLEN+1+HOSTLEN+1];
989 unsigned long hash;
990
991 if (IsAccount(np) && type & CFACCOUNT) {
992 if (np->exts[cfnext] == NULL) {
993 np->exts[cfnext] = (int*)malloc(sizeof(int));
994 *(int*)np->exts[cfnext] = crc32(np->authname);
995 }
996
997 return *(int*)np->exts[cfnext];
998 } else if (type == CFACCOUNT)
999 return 0; /* this should not happen */
1000
1001 if (type & CFHOST) {
1002 if (!IsAccount(np) && np->exts[cfnext])
1003 return *(int*)np->exts[cfnext];
1004 else {
1005 strcpy(buf, np->ident);
1006 strcat(buf, "@");
1007 strcat(buf, np->host->name->content);
1008 hash = crc32(buf);
1009
1010 /* if the user is not authed, update the hash */
1011 if (!IsAccount(np)) {
1012 np->exts[cfnext] = (int*)malloc(sizeof(int));
1013
1014 *(int*)np->exts[cfnext] = hash;
1015 }
1016
1017 return hash;
1018 }
1019 }
1020
1021 return 0; /* should not happen */
1022}
1023
1024/* This seems to be a lot faster than using sprintf */
1025int cf_equhost(const char *uhost, const char *user, const char *host) {
1026 char *p = strchr(uhost, '@');
1027
1028 /* We assume the uhost contains a @ - which it should do in all cases */
1029 /* if (!p)
1030 return 0; */
1031
1032 if (ircd_strncmp(uhost, user, p - uhost) == 0 && ircd_strcmp(p + 1, host) == 0)
1033 return 1;
1034 else
1035 return 0;
1036}
1037
1038/* Why do we actually store the users' real hosts/accounts instead of hashes?
1039 * - it allows operators to see the hosts/accounts in 'chanoplist' even if the
1040 * users are not online
1041 * - it avoids hash collisions (could be avoided with md5/sha1/etc.)
1042 */
1043int cf_cmpregopnick(regop *ro, nick *np) {
1044 if (ro->uh != NULL) {
1045 if (ro->type == CFACCOUNT && IsAccount(np))
1046 return (ro->hash == cf_gethash(np, CFACCOUNT) &&
1047 strcmp(ro->uh->content, np->authname) == 0);
1048 else {
1049 return (ro->hash == cf_gethash(np, CFHOST) &&
1050 cf_equhost(ro->uh->content, np->ident, np->host->name->content));
1051 }
1052 } else {
1053 if (ro->type == CFACCOUNT && IsAccount(np))
1054 return (ro->hash == cf_gethash(np, CFACCOUNT));
1055 else
1056 return (ro->hash == cf_gethash(np, CFHOST));
1057 }
1058}
1059
1060nick *cf_findnick(regop *ro, chanindex *cip) {
1061 chanfix *cf = cip->exts[cfext];
1062 channel *cp = cip->channel;
1063 nick *np2;
1064 int a;
1065
1066 if (cf == NULL || cp == NULL)
1067 return NULL;
1068
1069 if (ro->type == CFACCOUNT) {
1070 for(a=0;a<cp->users->hashsize;a++) {
1071 if(cp->users->content[a] != nouser) {
1072 np2 = getnickbynumeric(cp->users->content[a]);
1073
1074 if (!IsAccount(np2))
1075 continue;
1076
1077 if (cf_cmpregopnick(ro, np2))
1078 return np2;
1079 }
1080 }
1081 }
1082
1083 if (ro->type == CFHOST) {
1084 for(a=0;a<cp->users->hashsize;a++) {
1085 if(cp->users->content[a] != nouser) {
1086 np2 = getnickbynumeric(cp->users->content[a]);
1087
1088 if (cf_cmpregopnick(ro, np2))
1089 return np2;
1090 }
1091 }
1092 }
1093
1094 return NULL;
1095}
1096
1097regop *cf_findregop(nick *np, chanindex *cip, int type) {
1098 chanfix *cf = cip->exts[cfext];
1099 regop *ro;
1100 int i, ty;
1101
1102 if (cf == NULL)
1103 return NULL;
1104
1105 if (IsAccount(np) && type & CFACCOUNT)
1106 ty = CFACCOUNT;
1107 else
1108 ty = CFHOST;
1109
1110 for (i=0;i<cf->regops.cursi;i++) {
1111 ro = ((regop**)cf->regops.content)[i];
1112
1113 if (ro->type == ty && cf_cmpregopnick(ro, np))
1114 return ro;
1115 }
1116
1117 /* try using the uhost if we didn't find a user with the right account */
1118 if (ty == CFACCOUNT && type & CFHOST)
1119 return cf_findregop(np, cip, CFHOST);
1120 else
1121 return NULL;
1122
1123 return NULL;
1124}
1125
1126regop *cf_createregop(nick *np, chanindex *cip) {
1127 chanfix *cf = cip->exts[cfext];
1128 int slot, type;
1129 regop **rolist;
1130
1131 if (cf == NULL) {
1132 cf = (chanfix*)malloc(sizeof(chanfix));
1133 cf->index = cip;
1134
1135 array_init(&(cf->regops), sizeof(regop*));
1136
1137 cip->exts[cfext] = cf;
1138 }
1139
1140 slot = array_getfreeslot(&(cf->regops));
1141
1142 rolist = (regop**)cf->regops.content;
1143
1144 rolist[slot] = (regop*)malloc(sizeof(regop));
1145
1146 if (IsAccount(np))
1147 type = CFACCOUNT;
1148 else
1149 type = CFHOST;
1150
1151 rolist[slot]->type = type;
1152 rolist[slot]->hash = cf_gethash(np, type);
1153 rolist[slot]->uh = NULL;
1154 rolist[slot]->lastopped = 0;
1155 rolist[slot]->score = 0;
1156
1157 return rolist[slot];
1158}
1159
1160void cf_deleteregop(chanindex *cip, regop *ro) {
1161 chanfix *cf = cip->exts[cfext];
1162 int a;
1163
1164 if (cf == NULL)
1165 return;
1166
1167 for (a=0;a<cf->regops.cursi;a++) {
1168 if (((regop**)cf->regops.content)[a] == ro) {
1169 freesstring(((regop**)cf->regops.content)[a]->uh);
1170 free(((regop**)cf->regops.content)[a]);
1171 array_delslot(&(cf->regops), a);
1172 }
1173 }
1174
1175 /* get rid of chanfix* if there are no more regops */
1176 if (cf->regops.cursi == 0) {
1177 array_free(&(cf->regops));
1178 free(cf);
1179 cip->exts[cfext] = NULL;
1180
1181 /* we could try to free the chanindex* here
1182 but that would make cfsched_dosample a lot more
1183 complicated */
1184 }
1185}
1186
1187int cf_fixchannel(channel *cp) {
1188 int a,i;
1189 nick *np;
1190 modechanges changes;
1191 regop *rolist[50];
1192 int count,ops;
1193 chanfix *cf = cp->index->exts[cfext];
1194
1195 if (cf == NULL)
1196 return CFX_NOCHANFIX;
1197
1198 localsetmodeinit(&changes, cp, mynick);
1199
1200 count = 0;
1201
1202 /* reop services first and deop other users */
1203 for(a=0;a<cp->users->hashsize;a++) {
1204 if(cp->users->content[a] != nouser) {
1205 np = getnickbynumeric(cp->users->content[a]);
1206
93b87578 1207 if (IsService(np) && (np->nick[1] == '\0')) {
8a25ddc6
P
1208 localdosetmode_nick(&changes, np, MC_OP);
1209 count++;
1210 } else
1211 localdosetmode_nick(&changes, np, MC_DEOP);
1212 }
1213 }
1214
1215 /* don't reop users if we've already opped some services */
1216#if !CFDEBUG
d1cb1fd3
CP
1217 if (count > 0) {
1218 localsetmodeflush(&changes, 1);
1b0a2e39 1219 return CFX_FIXED;
d1cb1fd3 1220 }
8a25ddc6
P
1221#endif
1222
1223 /* now get a sorted list of regops */
1224 ops = cf_getsortedregops(cf, 50, rolist);
1225
1226 /* and op some of them */
1227 for (i=0;i<ops;i++) {
1228 if (count >= CFMAXOPS || rolist[i]->score < rolist[0]->score / 2)
1229 break;
1230
f940478e 1231 if (rolist[i]->score < CFMINSCORE && i != 0 )
8a25ddc6
P
1232 continue;
1233
1234 np = cf_findnick(rolist[i], cp->index);
1235
1236 /* only if it's not a service, so we don't screw up 'count' */
93b87578 1237 if (np && !(IsService(np) && np->nick[1] == '\0')) {
8a25ddc6
P
1238 localdosetmode_nick(&changes, np, MC_OP);
1239
1240 count++;
1241 }
1242 }
1243
1244 localsetmodeflush(&changes, 1);
1245
8a25ddc6
P
1246 if (count == CFMAXOPS)
1247 return CFX_FIXED;
1248 else if (count == 0)
1249 return CFX_NOUSERSAVAILABLE;
1250 else
1251 return CFX_FIXEDFEWOPS;
1252}
1253
1254int cf_storechanfix(void) {
1255 FILE *cfdata;
1256 regop *ro;
1257 chanfix *cf;
1258 chanindex *cip;
1259 char srcfile[300];
1260 char dstfile[300];
1261 int a, i, count = 0;
1262
1263 snprintf(dstfile, 300, "%s.%d", CFSTORAGE, CFSAVEFILES);
1264 unlink(dstfile);
1265
1266 for (i = CFSAVEFILES; i > 0; i--) {
1267 snprintf(srcfile, 300, "%s.%i", CFSTORAGE, i - 1);
1268 snprintf(dstfile, 300, "%s.%i", CFSTORAGE, i);
1269 rename(srcfile, dstfile);
1270 }
1271
1272 snprintf(srcfile, 300, "%s.0", CFSTORAGE);
1273 cfdata = fopen(srcfile, "w");
1274
1275 if (cfdata == NULL)
1276 return 0;
1277
1278 for (i=0; i<CHANNELHASHSIZE; i++) {
1279 for (cip=chantable[i]; cip; cip=cip->next) {
1280 if ((cf = cip->exts[cfext]) != NULL) {
1281 for (a=0;a<cf->regops.cursi;a++) {
1282 ro = ((regop**)cf->regops.content)[a];
1283
1284 if (ro->uh)
1285 fprintf(cfdata, "%s %lu %lu %lu %lu %s\n", cip->name->content,
1286 (unsigned long)ro->type, (unsigned long)ro->hash,
1287 (unsigned long)ro->lastopped, (unsigned long)ro->score,
1288 ro->uh->content);
1289 else
1290 fprintf(cfdata, "%s %lu %lu %lu %lu\n", cip->name->content,
1291 (unsigned long)ro->type, (unsigned long)ro->hash,
1292 (unsigned long)ro->lastopped, (unsigned long)ro->score);
1293 count++;
1294 }
1295 }
1296 }
1297 }
1298
1299 fclose(cfdata);
1300
1301 return count;
1302}
1303
1304/* channel type hash lastopped score host */
1305int cf_parseline(char *line) {
1306 chanindex *cip;
1307 chanfix *cf;
1308 int count;
1309 int slot;
1310 char chan[CHANNELLEN+1];
1311 char host[USERLEN+1+HOSTLEN+1];
1312 unsigned long type,hash,lastopped,score;
1313 regop **rolist;
1314
1315 count = sscanf(line, "%s %lu %lu %lu %lu %s", chan, &type, &hash, &lastopped, &score, host);
1316
1317 if (count < 5)
1318 return 0; /* invalid chanfix record */
1319
1320 cip = findorcreatechanindex(chan);
1321
1322 cf = cip->exts[cfext];
1323
1324 if (cf == NULL) {
1325 cf = (chanfix*)malloc(sizeof(chanfix));
1326 cf->index = cip;
1327
1328 array_init(&(cf->regops), sizeof(regop*));
1329
1330 cip->exts[cfext] = cf;
1331 }
1332
1333 slot = array_getfreeslot(&(cf->regops));
1334
1335 rolist = (regop**)cf->regops.content;
1336
1337 rolist[slot] = (regop*)malloc(sizeof(regop));
1338
1339 rolist[slot]->type = type;
1340 rolist[slot]->hash = hash;
1341 rolist[slot]->lastopped = lastopped;
1342 rolist[slot]->score = score;
1343
1344 if (count >= 6 && strchr(host, '@') != NULL)
1345 rolist[slot]->uh = getsstring(host, USERLEN+1+HOSTLEN);
1346 else
1347 rolist[slot]->uh = NULL;
1348
1349 return 1;
1350}
1351
1352int cf_loadchanfix(void) {
1353 char line[4096];
1354 FILE *cfdata;
1355 char srcfile[300];
1356 int count;
1357
1358 cf_free();
1359
1360 snprintf(srcfile, 300, "%s.0", CFSTORAGE);
1361 cfdata = fopen(srcfile, "r");
1362
1363 if (cfdata == NULL)
1364 return 0;
1365
1366 count = 0;
1367
1368 while (!feof(cfdata)) {
1369 if (fgets(line, sizeof(line), cfdata) == NULL)
1370 break;
1371
1372 if (line[strlen(line) - 1] == '\n')
1373 line[strlen(line) - 1] = '\0';
1374
1375 if (line[strlen(line) - 1] == '\r')
1376 line[strlen(line) - 1] = '\0';
1377
1378 if (line[0] != '\0') {
1379 if (cf_parseline(line))
1380 count++;
1381 }
1382 }
1383
1384 fclose(cfdata);
1385
1386 return count;
1387}
1388
1389/* functions for users of this module */
1390chanfix *cf_findchanfix(chanindex *cip) {
1391 return cip->exts[cfext];
1392}
1393
1394int cf_wouldreop(nick *np, channel *cp) {
1395 int i,topscore = 0;
1396 regop **rolist;
1397 regop *ro;
1398 chanfix *cf = cp->index->exts[cfext];
1399
1400 if (cf == NULL)
1401 return 1; /* too bad, we can't do anything about it */
1402
1403 ro = cf_findregop(np, cp->index, CFACCOUNT | CFHOST);
1404
1405 if (ro == NULL)
1406 return 0;
1407
1408 rolist = (regop**)cf->regops.content;
1409
1410 for (i=0; i<cf->regops.cursi; i++)
1411 if (rolist[i]->score > topscore)
1412 topscore = rolist[i]->score;
1413
1414 if (ro->score > topscore / 2 && ro->score > CFMINSCORE)
1415 return 1;
1416 else
1417 return 0;
1418}