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