kike: unfuck mode parsing
And fix a hidden memory leak while we're at it.
This commit is contained in:
parent
1019cc69b2
commit
bef6b42d9e
511
kike.c
511
kike.c
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue