From 1a5ed654b43b7d4b14636fddd4ee79d3ebe749fa Mon Sep 17 00:00:00 2001 From: Alexander Barton Date: Fri, 30 Dec 2011 14:52:48 +0100 Subject: [PATCH] Fixed handling of WHO commands This fixes two bugs: - "WHO " returned nothing at all if the user was "+i" (reported by Cahata, thanks). - "WHO " returned channel names instead of "*" when the user was member of a (visible) channel. Clean up code and add documentation as well. --- src/ngircd/irc-info.c | 223 +++++++++++++++++++++++---------------- src/testsuite/who-test.e | 66 ++++++++---- 2 files changed, 179 insertions(+), 110 deletions(-) diff --git a/src/ngircd/irc-info.c b/src/ngircd/irc-info.c index 01192570..84ba9d9b 100644 --- a/src/ngircd/irc-info.c +++ b/src/ngircd/irc-info.c @@ -784,8 +784,16 @@ who_flags_qualifier(const char *chan_user_modes) } +/** + * Send WHO reply for a "channel target" ("WHO #channel"). + * + * @param Client Client requesting the information. + * @param Chan Channel being requested. + * @param OnlyOps Only display IRC operators. + * @return CONNECTED or DISCONNECTED. + */ static bool -IRC_Send_WHO(CLIENT *Client, CHANNEL *Chan, bool OnlyOps) +IRC_WHO_Channel(CLIENT *Client, CHANNEL *Chan, bool OnlyOps) { bool is_visible, is_member, is_ircop; CL2CHAN *cl2chan; @@ -801,7 +809,8 @@ IRC_Send_WHO(CLIENT *Client, CHANNEL *Chan, bool OnlyOps) /* Secret channel? */ if (!is_member && strchr(Channel_Modes(Chan), 's')) - return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), Channel_Name(Chan)); + return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, + Client_ID(Client), Channel_Name(Chan)); cl2chan = Channel_FirstMember(Chan); for (; cl2chan ; cl2chan = Channel_NextMember(Chan, cl2chan)) { @@ -819,126 +828,156 @@ IRC_Send_WHO(CLIENT *Client, CHANNEL *Chan, bool OnlyOps) strlcat(flags, "*", sizeof(flags)); chan_user_modes = Channel_UserModes(Chan, c); - strlcat(flags, who_flags_qualifier(chan_user_modes), sizeof(flags)); + strlcat(flags, who_flags_qualifier(chan_user_modes), + sizeof(flags)); - if (!write_whoreply(Client, c, Channel_Name(Chan), flags)) + if (!write_whoreply(Client, c, Channel_Name(Chan), + flags)) return DISCONNECTED; } } - return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), Channel_Name(Chan)); -} /* IRC_Send_WHO */ + return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), + Channel_Name(Chan)); +} -GLOBAL bool -IRC_WHO( CLIENT *Client, REQUEST *Req ) +/** + * Send WHO reply for a "mask target" ("WHO m*sk"). + * + * @param Client Client requesting the information. + * @param Mask Mask being requested or NULL for "all" clients. + * @param OnlyOps Only display IRC operators. + * @return CONNECTED or DISCONNECTED. + */ +static bool +IRC_WHO_Mask(CLIENT *Client, char *Mask, bool OnlyOps) { - bool only_ops, have_arg, client_match; - const char *channelname, *client_modes, *chan_user_modes; - char pattern[COMMAND_LEN]; - char flags[4]; - CL2CHAN *cl2chan; - CHANNEL *chan, *cn; CLIENT *c; + CL2CHAN *cl2chan; + CHANNEL *chan; + bool client_match, is_visible; + char flags[4]; - assert( Client != NULL ); - assert( Req != NULL ); - - if (Req->argc > 2) - return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); - - only_ops = false; - have_arg = false; - - if (Req->argc == 2) { - if (strcmp(Req->argv[1], "o") == 0) - only_ops = true; -#ifdef STRICT_RFC - else return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); -#endif - } + assert (Client != NULL); - IRC_SetPenalty(Client, 1); - if (Req->argc >= 1) { /* Channel or Mask. */ - chan = Channel_Search(Req->argv[0]); - if (chan) - return IRC_Send_WHO(Client, chan, only_ops); - if (strcmp(Req->argv[0], "0") != 0) { /* RFC stupidity, same as no arguments */ - have_arg = true; - strlcpy(pattern, Req->argv[0], sizeof(pattern)); - ngt_LowerStr(pattern); - IRC_SetPenalty(Client, 3); - } - } + if (Mask) + ngt_LowerStr(Mask); for (c = Client_First(); c != NULL; c = Client_Next(c)) { if (Client_Type(c) != CLIENT_USER) continue; - /* - * RFC 2812, 3.6.1: - * In the absence of the parameter, all visible (users who aren't - * invisible (user mode +i) and who don't have a common channel - * with the requesting client) are listed. - * - * The same result can be achieved by using a [sic] of "0" - * or any wildcard which will end up matching every visible user. - * - * The [sic] passed to WHO is matched against users' host, server, real name and - * nickname if the channel cannot be found. - */ - client_modes = Client_Modes(c); - if (strchr(client_modes, 'i')) - continue; - if (only_ops && !strchr(client_modes, 'o')) + if (OnlyOps && !Client_HasMode(c, 'o')) continue; - if (have_arg) { /* match pattern against user host/server/name/nick */ - client_match = MatchCaseInsensitive(pattern, Client_Hostname(c)); /* user's host */ + if (Mask) { + /* Match pattern against user host/server/name/nick */ + client_match = MatchCaseInsensitive(Mask, + Client_Hostname(c)); if (!client_match) - client_match = MatchCaseInsensitive(pattern, Client_ID(Client_Introducer(c))); /* server */ + client_match = MatchCaseInsensitive(Mask, + Client_ID(Client_Introducer(c))); if (!client_match) - client_match = Match(Req->argv[0], Client_Info(c)); /* realname */ + client_match = MatchCaseInsensitive(Mask, + Client_Info(c)); if (!client_match) - client_match = MatchCaseInsensitive(pattern, Client_ID(c)); /* nick name */ - - if (!client_match) /* This isn't the client you're looking for */ - continue; + client_match = MatchCaseInsensitive(Mask, + Client_ID(c)); + if (!client_match) + continue; /* no match: skip this client */ } - strcpy(flags, who_flags_status(client_modes)); + is_visible = !Client_HasMode(c, 'i'); - if (strchr(client_modes, 'o')) /* this client is an operator */ - strlcat(flags, "*", sizeof(flags)); + /* Target client is invisible, but mask matches exactly? */ + if (!is_visible && Mask && strcasecmp(Client_ID(c), Mask) == 0) + is_visible = true; - /* Search suitable channel */ - cl2chan = Channel_FirstChannelOf(c); - while (cl2chan) { - cn = Channel_GetChannel(cl2chan); - if (Channel_IsMemberOf(cn, Client) || - !strchr(Channel_Modes(cn), 's')) - { - channelname = Channel_Name(cn); - break; + /* Target still invisible, but are both on the same channel? */ + if (!is_visible) { + cl2chan = Channel_FirstChannelOf(Client); + while (cl2chan && !is_visible) { + chan = Channel_GetChannel(cl2chan); + if (Channel_IsMemberOf(chan, c)) + is_visible = true; + cl2chan = Channel_NextChannelOf(Client, cl2chan); } - cl2chan = Channel_NextChannelOf(c, cl2chan); } - if (cl2chan) { - chan = Channel_GetChannel(cl2chan); - chan_user_modes = Channel_UserModes(chan, c); - strlcat(flags, who_flags_qualifier(chan_user_modes), sizeof(flags)); - } else - channelname = "*"; - - if (!write_whoreply(Client, c, channelname, flags)) + + if (!is_visible) /* target user is not visible */ + continue; + + strcpy(flags, who_flags_status(Client_Modes(c))); + if (strchr(Client_Modes(c), 'o')) + strlcat(flags, "*", sizeof(flags)); + + if (!write_whoreply(Client, c, "*", flags)) return DISCONNECTED; + } - if (Req->argc > 0) - channelname = Req->argv[0]; - else - channelname = "*"; + return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), + Mask ? Mask : "*"); +} + + +/** + * Handler for the IRC "WHO" command. + * + * See RFC 2812, 3.6.1 "Who query". + * + * @param Client The client from which this command has been received. + * @param Req Request structure with prefix and all parameters. + * @return CONNECTED or DISCONNECTED. + */ +GLOBAL bool +IRC_WHO(CLIENT *Client, REQUEST *Req) +{ + bool only_ops, have_arg; + CHANNEL *chan; + + assert (Client != NULL); + assert (Req != NULL); - return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), channelname); + if (Req->argc > 2) + return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, + Client_ID(Client), Req->command); + + only_ops = false; + have_arg = false; + + if (Req->argc == 2) { + if (strcmp(Req->argv[1], "o") == 0) + only_ops = true; +#ifdef STRICT_RFC + else + return IRC_WriteStrClient(Client, + ERR_NEEDMOREPARAMS_MSG, + Client_ID(Client), + Req->command); +#endif + } + + IRC_SetPenalty(Client, 1); + if (Req->argc >= 1) { + /* Channel or mask given */ + chan = Channel_Search(Req->argv[0]); + if (chan) { + /* Members of a channel have been requested */ + IRC_SetPenalty(Client, 1); + return IRC_WHO_Channel(Client, chan, only_ops); + } + if (strcmp(Req->argv[0], "0") != 0) { + /* A mask has been given. But please note this RFC + * stupidity: "0" is same as no arguments ... */ + IRC_SetPenalty(Client, 3); + return IRC_WHO_Mask(Client, Req->argv[0], only_ops); + } + } + + /* No channel or (valid) mask given */ + IRC_SetPenalty(Client, 2); + return IRC_WHO_Mask(Client, NULL, only_ops); } /* IRC_WHO */ diff --git a/src/testsuite/who-test.e b/src/testsuite/who-test.e index 54c19922..0a71e3f7 100644 --- a/src/testsuite/who-test.e +++ b/src/testsuite/who-test.e @@ -14,19 +14,13 @@ expect { send "who\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick \* * ngircd.test.server nick H :0 Real Name" -} - -send "join #channel\r" -expect { - timeout { exit 1 } - "@* JOIN :#channel" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick H :0 Real Name" } send "who 0\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #channel * ngircd.test.server nick H@ :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick H :0 Real Name" } send "away :testing\r" @@ -38,7 +32,19 @@ expect { send "who *\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #channel * ngircd.test.server nick G@ :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick G :0 Real Name" +} + +send "join #channel\r" +expect { + timeout { exit 1 } + "@* JOIN :#channel" +} + +send "who #channel\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick #channel * * ngircd.test.server nick G@ :0 Real Name" } send "mode #channel +v nick\r" @@ -47,10 +53,16 @@ expect { "@* MODE #channel +v nick\r" } +send "who #channel\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick #channel * * ngircd.test.server nick G@ :0 Real Name" +} + send "who localhos*\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #channel * ngircd.test.server nick G@ :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick G :0 Real Name" } send "mode #channel -o nick\r" @@ -59,10 +71,16 @@ expect { "@* MODE #channel -o nick\r" } +send "who #channel\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick #channel * * ngircd.test.server nick G+ :0 Real Name" +} + send "who ngircd.test.server\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #channel * ngircd.test.server nick G+ :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick G :0 Real Name" } send "part #channel\r" @@ -74,7 +92,7 @@ expect { send "who Real?Name\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick \* * ngircd.test.server nick G :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick G :0 Real Name" } send "oper TestOp 123\r" @@ -90,7 +108,7 @@ expect { send "who 0 o\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick \* * ngircd.test.server nick G* :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick G* :0 Real Name" } send "away\r" @@ -102,7 +120,7 @@ expect { send "who ??cal*ho*\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick \* * ngircd.test.server nick H* :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick H* :0 Real Name" } send "join #opers\r" @@ -114,7 +132,13 @@ expect { send "who #opers\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #opers * ngircd.test.server nick H*@ :0 Real Name" + ":ngircd.test.server 352 nick #opers * * ngircd.test.server nick H*@ :0 Real Name" +} + +send "who Re*me\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick H* :0 Real Name" } send "mode #opers -o nick\r" @@ -123,10 +147,16 @@ expect { "@* MODE #opers -o nick\r" } +send "who #opers\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick #opers * * ngircd.test.server nick H* :0 Real Name" +} + send "who *.server\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #opers * ngircd.test.server nick H* :0 Real Name" + ":ngircd.test.server 352 nick \* * * ngircd.test.server nick H* :0 Real Name" } send "mode #opers +v nick\r" @@ -135,10 +165,10 @@ expect { "@* MODE #opers +v nick\r" } -send "who Real*me\r" +send "who #opers\r" expect { timeout { exit 1 } - ":ngircd.test.server 352 nick #opers * ngircd.test.server nick H*+ :0 Real Name" + ":ngircd.test.server 352 nick #opers * * ngircd.test.server nick H*+ :0 Real Name" } send "mode #opers +s\r" -- 2.40.0