/* * channels.C: Channel event and state tracking * Rights to this code are documented in doc/pod/license.pod. * * Copyright © 2005-2007 Atheme Project (http://www.atheme.org) */ static char const rcsid[] = "$Id: channels.C,v 1.5 2007/08/30 06:57:25 pippijn Exp $"; #include "atheme.h" #include channel_map chanlist; channel_t::callbacks channel_t::callback; chanuser_t::callbacks chanuser_t::callback; /* * init_channels() * * Initializes the channel-related heaps and DTree structures. * * Inputs: * - nothing * * Outputs: * - nothing * * Side Effects: * - if the heaps or DTrees fail to initialize, the program will abort. */ void init_channels (void) { #if 0 chan_heap = BlockHeapCreate (sizeof (channel_t), HEAP_CHANNEL); chanuser_heap = BlockHeapCreate (sizeof (chanuser_t), HEAP_CHANUSER); chanban_heap = BlockHeapCreate (sizeof (chanban_t), HEAP_CHANUSER); #endif } /* * channel_add(char const * const name, unsigned int ts, server_t *creator) * * Channel object factory. * * Inputs: * - channel name * - timestamp of channel creation * - server that is creating the channel * * Outputs: * - on success, a channel object * - on failure, NULL * * Side Effects: * - the channel is automatically inserted into the channel DTree * - if the creator is not me.me: * - channel_add hook is called * - all services are joined if this is the snoop channel * - if the creator is me.me these actions must be performed by the * caller (i.e. join()) after joining the service */ channel_t * channel_add (char const * const name, unsigned int ts, server_t *creator) { channel_t *c; mychan_t *mc; if (*name != '#') { slog (LG_DEBUG, "channel_add(): got non #channel: %s", name); return NULL; } c = channel_find (name); if (c) { slog (LG_DEBUG, "channel_add(): channel already exists: %s", name); return c; } slog (LG_DEBUG, "channel_add(): %s by %s", name, creator->name); c = new channel_t; c->name = sstrdup (name); c->ts = ts; c->topic = NULL; c->topic_setter = NULL; c->bans.head = NULL; c->bans.tail = NULL; c->bans.count = 0; if ((mc = mychan_find (c->name))) mc->chan = c; chanlist[c->name] = c; cnt.chan++; if (creator != me.me) { c->callback.add (c); if (config_options.chan != NULL && !irccasecmp (config_options.chan, name)) joinall (config_options.chan); } return c; } /* * channel_delete(channel_t *c) * * Destroys a channel object and its children member objects. * * Inputs: * - channel object to destroy * * Outputs: * - nothing * * Side Effects: * - channel_delete hook is called * - a channel and all attached structures are destroyed * - no protocol messages are sent for any remaining members */ void channel_delete (channel_t *c) { mychan_t *mc; node_t *n, *tn; chanuser_t *cu; return_if_fail (c != NULL); slog (LG_DEBUG, "channel_delete(): %s", c->name); modestack_finalize_channel (c); /* If this is called from uplink_close(), there may still be services * in the channel. Remove them. Calling chanuser_delete() could lead * to a recursive call, so don't do that. * -- jilles */ LIST_FOREACH_SAFE (n, tn, c->members.head) { cu = static_cast (n->data); soft_assert (is_internal_client (cu->user) && !me.connected); node_del (&cu->cnode, &c->members); node_del (&cu->unode, &cu->user->channels); delete cu; cnt.chanuser--; } c->nummembers = 0; c->callback.remove (c); chanlist.erase (c->name); if ((mc = mychan_find (c->name))) mc->chan = NULL; clear_simple_modes (c); chanban_clear (c); sfree (c->name); if (c->topic != NULL) sfree (c->topic); if (c->topic_setter != NULL) sfree (c->topic_setter); delete c; cnt.chan--; } /* * channel_find(char const * const name) * * Looks up a channel object. * * Inputs: * - name of channel to look up * * Outputs: * - on success, the channel object * - on failure, NULL * * Side Effects: * - none */ channel_t * channel_find (char const * const name) { channel_map::iterator it = chanlist.find (name); if (it == chanlist.end ()) return NULL; return it->second; } /* * chanban_add(channel_t *c, char const * const mask, int type) * * Channel ban factory. * * Inputs: * - channel that the ban belongs to * - banmask * - type of ban, e.g. 'b' or 'e' * * Outputs: * - on success, a new channel ban object * - on failure, NULL * * Side Effects: * - the created channel ban object is added to the channel automatically. */ chanban_t * chanban_add (channel_t *c, char const * const mask, int type) { chanban_t *cb; node_t *n; if (mask == NULL) { slog (LG_ERROR, "chanban_add(): NULL +%c mask", type); return NULL; } /* this would break protocol and/or cause crashes */ if (*mask == '\0' || *mask == ':' || strchr (mask, ' ')) { slog (LG_ERROR, "chanban_add(): trying to add invalid +%c %s to channel %s", type, mask, c->name); return NULL; } cb = chanban_find (c, mask, type); if (cb) { slog (LG_DEBUG, "chanban_add(): channel ban %s:%s already exists", c->name, cb->mask); return NULL; } slog (LG_DEBUG, "chanban_add(): %s +%c %s", c->name, type, mask); n = node_create (); cb = new chanban_t; cb->chan = c; cb->mask = sstrdup (mask); cb->type = type; node_add (cb, n, &c->bans); return cb; } /* * chanban_delete(chanban_t *c) * * Destroys a channel ban. * * Inputs: * - channel ban object to destroy * * Outputs: * - nothing * * Side Effects: * - the channel ban is automatically removed from the channel that owned it */ void chanban_delete (chanban_t *c) { node_t *n; if (!c) { slog (LG_DEBUG, "chanban_delete(): called for nonexistant ban"); return; } n = node_find (c, &c->chan->bans); node_del (n, &c->chan->bans); node_free (n); sfree (c->mask); delete c; } /* * chanban_find(channel_t *chan, char const * const mask, int type) * * Looks up a channel ban. * * Inputs: * - channel that the ban is supposedly on * - mask that is being looked for * - type of ban that is being looked for * * Outputs: * - on success, returns the channel ban object requested * - on failure, returns NULL * * Side Effects: * - none */ chanban_t * chanban_find (channel_t *c, char const * const mask, int type) { chanban_t *cb; node_t *n; LIST_FOREACH (n, c->bans.head) { cb = static_cast (n->data); if (cb->type == type && !irccasecmp (cb->mask, mask)) return cb; } return NULL; } /* * chanban_clear(channel_t *c) * * Destroys all channel bans attached to a channel. * * Inputs: * - channel to clear banlist on * * Outputs: * - nothing * * Side Effects: * - the banlist on the channel is cleared * - no protocol messages are sent */ void chanban_clear (channel_t *c) { node_t *n, *tn; LIST_FOREACH_SAFE (n, tn, c->bans.head) { /* inefficient but avoids code duplication -- jilles */ chanban_delete (static_cast (n->data)); } } /* * chanuser_add(channel_t *c, char const * const nick) * * Channel user factory. * * Inputs: * - channel that the user should belong to * - nick/UID with any appropriate prefixes (e.g. ~, &, @, %, +) * (if the user has a UID it must be used) * * Outputs: * - on success, a new channel user object * - on failure (NULL channel, or user kicked by channel_join hook), NULL * * Side Effects: * - the channel user object is automatically associated to its parents * - channel_join hook is called */ /* * Rewritten 06/23/05 by nenolod: * * Iterate through the list of prefix characters we know about. * Continue to do so until all prefixes are covered. Then add the * nick to the channel, with the privs he has acquired thus far. * * Once, and only once we have done that do we start in on checking * privileges. Otherwise we have a very inefficient way of doing * things. It worked fine for shrike, but the old code was restricted * to handling only @, @+ and + as prefixes. */ chanuser_t * chanuser_add (channel_t *c, char const * const nickname) { char const *nick = nickname; user_t *u; chanuser_t *cu, *tcu; unsigned int flags = 0; int i = 0; if (c == NULL) return NULL; if (*c->name != '#') { slog (LG_DEBUG, "chanuser_add(): got non #channel: %s", c->name); return NULL; } while (*nick != '\0') { for (i = 0; prefix_mode_list[i].mode; i++) if (*nick == prefix_mode_list[i].mode) { flags |= prefix_mode_list[i].value; break; } if (!prefix_mode_list[i].mode) break; nick++; } u = user_find (nick); if (u == NULL) { slog (LG_DEBUG, "chanuser_add(): nonexist user: %s", nick); return NULL; } tcu = chanuser_find (c, u); if (tcu != NULL) { slog (LG_DEBUG, "chanuser_add(): user is already present: %s -> %s", c->name, u->nick); /* could be an OPME or other desyncher... */ tcu->modes |= flags; return tcu; } slog (LG_DEBUG, "chanuser_add(): %s -> %s", c->name, u->nick); cu = new chanuser_t; cu->chan = c; cu->user = u; cu->modes |= flags; c->nummembers++; node_add (cu, &cu->cnode, &c->members); node_add (cu, &cu->unode, &u->channels); cnt.chanuser++; /* Return NULL if a hook function kicked the user out */ return cu->callback.join (cu) ? cu : NULL; } /* * chanuser_delete(channel_t *c, user_t *user) * * Destroys a channel user object. * * Inputs: * - channel the user is on * - the user itself * * Outputs: * - nothing * * Side Effects: * if the user is on the channel: * - a channel user object is removed from the * channel's userlist and the user's channellist. * - channel_part hook is called * - if this empties the channel and the channel is not set permanent * (ircd->perm_mode), channel_delete() is called (q.v.) */ void chanuser_delete (channel_t *c, user_t *user) { chanuser_t *cu; if (!c) { slog (LG_DEBUG, "chanuser_delete(): called with NULL chan"); return; } if (!user) { slog (LG_DEBUG, "chanuser_delete(): called with NULL user"); return; } cu = chanuser_find (c, user); if (cu == NULL) return; /* this is called BEFORE we remove the user */ cu->callback.part (cu); slog (LG_DEBUG, "chanuser_delete(): %s -> %s (%d)", cu->chan->name, cu->user->nick, cu->chan->nummembers - 1); node_del (&cu->cnode, &c->members); node_del (&cu->unode, &user->channels); delete cu; c->nummembers--; cnt.chanuser--; if (c->nummembers == 0 && !(c->modes & ircd->perm_mode)) { /* empty channels die */ slog (LG_DEBUG, "chanuser_delete(): `%s' is empty, removing", c->name); channel_delete (c); } } /* * chanuser_find(channel_t *c, user_t *user) * * Looks up a channel user object. * * Inputs: * - channel object that the user is on * - target user object * * Outputs: * - on success, a channel user object * - on failure, NULL * * Side Effects: * - none */ chanuser_t * chanuser_find (channel_t *c, user_t *user) { node_t *n; chanuser_t *cu; if ((!c) || (!user)) return NULL; /* choose shortest list to search -- jilles */ if (LIST_LENGTH (&user->channels) < LIST_LENGTH (&c->members)) { LIST_FOREACH (n, user->channels.head) { cu = (chanuser_t *) n->data; if (cu->chan == c) return cu; } } else { LIST_FOREACH (n, c->members.head) { cu = (chanuser_t *) n->data; if (cu->user == user) return cu; } } return NULL; }