diff --git a/src/kike.c b/src/kike.c index aea6612..08da922 100644 --- a/src/kike.c +++ b/src/kike.c @@ -803,6 +803,7 @@ enum IRC_RPL_USERHOST = 302, IRC_RPL_UNAWAY = 305, IRC_RPL_NOWAWAY = 306, + IRC_RPL_ENDOFWHO = 315, IRC_RPL_LIST = 322, IRC_RPL_LISTEND = 323, IRC_RPL_CHANNELMODEIS = 324, @@ -813,6 +814,7 @@ enum IRC_RPL_EXCEPTLIST = 348, IRC_RPL_ENDOFEXCEPTLIST = 349, IRC_RPL_VERSION = 351, + IRC_RPL_WHOREPLY = 352, IRC_RPL_NAMREPLY = 353, IRC_RPL_ENDOFNAMES = 366, IRC_RPL_BANLIST = 367, @@ -868,6 +870,7 @@ static const char *g_default_replies[] = [IRC_RPL_USERHOST] = ":%s", [IRC_RPL_UNAWAY] = ":You are no longer marked as being away", [IRC_RPL_NOWAWAY] = ":You have been marked as being away", + [IRC_RPL_ENDOFWHO] = "%s :End of WHO list", [IRC_RPL_LIST] = "%s %d :%s", [IRC_RPL_LISTEND] = ":End of LIST", [IRC_RPL_CHANNELMODEIS] = "%s +%s", @@ -878,6 +881,7 @@ static const char *g_default_replies[] = [IRC_RPL_EXCEPTLIST] = "%s %s", [IRC_RPL_ENDOFEXCEPTLIST] = "%s :End of channel exception list", [IRC_RPL_VERSION] = "%s.%d %s :%s", + [IRC_RPL_WHOREPLY] = "%s %s %s %s %s %s :%d %s", [IRC_RPL_NAMREPLY] = "%c %s :%s", [IRC_RPL_ENDOFNAMES] = "%s :End of NAMES list", [IRC_RPL_BANLIST] = "%s %s", @@ -1445,6 +1449,109 @@ irc_handle_names (const struct irc_message *msg, struct client *c) } } +static void +irc_send_rpl_whoreply (struct client *c, const struct channel *chan, + const struct client *target) +{ + struct str chars; + str_init (&chars); + + str_append_c (&chars, target->away_message ? 'G' : 'H'); + if (target->mode & IRC_USER_MODE_OPERATOR) + str_append_c (&chars, '*'); + + struct channel_user *user; + if (chan && (user = channel_get_user (chan, target))) + { + if (user->modes & IRC_CHAN_MODE_OPERATOR) + str_append_c (&chars, '@'); + else if (user->modes & IRC_CHAN_MODE_VOICE) + str_append_c (&chars, '+'); + } + + irc_send_reply (c, IRC_RPL_WHOREPLY, chan ? chan->name : "*", + target->username, target->hostname, target->ctx->server_name, + target->nickname, chars.str, 0 /* hop count */, target->realname); + str_free (&chars); +} + +static void +irc_match_send_rpl_whoreply (struct client *c, struct client *target, + const char *mask) +{ + bool is_roommate = false; + struct str_map_iter iter; + str_map_iter_init (&iter, &c->ctx->channels); + struct channel *chan; + while ((chan = str_map_iter_next (&iter))) + if (channel_get_user (chan, target) && channel_get_user (chan, c)) + { + is_roommate = true; + break; + } + if ((target->mode & IRC_USER_MODE_INVISIBLE) && !is_roommate) + return; + + if (fnmatch (mask, target->hostname, 0) + && fnmatch (mask, target->nickname, 0) + && fnmatch (mask, target->realname, 0) + && fnmatch (mask, c->ctx->server_name, 0)) + return; + + // Try to find a channel they're on that's visible to us + struct channel *user_chan = NULL; + str_map_iter_init (&iter, &c->ctx->channels); + while ((chan = str_map_iter_next (&iter))) + if (channel_get_user (chan, target) + && (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET)) + || channel_get_user (chan, c))) + { + user_chan = chan; + break; + } + irc_send_rpl_whoreply (c, user_chan, target); +} + +static void +irc_handle_who (const struct irc_message *msg, struct client *c) +{ + bool only_ops = msg->params.len > 1 && !strcmp (msg->params.vector[1], "o"); + + const char *shown_mask = msg->params.vector[0], *used_mask; + if (!shown_mask) + used_mask = shown_mask = "*"; + else if (!strcmp (shown_mask, "0")) + used_mask = "*"; + else + used_mask = shown_mask; + + struct channel *chan; + if ((chan = str_map_find (&c->ctx->channels, used_mask))) + { + bool on_chan = !!channel_get_user (chan, c); + if (on_chan || !(chan->modes & IRC_CHAN_MODE_SECRET)) + for (struct channel_user *iter = chan->users; + iter; iter = iter->next) + { + struct client *target = + str_map_find (&c->ctx->users, iter->nickname); + if ((on_chan || !(target->mode & IRC_USER_MODE_INVISIBLE)) + && (!only_ops || (target->mode & IRC_USER_MODE_OPERATOR))) + irc_send_rpl_whoreply (c, chan, target); + } + } + else + { + struct str_map_iter iter; + str_map_iter_init (&iter, &c->ctx->users); + struct client *target; + while ((target = str_map_iter_next (&iter))) + if (!only_ops || (target->mode & IRC_USER_MODE_OPERATOR)) + irc_match_send_rpl_whoreply (c, target, used_mask); + } + irc_send_reply (c, IRC_RPL_ENDOFWHO, shown_mask); +} + static void irc_send_rpl_topic (struct client *c, struct channel *chan) { @@ -1686,6 +1793,7 @@ irc_register_handlers (struct server_context *ctx) { "TOPIC", true, irc_handle_topic }, { "LIST", true, irc_handle_list }, { "NAMES", true, irc_handle_names }, + { "WHO", true, irc_handle_who }, }; for (size_t i = 0; i < N_ELEMENTS (message_handlers); i++)