kike: unfuck mode parsing

And fix a hidden memory leak while we're at it.
This commit is contained in:
Přemysl Eric Janouch 2015-05-03 05:00:14 +02:00
parent 1019cc69b2
commit bef6b42d9e

511
kike.c
View File

@ -1356,236 +1356,325 @@ irc_check_expand_user_mask (const char *mask)
return str_steal (&result);
}
static void
irc_handle_chan_mode_change (struct client *c,
struct channel *chan, char *params[])
{
struct channel_user *user = channel_get_user (chan, c);
// This is by far the worst command to implement from the whole RFC;
// don't blame me if it doesn't work exactly as expected.
struct str added; struct str_vector added_params;
struct str removed; struct str_vector removed_params;
str_init (&added); str_vector_init (&added_params);
str_init (&removed); str_vector_init (&removed_params);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// TODO: try to convert this madness into functions; that will most
// likely require creating a special parser class
#define NEEDS_OPER \
if (!user || (!(user->modes & IRC_CHAN_MODE_OPERATOR) \
&& !(c->mode & IRC_USER_MODE_OPERATOR))) \
{ \
irc_send_reply (c, IRC_ERR_CHANOPRIVSNEEDED, chan->name); \
continue; \
}
// Channel MODE command handling. This is by far the worst command to implement
// from the whole RFC; don't blame me if it doesn't work exactly as expected.
#define HANDLE_USER(mode) \
if (!(target = *params)) \
continue; \
params++; \
NEEDS_OPER \
if (!(client = str_map_find (&c->ctx->users, target))) \
irc_send_reply (c, IRC_ERR_NOSUCHNICK, target); \
else if (!(target_user = channel_get_user (chan, client))) \
irc_send_reply (c, IRC_ERR_USERNOTINCHANNEL, \
target, chan->name); \
else if (irc_modify_mode (&target_user->modes, (mode), adding)) \
{ \
str_append_c (output, mode_char); \
str_vector_add (output_params, client->nickname); \
}
struct mode_processor
{
// Inputs to set after initialization:
#define HANDLE_LIST(list, list_msg, end_msg) \
{ \
if (!(target = *params)) \
{ \
if (adding) \
irc_send_channel_list (c, chan->name, list, list_msg, end_msg); \
continue; \
} \
params++; \
NEEDS_OPER \
char *mask = irc_check_expand_user_mask (target); \
if (!mask) \
continue; \
size_t i; \
for (i = 0; i < (list)->len; i++) \
if (!irc_strcmp ((list)->vector[i], mask)) \
break; \
if (!((i != (list)->len) ^ adding)) \
{ \
free (mask); \
continue; \
} \
if (adding) \
str_vector_add ((list), mask); \
else \
str_vector_remove ((list), i); \
str_append_c (output, mode_char); \
str_vector_add (output_params, mask); \
free (mask); \
char **params; ///< Mode string parameters
struct client *c; ///< Who does the changes
struct channel *channel; ///< The channel we're modifying
struct channel_user *user; ///< Presence of the client in the chan
// Internals:
bool adding; ///< Currently adding modes
char mode_char; ///< Currently processed mode char
struct str added; ///< Added modes
struct str removed; ///< Removed modes
struct str_vector added_params; ///< Params for added modes
struct str_vector removed_params; ///< Params for removed modes
struct str *output; ///< "added" or "removed"
struct str_vector *output_params; ///< Similarly for "*_params"
};
static void
mode_processor_init (struct mode_processor *self)
{
memset (self, 0, sizeof *self);
str_init (&self->added);
str_init (&self->removed);
str_vector_init (&self->added_params);
str_vector_init (&self->removed_params);
}
#define HANDLE_MODE(mode) \
NEEDS_OPER \
if (irc_modify_mode (&chan->modes, (mode), adding)) \
str_append_c (output, mode_char);
static void
mode_processor_free (struct mode_processor *self)
{
str_free (&self->added);
str_free (&self->removed);
#define REMOVE_MODE(removed_mode, removed_char) \
if (adding && irc_modify_mode (&chan->modes, (removed_mode), false)) \
str_append_c (&removed, (removed_char));
str_vector_free (&self->added_params);
str_vector_free (&self->removed_params);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static const char *
mode_processor_next_param (struct mode_processor *self)
{
if (!*self->params)
return NULL;
return *self->params++;
}
const char *mode_string;
while ((mode_string = *params++))
static bool
mode_processor_check_operator (struct mode_processor *self)
{
if (self->user && ((self->user->modes & IRC_CHAN_MODE_OPERATOR)
|| (self->c->mode & IRC_USER_MODE_OPERATOR)))
return true;
irc_send_reply (self->c, IRC_ERR_CHANOPRIVSNEEDED, self->channel->name);
return false;
}
static void
mode_processor_do_user (struct mode_processor *self, int mode)
{
const char *target = mode_processor_next_param (self);
if (!mode_processor_check_operator (self) || !target)
return;
struct client *client;
struct channel_user *target_user;
if (!(client = str_map_find (&self->c->ctx->users, target)))
irc_send_reply (self->c, IRC_ERR_NOSUCHNICK, target);
else if (!(target_user = channel_get_user (self->channel, client)))
irc_send_reply (self->c, IRC_ERR_USERNOTINCHANNEL,
target, self->channel->name);
else if (irc_modify_mode (&target_user->modes, mode, self->adding))
{
bool adding = true;
struct str *output = &added;
struct str_vector *output_params = &added_params;
str_append_c (self->output, self->mode_char); \
str_vector_add (self->output_params, client->nickname);
}
}
const char *target;
struct channel_user *target_user;
struct client *client;
static bool
mode_processor_do_chan (struct mode_processor *self, int mode)
{
if (!mode_processor_check_operator (self)
|| irc_modify_mode (&self->channel->modes, mode, self->adding))
return false;
char mode_char;
while (*mode_string)
switch ((mode_char = *mode_string++))
{
case '+':
adding = true;
output = &added;
output_params = &added_params;
break;
case '-':
adding = false;
output = &removed;
output_params = &removed_params;
break;
str_append_c (self->output, self->mode_char);
return true;
}
case 'o': HANDLE_USER (IRC_CHAN_MODE_OPERATOR) break;
case 'v': HANDLE_USER (IRC_CHAN_MODE_VOICE) break;
static void
mode_processor_do_chan_remove
(struct mode_processor *self, char mode_char, int mode)
{
if (self->adding
&& irc_modify_mode (&self->channel->modes, mode, false))
str_append_c (&self->removed, mode_char);
}
case 'i': HANDLE_MODE (IRC_CHAN_MODE_INVITE_ONLY) break;
case 'm': HANDLE_MODE (IRC_CHAN_MODE_MODERATED) break;
case 'n': HANDLE_MODE (IRC_CHAN_MODE_NO_OUTSIDE_MSGS) break;
case 'q': HANDLE_MODE (IRC_CHAN_MODE_QUIET) break;
case 't': HANDLE_MODE (IRC_CHAN_MODE_PROTECTED_TOPIC) break;
case 'p':
HANDLE_MODE (IRC_CHAN_MODE_PRIVATE)
REMOVE_MODE (IRC_CHAN_MODE_SECRET, 's')
break;
case 's':
HANDLE_MODE (IRC_CHAN_MODE_SECRET)
REMOVE_MODE (IRC_CHAN_MODE_PRIVATE, 'p')
break;
case 'b':
HANDLE_LIST (&chan->ban_list,
IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST)
break;
case 'e':
HANDLE_LIST (&chan->exception_list,
IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST)
break;
case 'I':
HANDLE_LIST (&chan->invite_list,
IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST)
break;
case 'k':
NEEDS_OPER
if (!adding)
{
if (!(target = *params))
continue;
params++;
if (!chan->key || irc_strcmp (target, chan->key))
continue;
str_append_c (&removed, mode_char);
str_vector_add (&removed_params, chan->key);
free (chan->key);
chan->key = NULL;
}
else if (!(target = *params))
continue;
else
{
params++;
if (chan->key)
irc_send_reply (c, IRC_ERR_KEYSET, chan->name);
else
{
chan->key = xstrdup (target);
str_append_c (&added, mode_char);
str_vector_add (&added_params, chan->key);
}
}
break;
case 'l':
NEEDS_OPER
if (!adding)
{
if (chan->user_limit == -1)
continue;
chan->user_limit = -1;
str_append_c (&removed, mode_char);
}
else if (!(target = *params))
continue;
else
{
params++;
unsigned long x;
if (xstrtoul (&x, target, 10) && x > 0 && x <= LONG_MAX)
{
chan->user_limit = x;
str_append_c (&added, mode_char);
str_vector_add (&added_params, target);
}
}
break;
default:
RETURN_WITH_REPLY (c, IRC_ERR_UNKNOWNMODE);
}
static void
mode_processor_do_list (struct mode_processor *self,
struct str_vector *list, int list_msg, int end_msg)
{
const char *target = mode_processor_next_param (self);
if (!target)
{
if (self->adding)
irc_send_channel_list (self->c, self->channel->name,
list, list_msg, end_msg);
return;
}
#undef NEEDS_OPER
#undef HANDLE_USER
#undef HANDLE_LIST
#undef HANDLE_MODE
#undef REMOVE_MODE
char *mask = irc_check_expand_user_mask (target);
if (!mode_processor_check_operator (self) || !mask)
return;
if (added.len || removed.len)
size_t i;
for (i = 0; i < list->len; i++)
if (!irc_strcmp (list->vector[i], mask))
break;
bool found = i != list->len;
if ((found ^ self->adding))
{
if (self->adding)
str_vector_add (list, mask);
else
str_vector_remove (list, i);
str_append_c (self->output, self->mode_char);
str_vector_add (self->output_params, mask);
}
free (mask);
}
static void
mode_processor_do_key (struct mode_processor *self)
{
const char *target = mode_processor_next_param (self);
if (!mode_processor_check_operator (self) || !target)
return;
if (!self->adding)
{
if (!self->channel->key || irc_strcmp (target, self->channel->key))
return;
str_append_c (&self->removed, self->mode_char);
str_vector_add (&self->removed_params, self->channel->key);
free (self->channel->key);
self->channel->key = NULL;
}
else
{
if (self->channel->key)
irc_send_reply (self->c, IRC_ERR_KEYSET, self->channel->name);
else
{
self->channel->key = xstrdup (target);
str_append_c (&self->added, self->mode_char);
str_vector_add (&self->added_params, self->channel->key);
}
}
}
static void
mode_processor_do_limit (struct mode_processor *self)
{
if (!mode_processor_check_operator (self))
return;
const char *target;
if (!self->adding)
{
if (self->channel->user_limit == -1)
return;
self->channel->user_limit = -1;
str_append_c (&self->removed, self->mode_char);
}
else if ((target = mode_processor_next_param (self)))
{
unsigned long x;
if (xstrtoul (&x, target, 10) && x > 0 && x <= LONG_MAX)
{
self->channel->user_limit = x;
str_append_c (&self->added, self->mode_char);
str_vector_add (&self->added_params, target);
}
}
}
static bool
mode_processor_step (struct mode_processor *self, char mode_char)
{
switch ((self->mode_char = mode_char))
{
case '+':
self->adding = true;
self->output = &self->added;
self->output_params = &self->added_params;
break;
case '-':
self->adding = false;
self->output = &self->removed;
self->output_params = &self->removed_params;
break;
#define USER(mode) mode_processor_do_user (self, (mode))
#define CHAN(mode) mode_processor_do_chan (self, (mode))
case 'o': USER (IRC_CHAN_MODE_OPERATOR); break;
case 'v': USER (IRC_CHAN_MODE_VOICE); break;
case 'i': CHAN (IRC_CHAN_MODE_INVITE_ONLY); break;
case 'm': CHAN (IRC_CHAN_MODE_MODERATED); break;
case 'n': CHAN (IRC_CHAN_MODE_NO_OUTSIDE_MSGS); break;
case 'q': CHAN (IRC_CHAN_MODE_QUIET); break;
case 't': CHAN (IRC_CHAN_MODE_PROTECTED_TOPIC); break;
case 'p':
if (CHAN (IRC_CHAN_MODE_PRIVATE))
mode_processor_do_chan_remove (self, 's', IRC_CHAN_MODE_SECRET);
break;
case 's':
if (CHAN (IRC_CHAN_MODE_SECRET))
mode_processor_do_chan_remove (self, 'p', IRC_CHAN_MODE_PRIVATE);
break;
#undef USER
#undef CHAN
case 'b':
mode_processor_do_list (self, &self->channel->ban_list,
IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST);
break;
case 'e':
mode_processor_do_list (self, &self->channel->exception_list,
IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST);
break;
case 'I':
mode_processor_do_list (self, &self->channel->invite_list,
IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST);
break;
case 'k':
mode_processor_do_key (self);
break;
case 'l':
mode_processor_do_limit (self);
break;
default:
// It's not safe to continue, results could be undesired
irc_send_reply (self->c, IRC_ERR_UNKNOWNMODE,
mode_char, self->channel->name);
return false;
}
return true;
}
static void
irc_handle_chan_mode_change
(struct client *c, struct channel *chan, char *params[])
{
struct mode_processor p;
mode_processor_init (&p);
p.params = params;
p.channel = chan;
p.c = c;
p.user = channel_get_user (chan, c);
const char *mode_string;
while ((mode_string = mode_processor_next_param (&p)))
{
mode_processor_step (&p, '+');
while (*mode_string)
if (!mode_processor_step (&p, *mode_string++))
break;
}
// TODO: limit to three changes with parameter per command
if (p.added.len || p.removed.len)
{
struct str message;
str_init (&message);
str_append_printf (&message, ":%s!%s@%s MODE %s ",
c->nickname, c->username, c->hostname, chan->name);
if (added.len)
str_append_printf (&message, "+%s", added.str);
if (removed.len)
str_append_printf (&message, "-%s", removed.str);
for (size_t i = 0; i < added_params.len; i++)
str_append_printf (&message, " %s", added_params.vector[i]);
for (size_t i = 0; i < removed_params.len; i++)
str_append_printf (&message, " %s", removed_params.vector[i]);
irc_channel_multicast (chan, message.str, NULL);
p.c->nickname, p.c->username, p.c->hostname,
p.channel->name);
if (p.added.len)
str_append_printf (&message, "+%s", p.added.str);
if (p.removed.len)
str_append_printf (&message, "-%s", p.removed.str);
for (size_t i = 0; i < p.added_params.len; i++)
str_append_printf (&message, " %s", p.added_params.vector[i]);
for (size_t i = 0; i < p.removed_params.len; i++)
str_append_printf (&message, " %s", p.removed_params.vector[i]);
irc_channel_multicast (p.channel, message.str, NULL);
str_free (&message);
}
str_free (&added); str_vector_free (&added_params);
str_free (&removed); str_vector_free (&removed_params);
mode_processor_free (&p);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
irc_handle_mode (const struct irc_message *msg, struct client *c)
{
@ -1594,6 +1683,8 @@ irc_handle_mode (const struct irc_message *msg, struct client *c)
const char *target = msg->params.vector[0];
struct client *client = str_map_find (&c->ctx->users, target);
struct channel *chan = str_map_find (&c->ctx->channels, target);
if (client)
{
if (irc_strcmp (target, c->nickname))
@ -1607,11 +1698,8 @@ irc_handle_mode (const struct irc_message *msg, struct client *c)
}
else
irc_handle_user_mode_change (c, msg->params.vector[1]);
return;
}
struct channel *chan = str_map_find (&c->ctx->channels, target);
if (chan)
else if (chan)
{
if (msg->params.len < 2)
{
@ -1621,10 +1709,9 @@ irc_handle_mode (const struct irc_message *msg, struct client *c)
}
else
irc_handle_chan_mode_change (c, chan, &msg->params.vector[1]);
return;
}
irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);
else
irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);
}
static void