diff --git a/.github/workflows/test-devel.yml b/.github/workflows/test-devel.yml index f3c4d38..640e680 100644 --- a/.github/workflows/test-devel.yml +++ b/.github/workflows/test-devel.yml @@ -66,7 +66,7 @@ jobs: - name: Build Bahamut run: | cd $GITHUB_WORKSPACE/Bahamut/ - patch src/s_user.c < $GITHUB_WORKSPACE/bahamut_localhost.patch + patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch echo "#undef THROTTLE_ENABLE" >> include/config.h libtoolize --force aclocal @@ -144,7 +144,7 @@ jobs: - name: Build InspIRCd run: | cd $GITHUB_WORKSPACE/inspircd/ - patch src/inspircd.cpp < $GITHUB_WORKSPACE/inspircd_mainloop.patch + patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch ./configure --prefix=$HOME/.local/inspircd --development make -j 4 make install @@ -184,6 +184,7 @@ jobs: - name: Build ngircd run: | cd $GITHUB_WORKSPACE/ngircd + patch src/ngircd/client.c < $GITHUB_WORKSPACE/patches/ngircd_whowas_delay.patch ./autogen.sh ./configure --prefix=$HOME/.local/ make -j 4 diff --git a/.github/workflows/test-devel_release.yml b/.github/workflows/test-devel_release.yml index 92fc3cc..0b25abb 100644 --- a/.github/workflows/test-devel_release.yml +++ b/.github/workflows/test-devel_release.yml @@ -57,7 +57,7 @@ jobs: - name: Build InspIRCd run: | cd $GITHUB_WORKSPACE/inspircd/ - patch src/inspircd.cpp < $GITHUB_WORKSPACE/inspircd_mainloop.patch + patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch ./configure --prefix=$HOME/.local/inspircd --development make -j 4 make install diff --git a/.github/workflows/test-stable.yml b/.github/workflows/test-stable.yml index 1df34a6..c908907 100644 --- a/.github/workflows/test-stable.yml +++ b/.github/workflows/test-stable.yml @@ -66,7 +66,7 @@ jobs: - name: Build Bahamut run: | cd $GITHUB_WORKSPACE/Bahamut/ - patch src/s_user.c < $GITHUB_WORKSPACE/bahamut_localhost.patch + patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch echo "#undef THROTTLE_ENABLE" >> include/config.h libtoolize --force aclocal @@ -184,7 +184,7 @@ jobs: - name: Build InspIRCd run: | cd $GITHUB_WORKSPACE/inspircd/ - patch src/inspircd.cpp < $GITHUB_WORKSPACE/inspircd_mainloop.patch + patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch ./configure --prefix=$HOME/.local/inspircd --development make -j 4 make install @@ -224,6 +224,7 @@ jobs: - name: Build ngircd run: | cd $GITHUB_WORKSPACE/ngircd + patch src/ngircd/client.c < $GITHUB_WORKSPACE/patches/ngircd_whowas_delay.patch ./autogen.sh ./configure --prefix=$HOME/.local/ make -j 4 diff --git a/Makefile b/Makefile index 4b76e4d..0f572bf 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ HYBRID_SELECTORS := \ # testNoticeNonexistentChannel fails because of https://github.com/inspircd/inspircd/issues/1849 # testBotPrivateMessage and testBotChannelMessage fail because https://github.com/inspircd/inspircd/pull/1910 is not released yet # testNamesInvalidChannel and testNamesNonexistingChannel fail because https://github.com/inspircd/inspircd/pull/1922 is not released yet. +# WHOWAS tests fail because https://github.com/inspircd/inspircd/pull/1967 and https://github.com/inspircd/inspircd/pull/1968 are not released yet INSPIRCD_SELECTORS := \ not Ergo \ and not deprecated \ @@ -62,6 +63,7 @@ INSPIRCD_SELECTORS := \ and not testNoticeNonexistentChannel \ and not testBotPrivateMessage and not testBotChannelMessage \ and not testNamesInvalidChannel and not testNamesNonexistingChannel \ + and not whowas \ $(EXTRA_SELECTORS) # buffering tests fail because ircu2 discards the whole buffer on long lines (TODO: refine how we exclude these tests) @@ -72,6 +74,7 @@ INSPIRCD_SELECTORS := \ # testKickDefaultComment fails because it uses the nick of the kickee rather than the kicker. # testEmptyRealname fails because it uses a default value instead of ERR_NEEDMOREPARAMS. # HelpTestCase fails because it returns NOTICEs instead of numerics +# testWhowasCountZero fails: https://github.com/UndernetIRC/ircu2/pull/19 IRCU2_SELECTORS := \ not Ergo \ and not deprecated \ @@ -84,6 +87,7 @@ IRCU2_SELECTORS := \ and not testKickDefaultComment \ and not testEmptyRealname \ and not HelpTestCase \ + and not testWhowasCountZero \ $(EXTRA_SELECTORS) # same justification as ircu2 diff --git a/README.md b/README.md index c5503af..7aac195 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ git clone https://github.com/inspircd/inspircd.git cd inspircd # optional, makes tests run considerably faster -patch src/inspircd.cpp < ~/irctest/inspircd_mainloop.patch +patch src/inspircd.cpp < ~/irctest/patches/inspircd_mainloop.patch ./configure --prefix=$HOME/.local/ --development make -j 4 diff --git a/irctest/controllers/ngircd.py b/irctest/controllers/ngircd.py index 5d0d845..17b3540 100644 --- a/irctest/controllers/ngircd.py +++ b/irctest/controllers/ngircd.py @@ -26,6 +26,9 @@ TEMPLATE_CONFIG = """ Passive = yes # don't connect to it ServiceMask = *Serv +[Options] + MorePrivacy = no # by default, always replies to WHOWAS with ERR_WASNOSUCHNICK + [Operator] Name = operuser Password = operpassword diff --git a/irctest/server_tests/whowas.py b/irctest/server_tests/whowas.py new file mode 100644 index 0000000..c3de2c9 --- /dev/null +++ b/irctest/server_tests/whowas.py @@ -0,0 +1,204 @@ +from irctest import cases +from irctest.exceptions import ConnectionClosed +from irctest.numerics import ( + RPL_ENDOFWHOWAS, + RPL_WHOISACTUALLY, + RPL_WHOISSERVER, + RPL_WHOWASUSER, +) +from irctest.patma import ANYSTR, StrRe + + +class WhowasTestCase(cases.BaseServerTestCase): + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasNumerics(self): + """ + https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self.connectClient("nick1") + + self.connectClient("nick2") + self.sendLine(2, "QUIT :bye") + try: + self.getMessages(2) + except ConnectionClosed: + pass + + self.sendLine(1, "WHOWAS nick2") + + messages = [] + for _ in range(10): + messages.extend(self.getMessages(1)) + if RPL_ENDOFWHOWAS in (m.command for m in messages): + break + + last_message = messages.pop() + + self.assertMessageMatch( + last_message, + command=RPL_ENDOFWHOWAS, + params=["nick1", "nick2", ANYSTR], + fail_msg=f"Last message was not RPL_ENDOFWHOWAS ({RPL_ENDOFWHOWAS})", + ) + + unexpected_messages = [] + + # Straight from the RFCs + for m in messages: + if m.command == RPL_WHOWASUSER: + host_re = "[0-9A-Za-z_:.-]+" + self.assertMessageMatch( + m, + params=[ + "nick1", + "nick2", + StrRe("~?username"), + StrRe(host_re), + "*", + "Realname", + ], + ) + elif m.command == RPL_WHOISSERVER: + self.assertMessageMatch( + m, params=["nick1", "nick2", "My.Little.Server", ANYSTR] + ) + elif m.command == RPL_WHOISACTUALLY: + # Technically not allowed by the RFCs, but Solanum uses it. + # Not checking the syntax here; WhoisTestCase does it. + pass + else: + unexpected_messages.append(m) + + self.assertEqual( + unexpected_messages, [], fail_msg="Unexpected numeric messages: {got}" + ) + + def _testWhowasMultiple(self, second_result, whowas_command): + """ + "The history is searched backward, returning the most recent entry first." + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + # TODO: this test assumes the order is always: RPL_WHOWASUSER, then + # optional RPL_WHOISACTUALLY, then RPL_WHOISSERVER; but the RFCs + # don't specify the order. + self.connectClient("nick1") + + self.connectClient("nick2", ident="ident2") + self.sendLine(2, "QUIT :bye") + try: + self.getMessages(2) + except ConnectionClosed: + pass + + self.connectClient("nick2", ident="ident3") + self.sendLine(3, "QUIT :bye") + try: + self.getMessages(3) + except ConnectionClosed: + pass + + self.sendLine(1, whowas_command) + + messages = self.getMessages(1) + + # nick2 with ident3 + self.assertMessageMatch( + messages.pop(0), + command=RPL_WHOWASUSER, + params=[ + "nick1", + "nick2", + StrRe("~?ident3"), + ANYSTR, + "*", + "Realname", + ], + ) + while messages[0].command in (RPL_WHOISACTUALLY, RPL_WHOISSERVER): + # don't care + messages.pop(0) + + if second_result: + # nick2 with ident2 + self.assertMessageMatch( + messages.pop(0), + command=RPL_WHOWASUSER, + params=[ + "nick1", + "nick2", + StrRe("~?ident2"), + ANYSTR, + "*", + "Realname", + ], + ) + if messages[0].command == RPL_WHOISACTUALLY: + # don't care + messages.pop(0) + while messages[0].command in (RPL_WHOISACTUALLY, RPL_WHOISSERVER): + # don't care + messages.pop(0) + + self.assertMessageMatch( + messages.pop(0), + command=RPL_ENDOFWHOWAS, + params=["nick1", "nick2", ANYSTR], + fail_msg=f"Last message was not RPL_ENDOFWHOWAS ({RPL_ENDOFWHOWAS})", + ) + + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasMultiple(self): + """ + "The history is searched backward, returning the most recent entry first." + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2") + + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasCount1(self): + """ + "If there are multiple entries, up to <count> replies will be returned" + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=False, whowas_command="WHOWAS nick2 1") + + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasCount2(self): + """ + "If there are multiple entries, up to <count> replies will be returned" + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 2") + + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasCountNegative(self): + """ + "If a non-positive number is passed as being <count>, then a full search + is done." + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 -1") + + @cases.mark_specifications("RFC1459", "RFC2812") + def testWhowasCountZero(self): + """ + "If a non-positive number is passed as being <count>, then a full search + is done." + -- https://datatracker.ietf.org/doc/html/rfc1459#section-4.5.3 + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS nick2 0") + + @cases.mark_specifications("RFC2812", deprecated=True) + def testWhowasWildcard(self): + """ + "Wildcards are allowed in the <target> parameter." + -- https://datatracker.ietf.org/doc/html/rfc2812#section-3.6.3 + """ + self._testWhowasMultiple(second_result=True, whowas_command="WHOWAS *ck2") diff --git a/bahamut_localhost.patch b/patches/bahamut_localhost.patch similarity index 100% rename from bahamut_localhost.patch rename to patches/bahamut_localhost.patch diff --git a/inspircd_mainloop.patch b/patches/inspircd_mainloop.patch similarity index 100% rename from inspircd_mainloop.patch rename to patches/inspircd_mainloop.patch diff --git a/patches/ngircd_whowas_delay.patch b/patches/ngircd_whowas_delay.patch new file mode 100644 index 0000000..80e322e --- /dev/null +++ b/patches/ngircd_whowas_delay.patch @@ -0,0 +1,19 @@ +ngIRCd skips WHOWAS entries for users that were connected for less +than 30 seconds. + +To avoid waiting 30s in every WHOWAS test, we need to remove this. + +diff --git a/src/ngircd/client.c b/src/ngircd/client.c +index 67c02604..66e8e540 100644 +--- a/src/ngircd/client.c ++++ b/src/ngircd/client.c +@@ -1490,9 +1490,6 @@ Client_RegisterWhowas( CLIENT *Client ) + return; + + now = time(NULL); +- /* Don't register clients that were connected less than 30 seconds. */ +- if( now - Client->starttime < 30 ) +- return; + + slot = Last_Whowas + 1; + if( slot >= MAX_WHOWAS || slot < 0 ) slot = 0; diff --git a/workflows.yml b/workflows.yml index 4909a0d..3ce954b 100644 --- a/workflows.yml +++ b/workflows.yml @@ -104,7 +104,7 @@ software: separate_build_job: true build_script: | cd $GITHUB_WORKSPACE/Bahamut/ - patch src/s_user.c < $GITHUB_WORKSPACE/bahamut_localhost.patch + patch src/s_user.c < $GITHUB_WORKSPACE/patches/bahamut_localhost.patch echo "#undef THROTTLE_ENABLE" >> include/config.h libtoolize --force aclocal @@ -152,7 +152,7 @@ software: separate_build_job: true build_script: &inspircd_build_script | cd $GITHUB_WORKSPACE/inspircd/ - patch src/inspircd.cpp < $GITHUB_WORKSPACE/inspircd_mainloop.patch + patch src/inspircd.cpp < $GITHUB_WORKSPACE/patches/inspircd_mainloop.patch ./configure --prefix=$HOME/.local/inspircd --development make -j 4 make install @@ -217,6 +217,7 @@ software: separate_build_job: true build_script: | cd $GITHUB_WORKSPACE/ngircd + patch src/ngircd/client.c < $GITHUB_WORKSPACE/patches/ngircd_whowas_delay.patch ./autogen.sh ./configure --prefix=$HOME/.local/ make -j 4