commit 4d152b771e17c23823af995f86912d1550f4c244 from: Florian Westphal date: Tue Feb 26 22:49:33 2008 UTC Make IRC_WHO also search username/servername/hostname. Dana Dahlstrom reported that IRC_WHO did not follow RFC 2812, Section 3.6.1. Specifically: - IRC_WHO did not send "G" flag instead if "H" if client was away - did not search username/servername/hostname etc. if argument was not a channel. Fix all of the above and tidy things up a bit. Also add IRC_WHO test script contributed by Dana. commit - c634303765c20083c8f554f4d4084526d3e58fef commit + 4d152b771e17c23823af995f86912d1550f4c244 blob - c18df41da05c86885ee254d6a24cd27b91784241 blob + af9d14ca7b5ffbe960939d9950f527a6ca88df04 --- ChangeLog +++ ChangeLog @@ -11,7 +11,10 @@ ngIRCd HEAD - + - IRC_WHO now supports search patterns and will test this + against user nickname/servername/hostname, etc. as required by + RFC 2812, Section 3.6.1. (reported by Dana Dahlstrom) + - Add test cases for "WHO" command. (Dana Dahlstrom) - implement RFC 2812 handling of "0" argument to 'JOIN': must be treated as if the user had sent PART commands for all channels the user is a member of. (Dana Dahlstrom) @@ -741,4 +744,4 @@ ngIRCd 0.0.1, 31.12.2001 -- -$Id: ChangeLog,v 1.336 2008/02/05 13:31:50 fw Exp $ +$Id: ChangeLog,v 1.337 2008/02/11 11:06:33 fw Exp $ blob - 1f37b88b94d605eea1db1bf1199213f2c0ec9ad9 blob + 9bd503f753c41213a1c97d00cb717f15281b12a1 --- NEWS +++ NEWS @@ -11,7 +11,9 @@ ngIRCd HEAD - + - IRC_WHO now supports search patterns and will test this + against user nickname/servername/hostname, etc. as required by + RFC 2812, Section 3.6.1. - implement RFC 2812 handling of "0" argument to 'JOIN': must be treated as if the user had sent PART commands for all channels the user is a member of. (Dana Dahlstrom) @@ -258,4 +260,4 @@ ngIRCd 0.0.1, 31.12.2001 -- -$Id: NEWS,v 1.85 2008/02/05 14:41:50 fw Exp $ +$Id: NEWS,v 1.86 2008/02/11 11:06:33 fw Exp $ blob - 24db75dea109b59a6242b4432310ce501a872e51 blob + 1a9f7b4bf5acde5fbf0137d76720de312b5873dc --- src/ngircd/irc-info.c +++ src/ngircd/irc-info.c @@ -14,7 +14,7 @@ #include "portab.h" -static char UNUSED id[] = "$Id: irc-info.c,v 1.41 2007/12/11 11:29:44 fw Exp $"; +static char UNUSED id[] = "$Id: irc-info.c,v 1.42 2008/02/11 11:06:31 fw Exp $"; #include "imp.h" #include @@ -35,6 +35,7 @@ static char UNUSED id[] = "$Id: irc-info.c,v 1.41 2007 #include "defines.h" #include "log.h" #include "messages.h" +#include "match.h" #include "tool.h" #include "parse.h" #include "irc-write.h" @@ -590,14 +591,89 @@ IRC_VERSION( CLIENT *Client, REQUEST *Req ) return IRC_WriteStrClient( Client, RPL_VERSION_MSG, Client_ID( prefix ), PACKAGE_NAME, PACKAGE_VERSION, NGIRCd_DebugLevel, Conf_ServerName, NGIRCd_VersionAddition ); #endif } /* IRC_VERSION */ + +static bool +write_whoreply(CLIENT *Client, CLIENT *c, const char *channelname, const char *flags) +{ + return IRC_WriteStrClient(Client, RPL_WHOREPLY_MSG, Client_ID(Client), channelname, + Client_User(c), Client_Hostname(c), Client_ID(Client_Introducer(c)), Client_ID(c), + flags, Client_Hops(c), Client_Info(c)); +} + + +static bool +IRC_Send_WHO(CLIENT *Client, CHANNEL *Chan, bool OnlyOps) +{ + bool is_visible, is_member, is_ircop; + CL2CHAN *cl2chan; + const char *client_modes; + const char *chan_user_modes; + char flags[8]; + CLIENT *c; + + assert( Client != NULL ); + assert( Chan != NULL ); + + is_member = Channel_IsMemberOf(Chan, Client); + + /* Secret channel? */ + if (!is_member && strchr(Channel_Modes(Chan), 's')) + return CONNECTED; + + cl2chan = Channel_FirstMember(Chan); + for (; cl2chan ; cl2chan = Channel_NextMember(Chan, cl2chan)) { + c = Channel_GetClient(cl2chan); + + client_modes = Client_Modes(c); + is_ircop = strchr(client_modes, 'o') != NULL; + if (OnlyOps && !is_ircop) + continue; + + is_visible = strchr(client_modes, 'i') == NULL; + if (is_member || is_visible) { + if (strchr(client_modes, 'a')) + strcpy(flags, "G"); /* away */ + else + strcpy(flags, "H"); + if (is_ircop) + strlcat(flags, "*", sizeof(flags)); + + chan_user_modes = Channel_UserModes(Chan, c); + if (strchr(chan_user_modes, 'o')) + strlcat(flags, "@", sizeof(flags)); + else if (strchr(chan_user_modes, 'v')) + strlcat(flags, "+", sizeof(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 */ + + +static bool +MatchCaseInsensitive(const char *pattern, const char *searchme) +{ + char haystack[COMMAND_LEN]; + + strlcpy(haystack, searchme, sizeof(haystack)); + + ngt_LowerStr(haystack); + + return Match(pattern, haystack); +} + + GLOBAL bool IRC_WHO( CLIENT *Client, REQUEST *Req ) { - bool ok, only_ops; - char flags[8]; - const char *ptr; + bool only_ops, have_arg, client_match; + const char *channelname, *client_modes; + char pattern[COMMAND_LEN]; + char flags[4]; CL2CHAN *cl2chan; CHANNEL *chan, *cn; CLIENT *c; @@ -605,78 +681,101 @@ IRC_WHO( CLIENT *Client, REQUEST *Req ) assert( Client != NULL ); assert( Req != NULL ); - /* Falsche Anzahl Parameter? */ - if(( Req->argc > 2 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); + if (Req->argc > 2) + return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); only_ops = false; - chan = NULL; + have_arg = false; - if( Req->argc == 2 ) - { - /* Nur OPs anzeigen? */ - if( strcmp( Req->argv[1], "o" ) == 0 ) only_ops = true; + 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 ); + else return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); #endif } - if( Req->argc >= 1 ) - { - /* wurde ein Channel oder Nick-Mask angegeben? */ - chan = Channel_Search( Req->argv[0] ); + 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( chan ) - { - /* User eines Channels ausgeben */ - if( ! IRC_Send_WHO( Client, chan, only_ops )) return DISCONNECTED; - } - - c = Client_First( ); - while( c ) - { - if(( Client_Type( c ) == CLIENT_USER ) && ( ! strchr( Client_Modes( c ), 'i' ))) - { - ok = false; - if( Req->argc == 0 ) ok = true; - else - { - if( strcasecmp( Req->argv[0], Client_ID( c )) == 0 ) ok = true; - else if( strcmp( Req->argv[0], "0" ) == 0 ) ok = true; - } - - if( ok && (( ! only_ops ) || ( strchr( Client_Modes( c ), 'o' )))) - { - /* Get flags */ - strcpy( flags, "H" ); - if( strchr( Client_Modes( c ), 'o' )) strlcat( flags, "*", sizeof( flags )); + 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; - /* Search suitable channel */ - cl2chan = Channel_FirstChannelOf( c ); - while( cl2chan ) - { - cn = Channel_GetChannel( cl2chan ); - if( Channel_IsMemberOf( cn, Client ) || - ! strchr( Channel_Modes( cn ), 's' )) - { - ptr = Channel_Name( cn ); - break; - } - cl2chan = Channel_NextChannelOf( c, cl2chan ); - } - if( ! cl2chan ) ptr = "*"; + if (only_ops && !strchr(client_modes, 'o')) + continue; - if( ! IRC_WriteStrClient( Client, RPL_WHOREPLY_MSG, Client_ID( Client ), ptr, Client_User( c ), Client_Hostname( c ), Client_ID( Client_Introducer( c )), Client_ID( c ), flags, Client_Hops( c ), Client_Info( c ))) return DISCONNECTED; + if (have_arg) { /* match pattern against user host/server/name/nick */ + client_match = MatchCaseInsensitive(pattern, Client_Hostname(c)); /* user's host */ + if (!client_match) + client_match = MatchCaseInsensitive(pattern, Client_ID(Client_Introducer(c))); /* server */ + if (!client_match) + client_match = Match(Req->argv[0], Client_Info(c)); /* realname */ + 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; + } + + if (strchr(client_modes, 'a')) + strcpy(flags, "G"); /* user is away */ + else + strcpy(flags, "H"); + + if (only_ops) /* this client is an operator */ + strlcat(flags, "*", sizeof(flags)); + + /* 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; } + cl2chan = Channel_NextChannelOf(c, cl2chan); } + if (!cl2chan) + channelname = "*"; - /* naechster Client */ - c = Client_Next( c ); + if (!write_whoreply(Client, c, channelname, flags)) + return DISCONNECTED; } - if( chan ) return IRC_WriteStrClient( Client, RPL_ENDOFWHO_MSG, Client_ID( Client ), Channel_Name( chan )); - else if( Req->argc == 0 ) return IRC_WriteStrClient( Client, RPL_ENDOFWHO_MSG, Client_ID( Client ), "*" ); - else return IRC_WriteStrClient( Client, RPL_ENDOFWHO_MSG, Client_ID( Client ), Req->argv[0] ); + if (Req->argc > 0) + channelname = Req->argv[0]; + else + channelname = "*"; + + return IRC_WriteStrClient(Client, RPL_ENDOFWHO_MSG, Client_ID(Client), channelname); } /* IRC_WHO */ @@ -1054,56 +1153,9 @@ IRC_Send_NAMES( CLIENT *Client, CHANNEL *Chan ) return CONNECTED; } /* IRC_Send_NAMES */ - - -GLOBAL bool -IRC_Send_WHO( CLIENT *Client, CHANNEL *Chan, bool OnlyOps ) -{ - bool is_visible, is_member; - CL2CHAN *cl2chan; - char flags[8]; - CLIENT *c; - - assert( Client != NULL ); - assert( Chan != NULL ); - - if( Channel_IsMemberOf( Chan, Client )) is_member = true; - else is_member = false; - - /* Secret channel? */ - if( ! is_member && strchr( Channel_Modes( Chan ), 's' )) return CONNECTED; - /* Alle Mitglieder suchen */ - cl2chan = Channel_FirstMember( Chan ); - while( cl2chan ) - { - c = Channel_GetClient( cl2chan ); - if( strchr( Client_Modes( c ), 'i' )) is_visible = false; - else is_visible = true; - if( is_member || is_visible ) - { - /* Flags zusammenbasteln */ - strcpy( flags, "H" ); - if( strchr( Client_Modes( c ), 'o' )) strlcat( flags, "*", sizeof( flags )); - if( strchr( Channel_UserModes( Chan, c ), 'o' )) strlcat( flags, "@", sizeof( flags )); - else if( strchr( Channel_UserModes( Chan, c ), 'v' )) strlcat( flags, "+", sizeof( flags )); - - /* ausgeben */ - if(( ! OnlyOps ) || ( strchr( Client_Modes( c ), 'o' ))) - { - if( ! IRC_WriteStrClient( Client, RPL_WHOREPLY_MSG, Client_ID( Client ), Channel_Name( Chan ), Client_User( c ), Client_Hostname( c ), Client_ID( Client_Introducer( c )), Client_ID( c ), flags, Client_Hops( c ), Client_Info( c ))) return DISCONNECTED; - } - } - - /* naechstes Mitglied suchen */ - cl2chan = Channel_NextMember( Chan, cl2chan ); - } - return CONNECTED; -} /* IRC_Send_WHO */ - - /** * Send the ISUPPORT numeric (005). * This numeric indicates the features that are supported by this server. blob - 41e3953d1cd5ffab5988acbf099f70d748409154 blob + 1aff85bfcca2ad58619f06a60ff0ea985294415c --- src/ngircd/irc-info.h +++ src/ngircd/irc-info.h @@ -8,7 +8,7 @@ * (at your option) any later version. * Please read the file COPYING, README and AUTHORS for more information. * - * $Id: irc-info.h,v 1.4 2007/11/21 12:16:36 alex Exp $ + * $Id: irc-info.h,v 1.5 2008/02/11 11:06:31 fw Exp $ * * IRC info commands (header) */ @@ -35,7 +35,6 @@ GLOBAL bool IRC_WHOWAS PARAMS(( CLIENT *Client, REQUES GLOBAL bool IRC_Send_LUSERS PARAMS(( CLIENT *Client )); GLOBAL bool IRC_Send_NAMES PARAMS(( CLIENT *Client, CHANNEL *Chan )); GLOBAL bool IRC_Show_MOTD PARAMS(( CLIENT *Client )); -GLOBAL bool IRC_Send_WHO PARAMS(( CLIENT *Client, CHANNEL *Chan, bool OnlyOps )); GLOBAL bool IRC_Send_ISUPPORT PARAMS(( CLIENT *Client )); blob - d02e4e1954a57fc245f9097544c0b3752c6c8337 blob + 58a617ee61b858705b4cf2bea7b6413639b8cf89 --- src/testsuite/Makefile.am +++ src/testsuite/Makefile.am @@ -9,7 +9,7 @@ # Naehere Informationen entnehmen Sie bitter der Datei COPYING. Eine Liste # der an ngIRCd beteiligten Autoren finden Sie in der Datei AUTHORS. # -# $Id: Makefile.am,v 1.15 2007/11/18 15:07:16 alex Exp $ +# $Id: Makefile.am,v 1.16 2008/02/11 11:06:32 fw Exp $ # AUTOMAKE_OPTIONS = ../portab/ansi2knr @@ -21,6 +21,7 @@ EXTRA_DIST = \ start-server.sh stop-server.sh tests.sh stress-server.sh \ test-loop.sh wait-tests.sh \ connect-test.e channel-test.e mode-test.e \ + who-away-test.e stress-A.e stress-B.e check-idle.e \ ngircd-test.conf @@ -47,6 +48,10 @@ channel-test: tests.sh rm -f channel-test ln -s $(srcdir)/tests.sh channel-test +who-away-test: tests.sh + rm -f who-away-test + ln -s $(srcdir)/tests.sh who-away-test + mode-test: tests.sh rm -f mode-test ln -s $(srcdir)/tests.sh mode-test @@ -54,6 +59,7 @@ mode-test: tests.sh TESTS = start-server.sh \ connect-test \ channel-test \ + who-away-test \ mode-test \ stress-server.sh \ stop-server.sh blob - /dev/null blob + e5e442a079f3dba31188d12983f792669023bee5 (mode 644) --- /dev/null +++ src/testsuite/who-away-test.e @@ -0,0 +1,100 @@ +# $Id: who-away-test.e,v 1.1 2008/02/11 11:06:32 fw Exp $ + +spawn telnet localhost 6789 +expect { + timeout { exit 1 } + "Connected" +} + +send "nick nick\r" +send "user user . . :Real Name\r" +expect { + timeout { exit 1 } + "376" +} + +send "who\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "who 0\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "who *\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "away :testing\r" +expect { + timeout { exit 1 } + "306 nick" +} + +send "who localhost\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick G :0 Real Name" +} + +send "who ngircd.test.server\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick G :0 Real Name" +} + +send "who Real?Name\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick G :0 Real Name" +} + +send "who nick\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick G :0 Real Name" +} + +send "away\r" +expect { + timeout { exit 1 } + "305 nick" +} + +send "who *cal*ho??\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "who *.server\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "who Real*me\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "who n?c?\r" +expect { + timeout { exit 1 } + ":ngircd.test.server 352 nick * ~user localhost ngircd.test.server nick H :0 Real Name" +} + +send "quit\r" +expect { + timeout { exit 1 } + "Connection closed" +} + +# -eof-