diff options
-rw-r--r-- | src/kike.c | 318 |
1 files changed, 279 insertions, 39 deletions
@@ -225,6 +225,12 @@ irc_is_valid_key (const char *key) #undef SP static bool +irc_is_valid_user_mask (const char *mask) +{ + return irc_regex_match ("^[^!@]+![^!@]+@[^@!]+$", mask); +} + +static bool irc_is_valid_fingerprint (const char *fp) { return irc_regex_match ("^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){19}$", fp); @@ -934,7 +940,9 @@ enum IRC_ERR_NOTREGISTERED = 451, IRC_ERR_NEEDMOREPARAMS = 461, IRC_ERR_ALREADYREGISTERED = 462, + IRC_ERR_KEYSET = 467, IRC_ERR_CHANNELISFULL = 471, + IRC_ERR_UNKNOWNMODE = 472, IRC_ERR_INVITEONLYCHAN = 473, IRC_ERR_BANNEDFROMCHAN = 474, IRC_ERR_BADCHANNELKEY = 475, @@ -1012,7 +1020,9 @@ static const char *g_default_replies[] = [IRC_ERR_NOTREGISTERED] = ":You have not registered", [IRC_ERR_NEEDMOREPARAMS] = "%s :Not enough parameters", [IRC_ERR_ALREADYREGISTERED] = ":Unauthorized command (already registered)", + [IRC_ERR_KEYSET] = "%s :Channel key already set", [IRC_ERR_CHANNELISFULL] = "%s :Cannot join channel (+l)", + [IRC_ERR_UNKNOWNMODE] = "%c :is unknown mode char to me for %s", [IRC_ERR_INVITEONLYCHAN] = "%s :Cannot join channel (+i)", [IRC_ERR_BANNEDFROMCHAN] = "%s :Cannot join channel (+b)", [IRC_ERR_BADCHANNELKEY] = "%s :Cannot join channel (+k)", @@ -1329,43 +1339,15 @@ irc_channel_multicast (struct channel *chan, const char *message, } } -static void -irc_send_channel_list (struct client *c, const char *channel_name, - const struct str_vector *list, int reply, int end_reply) -{ - for (size_t i = 0; i < list->len; i++) - irc_send_reply (c, reply, channel_name, list->vector[i]); - irc_send_reply (c, end_reply); -} - static bool -irc_maybe_send_channel_list (struct client *c, struct channel *chan, - const char *mode_string) -{ - if (*mode_string == '+') - mode_string++; - - if (!strcmp (mode_string, "b")) - irc_send_channel_list (c, chan->name, &chan->ban_list, - IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST); - else if (!strcmp (mode_string, "e")) - irc_send_channel_list (c, chan->name, &chan->exception_list, - IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST); - else if (!strcmp (mode_string, "I")) - irc_send_channel_list (c, chan->name, &chan->invite_list, - IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST); - else - return false; - return true; -} - -static void irc_modify_mode (unsigned *mask, unsigned mode, bool add) { + unsigned orig = *mask; if (add) *mask |= mode; else *mask &= ~mode; + return *mask != orig; } static void @@ -1419,15 +1401,16 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string) irc_modify_mode (&new_mode, IRC_USER_MODE_RX_WALLOPS, adding); break; case 'r': + // It's not possible to un-restrict yourself if (adding) - irc_modify_mode (&new_mode, IRC_USER_MODE_RESTRICTED, true); + new_mode |= IRC_USER_MODE_RESTRICTED; break; case 'o': if (!adding) - irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, false); + new_mode &= ~IRC_USER_MODE_OPERATOR; else if (c->ssl_cert_fingerprint && str_map_find (&c->ctx->operators, c->ssl_cert_fingerprint)) - irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, true); + new_mode |= IRC_USER_MODE_OPERATOR; else irc_send (c, ":%s NOTICE %s :Either you're not using an SSL" " client certificate, or the fingerprint doesn't match", @@ -1443,6 +1426,267 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string) } static void +irc_send_channel_list (struct client *c, const char *channel_name, + const struct str_vector *list, int reply, int end_reply) +{ + for (size_t i = 0; i < list->len; i++) + irc_send_reply (c, reply, channel_name, list->vector[i]); + irc_send_reply (c, end_reply); +} + +static char * +irc_check_expand_user_mask (const char *mask) +{ + struct str result; + str_init (&result); + str_append (&result, mask); + + // Make sure it is a complete mask + if (!strchr (result.str, '!')) + str_append (&result, "!*"); + if (!strchr (result.str, '@')) + str_append (&result, "@*"); + + // And validate whatever the result is + if (!irc_is_valid_user_mask (result.str)) + { + str_free (&result); + return NULL; + } + 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; \ + } + +#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, target_user->nickname); \ + } + +#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); \ +} + +#define HANDLE_MODE(mode) \ + NEEDS_OPER \ + if (irc_modify_mode (&chan->modes, (mode), adding)) \ + str_append_c (output, mode_char); + +#define REMOVE_MODE(removed_mode, removed_char) \ + if (adding && irc_modify_mode (&chan->modes, (removed_mode), false)) \ + str_append_c (&removed, (removed_char)); + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + const char *mode_string; + while ((mode_string = *params++)) + { + bool adding = true; + struct str *output = &added; + struct str_vector *output_params = &added_params; + + const char *target; + struct channel_user *target_user; + struct client *client; + + 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; + + case 'o': HANDLE_USER (IRC_CHAN_MODE_OPERATOR) break; + case 'v': HANDLE_USER (IRC_CHAN_MODE_VOICE) break; + + 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); + } + } + +#undef NEEDS_OPER +#undef HANDLE_USER +#undef HANDLE_LIST +#undef HANDLE_MODE +#undef REMOVE_MODE + + if (added.len || 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); + str_free (&message); + } + + str_free (&added); str_vector_free (&added_params); + str_free (&removed); str_vector_free (&removed_params); +} + +static void irc_handle_mode (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) @@ -1474,13 +1718,9 @@ irc_handle_mode (const struct irc_message *msg, struct client *c) char *mode = channel_get_mode (chan, channel_get_user (chan, c)); irc_send_reply (c, IRC_RPL_CHANNELMODEIS, target, mode); free (mode); - return; } - if (msg->params.len < 3 - && irc_maybe_send_channel_list (c, chan, msg->params.vector[1])) - return; - - // TODO: mode modification + else + irc_handle_chan_mode_change (c, chan, &msg->params.vector[1]); return; } |