From bef6b42d9e86389a841622d8b46498572b7e837e Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 3 May 2015 05:00:14 +0200
Subject: kike: unfuck mode parsing
And fix a hidden memory leak while we're at it.
---
kike.c | 489 ++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 288 insertions(+), 201 deletions(-)
diff --git a/kike.c b/kike.c
index 842e5a9..15c31df 100644
--- a/kike.c
+++ b/kike.c
@@ -1356,236 +1356,325 @@ irc_check_expand_user_mask (const char *mask)
return str_steal (&result);
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// 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.
+
+struct mode_processor
+{
+ // Inputs to set after initialization:
+
+ 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
-irc_handle_chan_mode_change (struct client *c,
- struct channel *chan, char *params[])
+mode_processor_init (struct mode_processor *self)
{
- struct channel_user *user = channel_get_user (chan, c);
+ memset (self, 0, sizeof *self);
- // 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.
+ str_init (&self->added);
+ str_init (&self->removed);
- struct str added; struct str_vector added_params;
- struct str removed; struct str_vector removed_params;
+ str_vector_init (&self->added_params);
+ str_vector_init (&self->removed_params);
+}
- str_init (&added); str_vector_init (&added_params);
- str_init (&removed); str_vector_init (&removed_params);
+static void
+mode_processor_free (struct mode_processor *self)
+{
+ str_free (&self->added);
+ str_free (&self->removed);
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ str_vector_free (&self->added_params);
+ str_vector_free (&self->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; \
- }
-
-#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); \
- }
+static const char *
+mode_processor_next_param (struct mode_processor *self)
+{
+ if (!*self->params)
+ return NULL;
+ return *self->params++;
+}
-#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); \
+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))
+ {
+ str_append_c (self->output, self->mode_char); \
+ str_vector_add (self->output_params, client->nickname);
+ }
}
-#define HANDLE_MODE(mode) \
- NEEDS_OPER \
- if (irc_modify_mode (&chan->modes, (mode), adding)) \
- str_append_c (output, mode_char);
+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;
-#define REMOVE_MODE(removed_mode, removed_char) \
- if (adding && irc_modify_mode (&chan->modes, (removed_mode), false)) \
- str_append_c (&removed, (removed_char));
+ str_append_c (self->output, self->mode_char);
+ return true;
+}
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+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);
+}
- const char *mode_string;
- while ((mode_string = *params++))
+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)
{
- bool adding = true;
- struct str *output = &added;
- struct str_vector *output_params = &added_params;
+ if (self->adding)
+ irc_send_channel_list (self->c, self->channel->name,
+ list, list_msg, end_msg);
+ return;
+ }
- const char *target;
- struct channel_user *target_user;
- struct client *client;
+ char *mask = irc_check_expand_user_mask (target);
+ if (!mode_processor_check_operator (self) || !mask)
+ return;
- 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;
+ size_t i;
+ for (i = 0; i < list->len; i++)
+ if (!irc_strcmp (list->vector[i], mask))
break;
- case 'o': HANDLE_USER (IRC_CHAN_MODE_OPERATOR) break;
- case 'v': HANDLE_USER (IRC_CHAN_MODE_VOICE) break;
+ bool found = i != list->len;
+ if ((found ^ self->adding))
+ {
+ if (self->adding)
+ str_vector_add (list, mask);
+ else
+ str_vector_remove (list, i);
- 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;
+ str_append_c (self->output, self->mode_char);
+ str_vector_add (self->output_params, mask);
+ }
+ free (mask);
+}
- 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;
+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;
- 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;
+ if (!self->adding)
+ {
+ if (!self->channel->key || irc_strcmp (target, self->channel->key))
+ return;
- 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;
+ 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);
+ }
+ }
+}
- 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;
+static void
+mode_processor_do_limit (struct mode_processor *self)
+{
+ if (!mode_processor_check_operator (self))
+ return;
- default:
- RETURN_WITH_REPLY (c, IRC_ERR_UNKNOWNMODE);
+ 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);
}
}
+}
-#undef NEEDS_OPER
-#undef HANDLE_USER
-#undef HANDLE_LIST
-#undef HANDLE_MODE
-#undef REMOVE_MODE
+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;
- if (added.len || removed.len)
+#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
--
cgit v1.2.3-70-g09d2