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