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