/* Channel-handling routines.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file COPYING for
* details.
*/
#include "services.h"
#include "modules.h"
/*************************************************************************/
#define HASHFUNC(key) (hashlookup[(uint8)((key)[1])]<<5 \
| ((key)[1] ? hashlookup[(uint8)((key)[2])] : 0))
#define HASHSIZE 1024
#include "hash.h"
void add_channel(Channel *chan); /* to avoid "no prototype" warning */
void del_channel(Channel *chan); /* same */
DEFINE_HASH(channel, Channel, name)
static int cb_create = -1;
static int cb_delete = -1;
static int cb_join = -1;
static int cb_join_check = -1;
static int cb_mode = -1;
static int cb_mode_change = -1;
static int cb_umode_change = -1;
static int cb_topic = -1;
/*************************************************************************/
/*************************************************************************/
int channel_init(int ac, char **av)
{
cb_create = register_callback(NULL, "channel create");
cb_delete = register_callback(NULL, "channel delete");
cb_join = register_callback(NULL, "channel JOIN");
cb_join_check = register_callback(NULL, "channel JOIN check");
cb_mode = register_callback(NULL, "channel MODE");
cb_mode_change = register_callback(NULL, "channel mode change");
cb_umode_change = register_callback(NULL, "channel umode change");
cb_topic = register_callback(NULL, "channel TOPIC");
if (cb_create < 0 || cb_delete < 0 || cb_join < 0 || cb_join_check < 0
|| cb_mode < 0 || cb_mode_change < 0 || cb_umode_change < 0
|| cb_topic < 0
) {
log("channel_init: register_callback() failed\n");
return 0;
}
return 1;
}
/*************************************************************************/
void channel_cleanup()
{
Channel *c;
for (c = first_channel(); c; c = next_channel())
del_channel(c);
unregister_callback(NULL, cb_topic);
unregister_callback(NULL, cb_umode_change);
unregister_callback(NULL, cb_mode_change);
unregister_callback(NULL, cb_mode);
unregister_callback(NULL, cb_join_check);
unregister_callback(NULL, cb_join);
unregister_callback(NULL, cb_delete);
unregister_callback(NULL, cb_create);
}
/*************************************************************************/
/* Return statistics. Pointers are assumed to be valid. */
void get_channel_stats(long *nrec, long *memuse)
{
long count = 0, mem = 0;
Channel *chan;
struct c_userlist *cu;
int i;
for (chan = first_channel(); chan; chan = next_channel()) {
count++;
mem += sizeof(*chan);
if (chan->topic)
mem += strlen(chan->topic)+1;
if (chan->key)
mem += strlen(chan->key)+1;
ARRAY_FOREACH (i, chan->bans) {
mem += sizeof(char *);
if (chan->bans[i])
mem += strlen(chan->bans[i])+1;
}
ARRAY_FOREACH (i, chan->excepts) {
mem += sizeof(char *);
if (chan->excepts[i])
mem += strlen(chan->excepts[i])+1;
}
LIST_FOREACH (cu, chan->users)
mem += sizeof(*cu);
}
*nrec = count;
*memuse = mem;
}
/*************************************************************************/
/*************************************************************************/
/* Add/remove a user to/from a channel, creating or deleting the channel as
* necessary. If creating the channel, restore mode lock and topic as
* necessary. Also check for auto-opping and auto-voicing. If a mode is
* given, it is assumed to have been set by the remote server.
* Returns the Channel structure for the given channel, or NULL if the user
* was refused access to the channel (by the join check callback).
*/
Channel *chan_adduser(User *user, const char *chan, int32 modes)
{
Channel *c = get_channel(chan);
int newchan = !c;
struct c_userlist *u;
if (call_callback_2(NULL, cb_join_check, chan, user) > 0)
return NULL;
if (newchan) {
if (debug)
log("debug: Creating channel %s", chan);
/* Allocate pre-cleared memory */
c = scalloc(sizeof(Channel), 1);
strscpy(c->name, chan, sizeof(c->name));
c->creation_time = time(NULL);
add_channel(c);
call_callback_3(NULL, cb_create, c, user, modes);
}
u = smalloc(sizeof(struct c_userlist));
LIST_INSERT(u, c->users);
u->user = user;
u->mode = modes;
u->flags = 0;
call_callback_2(NULL, cb_join, c, u);
return c;
}
void chan_deluser(User *user, Channel *c)
{
struct c_userlist *u;
int i;
LIST_SEARCH_SCALAR(c->users, user, user, u);
if (!u) {
log("channel: BUG: chan_deluser() called for %s in %s but they "
"were not found on the channel's userlist.",
user->nick, c->name);
return;
}
LIST_REMOVE(u, c->users);
free(u);
if (!c->users) {
if (debug)
log("debug: Deleting channel %s", c->name);
call_callback_1(NULL, cb_delete, c);
set_cmode(NULL, c); /* make sure nothing's left buffered */
free(c->topic);
free(c->key);
free(c->link);
free(c->flood);
for (i = 0; i < c->bans_count; i++)
free(c->bans[i]);
free(c->bans);
for (i = 0; i < c->excepts_count; i++)
free(c->excepts[i]);
free(c->excepts);
del_channel(c);
free(c);
}
}
/*************************************************************************/
/* Search for the given ban on the given channel, and return the index into
* chan->bans[] if found, -1 otherwise. Nicknames are compared using
* irc_stricmp(), usernames and hostnames using stricmp().
*/
static int find_ban(const Channel *chan, const char *ban)
{
char *s, *t;
int i;
t = strchr(ban, '!');
i = 0;
ARRAY_FOREACH (i, chan->bans) {
s = strchr(chan->bans[i], '!');
if (s && t) {
if (s-(chan->bans[i]) == t-ban
&& irc_strnicmp(chan->bans[i], ban, s-(chan->bans[i])) == 0
&& stricmp(s+1, t+1) == 0
) {
return i;
}
} else if (!s && !t) {
/* Bans without '!' should be impossible; just
* do a case-insensitive compare */
if (stricmp(chan->bans[i], ban) == 0)
return i;
}
}
return -1;
}
/*************************************************************************/
/* Search for the given ban (case-insensitive) on the channel; return 1 if
* it exists, 0 if not.
*/
int chan_has_ban(const char *chan, const char *ban)
{
Channel *c = get_channel(chan);
if (!c)
return 0;
return find_ban(c, ban) >= 0;
}
/*************************************************************************/
/* Handle a channel MODE command.
* When called internally to modify channel modes, callers may assume that
* the contents of the argument strings will not be modified.
*/
static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
const char *nick);
void do_cmode(const char *source, int ac, char **av)
{
Channel *chan;
char *s;
int add = 1; /* 1 if adding modes, 0 if deleting */
char *modestr;
chan = get_channel(av[0]);
if (!chan) {
log("channel: MODE %s for nonexistent channel %s",
merge_args(ac-1, av+1), av[0]);
return;
}
if ((protocol_features & PF_MODETS_FIRST) && isdigit(av[1][0])) {
ac--;
av++;
}
modestr = av[1];
if (!NoBouncyModes) {
/* Count identical server mode changes per second (mode bounce check)*/
/* Doesn't trigger on +/-[bov] or other multiply-settable modes */
static char multimodes[BUFSIZE];
if (!*multimodes) {
int i = 0;
i = snprintf(multimodes, sizeof(multimodes), "%s",
chanmode_multiple);
snprintf(multimodes+i, sizeof(multimodes)-i, "%s",
mode_flags_to_string(MODE_ALL,MODE_CHANUSER));
}
if (strchr(source, '.') && strcmp(source, ServerName) != 0
&& !modestr[strcspn(modestr, multimodes)]
) {
static char lastmodes[BUFSIZE];
if (time(NULL) != chan->server_modetime
|| strcmp(modestr, lastmodes) != 0
) {
chan->server_modecount = 0;
chan->server_modetime = time(NULL);
strscpy(lastmodes, modestr, sizeof(lastmodes));
}
chan->server_modecount++;
}
}
s = modestr;
ac -= 2;
av += 2;
while (*s) {
char modechar = *s++;
int32 flag;
int params;
if (modechar == '+') {
add = 1;
continue;
} else if (modechar == '-') {
add = 0;
continue;
} else if (add < 0) {
continue;
}
/* Check for it as a channel user mode */
flag = mode_char_to_flag(modechar, MODE_CHANUSER);
if (flag) {
if (--ac < 0) {
log("channel: MODE %s %s: missing parameter for %c%c",
chan->name, modestr, add ? '+' : '-', modechar);
break;
}
do_cumode(source, chan, flag, add, *av++);
continue;
}
/* Nope, must be a regular channel mode */
flag = mode_char_to_flag(modechar, MODE_CHANNEL);
if (!flag)
continue;
if (flag == MODE_INVALID)
flag = 0;
params = mode_char_to_params(modechar, MODE_CHANNEL);
params = (params >> (add*8)) & 0xFF;
if (ac < params) {
log("channel: MODE %s %s: missing parameter(s) for %c%c",
chan->name, modestr, add ? '+' : '-', modechar);
break;
}
if (call_callback_5(NULL, cb_mode, source,chan,modechar,add,av) <= 0) {
if (add)
chan->mode |= flag;
else
chan->mode &= ~flag;
switch (modechar) {
case 'k':
free(chan->key);
if (add)
chan->key = sstrdup(av[0]);
else
chan->key = NULL;
break;
case 'l':
if (add)
chan->limit = atoi(av[0]);
else
chan->limit = 0;
break;
case 'b':
if (add) {
ARRAY_EXTEND(chan->bans);
chan->bans[chan->bans_count-1] = sstrdup(av[0]);
} else {
int i = find_ban(chan, av[0]);
if (i >= 0) {
free(chan->bans[i]);
ARRAY_REMOVE(chan->bans, i);
} else {
log("channel: MODE %s -b %s: ban not found",
chan->name, *av);
}
}
break;
} /* switch (modechar) */
} /* if (callback() <= 0) */
ac -= params;
av += params;
} /* while (*s) */
call_callback_2(NULL, cb_mode_change, source, chan);
}
/* Modify a user's CUMODE. */
static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
const char *nick)
{
struct c_userlist *u;
User *user;
int32 oldmode;
user = get_user(nick);
if (!user) {
log("channel: MODE %s %c%c for nonexistent user %s",
chan->name, add ? '+' : '-',
mode_flag_to_char(flag, MODE_CHANUSER), nick);
return;
}
LIST_SEARCH_SCALAR(chan->users, user, user, u);
if (!u) {
log("channel: MODE %s %c%c for user %s not on channel",
chan->name, add ? '+' : '-',
mode_flag_to_char(flag, MODE_CHANUSER), nick);
return;
}
oldmode = u->mode;
if (flag == MODE_INVALID)
return;
if (add)
u->mode |= flag;
else
u->mode &= ~flag;
call_callback_4(NULL, cb_umode_change, source, chan, u, oldmode);
}
/*************************************************************************/
/* Handle a TOPIC command. */
void do_topic(const char *source, int ac, char **av)
{
Channel *c = get_channel(av[0]);
const char *topic;
char *s;
if (!c) {
log("channel: TOPIC %s for nonexistent channel %s",
merge_args(ac-1, av+1), av[0]);
return;
}
s = strchr(av[1], '!');
if (s)
*s = 0;
if (ac > 3)
topic = av[3];
else
topic = "";
if (call_callback_4(NULL, cb_topic, c, topic, av[1],
strtotime(av[2],NULL)) > 0)
return;
strscpy(c->topic_setter, av[1], sizeof(c->topic_setter));
if (c->topic) {
free(c->topic);
c->topic = NULL;
}
if (ac > 3 && *av[3])
c->topic = sstrdup(av[3]);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1