15 Commits

Author SHA1 Message Date
e9e37f5438 basic test case for KILL (#302) 2025-03-27 05:19:02 -04:00
2b6b666426 Fix irctest for the recent Anope SASL changes. 2025-03-04 07:37:50 +01:00
d218d2f98d Document the versions enc_sha2 and enc_sha256 are used on. 2025-03-04 07:37:50 +01:00
52178a99e5 Install libjansson-dev at build time instead of run time 2025-02-19 19:40:36 +01:00
7f2a631a1a Install libjansson-dev in order to build latest ircd-hybrid 2025-02-18 19:49:02 +01:00
cd9f801b92 Update actions/cache 2025-02-18 19:48:43 +01:00
ccd647ea61 sable: Actually check services are up in tests that rely on them
This should fix some flakiness.
2025-02-14 16:53:03 +01:00
9de64159ba upgrade ergo to go 1.24 (#298) 2025-02-13 08:01:15 +01:00
638f959c95 Fix irctest when using long passwords not supported by bcrypt. (#297) 2025-01-28 19:52:44 +01:00
06e08b52be Make LINKS deterministic for Sable (#296) 2024-12-30 21:07:33 +01:00
54a1ab95ce Relax testLinksWithServices for Sable 2024-12-28 22:54:57 +01:00
3e6d97ae42 Update Sable and make LINKS test support it 2024-12-28 20:30:37 +01:00
00c130d66c Use consistent name for services server 2024-12-28 20:30:37 +01:00
2680502dfe Allow any extban format on InspIRCd. (#294)
This should stop the mute extban test failing when extbanformat is set to name (which will soon be the default in git master).
2024-11-03 10:24:05 +01:00
d090f5455e Update Sable and enable more test (#293) 2024-10-28 18:15:10 +01:00
21 changed files with 172 additions and 42 deletions

View File

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

View File

@ -120,6 +120,8 @@ 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/
@ -558,7 +560,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.23.0
go-version: ^1.24.0
- run: go version
- name: Build Ergo
run: |

View File

@ -161,6 +161,8 @@ 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/
@ -636,7 +638,7 @@ jobs:
repository: ergochat/ergo
- uses: actions/setup-go@v3
with:
go-version: ^1.23.0
go-version: ^1.24.0
- run: go version
- name: Build Ergo
run: |
@ -1140,7 +1142,7 @@ jobs:
uses: actions/checkout@v4
with:
path: sable
ref: e9701e5e8d0c4f278ddd61ce7285f4918ecf99e9
ref: baed3ef9ac4550dc36a45b758436769e82e8ec58
repository: Libera-Chat/sable
- name: Install rust toolchain
uses: actions-rs/toolchain@v1

View File

@ -84,15 +84,12 @@ LIMNORIA_SELECTORS := \
$(EXTRA_SELECTORS)
# Tests marked with arbitrary_client_tags or react_tag can't pass because Sable does not support client tags yet
# Tests marked with private_chathistory can't pass because Sable does not implement CHATHISTORY for DMs
SABLE_SELECTORS := \
not Ergo \
and not deprecated \
and not strict \
and not arbitrary_client_tags \
and not react_tag \
and not private_chathistory \
and not list and not lusers and not time and not info \
$(EXTRA_SELECTORS)

View File

@ -8,7 +8,7 @@ from irctest.basecontrollers import BaseServicesController, DirectoryBasedContro
TEMPLATE_CONFIG = """
serverinfo {{
name = "services.example.org"
name = "My.Little.Services"
description = "Anope IRC Services"
numeric = "00A"
pid = "services.pid"
@ -66,8 +66,13 @@ options {{
warningtimeout = 4h
}}
module {{ name = "{module_prefix}sasl" }}
module {{ name = "enc_bcrypt" }}
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 = "ns_cert" }}
"""
@ -123,7 +128,6 @@ 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

@ -24,7 +24,7 @@ loadmodule "modules/saslserv/plain";
#loadmodule "modules/saslserv/scram";
serverinfo {{
name = "services.example.org";
name = "My.Little.Services";
desc = "Atheme IRC Services";
numeric = "00A";
netname = "testnet";

View File

@ -14,7 +14,7 @@ options {{
network_name unconfigured;
allow_split_ops; # Give ops in empty channels
services_name services.example.org;
services_name My.Little.Services;
// if you need to link more than 1 server, uncomment the following line
servtype hub;
@ -44,7 +44,7 @@ class {{
/* for services */
super {{
"services.example.org";
"My.Little.Services";
}};
@ -57,7 +57,7 @@ class {{
/* our services */
connect {{
name services.example.org;
name My.Little.Services;
host *@127.0.0.1; # unfortunately, masks aren't allowed here
apasswd password;
cpasswd password;
@ -91,7 +91,7 @@ class BahamutController(BaseServerController, DirectoryBasedController):
software_name = "Bahamut"
supported_sasl_mechanisms: Set[str] = set()
supports_sts = False
nickserv = "NickServ@services.example.org"
nickserv = "NickServ@My.Little.Services"
def create_config(self) -> None:
super().create_config()

View File

@ -44,7 +44,7 @@ channel {{
displayed_usercount = 0;
}};
connect "services.example.org" {{
connect "My.Little.Services" {{
host = "localhost"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -53,14 +53,14 @@ connect "services.example.org" {{
flags = topicburst;
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
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:remoteban, oper:local_kill,
usermode:servnotice, auspex:oper, auspex:hostname, auspex:umodes, auspex:cmodes,
oper:admin, oper:die, oper:rehash, oper:spy, oper:grant;
}};

View File

@ -13,7 +13,7 @@ TEMPLATE_DLK_CONFIG = """\
info {{
SID "00A";
network-name "testnetwork";
services-name "services.example.org";
services-name "My.Little.Services";
admin-email "admin@example.org";
}}

View File

@ -42,7 +42,7 @@ class {{
connectfreq = 5 minutes;
}};
connect {{
name = "services.example.org";
name = "My.Little.Services";
host = "127.0.0.1"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -50,7 +50,7 @@ connect {{
class = "server";
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
auth {{

View File

@ -19,7 +19,7 @@ TEMPLATE_CONFIG = """
<class
name="ServerOperators"
commands="WALLOPS GLOBOPS"
commands="WALLOPS GLOBOPS KILL"
privs="channels/auspex users/auspex channels/auspex servers/auspex"
>
<type
@ -33,14 +33,15 @@ TEMPLATE_CONFIG = """
class="ServerOperators"
>
<options casemapping="ascii">
<options casemapping="ascii"
extbanformat="any">
# Disable 'NOTICE #chan :*** foo invited bar into the channel-
<security announceinvites="none">
# Services:
<bind address="{services_hostname}" port="{services_port}" type="servers">
<link name="services.example.org"
<link name="My.Little.Services"
ipaddr="{services_hostname}"
port="{services_port}"
allowmask="*"
@ -50,7 +51,7 @@ TEMPLATE_CONFIG = """
<module name="spanningtree">
<module name="hidechans"> # Anope errors when missing
<sasl requiressl="no"
target="services.example.org">
target="My.Little.Services">
# Protocol:
<module name="banexception">

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::::
O:*:operpassword:operuser:::K:
"""

View File

@ -14,7 +14,7 @@ TEMPLATE_CONFIG = """
{password_field}
[Server]
Name = services.example.org
Name = My.Little.Services
MyPassword = password
PeerPassword = password
Passive = yes # don't connect to it

View File

@ -44,7 +44,7 @@ class {{
connectfreq = 5 minutes;
}};
connect {{
name = "services.example.org";
name = "My.Little.Services";
host = "127.0.0.1"; # Used to validate incoming connection
port = 0; # We don't need servers to connect to services
send_password = "password";
@ -52,7 +52,7 @@ connect {{
class = "server";
}};
service {{
name = "services.example.org";
name = "My.Little.Services";
}};
auth {{

View File

@ -14,6 +14,7 @@ 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
@ -496,6 +497,53 @@ 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

@ -64,7 +64,7 @@ listen {{
options {{ serversonly; }}
}}
link services.example.org {{
link My.Little.Services {{
incoming {{
mask *;
}}
@ -72,11 +72,11 @@ link services.example.org {{
class servers;
}}
ulines {{
services.example.org;
My.Little.Services;
}}
set {{
sasl-server services.example.org;
sasl-server My.Little.Services;
kline-address "example@example.org";
network-name "ExampleNET";
default-server "irc.example.org";

View File

@ -0,0 +1,67 @@
"""
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

@ -3,6 +3,13 @@ from irctest.numerics import ERR_UNKNOWNCOMMAND, RPL_ENDOFLINKS, RPL_LINKS
from irctest.patma import ANYSTR, StrRe
def _server_info_regexp(case: cases.BaseServerTestCase) -> str:
if case.controller.software_name == "Sable":
return ".+"
else:
return "test server"
class LinksTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("RFC1459", "RFC2812", "Modern")
def testLinksSingleServer(self):
@ -56,7 +63,7 @@ class LinksTestCase(cases.BaseServerTestCase):
"nick",
"My.Little.Server",
"My.Little.Server",
StrRe("0 (0042 )?test server"),
StrRe(f"0 (0042 )?{_server_info_regexp(self)}"),
],
)
@ -110,7 +117,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
# This server redacts links
return
messages.sort(key=lambda m: m.params[-1])
messages.sort(key=lambda m: tuple(m.params))
self.assertMessageMatch(
messages.pop(0),
@ -119,7 +126,7 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
"nick",
"My.Little.Server",
"My.Little.Server",
StrRe("0 (0042 )?test server"),
StrRe(f"0 (0042 )?{_server_info_regexp(self)}"),
],
)
self.assertMessageMatch(
@ -127,9 +134,9 @@ class ServicesLinksTestCase(cases.BaseServerTestCase):
command=RPL_LINKS,
params=[
"nick",
"services.example.org",
"My.Little.Services",
"My.Little.Server",
StrRe("1 .+"), # SID instead of description for Anope...
StrRe("[01] .+"), # SID instead of description for Anope...
],
)

View File

@ -221,7 +221,6 @@ class WhoisTestCase(_WhoisTestMixin, cases.BaseServerTestCase):
)
@cases.mark_specifications("RFC2812")
@cases.xfailIfSoftware(["Sable"], "https://github.com/Libera-Chat/sable/issues/101")
def testWhoisMissingUser(self):
"""Test WHOIS on a nonexistent nickname."""
self.connectClient("qux", name="qux")

View File

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

View File

@ -33,6 +33,9 @@ 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/
@ -136,7 +139,7 @@ software:
pre_deps:
- uses: actions/setup-go@v3
with:
go-version: '^1.23.0'
go-version: '^1.24.0'
- run: go version
separate_build_job: false
build_script: |
@ -249,7 +252,7 @@ software:
name: Sable
repository: Libera-Chat/sable
refs:
stable: e9701e5e8d0c4f278ddd61ce7285f4918ecf99e9
stable: baed3ef9ac4550dc36a45b758436769e82e8ec58
release: null
devel: master
devel_release: null