/* $Id: channels.c 417 2007-05-03 15:19:40Z tsaviran $
* -------------------------------------------------------
* Copyright (C) 2002-2006 Tommi Saviranta <wnd@iki.fi>
* (C) 2002 Lee Hardy <lee@leeh.co.uk>
* (C) 1998-2002 Sebastian Kienzl <zap@riot.org>
* -------------------------------------------------------
* 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 <config.h>
#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 <string.h>
/*
* 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 */
syntax highlighted by Code2HTML, v. 0.9.1