1 Commits

Author SHA1 Message Date
77c3c10847 [WIP] Add test for CHGHOST using services
Only Insp + Anope for now

TODO: cleanup code
2022-04-03 20:42:13 +02:00
5 changed files with 121 additions and 76 deletions

View File

@ -210,9 +210,10 @@ class BaseServerController(_BaseController):
case: irctest.cases.BaseServerTestCase, # type: ignore case: irctest.cases.BaseServerTestCase, # type: ignore
username: str, username: str,
password: Optional[str] = None, password: Optional[str] = None,
**kwargs: Any,
) -> None: ) -> None:
if self.services_controller is not None: if self.services_controller is not None:
self.services_controller.registerUser(case, username, password) self.services_controller.registerUser(case, username, password, **kwargs)
else: else:
raise NotImplementedByController("account registration") raise NotImplementedByController("account registration")
@ -293,7 +294,7 @@ class BaseServicesController(_BaseController):
timeout = time.time() + 5 timeout = time.time() + 5
while True: while True:
c.sendLine(f"PRIVMSG {self.server_controller.nickserv} :HELP") c.sendLine(f"PRIVMSG {self.server_controller.nickserv} :HELP")
msgs = self.getNickServResponse(c) msgs = self.getServiceResponse(c)
for msg in msgs: for msg in msgs:
if msg.command == "401": if msg.command == "401":
# NickServ not available yet # NickServ not available yet
@ -319,7 +320,7 @@ class BaseServicesController(_BaseController):
c.disconnect() c.disconnect()
self.services_up = True self.services_up = True
def getNickServResponse(self, client: Any) -> List[Message]: def getServiceResponse(self, client: Any) -> List[Message]:
"""Wrapper aroung getMessages() that waits longer, because NickServ """Wrapper aroung getMessages() that waits longer, because NickServ
is queried asynchronously.""" is queried asynchronously."""
msgs: List[Message] = [] msgs: List[Message] = []
@ -333,11 +334,14 @@ class BaseServicesController(_BaseController):
case: irctest.cases.BaseServerTestCase, # type: ignore case: irctest.cases.BaseServerTestCase, # type: ignore
username: str, username: str,
password: Optional[str] = None, password: Optional[str] = None,
**kwargs: Any,
) -> None: ) -> None:
if not case.run_services: if not case.run_services:
raise ValueError( raise ValueError(
"Attempted to register a nick, but `run_services` it not True." "Attempted to register a nick, but `run_services` it not True."
) )
if kwargs:
raise NotImplementedByController(", ".join(kwargs))
assert password assert password
client = case.addClient(show_io=True) client = case.addClient(show_io=True)
case.sendLine(client, "NICK " + username) case.sendLine(client, "NICK " + username)
@ -350,7 +354,7 @@ class BaseServicesController(_BaseController):
f"PRIVMSG {self.server_controller.nickserv} " f"PRIVMSG {self.server_controller.nickserv} "
f":REGISTER {password} foo@example.org", f":REGISTER {password} foo@example.org",
) )
msgs = self.getNickServResponse(case.clients[client]) msgs = self.getServiceResponse(case.clients[client])
if self.server_controller.software_name == "inspircd": if self.server_controller.software_name == "inspircd":
assert "900" in {msg.command for msg in msgs}, msgs assert "900" in {msg.command for msg in msgs}, msgs
assert "NOTICE" in {msg.command for msg in msgs}, msgs assert "NOTICE" in {msg.command for msg in msgs}, msgs

View File

@ -1,9 +1,12 @@
import os import os
import shutil import shutil
import subprocess import subprocess
from typing import Type from typing import Any, Optional, Type
from irctest import cases, runner
from irctest.basecontrollers import BaseServicesController, DirectoryBasedController from irctest.basecontrollers import BaseServicesController, DirectoryBasedController
from irctest.client_mock import ClientMock
from irctest.irc_utils.sasl import sasl_plain_blob
TEMPLATE_CONFIG = """ TEMPLATE_CONFIG = """
serverinfo {{ serverinfo {{
@ -30,12 +33,17 @@ networkinfo {{
userlen = 10 userlen = 10
hostlen = 64 hostlen = 64
chanlen = 32 chanlen = 32
vhost_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
}} }}
mail {{ mail {{
usemail = no usemail = no
}} }}
/************************
* NickServ:
*/
service {{ service {{
nick = "NickServ" nick = "NickServ"
user = "services" user = "services"
@ -57,6 +65,29 @@ module {{
}} }}
command {{ service = "NickServ"; name = "REGISTER"; command = "nickserv/register"; }} command {{ service = "NickServ"; name = "REGISTER"; command = "nickserv/register"; }}
/************************
* HostServ:
*/
service {{
nick = "HostServ"
user = "services"
host = "services.host"
gecos = "vHost Service"
}}
module {{
name = "hostserv"
client = "HostServ"
}}
module {{ name = "hs_set" }}
command {{ service = "HostServ"; name = "SET"; command = "hostserv/set"; }}
/************************
* Misc:
*/
options {{ options {{
casemap = "ascii" casemap = "ascii"
readtimeout = 5s readtimeout = 5s
@ -66,7 +97,6 @@ options {{
module {{ name = "m_sasl" }} module {{ name = "m_sasl" }}
module {{ name = "enc_sha256" }} module {{ name = "enc_sha256" }}
module {{ name = "ns_cert" }} module {{ name = "ns_cert" }}
""" """
@ -121,6 +151,39 @@ class AnopeController(BaseServicesController, DirectoryBasedController):
# stderr=subprocess.DEVNULL, # stderr=subprocess.DEVNULL,
) )
def registerUser(
self,
case: cases.BaseServerTestCase, # type: ignore
username: str,
password: Optional[str] = None,
vhost: Optional[str] = None,
**kwargs: Any,
) -> None:
super().registerUser(case, username, password)
if vhost:
if not password:
raise runner.NotImplementedByController(
"vHost for users with no password"
)
c = ClientMock(name="setVhost", show_io=True)
c.connect(self.server_controller.hostname, self.server_controller.port)
c.sendLine("CAP REQ :sasl")
c.sendLine("NICK " + username)
c.sendLine("USER r e g :user")
while c.getMessage(synchronize=False).command != "CAP":
pass
c.sendLine("AUTHENTICATE PLAIN")
while c.getMessage(synchronize=False).command != "AUTHENTICATE":
pass
c.sendLine(sasl_plain_blob(username, password))
c.sendLine("CAP END")
while c.getMessage(synchronize=False).command != "001":
pass
c.getMessages()
c.sendLine(f"PRIVMSG HostServ :SET {username} {vhost}")
self.getServiceResponse(c)
def get_irctest_controller_class() -> Type[AnopeController]: def get_irctest_controller_class() -> Type[AnopeController]:
return AnopeController return AnopeController

View File

@ -61,10 +61,12 @@ TEMPLATE_CONFIG = """
# Protocol: # Protocol:
<module name="botmode"> <module name="botmode">
<module name="cap"> <module name="cap">
<module name="chghost">
<module name="ircv3"> <module name="ircv3">
<module name="ircv3_accounttag"> <module name="ircv3_accounttag">
<module name="ircv3_batch"> <module name="ircv3_batch">
<module name="ircv3_capnotify"> <module name="ircv3_capnotify">
<module name="ircv3_chghost">
<module name="ircv3_ctctags"> <module name="ircv3_ctctags">
<module name="ircv3_echomessage"> <module name="ircv3_echomessage">
<module name="ircv3_invitenotify"> <module name="ircv3_invitenotify">
@ -74,7 +76,6 @@ TEMPLATE_CONFIG = """
<module name="monitor"> <module name="monitor">
<module name="m_muteban"> # for testing mute extbans <module name="m_muteban"> # for testing mute extbans
<module name="namesx"> # For multi-prefix <module name="namesx"> # For multi-prefix
<module name="noctcp">
<module name="sasl"> <module name="sasl">
# HELP/HELPOP # HELP/HELPOP

View File

@ -0,0 +1,46 @@
"""
<http://ircv3.net/specs/extensions/chghost.html>
"""
from irctest import cases
from irctest.irc_utils.sasl import sasl_plain_blob
from irctest.patma import ANYSTR, StrRe
@cases.mark_services
class ChghostServicesTestCase(cases.BaseServerTestCase, cases.OptionalityHelper):
def testChghostFromServices(self):
self.connectClient("observer", capabilities=["chghost"], skip_if_cap_nak=True)
self.connectClient("oldclient")
self.controller.registerUser(
self, "vhostuser", "sesame", vhost="vhost.example.com"
)
self.connectClient("vhost-user", capabilities=["sasl"], skip_if_cap_nak=True)
for i in (1, 2, 3):
self.sendLine(i, "JOIN #chan")
self.getMessages(i)
for i in (1, 2, 3):
self.getMessages(i)
self.sendLine(3, "AUTHENTICATE PLAIN")
self.assertMessageMatch(
self.getRegistrationMessage(3),
command="AUTHENTICATE",
params=["+"],
)
self.sendLine(3, sasl_plain_blob("vhostuser", "sesame"))
self.assertMessageMatch(
self.getRegistrationMessage(3),
command="900",
)
self.assertMessageMatch(
self.getMessage(1),
prefix=StrRe("vhost-user!.*@(?!vhost-user.example)"),
command="CHGHOST",
params=[ANYSTR, "vhost.example.com"],
)
self.assertEqual(self.getMessages(2), []) # cycle?

View File

@ -1,69 +0,0 @@
from irctest import cases, runner
from irctest.numerics import ERR_CANNOTSENDTOCHAN
from irctest.patma import ANYSTR
class NoctcpModeTestCase(cases.BaseServerTestCase):
@cases.mark_specifications("Modern")
def testNoctcpMode(self):
"""
"This mode is used in almost all IRC software today. The standard mode letter
used for it is `"+C"`.
When this mode is set, should not send [CTCP](/ctcp.html) messages, except
CTCP Action (also known as `/me`) to the channel.
When blocking a message because of this mode, servers SHOULD use
ERR_CANNOTSENDTOCHAN"
-- TODO add link
"""
self.connectClient("chanop")
if "C" not in self.server_support.get("CHANMODES", ""):
raise runner.NotImplementedByController("+C (noctcp) channel mode")
# Both users join:
self.sendLine(1, "JOIN #chan")
self.getMessages(1) # synchronize
self.connectClient("user")
self.sendLine(2, "JOIN #chan")
self.getMessages(2)
self.getMessages(1)
# Send ACTION and PING, both should go through:
self.sendLine(2, "PRIVMSG #chan :\x01ACTION is testing\x01")
self.sendLine(2, "PRIVMSG #chan :\x01PING 12345\x01")
self.assertEqual(self.getMessages(2), [])
self.assertEqual(
[(m.command, m.params[1]) for m in self.getMessages(1)],
[
("PRIVMSG", "\x01ACTION is testing\x01"),
("PRIVMSG", "\x01PING 12345\x01"),
],
)
# Set mode +C:
self.sendLine(1, "MODE #chan +C")
self.getMessages(1)
self.getMessages(2)
# Send ACTION and PING, only ACTION should go through:
self.sendLine(2, "PRIVMSG #chan :\x01ACTION is testing\x01")
self.assertEqual(self.getMessages(2), [])
self.sendLine(2, "PRIVMSG #chan :\x01PING 12345\x01")
self.assertMessageMatch(
self.getMessage(2),
command=ERR_CANNOTSENDTOCHAN,
params=["user", "#chan", ANYSTR],
)
self.assertEqual(
[(m.command, m.params[1]) for m in self.getMessages(1)],
[
("PRIVMSG", "\x01ACTION is testing\x01"),
],
)