]> jfr.im git - irc/quakenet/newserv.git/blob - channel/channel.c
LUA: add function for channel chanop notice
[irc/quakenet/newserv.git] / channel / channel.c
1 #/* channel.c */
2
3 #include "channel.h"
4 #include "../server/server.h"
5 #include "../nick/nick.h"
6 #include "../lib/irc_string.h"
7 #include "../irc/irc_config.h"
8 #include "../parser/parser.h"
9 #include "../irc/irc.h"
10 #include "../lib/base64.h"
11 #include "../lib/version.h"
12 #include "../core/nsmalloc.h"
13
14 #include <stdio.h>
15 #include <string.h>
16
17 MODULE_VERSION("");
18
19 #define channelhash(x) (irc_crc32i(x)%CHANNELHASHSIZE)
20
21 unsigned long nouser;
22
23 const flag cmodeflags[] = {
24 { 'n', CHANMODE_NOEXTMSG },
25 { 't', CHANMODE_TOPICLIMIT },
26 { 's', CHANMODE_SECRET },
27 { 'p', CHANMODE_PRIVATE },
28 { 'i', CHANMODE_INVITEONLY },
29 { 'l', CHANMODE_LIMIT },
30 { 'k', CHANMODE_KEY },
31 { 'm', CHANMODE_MODERATE },
32 { 'c', CHANMODE_NOCOLOUR },
33 { 'C', CHANMODE_NOCTCP },
34 { 'r', CHANMODE_REGONLY },
35 { 'D', CHANMODE_DELJOINS },
36 { 'u', CHANMODE_NOQUITMSG },
37 { 'N', CHANMODE_NONOTICE },
38 { 'M', CHANMODE_MODNOAUTH },
39 { 'T', CHANMODE_SINGLETARG },
40 { '\0', 0 } };
41
42 void channelstats(int hooknum, void *arg);
43 void sendchanburst(int hooknum, void *arg);
44
45 void _init() {
46 /* Set up the nouser marker according to our own numeric */
47 nouser=(mylongnum<<18)|CU_NOUSERMASK;
48
49 /* If we're connected to IRC, force a disconnect. This needs to be done
50 * before we register all our hooks which would otherwise get called
51 * during the disconnect. */
52 if (connected) {
53 irc_send("%s SQ %s 0 :Resync [adding channel support]",mynumeric->content,myserver->content); irc_disconnected(0);
54 }
55
56 /* Set up our hooks */
57 registerhook(HOOK_NICK_NEWNICK,&addordelnick);
58 registerhook(HOOK_NICK_LOSTNICK,&addordelnick);
59 registerhook(HOOK_CORE_STATSREQUEST,&channelstats);
60 registerhook(HOOK_IRC_SENDBURSTBURSTS,&sendchanburst);
61 registerhook(HOOK_NICK_WHOISCHANNELS,&handlewhoischannels);
62
63 registerserverhandler("B",&handleburstmsg,7);
64 registerserverhandler("J",&handlejoinmsg,2);
65 registerserverhandler("C",&handlecreatemsg,2);
66 registerserverhandler("L",&handlepartmsg,1);
67 registerserverhandler("K",&handlekickmsg,3);
68 registerserverhandler("T",&handletopicmsg,3);
69 registerserverhandler("M",&handlemodemsg,8);
70 registerserverhandler("OM",&handlemodemsg,8); /* Treat OPMODE just like MODE */
71 registerserverhandler("CM",&handleclearmodemsg,2);
72 }
73
74 void _fini() {
75 unsigned int i;
76 struct channel *cp;
77 struct chanindex *cip, *ncip;
78 nick *np;
79
80 deregisterserverhandler("B",&handleburstmsg);
81 deregisterserverhandler("J",&handlejoinmsg);
82 deregisterserverhandler("C",&handlecreatemsg);
83 deregisterserverhandler("L",&handlepartmsg);
84 deregisterserverhandler("K",&handlekickmsg);
85 deregisterserverhandler("T",&handletopicmsg);
86 deregisterserverhandler("M",&handlemodemsg);
87 deregisterserverhandler("OM",&handlemodemsg);
88 deregisterserverhandler("CM",&handleclearmodemsg);
89
90 deregisterhook(HOOK_NICK_NEWNICK,&addordelnick);
91 deregisterhook(HOOK_NICK_LOSTNICK,&addordelnick);
92 deregisterhook(HOOK_CORE_STATSREQUEST,&channelstats);
93 deregisterhook(HOOK_IRC_SENDBURSTBURSTS,&sendchanburst);
94 deregisterhook(HOOK_NICK_WHOISCHANNELS,&handlewhoischannels);
95
96 /* Free all the channels */
97 for(i=0;i<CHANNELHASHSIZE;i++) {
98 for (cip=chantable[i];cip;cip=ncip) {
99 ncip=cip->next;
100 if ((cp=cip->channel))
101 delchannel(cp);
102 }
103 }
104
105 /* We also need to remove the channels array from each user */
106 for (i=0;i<NICKHASHSIZE;i++) {
107 for (np=nicktable[i];np;np=np->next) {
108 array_free(np->channels);
109 free(np->channels);
110 }
111 }
112
113 nsfreeall(POOL_CHANNEL);
114 }
115
116 int addnicktochannel(channel *cp, long numeric) {
117 nick *np;
118 int i;
119 channel **ch;
120 void *args[2];
121
122 /* Add the channel to the user first, since it might fail */
123 if ((np=getnickbynumeric(numeric&CU_NUMERICMASK))==NULL) {
124 Error("channel",ERR_ERROR,"Non-existent numeric %ld joined channel %s",numeric,cp->index->name->content);
125 return 1;
126 }
127
128 if (getnumerichandlefromchanhash(cp->users,numeric)) {
129 Error("channel",ERR_ERROR,"User %s joined channel %s it was already on!",np->nick,cp->index->name->content);
130 return 1;
131 }
132
133 i=array_getfreeslot(np->channels);
134 ch=(channel **)(np->channels->content);
135 ch[i]=cp;
136
137 /* Add the user to the channel.
138 * I don't expect this while loop to go round many times
139 * in the majority of cases */
140 while (addnumerictochanuserhash(cp->users,numeric)) {
141 rehashchannel(cp);
142 }
143
144 /* Trigger the hook */
145 args[0]=(void *)cp;
146 args[1]=(void *)np;
147 triggerhook(HOOK_CHANNEL_NEWNICK,args);
148
149 return 0;
150 }
151
152 void delnickfromchannel(channel *cp, long numeric, int updateuser) {
153 int i;
154 channel **ch;
155 nick *np;
156 unsigned long *lp;
157 int found=0;
158 void *args[2];
159
160 if ((np=getnickbynumeric(numeric&CU_NUMERICMASK))==NULL) {
161 Error("channel",ERR_ERROR,"Trying to remove non-existent nick %lu from channel %s",numeric,cp->index->name->content);
162 return;
163 }
164
165 args[0]=(void *)cp;
166 args[1]=(void *)np;
167
168 if ((lp=getnumerichandlefromchanhash(cp->users,numeric))==NULL) {
169 /* User wasn't on the channel. It's perfectly legit that this can happen. */
170 return;
171 } else {
172 triggerhook(HOOK_CHANNEL_LOSTNICK,args);
173 *lp=nouser;
174 if (--cp->users->totalusers==0) {
175 /* We're deleting the channel; flag it here */
176 triggerhook(HOOK_CHANNEL_LOSTCHANNEL,cp);
177 delchannel(cp);
178 }
179 }
180
181 /* The updateuser part is optional; if the user is parting _all_ their channels
182 * at once we don't bother hunting each channel down we just blat the array */
183
184 if (updateuser) {
185 ch=(channel **)(np->channels->content);
186 for (i=0;i<np->channels->cursi;i++) {
187 if (ch[i]==cp) {
188 array_delslot(np->channels,i);
189 found=1;
190 break;
191 }
192 }
193 if (found==0) {
194 Error("channel",ERR_ERROR,"Trying to remove nick %s from channel %s it was not on (chan not on nick)",np->nick,cp->index->name->content);
195 }
196 }
197 }
198
199 void delchannel(channel *cp) {
200 chanindex *cip;
201
202 /* Remove entry from index */
203 cip=cp->index;
204 cip->channel=NULL;
205 releasechanindex(cip);
206
207 freesstring(cp->topic);
208 freesstring(cp->key);
209 freechanuserhash(cp->users);
210 clearallbans(cp);
211 freechan(cp);
212 }
213
214 channel *createchannel(char *name) {
215 channel *cp;
216 chanindex *cip;
217 sstring *ssp;
218
219 cip=findorcreatechanindex(name);
220
221 if (cip->channel) {
222 Error("channel",ERR_ERROR,"Attempting to create existing channel %s (%s).",cip->name->content,name);
223 return cip->channel;
224 }
225
226 /* Check that chanindex record has the same capitalisation as actual channel */
227 if (strcmp(cip->name->content,name)) {
228 ssp=cip->name;
229 cip->name=getsstring(name,CHANNELLEN);
230 freesstring(ssp);
231 }
232
233 cp=newchan();
234 cip->channel=cp;
235 cp->index=cip;
236
237 cp->timestamp=MAGIC_REMOTE_JOIN_TS;
238 cp->topic=NULL;
239 cp->topictime=0;
240 cp->flags=0;
241 cp->key=NULL;
242 cp->limit=0;
243 cp->bans=NULL;
244 cp->users=newchanuserhash(1);
245
246 return cp;
247 }
248
249 channel *findchannel(char *name) {
250 chanindex *cip;
251
252 cip=findchanindex(name);
253 if (cip==NULL) {
254 return NULL;
255 } else {
256 return cip->channel;
257 }
258 }
259
260 void channelstats(int hooknum, void *arg) {
261 long level=(long)arg;
262 int i,curchain,maxchain=0,total=0,buckets=0,realchans=0;
263 int users=0,slots=0;
264 chanindex *cip;
265 char buf[100];
266
267 for (i=0;i<CHANNELHASHSIZE;i++) {
268 if (chantable[i]!=NULL) {
269 buckets++;
270 curchain=0;
271 for (cip=chantable[i];cip;cip=cip->next) {
272 total++;
273 curchain++;
274 if (cip->channel!=NULL) {
275 realchans++;
276 users+=cip->channel->users->totalusers;
277 slots+=cip->channel->users->hashsize;
278 }
279 }
280 if (curchain>maxchain) {
281 maxchain=curchain;
282 }
283 }
284 }
285
286 if (level>5) {
287 /* Full stats */
288 sprintf(buf,"Channel : %6d channels (HASH: %6d/%6d, chain %3d)",total,buckets,CHANNELHASHSIZE,maxchain);
289 triggerhook(HOOK_CORE_STATSREPLY,buf);
290
291 sprintf(buf,"Channel :%7d channel users, %7d slots allocated, efficiency %.1f%%",users,slots,(float)(100*users)/slots);
292 triggerhook(HOOK_CORE_STATSREPLY,buf);
293 }
294
295 if (level>2) {
296 sprintf(buf,"Channel : %6d channels formed.",realchans);
297 triggerhook(HOOK_CORE_STATSREPLY,buf);
298 }
299 }
300
301
302 /*
303 * Deal with users joining the network (create and initialise their channel array)
304 * or leaving the network (remove them from all channels and delete the array)
305 */
306
307 void addordelnick(int hooknum, void *arg) {
308 nick *np=(nick *)arg;
309 channel **ch;
310 int i;
311
312 switch(hooknum) {
313 case HOOK_NICK_NEWNICK:
314 np->channels=(array *)malloc(sizeof(array));
315 array_init(np->channels,sizeof(channel **));
316 array_setlim1(np->channels,10);
317 array_setlim2(np->channels,15);
318 break;
319
320 case HOOK_NICK_LOSTNICK:
321 ch=(channel **)(np->channels->content);
322 for(i=0;i<np->channels->cursi;i++) {
323 delnickfromchannel(ch[i],np->numeric,0);
324 }
325 array_free(np->channels);
326 free(np->channels);
327 break;
328 }
329 }
330
331 /*
332 * Spam our local burst on connect..
333 */
334
335 void sendchanburst(int hooknum, void *arg) {
336 chanindex *cip;
337 channel *cp;
338 chanban *ban;
339 char buf[BUFSIZE];
340 char buf2[20];
341 char *banstr;
342 int i;
343 int j,k;
344 int bufpos;
345 int newmode=1;
346 int newline=1;
347 long modeorder[] = { 0, CUMODE_OP, CUMODE_VOICE, CUMODE_OP|CUMODE_VOICE };
348 long curmode;
349
350 for (i=0;i<CHANNELHASHSIZE;i++) {
351 for (cip=chantable[i];cip;cip=cip->next) {
352 cp=cip->channel;
353 if (cp==NULL) {
354 continue;
355 }
356 /* Set up the burst */
357 sprintf(buf2,"%d ",cp->limit);
358 bufpos=sprintf(buf,"%s B %s %lu %s %s%s%s",mynumeric->content,cip->name->content,cp->timestamp,
359 printflags(cp->flags,cmodeflags),IsLimit(cp)?buf2:"",
360 IsKey(cp)?cp->key->content:"",IsKey(cp)?" ":"");
361 for(j=0;j<4;j++) {
362 curmode=modeorder[j];
363 newmode=1;
364 for (k=0;k<cp->users->hashsize;k++) {
365 if (cp->users->content[k]!=nouser && ((cp->users->content[k]&(CU_MODEMASK))==curmode)) {
366 /* We found a user of the correct type for this pass */
367 if (BUFSIZE-bufpos<10) { /* Out of space.. wrap up the old line and send a new one */
368 newmode=newline=1;
369 irc_send("%s",buf);
370 bufpos=sprintf(buf,"%s B %s %lu ",mynumeric->content,cip->name->content,cp->timestamp);
371 }
372 if (newmode) {
373 bufpos+=sprintf(buf+bufpos,"%s%s%s%s%s",newline?"":",",longtonumeric(cp->users->content[k]&CU_NUMERICMASK,5),
374 (curmode==0?"":":"),(curmode&CUMODE_OP)?"o":"",(curmode&CUMODE_VOICE)?"v":"");
375 } else {
376 bufpos+=sprintf(buf+bufpos,",%s",longtonumeric(cp->users->content[k]&CU_NUMERICMASK,5));
377 }
378 newmode=newline=0;
379 } /* if(...) */
380 } /* for (k=..) */
381 } /* for (j=..) */
382
383 /* And now the bans */
384 newline=1;
385 for(ban=cp->bans;ban;ban=ban->next) {
386 banstr=bantostring(ban);
387 if ((BUFSIZE-bufpos)<(strlen(banstr)+10)) { /* Out of space.. wrap up the old line and send a new one */
388 newline=1;
389 irc_send("%s",buf);
390 bufpos=sprintf(buf,"%s B %s %lu",mynumeric->content,cip->name->content,cp->timestamp);
391 }
392 bufpos+=sprintf(buf+bufpos,"%s%s ",(newline?" :%":""),banstr);
393 newline=0;
394 }
395 irc_send("%s",buf);
396 } /* for(cp..) */
397 } /* for (i..) */
398 }
399
400 /*
401 * countuniquehosts:
402 * Uses the marker on all host records to count unique hosts
403 * on a channel in O(n) time (n is channel user hash size).
404 */
405
406 unsigned int countuniquehosts(channel *cp) {
407 unsigned int marker;
408 int i;
409 int count=0;
410 nick *np;
411
412 marker=nexthostmarker();
413 for (i=0;i<cp->users->hashsize;i++) {
414 if (cp->users->content[i]==nouser)
415 continue;
416
417 if ((np=getnickbynumeric(cp->users->content[i]))==NULL) {
418 Error("channel",ERR_ERROR,"Found unknown numeric %lu on channel %s",cp->users->content[i],cp->index->name->content);
419 continue;
420 }
421
422 if (np->host->marker==marker)
423 continue;
424
425 np->host->marker=marker;
426 count++;
427 }
428
429 return count;
430 }
431
432 /*
433 * clean_key: returns a "cleaned" version of the key like ircu does.
434 *
435 * Note that s is a signed char, so we are basically allowing everything from 33-127 except : or ,
436 *
437 * Unlike ircu we don't check against KEYLEN here, this is done elsewhere.
438 */
439 void clean_key(char *key) {
440 for (;*key;key++) {
441 if (*key<=32 || *key==':' || *key==',') {
442 *key=0;
443 return;
444 }
445 }
446 }