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