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