mirror of
https://github.com/NishiOwO/ircservices5.git
synced 2025-04-21 08:44:38 +00:00
501 lines
14 KiB
C
501 lines
14 KiB
C
/* Channel-handling routines.
|
|
*
|
|
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
|
* E-mail: <achurch@achurch.org>
|
|
* Parts written by Andrew Kempe and others.
|
|
* This program is free but copyrighted software; see the file GPL.txt for
|
|
* details.
|
|
*/
|
|
|
|
#include "services.h"
|
|
#include "modules.h"
|
|
|
|
/*************************************************************************/
|
|
|
|
#define HASHFUNC(key) DEFAULT_HASHFUNC(key+1)
|
|
#define add_channel static add_channel
|
|
#define del_channel static del_channel
|
|
#include "hash.h"
|
|
DEFINE_HASH(channel, Channel, name)
|
|
#undef add_channel
|
|
#undef del_channel
|
|
|
|
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("channel create");
|
|
cb_delete = register_callback("channel delete");
|
|
cb_join = register_callback("channel JOIN");
|
|
cb_join_check = register_callback("channel JOIN check");
|
|
cb_mode = register_callback("channel MODE");
|
|
cb_mode_change = register_callback("channel mode change");
|
|
cb_umode_change = register_callback("channel umode change");
|
|
cb_topic = register_callback("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(void)
|
|
{
|
|
Channel *c;
|
|
|
|
for (c = first_channel(); c; c = next_channel())
|
|
del_channel(c);
|
|
unregister_callback(cb_topic);
|
|
unregister_callback(cb_umode_change);
|
|
unregister_callback(cb_mode_change);
|
|
unregister_callback(cb_mode);
|
|
unregister_callback(cb_join_check);
|
|
unregister_callback(cb_join);
|
|
unregister_callback(cb_delete);
|
|
unregister_callback(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(cb_join_check, chan, user) > 0)
|
|
return NULL;
|
|
if (newchan) {
|
|
log_debug(1, "Creating channel %s", chan);
|
|
/* Allocate pre-cleared memory */
|
|
c = scalloc(sizeof(Channel), 1);
|
|
strbcpy(c->name, chan);
|
|
c->creation_time = time(NULL);
|
|
add_channel(c);
|
|
call_callback_3(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(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) {
|
|
log_debug(1, "Deleting channel %s", c->name);
|
|
call_callback_1(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.
|
|
*/
|
|
|
|
/* Hack to allow -o+v to work without having to search the whole channel
|
|
* user list for changed modes. */
|
|
#define MAX_CUMODES 16
|
|
static struct {
|
|
struct c_userlist *user;
|
|
int32 add, remove;
|
|
} cumode_changes[MAX_CUMODES];
|
|
static int cumode_count = 0;
|
|
|
|
static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
|
|
const char *nick);
|
|
static void finish_cumode(const char *source, Channel *chan);
|
|
|
|
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_debug(1, "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);
|
|
strbcpy(lastmodes, modestr);
|
|
}
|
|
chan->server_modecount++;
|
|
}
|
|
}
|
|
|
|
s = modestr;
|
|
ac -= 2;
|
|
av += 2;
|
|
cumode_count = 0;
|
|
|
|
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(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': {
|
|
int i = find_ban(chan, av[0]);
|
|
if (add) {
|
|
if (i < 0) { /* Don't add if it already exists */
|
|
ARRAY_EXTEND(chan->bans);
|
|
chan->bans[chan->bans_count-1] = sstrdup(av[0]);
|
|
}
|
|
} else {
|
|
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;
|
|
} /* case 'b' */
|
|
|
|
} /* switch (modechar) */
|
|
|
|
} /* if (callback() <= 0) */
|
|
|
|
ac -= params;
|
|
av += params;
|
|
|
|
} /* while (*s) */
|
|
|
|
call_callback_2(cb_mode_change, source, chan);
|
|
finish_cumode(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;
|
|
int i;
|
|
|
|
user = get_user(nick);
|
|
if (!user) {
|
|
log_debug(1, "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;
|
|
}
|
|
|
|
if (flag == MODE_INVALID)
|
|
return;
|
|
for (i = 0; i < cumode_count; i++) {
|
|
if (cumode_changes[i].user == u)
|
|
break;
|
|
}
|
|
if (i >= MAX_CUMODES) {
|
|
finish_cumode(source, chan);
|
|
i = cumode_count = 0;
|
|
}
|
|
cumode_changes[i].user = u;
|
|
if (i >= cumode_count) {
|
|
cumode_changes[i].add = cumode_changes[i].remove = 0;
|
|
}
|
|
if (add) {
|
|
cumode_changes[i].add |= flag;
|
|
cumode_changes[i].remove &= ~flag;
|
|
} else {
|
|
cumode_changes[i].remove |= flag;
|
|
cumode_changes[i].add &= ~flag;
|
|
}
|
|
if (i >= cumode_count)
|
|
cumode_count = i+1;
|
|
}
|
|
|
|
static void finish_cumode(const char *source, Channel *chan)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cumode_count; i++) {
|
|
struct c_userlist *u = cumode_changes[i].user;
|
|
int32 oldmode = u->mode;
|
|
u->mode |= cumode_changes[i].add;
|
|
u->mode &= ~cumode_changes[i].remove;
|
|
if (u->mode != oldmode)
|
|
call_callback_4(cb_umode_change, source, chan, u, oldmode);
|
|
}
|
|
cumode_count = 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* 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_debug(1, "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(cb_topic, c, topic, av[1], strtotime(av[2],NULL)) > 0)
|
|
return;
|
|
strbcpy(c->topic_setter, av[1]);
|
|
if (c->topic) {
|
|
free(c->topic);
|
|
c->topic = NULL;
|
|
}
|
|
if (ac > 3 && *av[3])
|
|
c->topic = sstrdup(av[3]);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|