1 /*
2 * atheme-services: A collection of minimalist IRC services
3 * channels.c: Channel event and state tracking
4 *
5 * Copyright (c) 2005-2007 Atheme Project (http://www.atheme.org)
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21 * POSSIBILITY OF SUCH DAMAGE.
22 */
23
24 #include "atheme.h"
25
26 mowgli_patricia_t *chanlist;
27
28 mowgli_heap_t *chan_heap;
29 mowgli_heap_t *chanuser_heap;
30 mowgli_heap_t *chanban_heap;
31
32 /*
33 * init_channels()
34 *
35 * Initializes the channel-related heaps and DTree structures.
36 *
37 * Inputs:
38 * - nothing
39 *
40 * Outputs:
41 * - nothing
42 *
43 * Side Effects:
44 * - if the heaps or DTrees fail to initialize, the program will abort.
45 */
init_channels(void)46 void init_channels(void)
47 {
48 chan_heap = sharedheap_get(sizeof(channel_t));
49 chanuser_heap = sharedheap_get(sizeof(chanuser_t));
50 chanban_heap = sharedheap_get(sizeof(chanban_t));
51
52 if (chan_heap == NULL || chanuser_heap == NULL || chanban_heap == NULL)
53 {
54 slog(LG_INFO, "init_channels(): block allocator failure.");
55 exit(EXIT_FAILURE);
56 }
57
58 chanlist = mowgli_patricia_create(irccasecanon);
59 }
60
61 /*
62 * channel_add(const char *name, time_t ts, server_t *creator)
63 *
64 * Channel object factory.
65 *
66 * Inputs:
67 * - channel name
68 * - timestamp of channel creation
69 * - server that is creating the channel
70 *
71 * Outputs:
72 * - on success, a channel object
73 * - on failure, NULL
74 *
75 * Side Effects:
76 * - the channel is automatically inserted into the channel DTree
77 * - if the creator is not me.me:
78 * - channel_add hook is called
79 * - all services are joined if this is the snoop channel
80 * - if the creator is me.me these actions must be performed by the
81 * caller (i.e. join()) after joining the service
82 */
channel_add(const char * name,time_t ts,server_t * creator)83 channel_t *channel_add(const char *name, time_t ts, server_t *creator)
84 {
85 channel_t *c;
86 mychan_t *mc;
87
88 if (!VALID_GLOBAL_CHANNEL_PFX(name))
89 {
90 slog(LG_DEBUG, "channel_add(): got channel with invalid global prefix: %s", name);
91 return NULL;
92 }
93
94 c = channel_find(name);
95
96 if (c)
97 {
98 slog(LG_DEBUG, "channel_add(): channel already exists: %s", name);
99 return c;
100 }
101
102 slog(LG_DEBUG, "channel_add(): %s by %s", name, creator->name);
103
104 c = mowgli_heap_alloc(chan_heap);
105
106 c->name = sstrdup(name);
107 c->ts = ts;
108
109 c->topic = NULL;
110 c->topic_setter = NULL;
111
112 if (ignore_mode_list_size != 0)
113 c->extmodes = scalloc(sizeof(char *), ignore_mode_list_size);
114
115 c->bans.head = NULL;
116 c->bans.tail = NULL;
117 c->bans.count = 0;
118
119 if ((mc = mychan_find(c->name)))
120 mc->chan = c;
121
122 mowgli_patricia_add(chanlist, c->name, c);
123
124 cnt.chan++;
125
126 if (creator != me.me)
127 {
128 hook_call_channel_add(c);
129
130 }
131
132 return c;
133 }
134
135 /*
136 * channel_delete(channel_t *c)
137 *
138 * Destroys a channel object and its children member objects.
139 *
140 * Inputs:
141 * - channel object to destroy
142 *
143 * Outputs:
144 * - nothing
145 *
146 * Side Effects:
147 * - channel_delete hook is called
148 * - a channel and all attached structures are destroyed
149 * - no protocol messages are sent for any remaining members
150 */
channel_delete(channel_t * c)151 void channel_delete(channel_t *c)
152 {
153 mychan_t *mc;
154 mowgli_node_t *n, *tn;
155 chanuser_t *cu;
156
157 return_if_fail(c != NULL);
158
159 slog(LG_DEBUG, "channel_delete(): %s", c->name);
160
161 modestack_finalize_channel(c);
162
163 /* If this is called from uplink_close(), there may still be services
164 * in the channel. Remove them. Calling chanuser_delete() could lead
165 * to a recursive call, so don't do that.
166 * -- jilles */
167 MOWGLI_ITER_FOREACH_SAFE(n, tn, c->members.head)
168 {
169 cu = n->data;
170 soft_assert(is_internal_client(cu->user) && !me.connected);
171 mowgli_node_delete(&cu->cnode, &c->members);
172 mowgli_node_delete(&cu->unode, &cu->user->channels);
173 mowgli_heap_free(chanuser_heap, cu);
174 cnt.chanuser--;
175 }
176 c->nummembers = 0;
177 c->numsvcmembers = 0;
178
179 hook_call_channel_delete(c);
180
181 mowgli_patricia_delete(chanlist, c->name);
182
183 if ((mc = mychan_find(c->name)))
184 mc->chan = NULL;
185
186 clear_simple_modes(c);
187 chanban_clear(c);
188
189 if (c->extmodes != NULL)
190 free(c->extmodes);
191
192 free(c->name);
193 if (c->topic != NULL)
194 free(c->topic);
195 if (c->topic_setter != NULL)
196 free(c->topic_setter);
197
198 mowgli_heap_free(chan_heap, c);
199
200 cnt.chan--;
201 }
202
203 /*
204 * chanban_add(channel_t *chan, const char *mask, int type)
205 *
206 * Channel ban factory.
207 *
208 * Inputs:
209 * - channel that the ban belongs to
210 * - banmask
211 * - type of ban, e.g. 'b' or 'e'
212 *
213 * Outputs:
214 * - on success, a new channel ban object
215 * - on failure, NULL
216 *
217 * Side Effects:
218 * - the created channel ban object is added to the channel automatically.
219 */
chanban_add(channel_t * chan,const char * mask,int type)220 chanban_t *chanban_add(channel_t *chan, const char *mask, int type)
221 {
222 chanban_t *c;
223
224 return_val_if_fail(chan != NULL, NULL);
225 return_val_if_fail(mask != NULL, NULL);
226
227 /* this would break protocol and/or cause crashes */
228 if (*mask == '\0' || *mask == ':' || strchr(mask, ' '))
229 {
230 slog(LG_ERROR, "chanban_add(): trying to add invalid +%c %s to channel %s", type, mask, chan->name);
231 return NULL;
232 }
233
234 c = chanban_find(chan, mask, type);
235
236 if (c)
237 {
238 slog(LG_DEBUG, "chanban_add(): channel ban %s:%s already exists", chan->name, c->mask);
239 return NULL;
240 }
241
242 slog(LG_DEBUG, "chanban_add(): %s +%c %s", chan->name, type, mask);
243
244 c = mowgli_heap_alloc(chanban_heap);
245
246 c->chan = chan;
247 c->mask = sstrdup(mask);
248 c->type = type;
249
250 mowgli_node_add(c, &c->node, &chan->bans);
251
252 return c;
253 }
254
255 /*
256 * chanban_delete(chanban_t *c)
257 *
258 * Destroys a channel ban.
259 *
260 * Inputs:
261 * - channel ban object to destroy
262 *
263 * Outputs:
264 * - nothing
265 *
266 * Side Effects:
267 * - the channel ban is automatically removed from the channel that owned it
268 */
chanban_delete(chanban_t * c)269 void chanban_delete(chanban_t * c)
270 {
271 return_if_fail(c != NULL);
272
273 mowgli_node_delete(&c->node, &c->chan->bans);
274
275 free(c->mask);
276 mowgli_heap_free(chanban_heap, c);
277 }
278
279 /*
280 * chanban_find(channel_t *chan, const char *mask, int type)
281 *
282 * Looks up a channel ban.
283 *
284 * Inputs:
285 * - channel that the ban is supposedly on
286 * - mask that is being looked for
287 * - type of ban that is being looked for
288 *
289 * Outputs:
290 * - on success, returns the channel ban object requested
291 * - on failure, returns NULL
292 *
293 * Side Effects:
294 * - none
295 */
chanban_find(channel_t * chan,const char * mask,int type)296 chanban_t *chanban_find(channel_t *chan, const char *mask, int type)
297 {
298 chanban_t *c;
299 mowgli_node_t *n;
300
301 return_val_if_fail(chan != NULL, NULL);
302 return_val_if_fail(mask != NULL, NULL);
303
304 MOWGLI_ITER_FOREACH(n, chan->bans.head)
305 {
306 c = n->data;
307
308 if (c->type == type && !irccasecmp(c->mask, mask))
309 return c;
310 }
311
312 return NULL;
313 }
314
315 /*
316 * chanuser_add(channel_t *chan, const char *nick)
317 *
318 * Channel user factory.
319 *
320 * Inputs:
321 * - channel that the user should belong to
322 * - nick/UID with any appropriate prefixes (e.g. ~, &, @, %, +)
323 * (if the user has a UID it must be used)
324 *
325 * Outputs:
326 * - on success, a new channel user object
327 * - on failure (NULL channel, or user kicked by channel_join hook), NULL
328 *
329 * Side Effects:
330 * - the channel user object is automatically associated to its parents
331 * - channel_join hook is called
332 */
333
334 /*
335 * Rewritten 06/23/05 by nenolod:
336 *
337 * Iterate through the list of prefix characters we know about.
338 * Continue to do so until all prefixes are covered. Then add the
339 * nick to the channel, with the privs they have acquired thus far.
340 *
341 * Once, and only once we have done that do we start in on checking
342 * privileges. Otherwise we have a very inefficient way of doing
343 * things. It worked fine for shrike, but the old code was restricted
344 * to handling only @, @+ and + as prefixes.
345 */
chanuser_add(channel_t * chan,const char * nick)346 chanuser_t *chanuser_add(channel_t *chan, const char *nick)
347 {
348 user_t *u;
349 chanuser_t *cu, *tcu;
350 unsigned int flags = 0;
351 int i = 0;
352 hook_channel_joinpart_t hdata;
353
354 return_val_if_fail(chan != NULL, NULL);
355 return_val_if_fail(chan->name != NULL, NULL);
356 return_val_if_fail(nick != NULL, NULL);
357
358 if (!VALID_GLOBAL_CHANNEL_PFX(chan->name))
359 {
360 slog(LG_DEBUG, "chanuser_add(): got an invalid global channel prefix: %s", chan->name);
361 return NULL;
362 }
363
364 while (*nick != '\0')
365 {
366 for (i = 0; prefix_mode_list[i].mode; i++)
367 if (*nick == prefix_mode_list[i].mode)
368 {
369 flags |= prefix_mode_list[i].value;
370 break;
371 }
372 if (!prefix_mode_list[i].mode)
373 break;
374 nick++;
375 }
376
377 u = user_find(nick);
378 if (u == NULL)
379 {
380 slog(LG_DEBUG, "chanuser_add(): nonexist user: %s", nick);
381 return NULL;
382 }
383
384 tcu = chanuser_find(chan, u);
385 if (tcu != NULL)
386 {
387 slog(LG_DEBUG, "chanuser_add(): user is already present: %s -> %s", chan->name, u->nick);
388
389 /* could be an OPME or other desyncher... */
390 tcu->modes |= flags;
391
392 return tcu;
393 }
394
395 slog(LG_DEBUG, "chanuser_add(): %s -> %s", chan->name, u->nick);
396
397 cu = mowgli_heap_alloc(chanuser_heap);
398
399 cu->chan = chan;
400 cu->user = u;
401 cu->modes = flags;
402
403 chan->nummembers++;
404 if (is_internal_client(u))
405 chan->numsvcmembers++;
406
407 mowgli_node_add(cu, &cu->cnode, &chan->members);
408 mowgli_node_add(cu, &cu->unode, &u->channels);
409
410 cnt.chanuser++;
411
412 hdata.cu = cu;
413 hook_call_channel_join(&hdata);
414
415 /* Return NULL if a hook function kicked the user out */
416 return hdata.cu;
417 }
418
419 /*
420 * chanuser_delete(channel_t *chan, user_t *user)
421 *
422 * Destroys a channel user object.
423 *
424 * Inputs:
425 * - channel the user is on
426 * - the user itself
427 *
428 * Outputs:
429 * - nothing
430 *
431 * Side Effects:
432 * if the user is on the channel:
433 * - a channel user object is removed from the
434 * channel's userlist and the user's channellist.
435 * - channel_part hook is called
436 * - if this empties the channel and the channel is not set permanent
437 * (ircd->perm_mode), channel_delete() is called (q.v.)
438 */
chanuser_delete(channel_t * chan,user_t * user)439 void chanuser_delete(channel_t *chan, user_t *user)
440 {
441 chanuser_t *cu;
442 hook_channel_joinpart_t hdata;
443
444 return_if_fail(chan != NULL);
445 return_if_fail(user != NULL);
446
447 cu = chanuser_find(chan, user);
448 if (cu == NULL)
449 return;
450
451 /* this is called BEFORE we remove the user */
452 hdata.cu = cu;
453 hook_call_channel_part(&hdata);
454
455 slog(LG_DEBUG, "chanuser_delete(): %s -> %s (%d)", cu->chan->name, cu->user->nick, cu->chan->nummembers - 1);
456
457 mowgli_node_delete(&cu->cnode, &chan->members);
458 mowgli_node_delete(&cu->unode, &user->channels);
459
460 mowgli_heap_free(chanuser_heap, cu);
461
462 chan->nummembers--;
463 cnt.chanuser--;
464
465 if (is_internal_client(user))
466 chan->numsvcmembers--;
467
468 if (chan->nummembers == 0 && !(chan->modes & ircd->perm_mode))
469 {
470 /* empty channels die */
471 slog(LG_DEBUG, "chanuser_delete(): `%s' is empty, removing", chan->name);
472
473 channel_delete(chan);
474 }
475 }
476
477 /*
478 * chanuser_find(channel_t *chan, user_t *user)
479 *
480 * Looks up a channel user object.
481 *
482 * Inputs:
483 * - channel object that the user is on
484 * - target user object
485 *
486 * Outputs:
487 * - on success, a channel user object
488 * - on failure, NULL
489 *
490 * Side Effects:
491 * - none
492 */
chanuser_find(channel_t * chan,user_t * user)493 chanuser_t *chanuser_find(channel_t *chan, user_t *user)
494 {
495 mowgli_node_t *n;
496 chanuser_t *cu;
497
498 return_val_if_fail(chan != NULL, NULL);
499 return_val_if_fail(user != NULL, NULL);
500
501 /* choose shortest list to search -- jilles */
502 if (MOWGLI_LIST_LENGTH(&user->channels) < MOWGLI_LIST_LENGTH(&chan->members))
503 {
504 MOWGLI_ITER_FOREACH(n, user->channels.head)
505 {
506 cu = (chanuser_t *)n->data;
507
508 if (cu->chan == chan)
509 return cu;
510 }
511 }
512 else
513 {
514 MOWGLI_ITER_FOREACH(n, chan->members.head)
515 {
516 cu = (chanuser_t *)n->data;
517
518 if (cu->user == user)
519 return cu;
520 }
521 }
522
523 return NULL;
524 }
525
526 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
527 * vim:ts=8
528 * vim:sw=8
529 * vim:noexpandtab
530 */
531