/* $Id: channels.c 417 2007-05-03 15:19:40Z tsaviran $ * ------------------------------------------------------- * Copyright (C) 2002-2006 Tommi Saviranta * (C) 2002 Lee Hardy * (C) 1998-2002 Sebastian Kienzl * ------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifdef HAVE_CONFIG_H #include #endif /* ifdef HAVE_CONFIG_H */ #include "channels.h" #include "client.h" #include "common.h" #include "qlog.h" #include "irc.h" #include "miau.h" #include "messages.h" #include "error.h" #include "chanlog.h" #include "automode.h" #include /* * active_channels * miau is on these channels * * passive_channels * miau will be on these channels. * For example, if cfg.leave == cfg.rejoin == true, active_channels are * moved to passive_channels when client detached. * * old_channels * miau was on these channels - and they still contain some */ llist_list active_channels; llist_list passive_channels; llist_list old_channels; /* * Free data in channel_type -structure. */ void channel_free(channel_type *chan) { if (chan == NULL) { #ifdef ENDUSERDEBUG enduserdebug("channel_free(NULL)"); #endif /* ifdef ENDUSERDEBUG */ return; } if (chan->simple_name != chan->name) { xfree(chan->simple_name); } xfree(chan->name); xfree(chan->topic); xfree(chan->topicwhen); xfree(chan->topicwho); xfree(chan->key); xfree(chan); } /* void channel_free(channel_type *chan) */ /* * Adds a channel to miaus internal list. */ channel_type * channel_add(const char *channel, const char *key, const int list) { llist_list *target; channel_type *chptr; /* See if channel is already on active list. */ if (channel_find(channel, LIST_ACTIVE) != NULL) { #ifdef ENDUSERDEBUG if (list == LIST_PASSIVE) { enduserdebug("add chan to pass when already on active"); } #endif /* ENDUSERDEBUG */ return NULL; } /* * See if channel is already on passive list. If it is, it isn't * a bad thing, tho. */ if (list == LIST_PASSIVE) { if (channel_find(channel, LIST_PASSIVE) != NULL) { return NULL; } } /* * We must do this to keep old GCC (and probably some other compilers * too) happy. * * if (list == LIST_ACTIVE) { * either { * target = foo; * } or { * chptr == NULL; * } * } * if (chptr == NULL) { * target = bar; * } * * Some compilers just don't get it! */ target = NULL; /* * See if we could simply move channels from passive/old_channels to * active_channels. If we can do this, it's all we need to do. */ if (list == LIST_ACTIVE) { llist_list *source; /* find a list to remove from */ source = NULL; chptr = channel_find(channel, LIST_PASSIVE); if (chptr != NULL) { source = &passive_channels; } else { chptr = channel_find(channel, LIST_OLD); if (chptr != NULL) { source = &old_channels; } } if (source != NULL) { llist_node *ptr; ptr = llist_find((void *) chptr, source); if (ptr != NULL) { llist_delete(ptr, source); } #ifdef ENDUSERDEBUG else { enduserdebug("first the channel found, now it's not. what's going on!?"); } #endif /* ifdef ENDUSERDEBUG */ target = &active_channels; } } else { chptr = NULL; } /* Perhaps channel was not on passive/old_channels, after all. */ if (chptr == NULL) { /* Create new node to channel list. */ chptr = (channel_type *) xcalloc(1, sizeof(channel_type)); chptr->name = xstrdup(channel); chptr->simple_name = chptr->name; /* assume to be the same... */ chptr->simple_set = 0; /* ...but mark as not confirmed */ chptr->name_set = 0; /* real name isn't known either */ /* * We don't need to touch other variabels - calloc did the * job. This is neat, since a few values are set to 0 by * default. */ chptr->key = xstrdup(key == NULL ? "-" : key); chptr->jointries = JOINTRIES_UNSET; #ifdef AUTOMODE chptr->oper = -1; /* Don't know our status. */ #endif /* AUTOMODE */ /* Get list on which to add this channel to. */ if (list == LIST_ACTIVE) { target = &active_channels; } else if (list == LIST_PASSIVE) { target = &passive_channels; } else { target = &old_channels; } } llist_add_tail(llist_create(chptr), target); /* * From now on, we know two things: * - channel is only on one list * - chptr is non-NULL */ if (list == LIST_ACTIVE) { /* adding to ACTIVE -> we know real name of the channel */ if (chptr->name_set == 0) { /* real name wasn't known before */ xfree(chptr->name); chptr->name = xstrdup(channel); chptr->name_set = 1; if (chptr->simple_set == 0) { chptr->simple_name = chptr->name; } } if (chptr->simple_set == 0) { chptr->simple_name = channel_simplify_name(chptr->name); chptr->simple_set = 1; } #ifdef CHANLOG /* active channels may need log-structures. */ chanlog_open(chptr); #endif /* CHANLOG */ } return chptr; } /* channel_type *channel_add(const char *channel, const char *key, const int list) */ /* * Rmoves a channel from miaus internal list. * * "list" defines list hannel is to be removed from. */ void channel_rem(channel_type *chptr, const int list) { llist_list *source; llist_node *node; if (list == LIST_ACTIVE) { source = &active_channels; #ifdef QUICKLOG /* Need to know which active channels have qlog. */ qlog_check(cfg.qloglength * 60); #endif /* QUICKLOG */ } else { source = (list == LIST_PASSIVE) ? &passive_channels : &old_channels; } /* Only remove existing channels. :-) */ if (chptr == NULL) { #ifdef ENDUSERDEBUG enduserdebug("channel_rem(NULL, %d) called", list); #endif /* ENDUSERDEBUG */ return; } #ifdef AUTOMODE automode_drop_channel(chptr, NULL, ANY_MODE); #endif /* AUTOMODE */ #ifdef CHANLOG /* Close the logfile if we have one. */ if (chptr->log != NULL) { chanlog_close(chptr); } #endif /* CHANLOG */ node = llist_find(chptr, source); if (node == NULL) { #ifdef ENDUSERDEBUG if (list == LIST_ACTIVE) { enduserdebug("removing non-existing channel"); } #endif /* ENDUSERDEBUG */ return; } #ifdef QUICKLOG /* See if we need to move this channel to list of old channels. */ if (chptr->hasqlog && list == LIST_ACTIVE) { /* Yep, we'll just move it. */ llist_add_tail(llist_create(chptr), &old_channels); } else { /* No moving, just freeing the resources. */ channel_free(chptr); } #else /* QUICKLOG */ /* Free resources. */ channel_free(chptr); #endif /* QUICKLOG */ /* And finally, remove channel from the list. */ llist_delete(node, source); } /* void channel_rem(channel_type *, const int) */ /* * Searches for a channel, returning it if found, else NULL. * * "list" declares which list to search. */ channel_type * channel_find(const char *name, int list) { llist_node *node; channel_type *chan; channel_type *bmatch; char *sname; /* this means we won't work with new channel modes */ if (channel_is_name(name) == 0) { return NULL; } if (list == LIST_ACTIVE) { node = active_channels.head; } else if (list == LIST_PASSIVE) { node = passive_channels.head; } else { node = old_channels.head; } sname = channel_simplify_name(name); for (bmatch = NULL; node != NULL; node = node->next) { chan = (channel_type *) node->data; if (xstrcasecmp(chan->name, name) == 0) { /* exact match */ xfree(sname); return chan; } else if (xstrcasecmp(chan->simple_name, sname) == 0) { bmatch = chan; } } xfree(sname); if (bmatch != NULL) { return bmatch; } /* no match found */ return NULL; } /* channel_type *channel_find(char *name, int list) */ /* * Stores a topic for a channel we're in. */ void channel_topic(channel_type *chan, const char *topic) { xfree(chan->topic); xfree(chan->topicwho); xfree(chan->topicwhen); chan->topic = NULL; chan->topicwho = NULL; chan->topicwhen = NULL; if (topic != NULL && topic[0] != '\0') { chan->topic = xstrdup(topic); } } /* void channel_topic(channel_type *chan, const char *topic) */ /* * Stores who set the topic for a channel we're in. */ void channel_when(channel_type *chan, const char *who, const char *when) { if (chan->topic != NULL) { xfree(chan->topicwho); xfree(chan->topicwhen); chan->topicwho = xstrdup(who); chan->topicwhen = xstrdup(when); } } /* void channel_when(channel_type *chan, const char *who, char *when) */ void channel_join_list(const int list, const int rejoin, connection_type *client) { llist_node *first; char *chans, *keys; size_t csize, ksize; size_t tclen, tklen; int try_joining; if (list == LIST_PASSIVE) { first = passive_channels.head; } else { first = active_channels.head; } try_joining = 0; /* Join old_channels if client was defined. */ if (client != NULL) { LLIST_WALK_H(old_channels.head, channel_type *); #ifdef QUICKLOG if (data->hasqlog) { irc_write(client, ":%s!%s JOIN :%s", status.nickname, status.idhostname, data->name); } else { channel_rem(data, LIST_OLD); } #else /* ifdef QUICKLOG */ channel_rem(data, LIST_OLD); #endif /* ifdef else QUICKLOG */ LLIST_WALK_F; } if (first == NULL) { return; } csize = ksize = 256; chans = (char *) xcalloc(csize, 1); keys = (char *) xcalloc(ksize, 1); tclen = tklen = 1; LLIST_WALK_H(first, channel_type *); if (list == LIST_PASSIVE) { if ((rejoin == 1 || (cfg.rejoin == 1 && cfg.leave == 0)) && data->jointries == JOINTRIES_UNSET) { /* * Set data->jointries to 1 even if * cfg.jointries = 0. This way we can try to * join channels in miaurc. If the channel * is a safe channel, try joining only once. */ if (cfg.jointries == 0) { data->jointries = 1; } else { if (data->name[0] == '!') { data->jointries = 1; } else { data->jointries = cfg.jointries; } } } if (data->jointries > 0) { size_t clen, klen; try_joining = 1; data->jointries--; /* Add channel and key in queue. */ /* * name and key guaranteed * terminated and valid * * strncat == paranoid */ /* * join with "simple" name, "real" name * of a safe channel won't do here */ clen = strlen(data->name); klen = strlen(data->key); tclen += clen + 1; tklen += klen + 1; if (tclen > csize) { csize = tclen; chans = (char *) xrealloc(chans, csize); } if (tklen > ksize) { ksize = tklen; keys = (char *) xrealloc(keys, ksize); } strcat(chans, ","); strncat(chans, data->name, clen); strcat(keys, ","); strncat(keys, data->key, klen); } else if (data->jointries == 0) { channel_type *chan; /* * If cfg.jointries is 0, remove channel from * list after unsuccesfull attempt to join it. */ chan = channel_find(data->name, LIST_PASSIVE); channel_rem(chan, LIST_PASSIVE); } } else { /* Tell client to join this channel. */ irc_write(client, ":%s!%s JOIN :%s", status.nickname, status.idhostname, data->name); /* Also tell client about this channel. */ irc_write(client, ":%s %d %s %s %s", i_server.realname, (data->topic != NULL ? RPL_TOPIC : RPL_NOTOPIC), status.nickname, data->name, (data->topic != NULL ? data->topic : ":No topic is set")); if (data->topicwho != NULL && data->topicwhen != NULL) { irc_write(client, "%s %d %s %s %s %s", i_server.realname, RPL_TOPICWHO, status.nickname, data->name, data->topicwho, data->topicwhen); } #ifdef CNANLOG if (chanlog_has_log((channel_type *) data, LOG_MIAU)) { log_write_entry(data, LOGM_MIAU, get_short_localtime(), ""); } #endif /* CHANLOG */ irc_write(&c_server, "NAMES %s", data->name); } LLIST_WALK_F; if (try_joining == 1) { report(rejoin ? MIAU_REINTRODUCE : MIAU_JOINING, chans + 1); irc_write(&c_server, "JOIN %s %s", chans + 1, keys + 1); } xfree(chans); xfree(keys); } /* void channel_join_list(const int, const int, connection_type *) */ char * channel_simplify_name(const char *chan) { if (chan[0] != '!') { /* not safe-channel */ return xstrdup(chan); } else { size_t len; char *name; len = strlen(chan); if (len < 6) { #ifdef ENDUSERDEBUG enduserdebug("weird channel: '%s'", chan); #endif /* ifdef ENDUSERDEBUG */ return xstrdup(chan); /* some weird channel */ } /* safe channel */ len -= 4; /* original length - 5 + terminator */ name = (char *) xmalloc(len); snprintf(name, len, "!%s", chan + 6); name[len - 1] = '\0'; return name; } } /* char *channel_simplify_name(const char *chan) */ /* * Check if name could be a channel name. * * Return non-zero if name could be a channel name. */ int channel_is_name(const char *name) { if (name == NULL) { return 0; } switch (name[0]) { case '#': /* ordinary channel (rfc 1459) */ case '&': /* local channel (rfc 1459) */ case '!': /* safe channel (rfc 2811) */ case '+': /* modeless channel (rfc 2811) */ case '.': /* programmable channel (kineircd) */ case '~': /* global channel (what is that? kineircd) */ return (int) name[0]; break; /* dummy */ default: return 0; break; /* dummy */ } } /* int channel_is_name(const char *name) */ #ifdef OBSOLETE /* Never defined - ignore this. */ static void set_simple_name(channel_type *chan, const char *name) { if (chan->name[0] != '!') { /* not safe-channel */ chan->simple_set = 1; } else { if (strlen(chan->name) < 2 || strlen(name) < 6) { /* channel name too short -- not safe channel */ return; } if (xstrcasecmp(chan->name + 2, name + 6) == 0) { chan->simplename = xstrdup(name); chan->simple_set = 1; } } } /* static void set_simple_name(channel_type *chan, const char *name) */ /* * Creates a hash value based on the first 25 chars of a channel name. */ unsigned int channel_hash(char *p) { int i = 25; unsigned int hash = 0; while (*p && --i) { hash = (hash << 4) - (hash + (unsigned char) tolower(*p++)); } return (hash & (MAX_CHANNELS - 1)); } /* unsigned int channel_hash(char *) */ #endif /* OBSOLETE */