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