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