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 #include "../lib/version.h"
28 /* user accessible commands */
29 int cfcmd_debug(void *source
, int cargc
, char **cargv
);
30 int cfcmd_debughistogram(void *source
, int cargc
, char **cargv
);
31 int cfcmd_debugsample(void *source
, int cargc
, char **cargv
);
32 int cfcmd_debugexpire(void *source
, int cargc
, char **cargv
);
33 int cfcmd_chanopstat(void *source
, int cargc
, char **cargv
);
34 int cfcmd_chanoplist(void *source
, int cargc
, char **cargv
);
35 int cfcmd_chanfix(void *source
, int cargc
, char **cargv
);
36 int cfcmd_showregs(void *source
, int cargc
, char **cargv
);
37 int cfcmd_requestop(void *source
, int cargc
, char **cargv
);
38 int cfcmd_save(void *source
, int cargc
, char **cargv
);
39 int cfcmd_load(void *source
, int cargc
, char **cargv
);
41 /* scheduled events */
42 void cfsched_dosample(void *arg
);
43 void cfsched_doexpire(void *arg
);
44 void cfsched_dosave(void *arg
);
47 void cfhook_autofix(int hook
, void *arg
);
48 void cfhook_statsreport(int hook
, void *arg
);
49 void cfhook_auth(int hook
, void *arg
);
51 /* helper functions */
52 regop
*cf_createregop(nick
*np
, chanindex
*cip
);
53 void cf_deleteregop(chanindex
*cip
, regop
*ro
);
54 unsigned long cf_gethash(nick
*np
, int type
);
56 int cf_storechanfix(void);
57 int cf_loadchanfix(void);
60 #define min(a,b) ((a > b) ? b : a)
63 cfext
= registerchanext("chanfix");
64 cfnext
= registernickext("chanfix");
66 if (cfext
< 0 || cfnext
< 0) {
67 Error("chanfix", ERR_ERROR
, "Couldn't register channel and/or nick extension");
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
);
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");
77 registercontrolhelpcmd("cfsample", NO_DEVELOPER
, 1, &cfcmd_debugsample
, "DEBUG Command - must not be loaded on live instances");
78 registercontrolhelpcmd("cfexpire", NO_DEVELOPER
, 1, &cfcmd_debugexpire
, "DEBUG Command - must not be loaded on live instances");
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");
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)");
86 /* should we disable this in the 'final' build? */
87 /* registercontrolcmd("requestop", 0, 2, &cfcmd_requestop); */
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");
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
);
99 registerhook(HOOK_CORE_STATSREQUEST
, &cfhook_statsreport
);
100 registerhook(HOOK_NICK_ACCOUNT
, &cfhook_auth
);
109 if (cffailedinit
== 0) {
110 deleteschedule(NULL
, &cfsched_dosample
, NULL
);
111 deleteschedule(NULL
, &cfsched_doexpire
, NULL
);
112 deleteschedule(NULL
, &cfsched_dosave
, NULL
);
118 deregistercontrolcmd("cfdebug", &cfcmd_debug
);
119 deregistercontrolcmd("cfhistogram", &cfcmd_debughistogram
);
121 deregistercontrolcmd("cfsample", &cfcmd_debugsample
);
122 deregistercontrolcmd("cfexpire", &cfcmd_debugexpire
);
124 deregistercontrolcmd("chanopstat", &cfcmd_chanopstat
);
125 deregistercontrolcmd("chanoplist", &cfcmd_chanoplist
);
126 deregistercontrolcmd("chanfix", &cfcmd_chanfix
);
127 deregistercontrolcmd("showregs", &cfcmd_showregs
);
129 // deregistercontrolcmd("requestop", &cfcmd_requestop);
131 deregistercontrolcmd("cfsave", &cfcmd_save
);
132 deregistercontrolcmd("cfload", &cfcmd_load
);
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
);
141 deregisterhook(HOOK_CORE_STATSREQUEST
, &cfhook_statsreport
);
142 deregisterhook(HOOK_NICK_ACCOUNT
, &cfhook_auth
);
146 releasechanext(cfext
);
150 releasenickext(cfnext
);
154 int cfcmd_debug(void *source
, int cargc
, char **cargv
) {
155 nick
*np
= (nick
*)source
;
162 controlreply(np
, "Syntax: cfdebug <#channel>");
167 cip
= findchanindex(cargv
[0]);
170 controlreply(np
, "No such channel.");
174 controlreply(np
, "Found channel %s. Retrieving chanfix information...", cargv
[0]);
176 cf
= cip
->exts
[cfext
];
179 controlreply(np
, "No chanfix information for %s", cargv
[0]);
183 controlreply(np
, "Found chanfix information. Dumping...");
185 for (i
=0;i
<cf
->regops
.cursi
;i
++) {
186 ro
= ((regop
**)cf
->regops
.content
)[i
];
188 controlreply(np
, "%d. type: %s hash: 0x%lx lastopped: %lu uh: %s score: %d",
189 i
+ 1, ro
->type
== CFACCOUNT
? "CFACCOUNT" : "CFHOST", ro
->hash
,
190 ro
->lastopped
, ro
->uh
? ro
->uh
->content
: "(unknown)", ro
->score
);
193 controlreply(np
, "Done.");
198 int cfcmd_debughistogram(void *source
, int cargc
, char **cargv
) {
199 nick
*np
= (nick
*)source
;
203 int histogram
[10001]; /* 625 (lines) * 16 (columns/line) + 1 (for histogram[0]) */
205 for (i
= 0; i
< 10001; i
++)
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
;
221 controlreply(np
, "--- Histogram of chanfix scores");
223 for (i
= 1; i
< 10001; i
+= 16) {
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],
231 controlreply(np
, "--- End of histogram");
236 int cfcmd_debugsample(void *source
, int cargc
, char **cargv
) {
237 cfsched_dosample(NULL
);
239 controlreply((nick
*)source
, "Done.");
244 int cfcmd_debugexpire(void *source
, int cargc
, char **cargv
) {
245 cfsched_doexpire(NULL
);
247 controlreply((nick
*)source
, "Done.");
252 /* used for qsorting int arrays */
253 int cmpint(const void *a
, const void *b
) {
265 /* used for qsorting regop* arrays */
266 int cmpregop(const void *a
, const void *b
) {
267 regop
*p
= *(regop
**)a
;
268 regop
*q
= *(regop
**)b
;
270 if (p
->score
> q
->score
)
272 else if (p
->score
< q
->score
)
278 int cf_getsortedregops(chanfix
*cf
, int max
, regop
**list
) {
284 qsort(cf
->regops
.content
, cf
->regops
.cursi
, sizeof(regop
*), cmpregop
);
286 for (i
= 0; i
< min(max
, cf
->regops
.cursi
); i
++) {
287 list
[i
] = ((regop
**)cf
->regops
.content
)[i
];
293 int cfcmd_chanopstat(void *source
, int cargc
, char **cargv
) {
294 nick
*np
= (nick
*)source
;
304 controlreply(np
, "Syntax: chanopstat <#channel>");
309 cp
= findchannel(cargv
[0]);
312 controlreply(np
, "No such channel.");
317 cf
= cp
->index
->exts
[cfext
];
320 controlreply(np
, "No chanfix information for %s", cargv
[0]);
326 count
= cf_getsortedregops(cf
, 10, rolist
);
327 controlreply(np
, "Scores of \"best ops\" on %s are:", cargv
[0]);
329 for (i
=0;i
<count
;i
++) {
330 controlreply(np
, " %d", rolist
[i
]->score
);
334 scores
= (int*)malloc(sizeof(int) * cp
->users
->hashsize
);
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
]);
342 ro
= cf_findregop(np2
, cp
->index
, CFACCOUNT
| CFHOST
);
345 scores
[i
++] = ro
->score
;
349 qsort(scores
, i
, sizeof(int), &cmpint
);
350 controlreply(np
, "Scores of current ops on %s are:", cargv
[0]);
352 for (a
=0;a
<min(i
,20);a
++) {
356 controlreply(np
, " %d", scores
[a
]);
360 controlreply(np
, "Done.");
365 int cfcmd_chanoplist(void *source
, int cargc
, char **cargv
) {
366 nick
*np
= (nick
*)source
;
378 controlreply(np
, "Syntax: chanoplist <#channel>");
383 cip
= findchanindex(cargv
[0]);
386 controlreply(np
, "No such channel.");
391 cf
= cip
->exts
[cfext
];
394 controlreply(np
, "No chanfix information for %s", cargv
[0]);
399 count
= cf_getsortedregops(cf
, 50, rolist
);
401 controlreply(np
, "Pos Score Type User/Last seen");
403 for (i
=0;i
<count
;i
++) {
404 np2
= cf_findnick(rolist
[i
], cip
);
407 hand
= getnumerichandlefromchanhash(cip
->channel
->users
, np2
->numeric
);
409 /* hand should be non-null in all cases */
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
);
416 ct
= rolist
[i
]->lastopped
;
418 strftime(date
,sizeof(date
),"%d-%m-%Y %H:%M:%S",tm
);
419 controlreply(np
, "%3d %5d %-7s %1s%-16s %s Last seen: %s", i
+ 1, rolist
[i
]->score
,
420 (rolist
[i
]->type
== CFACCOUNT
) ? "account" : "host", "", "!NONE!",
421 rolist
[i
]->uh
? rolist
[i
]->uh
->content
: "!UNKNOWN!", date
);
425 controlreply(np
, "Done.");
430 int cfcmd_chanfix(void *source
, int cargc
, char **cargv
) {
431 nick
*np
= (nick
*)source
;
436 controlreply(np
, "Syntax: chanfix <#channel>");
441 cp
= findchannel(cargv
[0]);
444 controlreply(np
, "No such channel.");
449 if (sp_countsplitservers(SERVERTYPEFLAG_USER_STATE
) > 0) {
450 controlreply(np
, "Chanfix cannot be used during a netsplit.");
455 ret
= cf_fixchannel(cp
);
459 controlreply(np
, "Channel was fixed.");
462 controlreply(np
, "Channel cannot be fixed: no chanfix information");
464 case CFX_FIXEDFEWOPS
:
465 controlreply(np
, "Channel was fixed but only a few ops could be found and reopped.");
475 int cfcmd_showregs(void *source
, int cargc
, char **cargv
) {
476 nick
*np
= (nick
*)source
;
484 controlreply(np
, "Syntax: showregs <#channel>");
489 cp
= findchannel(cargv
[0]);
492 controlreply(np
, "No such channel.");
497 cf
= cp
->index
->exts
[cfext
];
500 controlreply(np
, "No chanfix information for %s", cargv
[0]);
507 for(i
=0;i
<cp
->users
->hashsize
;i
++) {
508 if(cp
->users
->content
[i
] != nouser
) {
509 np2
= getnickbynumeric(cp
->users
->content
[i
]);
511 if (IsService(np2
)) {
512 controlreply(np
, "%s (service)", np2
->nick
);
518 /* now get a sorted list of regops */
519 ops
= cf_getsortedregops(cf
, 50, rolist
);
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
)
526 np2
= cf_findnick(rolist
[i
], cp
->index
);
529 controlreply(np
, "%s (%s)", np2
->nick
, rolist
[i
]->type
== CFACCOUNT
? "account" : "host");
535 controlreply((nick
*)source
, "--- End of list: %d users listed", count
);
540 int cfcmd_requestop(void *source
, int cargc
, char **cargv
) {
541 nick
*np
= (nick
*)source
;
549 controlreply(np
, "Syntax: requestop <#channel> [nick]");
554 cp
= findchannel(cargv
[0]);
557 controlreply(np
, "No such channel.");
563 user
= getnickbynick(cargv
[1]);
566 controlreply(np
, "No such user.");
572 hand
= getnumerichandlefromchanhash(cp
->users
, user
->numeric
);
575 controlreply(np
, "User %s is not on channel %s.", user
->nick
, cargv
[0]);
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]);
589 if (sp_countsplitservers(SERVERTYPEFLAG_USER_STATE
) > 0) {
590 controlreply(np
, "One or more servers are currently split. Wait until the"
591 " netsplit is over and try again.");
596 if (cf_wouldreop(user
, cp
)) {
597 localsetmodeinit(&changes
, cp
, mynick
);
598 localdosetmode_nick(&changes
, user
, MC_OP
);
599 localsetmodeflush(&changes
, 1);
601 controlreply(np
, "Chanfix opped you on the specified channel.");
603 ret
= cf_fixchannel(cp
);
605 if (ret
== CFX_NOUSERSAVAILABLE
)
606 controlreply(np
, "Chanfix knows regular ops for that channel. They will"
607 " be opped when they return.");
609 controlreply(np
, "Chanfix has opped known ops for that channel.");
615 int cfcmd_save(void *source
, int cargc
, char **cargv
) {
616 nick
*np
= (nick
*)source
;
619 count
= cf_storechanfix();
621 controlreply(np
, "%d chanfix records saved.", count
);
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
]);
640 array_free(&(((chanfix
*)cip
->exts
[cfext
])->regops
));
643 free(cip
->exts
[cfext
]);
644 cip
->exts
[cfext
] = NULL
;
649 int cfcmd_load(void *source
, int cargc
, char **cargv
) {
650 nick
*np
= (nick
*)source
;
653 count
= cf_loadchanfix();
655 controlreply(np
, "%d chanfix records loaded.", count
);
660 int cf_hasauthedcloneonchan(nick
*np
, channel
*cp
) {
664 for (jp
= np
->host
->nicks
; jp
; jp
= jp
->nextbyhost
) {
665 if (!IsAccount(jp
) || jp
->numeric
== np
->numeric
)
668 hand
= getnumerichandlefromchanhash(cp
->users
, jp
->numeric
);
670 if (hand
&& (*hand
& CUMODE_OP
) && strcmp(np
->ident
, jp
->ident
) == 0)
677 void cfsched_dosample(void *arg
) {
678 int i
,a
,now
,cfscore
,cfnewro
,cfuhost
,diff
;
683 struct timeval start
;
685 char buf
[USERLEN
+1+HOSTLEN
+1];
689 cfuhost
= cfscore
= cfnewro
= 0;
691 if (sp_countsplitservers(SERVERTYPEFLAG_USER_STATE
) > CFMAXSPLITSERVERS
)
694 gettimeofday(&start
, NULL
);
696 for (i
=0; i
<CHANNELHASHSIZE
; i
++) {
697 for (cip
=chantable
[i
]; cip
; cip
=cip
->next
) {
700 if (!cp
|| cp
->users
->totalusers
< CFMINUSERS
)
703 for (a
=0;a
<cp
->users
->hashsize
;a
++) {
704 if ((cp
->users
->content
[a
] != nouser
) && (cp
->users
->content
[a
] & CUMODE_OP
)) {
705 np
= getnickbynumeric(cp
->users
->content
[a
]);
715 roh
= ro
= cf_findregop(np
, cip
, CFACCOUNT
| CFHOST
);
717 if ((ro
== NULL
|| (IsAccount(np
) && ro
->type
== CFHOST
)) &&
718 !cf_hasauthedcloneonchan(np
, cp
)) {
719 ro
= cf_createregop(np
, cip
);
723 /* lastopped == now if the user has clones, we obviously
724 * don't want to give them points in this case */
725 if (ro
&& ro
->lastopped
!= now
) {
726 if (ro
->type
!= CFHOST
|| !cf_hasauthedcloneonchan(np
, cp
)) {
731 /* merge any matching CFHOST records */
732 if (roh
&& roh
->type
== CFHOST
&& ro
->type
== CFACCOUNT
) {
734 ro
->score
+= roh
->score
;
736 cf_deleteregop(cip
, roh
);
739 /* store the user's account/host if we have to */
740 if (ro
->uh
== NULL
&& ro
->score
>= CFMINSCOREUH
) {
741 if (ro
->type
== CFACCOUNT
)
742 ro
->uh
= getsstring(np
->authname
, ACCOUNTLEN
);
744 snprintf(buf
, sizeof(buf
), "%s@%s", np
->ident
, np
->host
->name
->content
);
745 roh
->uh
= getsstring(buf
, USERLEN
+1+HOSTLEN
);
758 cp
= findchannel("#qnet.chanfix");
761 gettimeofday(&end
, NULL
);
763 diff
= (end
.tv_sec
* 1000 + end
.tv_usec
/ 1000) -
764 (start
.tv_sec
* 1000 + start
.tv_usec
/ 1000);
766 sendmessagetochannel(mynick
, cp
, "sampled chanfix scores, assigned %d new"
767 " points, %d new regops, %d user@hosts added, deltaT: %dms", cfscore
,
768 cfnewro
, cfuhost
, diff
);
772 void cfsched_doexpire(void *arg
) {
777 int i
,a
,cfscore
,cfregop
,cffreeuh
,diff
;
780 struct timeval start
;
784 cffreeuh
= cfscore
= cfregop
= 0;
786 gettimeofday(&start
, NULL
);
787 currenttime
=getnettime();
789 for (i
=0; i
<CHANNELHASHSIZE
; i
++) {
790 for (cip
=chantable
[i
]; cip
; cip
=cip
->next
) {
791 cf
= (chanfix
*)cip
->exts
[cfext
];
794 rolist
= (regop
**)cf
->regops
.content
;
796 for (a
=0;a
<cf
->regops
.cursi
;a
++) {
799 if (((currenttime
- ro
->lastopped
) > (2 * CFSAMPLEINTERVAL
)) && ro
->score
) {
804 if ((ro
->score
< CFMINSCOREUH
) && ro
->uh
) {
811 if (ro
->score
== 0 || ro
->lastopped
< (currenttime
- CFREMEMBEROPS
)) {
812 cf_deleteregop(cip
, ro
);
820 /* stolen from channel/channelindex.c */
821 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
822 for (cip
=chantable
[i
];cip
;cip
=ncip
) {
823 /* CAREFUL: deleting items from chains you're walking is bad */
826 /* try to delete it if there's no chanfix* pointer */
827 if (cip
->exts
[cfext
] == NULL
)
828 releasechanindex(cip
);
832 cp
= findchannel("#qnet.chanfix");
835 gettimeofday(&end
, NULL
);
837 diff
= (end
.tv_sec
* 1000 + end
.tv_usec
/ 1000) -
838 (start
.tv_sec
* 1000 + start
.tv_usec
/ 1000);
840 sendmessagetochannel(mynick
, cp
, "expired chanfix scores, purged %d points,"
841 " scrapped %6d regops, %d user@hosts freed, deltaT: %dms", cfscore
,
842 cfregop
, cffreeuh
, diff
);
847 void cfsched_dosave(void *arg
) {
852 void cfhook_autofix(int hook
, void *arg
) {
854 void **args
= (void**)arg
;
857 if (hook
== HOOK_CHANNEL_DEOPPED
|| hook
== HOOK_CHANNEL_PART
||
858 hook
== HOOK_CHANNEL_KICK
|| hook
== HOOK_CHANNEL_JOIN
) {
861 if (sp_countsplitservers(SERVERTYPEFLAG_USER_STATE
) > 0)
864 for(a
=0;a
<cp
->users
->hashsize
;a
++) {
865 if (cp
->users
->content
[a
] != nouser
) {
868 if (cp
->users
->content
[a
] & CUMODE_OP
)
873 /* don't fix small channels.. it's inaccurate and
874 * they could just cycle the channel */
878 cf_fixchannel((channel
*)args
[0]);
883 void cfhook_statsreport(int hook
, void *arg
) {
885 int i
,a
,rc
,mc
,memory
;
890 memory
= rc
= mc
= 0;
892 for (i
=0; i
<CHANNELHASHSIZE
; i
++) {
893 for (cip
=chantable
[i
]; cip
; cip
=cip
->next
) {
894 if ((cf
= cip
->exts
[cfext
]) != NULL
) {
895 for (a
=0;a
<cf
->regops
.cursi
;a
++) {
896 memory
+= sizeof(regop
) + sizeof(regop
*);
898 if (((regop
**)cf
->regops
.content
)[a
]->uh
)
899 memory
+= sizeof(sstring
) + strlen(((regop
**)cf
->regops
.content
)[a
]->uh
->content
) + 1;
904 memory
+= sizeof(chanfix
);
911 snprintf(buf
, sizeof(buf
), "Chanfix : %6d registered ops, %9d monitored channels. %9d"
912 " kbytes of memory used", rc
, mc
, (memory
/ 1024));
913 triggerhook(HOOK_CORE_STATSREPLY
, buf
);
917 void cfhook_auth(int hook
, void *arg
) {
918 nick
*np
= (nick
*)arg
;
920 /* Invalidate the user's hash */
921 np
->exts
[cfnext
] = NULL
;
923 /* Calculate the new hash */
924 cf_gethash(np
, CFACCOUNT
);
927 /* Returns the hash of a specific user (np), type can be either CFACCOUNT,
928 CFHOST or both (binary or'd values). cf_gethash will also cache the user's
929 hash in a nick extension */
930 unsigned long cf_gethash(nick
*np
, int type
) {
931 char buf
[USERLEN
+1+HOSTLEN
+1];
934 if (IsAccount(np
) && (type
& CFACCOUNT
)) {
935 if (np
->exts
[cfnext
] == NULL
) {
936 np
->exts
[cfnext
] = (void *)crc32(np
->authname
);
939 return (unsigned long)np
->exts
[cfnext
];
940 } else if (type
== CFACCOUNT
)
941 return 0; /* this should not happen */
944 if (!IsAccount(np
) && np
->exts
[cfnext
])
945 return (unsigned long)np
->exts
[cfnext
];
947 snprintf(buf
, sizeof(buf
), "%s@%s", np
->ident
, np
->host
->name
->content
);
950 /* if the user is not authed, update the hash */
951 if (!IsAccount(np
)) {
952 np
->exts
[cfnext
] = (void *)hash
;
959 return 0; /* should not happen */
962 /* This seems to be a lot faster than using sprintf */
963 int cf_equhost(const char *uhost
, const char *user
, const char *host
) {
964 char *p
= strchr(uhost
, '@');
966 /* We assume the uhost contains a @ - which it should do in all cases */
970 if (ircd_strncmp(uhost
, user
, p
- uhost
) == 0 && ircd_strcmp(p
+ 1, host
) == 0)
976 /* Why do we actually store the users' real hosts/accounts instead of hashes?
977 * - it allows operators to see the hosts/accounts in 'chanoplist' even if the
978 * users are not online
979 * - it avoids hash collisions (could be avoided with md5/sha1/etc.)
981 int cf_cmpregopnick(regop
*ro
, nick
*np
) {
982 if (ro
->uh
!= NULL
) {
983 if (ro
->type
== CFACCOUNT
&& IsAccount(np
))
984 return (ro
->hash
== cf_gethash(np
, CFACCOUNT
) &&
985 strcmp(ro
->uh
->content
, np
->authname
) == 0);
987 return (ro
->hash
== cf_gethash(np
, CFHOST
) &&
988 cf_equhost(ro
->uh
->content
, np
->ident
, np
->host
->name
->content
));
991 if (ro
->type
== CFACCOUNT
&& IsAccount(np
))
992 return (ro
->hash
== cf_gethash(np
, CFACCOUNT
));
994 return (ro
->hash
== cf_gethash(np
, CFHOST
));
998 nick
*cf_findnick(regop
*ro
, chanindex
*cip
) {
999 chanfix
*cf
= cip
->exts
[cfext
];
1000 channel
*cp
= cip
->channel
;
1004 if (cf
== NULL
|| cp
== NULL
)
1007 if (ro
->type
== CFACCOUNT
) {
1008 for(a
=0;a
<cp
->users
->hashsize
;a
++) {
1009 if(cp
->users
->content
[a
] != nouser
) {
1010 np2
= getnickbynumeric(cp
->users
->content
[a
]);
1012 if (!IsAccount(np2
))
1015 if (cf_cmpregopnick(ro
, np2
))
1021 if (ro
->type
== CFHOST
) {
1022 for(a
=0;a
<cp
->users
->hashsize
;a
++) {
1023 if(cp
->users
->content
[a
] != nouser
) {
1024 np2
= getnickbynumeric(cp
->users
->content
[a
]);
1026 if (cf_cmpregopnick(ro
, np2
))
1035 regop
*cf_findregop(nick
*np
, chanindex
*cip
, int type
) {
1036 chanfix
*cf
= cip
->exts
[cfext
];
1043 if (IsAccount(np
) && (type
& CFACCOUNT
))
1048 for (i
=0;i
<cf
->regops
.cursi
;i
++) {
1049 ro
= ((regop
**)cf
->regops
.content
)[i
];
1051 if (ro
->type
== ty
&& cf_cmpregopnick(ro
, np
))
1055 /* try using the uhost if we didn't find a user with the right account */
1056 if (ty
== CFACCOUNT
&& (type
& CFHOST
))
1057 return cf_findregop(np
, cip
, CFHOST
);
1064 regop
*cf_createregop(nick
*np
, chanindex
*cip
) {
1065 chanfix
*cf
= cip
->exts
[cfext
];
1070 cf
= (chanfix
*)malloc(sizeof(chanfix
));
1073 array_init(&(cf
->regops
), sizeof(regop
*));
1075 cip
->exts
[cfext
] = cf
;
1078 slot
= array_getfreeslot(&(cf
->regops
));
1080 rolist
= (regop
**)cf
->regops
.content
;
1082 rolist
[slot
] = (regop
*)malloc(sizeof(regop
));
1089 rolist
[slot
]->type
= type
;
1090 rolist
[slot
]->hash
= cf_gethash(np
, type
);
1091 rolist
[slot
]->uh
= NULL
;
1092 rolist
[slot
]->lastopped
= 0;
1093 rolist
[slot
]->score
= 0;
1095 return rolist
[slot
];
1098 void cf_deleteregop(chanindex
*cip
, regop
*ro
) {
1099 chanfix
*cf
= cip
->exts
[cfext
];
1105 for (a
=0;a
<cf
->regops
.cursi
;a
++) {
1106 if (((regop
**)cf
->regops
.content
)[a
] == ro
) {
1107 freesstring(((regop
**)cf
->regops
.content
)[a
]->uh
);
1108 free(((regop
**)cf
->regops
.content
)[a
]);
1109 array_delslot(&(cf
->regops
), a
);
1113 /* get rid of chanfix* if there are no more regops */
1114 if (cf
->regops
.cursi
== 0) {
1115 array_free(&(cf
->regops
));
1117 cip
->exts
[cfext
] = NULL
;
1119 /* we could try to free the chanindex* here
1120 but that would make cfsched_dosample a lot more
1125 int cf_fixchannel(channel
*cp
) {
1128 modechanges changes
;
1131 chanfix
*cf
= cp
->index
->exts
[cfext
];
1134 return CFX_NOCHANFIX
;
1136 localsetmodeinit(&changes
, cp
, mynick
);
1140 /* reop services first and deop other users */
1141 for(a
=0;a
<cp
->users
->hashsize
;a
++) {
1142 if(cp
->users
->content
[a
] != nouser
) {
1143 np
= getnickbynumeric(cp
->users
->content
[a
]);
1145 if (IsService(np
) && (np
->nick
[1] == '\0')) {
1146 localdosetmode_nick(&changes
, np
, MC_OP
);
1149 localdosetmode_nick(&changes
, np
, MC_DEOP
);
1153 /* don't reop users if we've already opped some services */
1156 localsetmodeflush(&changes
, 1);
1161 /* now get a sorted list of regops */
1162 ops
= cf_getsortedregops(cf
, 50, rolist
);
1164 /* and op some of them */
1165 for (i
=0;i
<ops
;i
++) {
1166 if (count
>= CFMAXOPS
|| rolist
[i
]->score
< (rolist
[0]->score
/ 2))
1169 if (rolist
[i
]->score
< CFMINSCORE
&& i
!= 0 )
1172 np
= cf_findnick(rolist
[i
], cp
->index
);
1174 /* only if it's not a service, so we don't screw up 'count' */
1175 if (np
&& !(IsService(np
) && np
->nick
[1] == '\0')) {
1176 localdosetmode_nick(&changes
, np
, MC_OP
);
1182 localsetmodeflush(&changes
, 1);
1184 if (count
== CFMAXOPS
)
1186 else if (count
== 0)
1187 return CFX_NOUSERSAVAILABLE
;
1189 return CFX_FIXEDFEWOPS
;
1192 int cf_storechanfix(void) {
1199 int a
, i
, count
= 0;
1201 snprintf(dstfile
, sizeof(dstfile
), "%s.%d", CFSTORAGE
, CFSAVEFILES
);
1204 for (i
= CFSAVEFILES
; i
> 0; i
--) {
1205 snprintf(srcfile
, sizeof(srcfile
), "%s.%i", CFSTORAGE
, i
- 1);
1206 snprintf(dstfile
, sizeof(dstfile
), "%s.%i", CFSTORAGE
, i
);
1207 rename(srcfile
, dstfile
);
1210 snprintf(srcfile
, sizeof(srcfile
), "%s.0", CFSTORAGE
);
1211 cfdata
= fopen(srcfile
, "w");
1216 for (i
=0; i
<CHANNELHASHSIZE
; i
++) {
1217 for (cip
=chantable
[i
]; cip
; cip
=cip
->next
) {
1218 if ((cf
= cip
->exts
[cfext
]) != NULL
) {
1219 for (a
=0;a
<cf
->regops
.cursi
;a
++) {
1220 ro
= ((regop
**)cf
->regops
.content
)[a
];
1223 fprintf(cfdata
, "%s %d %lu %lu %d %s\n", cip
->name
->content
,
1224 ro
->type
, ro
->hash
, ro
->lastopped
, ro
->score
, ro
->uh
->content
);
1226 fprintf(cfdata
, "%s %d %lu %lu %d\n", cip
->name
->content
,
1227 ro
->type
, ro
->hash
, ro
->lastopped
, ro
->score
);
1239 /* channel type hash lastopped score host */
1240 int cf_parseline(char *line
) {
1245 char chan
[CHANNELLEN
+1];
1249 char host
[USERLEN
+1+HOSTLEN
+1];
1252 count
= sscanf(line
, "%s %d %lu %lu %d %s", chan
, &type
, &hash
, &lastopped
, &score
, host
);
1255 return 0; /* invalid chanfix record */
1257 cip
= findorcreatechanindex(chan
);
1259 cf
= cip
->exts
[cfext
];
1262 cf
= (chanfix
*)malloc(sizeof(chanfix
));
1265 array_init(&(cf
->regops
), sizeof(regop
*));
1267 cip
->exts
[cfext
] = cf
;
1270 slot
= array_getfreeslot(&(cf
->regops
));
1272 rolist
= (regop
**)cf
->regops
.content
;
1274 rolist
[slot
] = (regop
*)malloc(sizeof(regop
));
1276 rolist
[slot
]->type
= type
;
1277 rolist
[slot
]->hash
= hash
;
1278 rolist
[slot
]->lastopped
= lastopped
;
1279 rolist
[slot
]->score
= score
;
1282 rolist
[slot
]->uh
= getsstring(host
, USERLEN
+1+HOSTLEN
);
1284 rolist
[slot
]->uh
= NULL
;
1289 int cf_loadchanfix(void) {
1297 snprintf(srcfile
, sizeof(srcfile
), "%s.0", CFSTORAGE
);
1298 cfdata
= fopen(srcfile
, "r");
1305 while (!feof(cfdata
)) {
1306 if (fgets(line
, sizeof(line
), cfdata
) == NULL
)
1309 if (line
[strlen(line
) - 1] == '\n')
1310 line
[strlen(line
) - 1] = '\0';
1312 if (line
[strlen(line
) - 1] == '\r')
1313 line
[strlen(line
) - 1] = '\0';
1315 if (line
[0] != '\0') {
1316 if (cf_parseline(line
))
1326 /* functions for users of this module */
1327 chanfix
*cf_findchanfix(chanindex
*cip
) {
1328 return cip
->exts
[cfext
];
1331 int cf_wouldreop(nick
*np
, channel
*cp
) {
1335 chanfix
*cf
= cp
->index
->exts
[cfext
];
1338 return 1; /* too bad, we can't do anything about it */
1340 ro
= cf_findregop(np
, cp
->index
, CFACCOUNT
| CFHOST
);
1345 rolist
= (regop
**)cf
->regops
.content
;
1347 for (i
=0; i
<cf
->regops
.cursi
; i
++)
1348 if (rolist
[i
]->score
> topscore
)
1349 topscore
= rolist
[i
]->score
;
1351 if (ro
->score
> topscore
/ 2 && ro
->score
> CFMINSCORE
)