2 Commits

Author SHA1 Message Date
63def483a1 Update Sable and make LINKS test support it 2024-12-28 18:03:04 +01:00
93363e4b05 Use consistent name for services server 2024-12-28 17:55:56 +01:00
15 changed files with 64 additions and 291 deletions

View File

@ -19,7 +19,7 @@ jobs:
python-version: 3.11
- name: Cache dependencies
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: |
~/.cache

View File

@ -120,8 +120,6 @@ jobs:
path: ircd-hybrid
ref: 8.2.x
repository: ircd-hybrid/ircd-hybrid
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime libjansson-dev
- name: Build Hybrid
run: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -560,7 +558,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.24.0
go-version: ^1.23.0
- run: go version
- name: Build Ergo
run: |

View File

@ -161,8 +161,6 @@ jobs:
path: ircd-hybrid
ref: 8.2.39
repository: ircd-hybrid/ircd-hybrid
- name: Install system dependencies
run: sudo apt-get install atheme-services faketime libjansson-dev
- name: Build Hybrid
run: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -638,7 +636,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.24.0
go-version: ^1.23.0
- run: go version
- name: Build Ergo
run: |

View File

@ -66,13 +66,8 @@ options {{
warningtimeout = 4h
}}
module {{ name = "ns_sasl" }} # since 2.1.13
module {{ name = "sasl" }} # 2.1.2 to 2.1.12
module {{ name = "m_sasl" }} # 2.0 to 2.1.1
module {{ name = "enc_sha2" }} # 2.1
module {{ name = "enc_sha256" }} # 2.0
module {{ name = "{module_prefix}sasl" }}
module {{ name = "enc_bcrypt" }}
module {{ name = "ns_cert" }}
"""
@ -128,6 +123,7 @@ class AnopeController(BaseServicesController, DirectoryBasedController):
protocol=protocol,
server_hostname=server_hostname,
server_port=server_port,
module_prefix="" if self.software_version >= (2, 1, 2) else "m_",
)
)

View File

@ -60,7 +60,7 @@ privset "omnioper" {{
privs = oper:general, oper:privs, oper:testline, oper:kill, oper:operwall, oper:message,
oper:routing, oper:kline, oper:unkline, oper:xline,
oper:resv, oper:cmodes, oper:mass_notice, oper:wallops,
oper:remoteban, oper:local_kill,
oper:remoteban,
usermode:servnotice, auspex:oper, auspex:hostname, auspex:umodes, auspex:cmodes,
oper:admin, oper:die, oper:rehash, oper:spy, oper:grant;
}};

View File

@ -19,7 +19,7 @@ TEMPLATE_CONFIG = """
<class
name="ServerOperators"
commands="WALLOPS GLOBOPS KILL"
commands="WALLOPS GLOBOPS"
privs="channels/auspex users/auspex channels/auspex servers/auspex"
>
<type

View File

@ -24,7 +24,7 @@ Y:10:90::100:512000:100.100:100.100:
I::{password_field}:::10::
# O:<TARGET Host NAME>:<Password>:<Nickname>:<Port>:<Class>:<Flags>:
O:*:operpassword:operuser:::K:
O:*:operpassword:operuser::::
"""

View File

@ -14,7 +14,6 @@ from irctest.basecontrollers import (
NotImplementedByController,
)
from irctest.cases import BaseServerTestCase
from irctest.client_mock import ClientMock
from irctest.exceptions import NoMessageException
from irctest.patma import ANYSTR
@ -497,53 +496,6 @@ class SableServicesController(BaseServicesController):
os.killpg(self.pgroup_id, signal.SIGKILL)
super().kill_proc()
def wait_for_services(self) -> None:
# by default, wait_for_services() connects a user that sends a HELP command
# to NickServ and assumes services are up when it receives a non-ERR_NOSUCHNICK
# reply.
# However, with Sable, NickServ is always up, even when services are not linked,
# so we need to check a different way. We check presence of a non-EXTERNAL
# value to the sasl capability, but LINKS would
if self.services_up:
# Don't check again if they are already available
return
self.server_controller.wait_for_port()
c = ClientMock(name="chkSvs", show_io=True)
c.connect(self.server_controller.hostname, self.server_controller.port)
c.sendLine("NICK chkSvs")
c.sendLine("USER chk chk chk chk")
time.sleep(self.server_controller.sync_sleep_time)
got_end_of_motd = False
while not got_end_of_motd:
for msg in c.getMessages(synchronize=False):
if msg.command == "PING":
# Hi Unreal
c.sendLine("PONG :" + msg.params[0])
if msg.command in ("376", "422"): # RPL_ENDOFMOTD / ERR_NOMOTD
got_end_of_motd = True
timeout = time.time() + 10
while not self.services_up:
if time.time() > timeout:
raise Exception("Timeout while waiting for services")
c.sendLine("CAP LS 302")
msgs = self.getNickServResponse(c, timeout=1)
for msg in msgs:
if msg.command == "CAP":
pass
for token in msg.params[-1].split():
if token.startswith("sasl="):
if "PLAIN" in token.removeprefix("sasl=").split(","):
# SASL PLAIN is available, so services are linked.
self.services_up = True
break
c.sendLine("QUIT")
c.getMessages()
c.disconnect()
def get_irctest_controller_class() -> Type[SableController]:
return SableController

View File

@ -12,8 +12,9 @@ from irctest.patma import ANYSTR, StrRe
@cases.mark_services
class BouncerTestCase(cases.BaseServerTestCase):
def setUp(self):
super().setUp()
@cases.mark_specifications("Ergo")
def testBouncer(self):
"""Test basic bouncer functionality."""
self.controller.registerUser(self, "observer", "observerpassword")
self.controller.registerUser(self, "testuser", "mypassword")
@ -32,27 +33,13 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.sendLine(2, "USER a 0 * a")
self.sendLine(2, "CAP REQ :server-time message-tags")
self.sendLine(2, "CAP END")
self._waitForWelcome(2, "testnick")
messages = self.getMessages(2)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
# should see a regburst for testnick
self.assertMessageMatch(welcomes[0], params=["testnick", ANYSTR])
self.joinChannel(2, "#chan")
def _waitForWelcome(self, client, nick):
"""Waits for numeric 001, and returns every message after that."""
while True:
messages = iter(self.getMessages(client, synchronize=False))
for message in messages:
if message.command == "001":
# should see a regburst for testnick
self.assertMessageMatch(message, params=[nick, ANYSTR])
messages_after_welcome = list(messages)
messages_after_welcome += self.getMessages(client)
# check there is no other 001
for message in messages_after_welcome:
self.assertNotEqual(message.command, "001")
return messages_after_welcome
def _connectClient3(self):
self.addClient()
self.sendLine(3, "CAP LS 302")
self.sendLine(3, "AUTHENTICATE PLAIN")
@ -61,37 +48,49 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.sendLine(3, "USER a 0 * a")
self.sendLine(3, "CAP REQ :server-time message-tags account-tag")
self.sendLine(3, "CAP END")
messages = self.getMessages(3)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
# should see the *same* regburst for testnick
messages = self._waitForWelcome(3, "testnick")
self.assertMessageMatch(welcomes[0], params=["testnick", ANYSTR])
joins = [message for message in messages if message.command == "JOIN"]
# we should be automatically joined to #chan
self.assertMessageMatch(joins[0], params=["#chan"])
def _connectClient4(self):
# connect a client similar to 3, but without the message-tags and account-tag
# capabilities, to make sure it does not receive the associated tags
# disable multiclient in nickserv
self.sendLine(3, "NS SET MULTICLIENT OFF")
self.getMessages(3)
self.addClient()
self.sendLine(4, "CAP LS 302")
self.sendLine(4, "AUTHENTICATE PLAIN")
self.sendLine(4, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(4, "NICK testnick")
self.sendLine(4, "USER a 0 * a")
self.sendLine(4, "CAP REQ server-time")
self.sendLine(4, "CAP REQ :server-time message-tags")
self.sendLine(4, "CAP END")
# should see the *same* regburst for testnick
self._waitForWelcome(4, "testnick")
# with multiclient disabled, we should not be able to attach to the nick
messages = self.getMessages(4)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 0)
errors = [
message for message in messages if message.command == ERR_NICKNAMEINUSE
]
self.assertEqual(len(errors), 1)
@cases.mark_specifications("Ergo", "Sable")
def testAutomaticResumption(self):
"""Test logging into an account that already has a client joins the client's session"""
self._connectClient3()
@cases.mark_specifications("Ergo", "Sable")
def testChannelMessageFromOther(self):
"""Test that all clients attached to a session get messages sent by someone else
to a channel"""
self._connectClient3()
self._connectClient4()
self.sendLine(3, "NS SET MULTICLIENT ON")
self.getMessages(3)
self.addClient()
self.sendLine(5, "CAP LS 302")
self.sendLine(5, "AUTHENTICATE PLAIN")
self.sendLine(5, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(5, "NICK testnick")
self.sendLine(5, "USER a 0 * a")
self.sendLine(5, "CAP REQ server-time")
self.sendLine(5, "CAP END")
messages = self.getMessages(5)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 1)
self.sendLine(1, "@+clientOnlyTag=Value PRIVMSG #chan :hey")
self.getMessages(1)
@ -105,84 +104,22 @@ class BouncerTestCase(cases.BaseServerTestCase):
self.assertEqual(len(messagesforthree), 1)
messagefortwo = messagesfortwo[0]
messageforthree = messagesforthree[0]
messageforfour = self.getMessage(4)
messageforfive = self.getMessage(5)
self.assertMessageMatch(messagefortwo, params=["#chan", "hey"])
self.assertMessageMatch(messageforthree, params=["#chan", "hey"])
self.assertMessageMatch(messageforfour, params=["#chan", "hey"])
self.assertMessageMatch(messageforfive, params=["#chan", "hey"])
self.assertIn("time", messagefortwo.tags)
self.assertIn("time", messageforthree.tags)
self.assertIn("time", messageforfour.tags)
self.assertIn("time", messageforfive.tags)
# 3 has account-tag
self.assertIn("account", messageforthree.tags)
# should get same msgid
self.assertEqual(messagefortwo.tags["msgid"], messageforthree.tags["msgid"])
# 4 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfour.tags)
self.assertNotIn("msgid", messageforfour.tags)
@cases.mark_specifications("Ergo", "Sable")
def testChannelMessageFromSelf(self):
"""Test that all clients attached to a session get messages sent by an other client
(TODO: check when the initial sender has echo-message too)"""
self._connectClient3()
self._connectClient4()
self.sendLine(2, "@+clientOnlyTag=Value PRIVMSG #chan :hey")
messagesfortwo = [
msg for msg in self.getMessages(2) if msg.command == "PRIVMSG"
]
messagesforone = [
msg for msg in self.getMessages(1) if msg.command == "PRIVMSG"
]
messagesforthree = [
msg for msg in self.getMessages(3) if msg.command == "PRIVMSG"
]
self.assertEqual(len(messagesforone), 1)
self.assertEqual(len(messagesfortwo), 0) # echo-message not enabled
self.assertEqual(len(messagesforthree), 1)
messageforone = messagesforone[0]
messageforthree = messagesforthree[0]
messageforfour = self.getMessage(4)
self.assertMessageMatch(messageforone, params=["#chan", "hey"])
self.assertMessageMatch(messageforthree, params=["#chan", "hey"])
self.assertMessageMatch(messageforfour, params=["#chan", "hey"])
self.assertIn("time", messageforone.tags)
self.assertIn("time", messageforthree.tags)
self.assertIn("time", messageforfour.tags)
# 3 has account-tag
self.assertIn("account", messageforthree.tags)
# should get same msgid
self.assertEqual(messageforone.tags["msgid"], messageforthree.tags["msgid"])
# 4 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfour.tags)
self.assertNotIn("msgid", messageforfour.tags)
@cases.mark_specifications("Ergo", "Sable")
def testDirectMessageFromOther(self):
"""Test that all clients attached to a session get copies of messages sent
by an other client of that session directly to an other user"""
self._connectClient3()
self._connectClient4()
self.sendLine(1, "PRIVMSG testnick :this is a direct message")
self.getMessages(1)
messagefortwo = [
msg for msg in self.getMessages(2) if msg.command == "PRIVMSG"
][0]
messageforthree = [
msg for msg in self.getMessages(3) if msg.command == "PRIVMSG"
][0]
self.assertEqual(messagefortwo.params, messageforthree.params)
self.assertEqual(messagefortwo.tags["msgid"], messageforthree.tags["msgid"])
@cases.mark_specifications("Ergo", "Sable")
def testDirectMessageFromSelf(self):
"""Test that all clients attached to a session get copies of messages sent
by an other client of that session directly to an other user"""
self._connectClient3()
self._connectClient4()
# 5 only has server-time, shouldn't get account or msgid tags
self.assertNotIn("account", messageforfive.tags)
self.assertNotIn("msgid", messageforfive.tags)
# test that copies of sent messages go out to other sessions
self.sendLine(2, "PRIVMSG observer :this is a direct message")
self.getMessages(2)
messageForRecipient = [
@ -196,20 +133,10 @@ class BouncerTestCase(cases.BaseServerTestCase):
messageForRecipient.tags["msgid"], copyForOtherSession.tags["msgid"]
)
@cases.mark_specifications("Ergo", "Sable")
def testQuit(self):
"""Test that a single client of a session does not make the whole user quit
(and is generally not visible to anyone else, not even their other sessions),
until the last client quits"""
self._connectClient3()
self._connectClient4()
self.sendLine(2, "QUIT :two out")
quitLines = [
msg for msg in self.getMessages(2) if msg.command in ("QUIT", "ERROR")
]
quitLines = [msg for msg in self.getMessages(2) if msg.command == "QUIT"]
self.assertEqual(len(quitLines), 1)
if quitLines[0].command == "QUIT":
self.assertMessageMatch(quitLines[0], params=[StrRe(".*two out.*")])
self.assertMessageMatch(quitLines[0], params=[StrRe(".*two out.*")])
# neither the observer nor the other attached session should see a quit here
quitLines = [msg for msg in self.getMessages(1) if msg.command == "QUIT"]
self.assertEqual(quitLines, [])
@ -227,39 +154,13 @@ class BouncerTestCase(cases.BaseServerTestCase):
messagesforthree[0], command="PRIVMSG", params=["#chan", "hey again"]
)
self.sendLine(4, "QUIT :four out")
self.getMessages(4)
self.sendLine(5, "QUIT :five out")
self.getMessages(5)
self.sendLine(3, "QUIT :three out")
quitLines = [
msg for msg in self.getMessages(3) if msg.command in ("QUIT", "ERROR")
]
quitLines = [msg for msg in self.getMessages(3) if msg.command == "QUIT"]
self.assertEqual(len(quitLines), 1)
if quitLines[0].command == "QUIT":
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
# observer should see *this* quit
quitLines = [msg for msg in self.getMessages(1) if msg.command == "QUIT"]
self.assertEqual(len(quitLines), 1)
self.assertMessageMatch(quitLines[0], params=[StrRe(".*three out.*")])
@cases.mark_specifications("Ergo")
def testDisableAutomaticResumption(self):
# disable multiclient in nickserv
self.sendLine(2, "NS SET MULTICLIENT OFF")
self.getMessages(2)
self.addClient()
self.sendLine(3, "CAP LS 302")
self.sendLine(3, "AUTHENTICATE PLAIN")
self.sendLine(3, sasl_plain_blob("testuser", "mypassword"))
self.sendLine(3, "NICK testnick")
self.sendLine(3, "USER a 0 * a")
self.sendLine(3, "CAP REQ :server-time message-tags")
self.sendLine(3, "CAP END")
# with multiclient disabled, we should not be able to attach to the nick
messages = self.getMessages(3)
welcomes = [message for message in messages if message.command == RPL_WELCOME]
self.assertEqual(len(welcomes), 0)
errors = [
message for message in messages if message.command == ERR_NICKNAMEINUSE
]
self.assertEqual(len(errors), 1)

View File

@ -1,67 +0,0 @@
"""
The KILL command (`Modern <https://modern.ircdocs.horse/#kill-message>`__)
"""
from irctest import cases
from irctest.numerics import ERR_NOPRIVILEGES, RPL_YOUREOPER
class KillTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Modern")
@cases.xfailIfSoftware(["Sable"], "https://github.com/Libera-Chat/sable/issues/154")
def testKill(self):
self.connectClient("ircop", name="ircop")
self.connectClient("alice", name="alice")
self.connectClient("bob", name="bob")
self.sendLine("ircop", "OPER operuser operpassword")
self.assertIn(
RPL_YOUREOPER,
[m.command for m in self.getMessages("ircop")],
fail_msg="OPER failed",
)
self.sendLine("alice", "KILL bob :some arbitrary reason")
self.assertIn(
ERR_NOPRIVILEGES,
[m.command for m in self.getMessages("alice")],
fail_msg="unprivileged KILL not rejected",
)
# bob is not killed
self.getMessages("bob")
self.sendLine("alice", "KILL alice :some arbitrary reason")
self.assertIn(
ERR_NOPRIVILEGES,
[m.command for m in self.getMessages("alice")],
fail_msg="unprivileged KILL not rejected",
)
# alice is not killed
self.getMessages("alice")
# privileged KILL should succeed
self.sendLine("ircop", "KILL alice :some arbitrary reason")
self.getMessages("ircop")
self.assertDisconnected("alice")
self.sendLine("ircop", "KILL bob :some arbitrary reason")
self.getMessages("ircop")
self.assertDisconnected("bob")
@cases.mark_specifications("Ergo")
def testKillOneArgument(self):
self.connectClient("ircop", name="ircop")
self.connectClient("alice", name="alice")
self.sendLine("ircop", "OPER operuser operpassword")
self.assertIn(
RPL_YOUREOPER,
[m.command for m in self.getMessages("ircop")],
fail_msg="OPER failed",
)
# 1-argument kill command, accepted by Ergo and some implementations
self.sendLine("ircop", "KILL alice")
self.getMessages("ircop")
self.assertDisconnected("alice")

View File

@ -117,7 +117,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
# This server redacts links
return
messages.sort(key=lambda m: tuple(m.params))
messages.sort(key=lambda m: m.params[-1])
self.assertMessageMatch(
messages.pop(0),
@ -136,7 +136,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
"nick",
"My.Little.Services",
"My.Little.Server",
StrRe("[01] .+"), # SID instead of description for Anope...
StrRe("1 .+"), # SID instead of description for Anope...
],
)

View File

@ -9,7 +9,6 @@ class Specifications(enum.Enum):
RFC2812 = "RFC2812"
IRCv3 = "IRCv3" # Mark with capabilities whenever possible
Ergo = "Ergo"
SABLE = "Sable"
Ircdocs = "ircdocs"
"""Any document on ircdocs.horse (especially defs.ircdocs.horse),

View File

@ -1,5 +1,5 @@
[mypy]
python_version = 3.9
python_version = 3.8
warn_return_any = True
warn_unused_configs = True

View File

@ -8,7 +8,6 @@ markers =
modern
ircdocs
Ergo
Sable
# misc marks
strict

View File

@ -33,9 +33,6 @@ software:
devel: "8.2.x"
devel_release: null
path: ircd-hybrid
pre_deps:
- name: "Install system dependencies"
run: "sudo apt-get install atheme-services faketime libjansson-dev"
separate_build_job: true
build_script: |
cd $GITHUB_WORKSPACE/ircd-hybrid/
@ -139,7 +136,7 @@ software:
pre_deps:
- uses: actions/setup-go@v3
with:
go-version: '^1.24.0'
go-version: '^1.23.0'
- run: go version
separate_build_job: false
build_script: |