diff --git a/src/kike.c b/src/kike.c index d3415e7..cdb20ca 100644 --- a/src/kike.c +++ b/src/kike.c @@ -292,20 +292,24 @@ client_free (struct client *self) free (self->away_message); } +static void +client_mode_to_str (unsigned m, struct str *out) +{ + if (m & IRC_USER_MODE_INVISIBLE) str_append_c (out, 'i'); + if (m & IRC_USER_MODE_RX_WALLOPS) str_append_c (out, 'w'); + if (m & IRC_USER_MODE_RESTRICTED) str_append_c (out, 'r'); + if (m & IRC_USER_MODE_OPERATOR) str_append_c (out, 'o'); + if (m & IRC_USER_MODE_RX_SERVER_NOTICES) str_append_c (out, 's'); +} + static char * client_get_mode (struct client *self) { struct str mode; str_init (&mode); - - if (self->away_message) str_append_c (&mode, 'a'); - - unsigned m = self->mode; - if (m & IRC_USER_MODE_INVISIBLE) str_append_c (&mode, 'i'); - if (m & IRC_USER_MODE_RX_WALLOPS) str_append_c (&mode, 'w'); - if (m & IRC_USER_MODE_RESTRICTED) str_append_c (&mode, 'r'); - if (m & IRC_USER_MODE_OPERATOR) str_append_c (&mode, 'o'); - if (m & IRC_USER_MODE_RX_SERVER_NOTICES) str_append_c (&mode, 's'); + if (self->away_message) + str_append_c (&mode, 'a'); + client_mode_to_str (self->mode, &mode); return str_steal (&mode); } @@ -887,6 +891,7 @@ enum IRC_ERR_NOPRIVILEGES = 481, IRC_ERR_CHANOPRIVSNEEDED = 482, + IRC_ERR_UMODEUNKNOWNFLAG = 501, IRC_ERR_USERSDONTMATCH = 502 }; @@ -963,6 +968,7 @@ static const char *g_default_replies[] = [IRC_ERR_NOPRIVILEGES] = ":Permission Denied- You're not an IRC operator", [IRC_ERR_CHANOPRIVSNEEDED] = "%s :You're not channel operator", + [IRC_ERR_UMODEUNKNOWNFLAG] = ":Unknown MODE flag", [IRC_ERR_USERSDONTMATCH] = ":Cannot change mode for other users", }; @@ -1295,6 +1301,83 @@ irc_maybe_send_channel_list (struct client *c, struct channel *chan, return true; } +static void +irc_modify_mode (unsigned *mask, unsigned mode, bool add) +{ + if (add) + *mask |= mode; + else + *mask &= ~mode; +} + +static void +irc_update_user_mode (struct client *c, unsigned new_mode) +{ + unsigned old_mode = c->mode; + c->mode = new_mode; + + unsigned added = new_mode & ~old_mode; + unsigned removed = old_mode & ~new_mode; + + struct str diff; + str_init (&diff); + + if (added) + { + str_append_c (&diff, '+'); + client_mode_to_str (added, &diff); + } + if (removed) + { + str_append_c (&diff, '-'); + client_mode_to_str (removed, &diff); + } + + if (diff.len) + irc_send (c, ":%s MODE %s :%s", + c->nickname, c->nickname, diff.str); + str_free (&diff); +} + +static void +irc_handle_user_mode_change (struct client *c, const char *mode_string) +{ + unsigned new_mode = c->mode; + bool adding = true; + + while (*mode_string) + switch (*mode_string++) + { + case '+': adding = true; break; + case '-': adding = false; break; + + case 'a': + // Ignore, the client should use AWAY + break; + case 'i': + irc_modify_mode (&new_mode, IRC_USER_MODE_INVISIBLE, adding); + break; + case 'w': + irc_modify_mode (&new_mode, IRC_USER_MODE_RX_WALLOPS, adding); + break; + case 'r': + if (adding) + irc_modify_mode (&new_mode, IRC_USER_MODE_RESTRICTED, true); + break; + case 'o': + if (!adding) + irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, false); + // TODO: check public key fingerprint when adding + break; + case 's': + irc_modify_mode (&new_mode, IRC_USER_MODE_RX_SERVER_NOTICES, adding); + break; + default: + RETURN_WITH_REPLY (c, IRC_ERR_UMODEUNKNOWNFLAG); + } + irc_update_user_mode (c, new_mode); +} + static void irc_handle_mode (const struct irc_message *msg, struct client *c) { @@ -1308,11 +1391,14 @@ irc_handle_mode (const struct irc_message *msg, struct client *c) if (irc_strcmp (target, c->nickname)) RETURN_WITH_REPLY (c, IRC_ERR_USERSDONTMATCH); - char *mode = client_get_mode (client); - irc_send_reply (c, IRC_RPL_UMODEIS, mode); - free (mode); - - // TODO: mode modification + if (msg->params.len < 2) + { + char *mode = client_get_mode (client); + irc_send_reply (c, IRC_RPL_UMODEIS, mode); + free (mode); + } + else + irc_handle_user_mode_change (c, msg->params.vector[1]); return; }