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