Scope: verify that a restricted user issuing LIST receives the numeric stream 321 (RPL_LISTSTART) → 323 (RPL_LISTEND) → 447 (ERR_RESTRICTED), instead of the current bare 447.
- Testnet
ircd.conf:restriction_enabled = YES(top-level) — impliesrestrictionblock present.- One
I:line with flagR(CONF_FLAGS_I_RESTRICTED = 0x40) matching the test client's host/mask. - Services linked (so
IsKnownNicktransitions at/ns identifytime; restricted path is only taken while unidentified).
- The restriction flag is applied only to unregistered/unidentified nicks — per
check_restricted_useratsrc/s_misc.c:1211.
Expected stream after LIST:
:hub.azzurra.chat 447 NICK :Your connection is restricted!
mIRC: list dialog stays open indefinitely, waiting for 323 that never comes.
Expected stream after LIST:
:hub.azzurra.chat 321 NICK Channel :Users Name
:hub.azzurra.chat 323 NICK :End of /LIST
:hub.azzurra.chat 447 NICK :Your connection is restricted!
mIRC: list dialog opens, closes cleanly; restriction notice surfaces as server message.
#!/usr/bin/env python3
import socket, ssl, sys, time
HOST = "127.0.0.1" # testnet hub
PORT = 6697 # TLS listener
NICK = "rtest"
USER = "rtest 0 * :restricted test"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
s = ctx.wrap_socket(socket.create_connection((HOST, PORT)), server_hostname=HOST)
def send(l): s.sendall((l + "\r\n").encode()); print(">>", l)
def recv_until(pred, timeout=10.0):
s.settimeout(timeout)
buf = b""
seen = []
end = time.time() + timeout
while time.time() < end:
try: chunk = s.recv(4096)
except socket.timeout: break
if not chunk: break
buf += chunk
while b"\r\n" in buf:
line, buf = buf.split(b"\r\n", 1)
line = line.decode(errors="replace")
print("<<", line)
seen.append(line)
if pred(line): return seen
return seen
send(f"NICK {NICK}")
send(f"USER {USER}")
recv_until(lambda l: " 001 " in l) # RPL_WELCOME — connected without identify
send("LIST")
# Give the ircd a second to emit the full stream, then collect.
time.sleep(1.5)
# Read anything pending.
s.settimeout(1.0)
try:
while True:
chunk = s.recv(4096)
if not chunk: break
for line in chunk.decode(errors="replace").splitlines():
print("<<", line)
except socket.timeout:
pass
# Assertions (evaluate against captured stream):
# - line containing " 321 " present
# - line containing " 323 " present
# - line containing " 447 " present
# - order: 321 index < 323 index < 447 index- Connect mIRC to
testnet:6697asrtest, without identifying. - Type
/listin server window. - Before fix: channel list dialog opens blank, never closes;
Your connection is restricted!appears in status window. - After fix: channel list dialog opens, closes cleanly (empty list); restriction notice appears in status.
- Unrestricted
LISTstill works: connect identified →/listreturns actual channels. check_restricted_userother callers unchanged:m_userhost,m_join,m_privmsg, umode changes still emit immediateERR_RESTRICTEDwith no extra numerics.ERR_NOUNKNOWNLISTScase (line 3188, just above) has the same LISTSTART/LISTEND hang — out of scope for this PR, flagged as follow-up.