From f443289e3793fba57b19e460655728c45e8c4604 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 18 May 2026 22:43:34 +0200 Subject: [PATCH 1/4] Add regression test for Solanum crashing on labeled QUIT --- irctest/server_tests/labeled_responses.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/irctest/server_tests/labeled_responses.py b/irctest/server_tests/labeled_responses.py index 377a2629..f20b6104 100644 --- a/irctest/server_tests/labeled_responses.py +++ b/irctest/server_tests/labeled_responses.py @@ -525,3 +525,35 @@ def testUnknownCommand(self): self.assertMessageMatch( unknowncommand, command=ERR_UNKNOWNCOMMAND, tags={"label": "deadbeef"} ) + + @cases.mark_capabilities("labeled-response") + def testQuit(self): + """Regression test for Solanum crashing on labeled QUIT: + https://github.com/solanum-ircd/solanum/pull/526""" + self.connectClient( + "foo", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True + ) + self.connectClient( + "bar", capabilities=["batch", "labeled-response"], skip_if_cap_nak=True + ) + self.joinChannel(1, "#chan") + self.joinChannel(2, "#chan") + + self.getMessages(2) + self.getMessages(1) + + self.sendLine(1, "@label=deadbeef QUIT :foo out") + m = self.getMessage(1) + self.assertMessageMatch( + m, command="QUIT", params=[StrRe(".*foo out.*")], tags={"label": "deadbeef"} + ) + + m = self.getMessage(2) + self.assertMessageMatch( + m, command="QUIT", params=[StrRe(".*foo out.*")], tags={} + ) + + # check the server didn't crash (the QUIT may be buffered on the client side) + self.sendLine(2, "@label=76T893 PRIVMSG #chan :hello") + m = self.getMessage(2) + self.assertMessageMatch(m, command="ACK", params=[], tags={"label": "76T893"}) From 5336c3e54f7de0d8bdd30d883a6dbf0239eb27b5 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 18 May 2026 23:44:38 +0200 Subject: [PATCH 2/4] Fix for Insp, which doesn't echo anything and closes the connection instead --- irctest/server_tests/labeled_responses.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/irctest/server_tests/labeled_responses.py b/irctest/server_tests/labeled_responses.py index f20b6104..c4e8e0c9 100644 --- a/irctest/server_tests/labeled_responses.py +++ b/irctest/server_tests/labeled_responses.py @@ -10,6 +10,7 @@ import pytest from irctest import cases, runner +from irctest.exceptions import ConnectionClosed from irctest.numerics import ERR_UNKNOWNCOMMAND from irctest.patma import ANYDICT, ANYOPTSTR, NotStrRe, RemainingKeys, StrRe from irctest.specifications import OptionalBehaviors @@ -543,10 +544,18 @@ def testQuit(self): self.getMessages(1) self.sendLine(1, "@label=deadbeef QUIT :foo out") - m = self.getMessage(1) - self.assertMessageMatch( - m, command="QUIT", params=[StrRe(".*foo out.*")], tags={"label": "deadbeef"} - ) + try: + m = self.getMessage(1) + except ConnectionClosed: + # InspIRCd closes connection without echoeing anything + print("Connection closed") + else: + self.assertMessageMatch( + m, + command="QUIT", + params=[StrRe(".*foo out.*")], + tags={"label": "deadbeef"}, + ) m = self.getMessage(2) self.assertMessageMatch( From 10bff8f1a1bf03f94662ed57cbc5bdb55b23a1d2 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 18 May 2026 23:45:57 +0200 Subject: [PATCH 3/4] Fix for Sable and Unreal, which return ERROR instead of QUIT --- irctest/server_tests/labeled_responses.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/irctest/server_tests/labeled_responses.py b/irctest/server_tests/labeled_responses.py index c4e8e0c9..8b37973d 100644 --- a/irctest/server_tests/labeled_responses.py +++ b/irctest/server_tests/labeled_responses.py @@ -550,12 +550,21 @@ def testQuit(self): # InspIRCd closes connection without echoeing anything print("Connection closed") else: - self.assertMessageMatch( - m, - command="QUIT", - params=[StrRe(".*foo out.*")], - tags={"label": "deadbeef"}, - ) + if m.command == "ERROR": + # Sable and UnrealIRCd + self.assertMessageMatch( + m, + command="ERROR", + params=[StrRe("(Client quit|.*foo out.*)")], + tags={"label": "deadbeef"}, + ) + else: + self.assertMessageMatch( + m, + command="QUIT", + params=[StrRe(".*foo out.*")], + tags={"label": "deadbeef"}, + ) m = self.getMessage(2) self.assertMessageMatch( From 9526a6c15e3cbc24140ed4b628e8c3985a01786b Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Wed, 20 May 2026 11:21:48 +0200 Subject: [PATCH 4/4] Revert "Fix for Insp, which doesn't echo anything and closes the connection instead" This reverts commit 5336c3e54f7de0d8bdd30d883a6dbf0239eb27b5. --- irctest/server_tests/labeled_responses.py | 36 ++++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/irctest/server_tests/labeled_responses.py b/irctest/server_tests/labeled_responses.py index 8b37973d..f9c7f3ee 100644 --- a/irctest/server_tests/labeled_responses.py +++ b/irctest/server_tests/labeled_responses.py @@ -10,7 +10,6 @@ import pytest from irctest import cases, runner -from irctest.exceptions import ConnectionClosed from irctest.numerics import ERR_UNKNOWNCOMMAND from irctest.patma import ANYDICT, ANYOPTSTR, NotStrRe, RemainingKeys, StrRe from irctest.specifications import OptionalBehaviors @@ -544,27 +543,22 @@ def testQuit(self): self.getMessages(1) self.sendLine(1, "@label=deadbeef QUIT :foo out") - try: - m = self.getMessage(1) - except ConnectionClosed: - # InspIRCd closes connection without echoeing anything - print("Connection closed") + m = self.getMessage(1) + if m.command == "ERROR": + # InspIRCd, Sable, and UnrealIRCd + self.assertMessageMatch( + m, + command="ERROR", + params=[StrRe("(Client quit|.*foo out.*)")], + tags={"label": "deadbeef"}, + ) else: - if m.command == "ERROR": - # Sable and UnrealIRCd - self.assertMessageMatch( - m, - command="ERROR", - params=[StrRe("(Client quit|.*foo out.*)")], - tags={"label": "deadbeef"}, - ) - else: - self.assertMessageMatch( - m, - command="QUIT", - params=[StrRe(".*foo out.*")], - tags={"label": "deadbeef"}, - ) + self.assertMessageMatch( + m, + command="QUIT", + params=[StrRe(".*foo out.*")], + tags={"label": "deadbeef"}, + ) m = self.getMessage(2) self.assertMessageMatch(