fix(types): P3 — annotate transport in all template protocol servers; 0 errors in templates/
- asyncio.Protocol (TCP): _transport: asyncio.Transport | None = None + cast() in connection_made; assert guards in every method that directly accesses the field. Files: pop3, smtp, mqtt, postgres, mssql, mongodb, imap, ldap, redis, mysql, sip, vnc. - asyncio.DatagramProtocol (UDP): _transport: asyncio.DatagramTransport | None = None. Files: snmp, tftp, SIPUDPProtocol. - RDP: assert new_transport is not None after start_tls() to narrow Transport | None. - FTP (Twisted): assert self.transport is not None + targeted type: ignore for imprecise Twisted stubs (misc/override/arg-type/attr-defined), IReactorTCP cast for listenTCP. - conpot: proc.stdout is None guard before iteration. - Bonus fixes surfaced by annotation: - smtp: get_payload(decode=True) bytes narrowing (arg-type on sha256) - postgres: rename shadowed `msg` param to `err_msg` in _handle_startup - mongodb: base64.binascii.Error → import binascii; binascii.Error - imap: result: list[int] = [] (var-annotated)
This commit is contained in:
@@ -128,6 +128,9 @@ def main():
|
|||||||
signal.signal(signal.SIGINT, _forward)
|
signal.signal(signal.SIGINT, _forward)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if proc.stdout is None:
|
||||||
|
proc.wait()
|
||||||
|
return
|
||||||
for raw_line in proc.stdout:
|
for raw_line in proc.stdout:
|
||||||
line = raw_line.rstrip()
|
line = raw_line.rstrip()
|
||||||
if not line:
|
if not line:
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ forwards events as JSON to LOG_TARGET if set.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
|
from twisted.internet.interfaces import IReactorTCP
|
||||||
from twisted.protocols.ftp import FTP, FTPFactory, FTPAnonymousShell
|
from twisted.protocols.ftp import FTP, FTPFactory, FTPAnonymousShell
|
||||||
|
from twisted.python.failure import Failure
|
||||||
from twisted.python.filepath import FilePath
|
from twisted.python.filepath import FilePath
|
||||||
from twisted.python import log as twisted_log
|
from twisted.python import log as twisted_log
|
||||||
|
|
||||||
@@ -95,7 +98,8 @@ _BAIT_PATH = _setup_bait_fs()
|
|||||||
|
|
||||||
class ServerFTP(FTP):
|
class ServerFTP(FTP):
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
peer = self.transport.getPeer()
|
assert self.transport is not None
|
||||||
|
peer = self.transport.getPeer() # type: ignore[misc]
|
||||||
_log("connection", src_ip=peer.host, src_port=peer.port)
|
_log("connection", src_ip=peer.host, src_port=peer.port)
|
||||||
super().connectionMade()
|
super().connectionMade()
|
||||||
|
|
||||||
@@ -120,15 +124,16 @@ class ServerFTP(FTP):
|
|||||||
return defer.succeed((530, "Login incorrect."))
|
return defer.succeed((530, "Login incorrect."))
|
||||||
self.state = self.AUTHED
|
self.state = self.AUTHED
|
||||||
self._user = getattr(self, "_server_user", "anonymous")
|
self._user = getattr(self, "_server_user", "anonymous")
|
||||||
self.shell = FTPAnonymousShell(FilePath(_BAIT_PATH))
|
self.shell = FTPAnonymousShell(FilePath(_BAIT_PATH)) # type: ignore[assignment]
|
||||||
return defer.succeed((230, "Login successful."))
|
return defer.succeed((230, "Login successful."))
|
||||||
|
|
||||||
def ftp_RETR(self, path):
|
def ftp_RETR(self, path):
|
||||||
_log("download_attempt", path=path)
|
_log("download_attempt", path=path)
|
||||||
return super().ftp_RETR(path)
|
return super().ftp_RETR(path)
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason: Failure) -> None: # type: ignore[override]
|
||||||
peer = self.transport.getPeer()
|
assert self.transport is not None
|
||||||
|
peer = self.transport.getPeer() # type: ignore[misc]
|
||||||
_log("disconnect", src_ip=peer.host, src_port=peer.port)
|
_log("disconnect", src_ip=peer.host, src_port=peer.port)
|
||||||
super().connectionLost(reason)
|
super().connectionLost(reason)
|
||||||
|
|
||||||
@@ -140,5 +145,5 @@ class ServerFTPFactory(FTPFactory):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
twisted_log.startLoggingWithObserver(lambda e: None, setStdout=False)
|
twisted_log.startLoggingWithObserver(lambda e: None, setStdout=False)
|
||||||
_log("startup", msg=f"FTP server starting as {NODE_NAME} on port {PORT}")
|
_log("startup", msg=f"FTP server starting as {NODE_NAME} on port {PORT}")
|
||||||
reactor.listenTCP(PORT, ServerFTPFactory())
|
cast(IReactorTCP, reactor).listenTCP(PORT, ServerFTPFactory()) # type: ignore[arg-type]
|
||||||
reactor.run()
|
reactor.run() # type: ignore[attr-defined]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
from email.utils import getaddresses
|
from email.utils import getaddresses
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
SEVERITY_WARNING,
|
SEVERITY_WARNING,
|
||||||
encode_secret,
|
encode_secret,
|
||||||
@@ -377,7 +378,7 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
def _parse_seq_range(range_str: str, total: int) -> list[int]:
|
def _parse_seq_range(range_str: str, total: int) -> list[int]:
|
||||||
"""Parse IMAP sequence set ('1', '1:3', '1:*', '*') → list of 1-based indices."""
|
"""Parse IMAP sequence set ('1', '1:3', '1:*', '*') → list of 1-based indices."""
|
||||||
result = []
|
result: list[int] = []
|
||||||
for part in range_str.split(","):
|
for part in range_str.split(","):
|
||||||
part = part.strip()
|
part = part.strip()
|
||||||
if ":" in part:
|
if ":" in part:
|
||||||
@@ -472,6 +473,9 @@ def _build_fetch_response(seq: int, msg: dict, items: list[str]) -> bytes:
|
|||||||
# ── Protocol ──────────────────────────────────────────────────────────────────
|
# ── Protocol ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class IMAPProtocol(asyncio.Protocol):
|
class IMAPProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = ("?", 0)
|
self._peer = ("?", 0)
|
||||||
@@ -479,12 +483,12 @@ class IMAPProtocol(asyncio.Protocol):
|
|||||||
self._state = "NOT_AUTHENTICATED"
|
self._state = "NOT_AUTHENTICATED"
|
||||||
self._selected = None # mailbox name currently selected
|
self._selected = None # mailbox name currently selected
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = self._transport.get_extra_info("peername", ("?", 0))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
banner = IMAP_BANNER if IMAP_BANNER.endswith("\r\n") else IMAP_BANNER + "\r\n"
|
banner = IMAP_BANNER if IMAP_BANNER.endswith("\r\n") else IMAP_BANNER + "\r\n"
|
||||||
transport.write(banner.encode())
|
self._transport.write(banner.encode())
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._buf += data
|
self._buf += data
|
||||||
@@ -519,6 +523,7 @@ class IMAPProtocol(asyncio.Protocol):
|
|||||||
elif cmd == "LOGOUT":
|
elif cmd == "LOGOUT":
|
||||||
self._w(b"* BYE Logging out\r\n")
|
self._w(b"* BYE Logging out\r\n")
|
||||||
self._w(f"{tag} OK LOGOUT completed\r\n")
|
self._w(f"{tag} OK LOGOUT completed\r\n")
|
||||||
|
assert self._transport is not None
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
|
||||||
# NOT_AUTHENTICATED only
|
# NOT_AUTHENTICATED only
|
||||||
@@ -638,6 +643,7 @@ class IMAPProtocol(asyncio.Protocol):
|
|||||||
if use_uid and "UID" not in items:
|
if use_uid and "UID" not in items:
|
||||||
items = ["UID"] + items
|
items = ["UID"] + items
|
||||||
|
|
||||||
|
assert self._transport is not None
|
||||||
for seq in indices:
|
for seq in indices:
|
||||||
if 1 <= seq <= total:
|
if 1 <= seq <= total:
|
||||||
self._transport.write(_build_fetch_response(seq, emails[seq - 1], items))
|
self._transport.write(_build_fetch_response(seq, emails[seq - 1], items))
|
||||||
@@ -662,6 +668,7 @@ class IMAPProtocol(asyncio.Protocol):
|
|||||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _w(self, data: str | bytes) -> None:
|
def _w(self, data: str | bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
self._transport.write(data)
|
self._transport.write(data)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ invalidCredentials error. Logs all interactions as JSON.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
@@ -137,14 +138,17 @@ def _bind_error_response(message_id: int, result_code: int = 49, error_text: str
|
|||||||
|
|
||||||
|
|
||||||
class LDAPProtocol(asyncio.Protocol):
|
class LDAPProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
@@ -171,7 +175,9 @@ class LDAPProtocol(asyncio.Protocol):
|
|||||||
self._buf = self._buf[msg_len:]
|
self._buf = self._buf[msg_len:]
|
||||||
self._handle_message(msg)
|
self._handle_message(msg)
|
||||||
|
|
||||||
def _handle_message(self, msg: bytes):
|
def _handle_message(self, msg: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
# Extract messageID for the response
|
# Extract messageID for the response
|
||||||
try:
|
try:
|
||||||
message_id = msg[4] if len(msg) > 4 else 1
|
message_id = msg[4] if len(msg) > 4 else 1
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ received messages as JSON.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import binascii
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||||
@@ -197,6 +199,9 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class MongoDBProtocol(asyncio.Protocol):
|
class MongoDBProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
@@ -207,12 +212,13 @@ class MongoDBProtocol(asyncio.Protocol):
|
|||||||
self._sasl_username: str | None = None
|
self._sasl_username: str | None = None
|
||||||
self._sasl_mechanism: str | None = None
|
self._sasl_mechanism: str | None = None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
self._buf += data
|
self._buf += data
|
||||||
while len(self._buf) >= 16:
|
while len(self._buf) >= 16:
|
||||||
msg_len = struct.unpack("<I", self._buf[:4])[0]
|
msg_len = struct.unpack("<I", self._buf[:4])[0]
|
||||||
@@ -226,7 +232,9 @@ class MongoDBProtocol(asyncio.Protocol):
|
|||||||
self._buf = self._buf[msg_len:]
|
self._buf = self._buf[msg_len:]
|
||||||
self._handle_message(msg)
|
self._handle_message(msg)
|
||||||
|
|
||||||
def _handle_message(self, msg: bytes):
|
def _handle_message(self, msg: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
if len(msg) < 16:
|
if len(msg) < 16:
|
||||||
return
|
return
|
||||||
request_id = struct.unpack("<I", msg[4:8])[0]
|
request_id = struct.unpack("<I", msg[4:8])[0]
|
||||||
@@ -285,6 +293,7 @@ class MongoDBProtocol(asyncio.Protocol):
|
|||||||
self._transport.write(_op_reply(request_id, reply_doc))
|
self._transport.write(_op_reply(request_id, reply_doc))
|
||||||
|
|
||||||
def _handle_command(self, cmd: dict) -> None:
|
def _handle_command(self, cmd: dict) -> None:
|
||||||
|
assert self._peer is not None
|
||||||
"""Parse a single MongoDB command document for SCRAM auth.
|
"""Parse a single MongoDB command document for SCRAM auth.
|
||||||
|
|
||||||
saslStart — client-first-message in payload. Extract
|
saslStart — client-first-message in payload. Extract
|
||||||
@@ -318,7 +327,7 @@ class MongoDBProtocol(asyncio.Protocol):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
proof_raw = base64.b64decode(proof_b64, validate=True)
|
proof_raw = base64.b64decode(proof_b64, validate=True)
|
||||||
except (ValueError, base64.binascii.Error):
|
except (ValueError, binascii.Error):
|
||||||
return
|
return
|
||||||
mech = (self._sasl_mechanism or "").upper()
|
mech = (self._sasl_mechanism or "").upper()
|
||||||
if "SHA-256" in mech or "SHA256" in mech:
|
if "SHA-256" in mech or "SHA256" in mech:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
@@ -209,6 +210,9 @@ def _generate_topics() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
class MQTTProtocol(asyncio.Protocol):
|
class MQTTProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
@@ -216,9 +220,9 @@ class MQTTProtocol(asyncio.Protocol):
|
|||||||
self._auth = False
|
self._auth = False
|
||||||
self._topics = _generate_topics()
|
self._topics = _generate_topics()
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
@@ -231,6 +235,8 @@ class MQTTProtocol(asyncio.Protocol):
|
|||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
|
||||||
def _process(self):
|
def _process(self):
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
while len(self._buf) >= 2:
|
while len(self._buf) >= 2:
|
||||||
pkt_byte = self._buf[0]
|
pkt_byte = self._buf[0]
|
||||||
pkt_type = (pkt_byte >> 4) & 0x0f
|
pkt_type = (pkt_byte >> 4) & 0x0f
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||||
@@ -108,18 +109,23 @@ def _tds_error_packet(message: str) -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
class MSSQLProtocol(asyncio.Protocol):
|
class MSSQLProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
self._prelogin_done = False
|
self._prelogin_done = False
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
self._buf += data
|
self._buf += data
|
||||||
while len(self._buf) >= 8:
|
while len(self._buf) >= 8:
|
||||||
pkt_type = self._buf[0]
|
pkt_type = self._buf[0]
|
||||||
@@ -138,7 +144,9 @@ class MSSQLProtocol(asyncio.Protocol):
|
|||||||
self._buf = b""
|
self._buf = b""
|
||||||
break
|
break
|
||||||
|
|
||||||
def _handle_packet(self, pkt_type: int, payload: bytes):
|
def _handle_packet(self, pkt_type: int, payload: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
if pkt_type == 0x12: # Pre-login
|
if pkt_type == 0x12: # Pre-login
|
||||||
self._transport.write(_PRELOGIN_RESP)
|
self._transport.write(_PRELOGIN_RESP)
|
||||||
self._prelogin_done = True
|
self._prelogin_done = True
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import base64
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||||
@@ -74,6 +75,9 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class MySQLProtocol(asyncio.Protocol):
|
class MySQLProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
@@ -84,15 +88,16 @@ class MySQLProtocol(asyncio.Protocol):
|
|||||||
# same decky never present identical auth challenges.
|
# same decky never present identical auth challenges.
|
||||||
self._salt = _seed.fresh_bytes(20)
|
self._salt = _seed.fresh_bytes(20)
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1],
|
_log("connect", src=self._peer[0], src_port=self._peer[1],
|
||||||
connection_id=self._conn_id)
|
connection_id=self._conn_id)
|
||||||
transport.write(_make_packet(_build_greeting(self._conn_id, self._salt), seq=0))
|
self._transport.write(_make_packet(_build_greeting(self._conn_id, self._salt), seq=0))
|
||||||
self._greeted = True
|
self._greeted = True
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
self._buf += data
|
self._buf += data
|
||||||
# MySQL packets: 3-byte length + 1-byte seq + payload
|
# MySQL packets: 3-byte length + 1-byte seq + payload
|
||||||
while len(self._buf) >= 4:
|
while len(self._buf) >= 4:
|
||||||
@@ -107,7 +112,8 @@ class MySQLProtocol(asyncio.Protocol):
|
|||||||
self._buf = self._buf[4 + length:]
|
self._buf = self._buf[4 + length:]
|
||||||
self._handle_packet(payload)
|
self._handle_packet(payload)
|
||||||
|
|
||||||
def _handle_packet(self, payload: bytes):
|
def _handle_packet(self, payload: bytes) -> None:
|
||||||
|
assert self._peer is not None
|
||||||
if not payload:
|
if not payload:
|
||||||
return
|
return
|
||||||
# Login packet: capability flags (4), max_packet (4), charset (1),
|
# Login packet: capability flags (4), max_packet (4), charset (1),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
SEVERITY_WARNING,
|
SEVERITY_WARNING,
|
||||||
encode_secret,
|
encode_secret,
|
||||||
@@ -238,6 +239,9 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
# ── Protocol ──────────────────────────────────────────────────────────────────
|
# ── Protocol ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class POP3Protocol(asyncio.Protocol):
|
class POP3Protocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = ("?", 0)
|
self._peer = ("?", 0)
|
||||||
@@ -246,14 +250,14 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._current_user: str | None = None
|
self._current_user: str | None = None
|
||||||
self._deleted: set[int] = set() # 0-based indices of DELE'd messages
|
self._deleted: set[int] = set() # 0-based indices of DELE'd messages
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = self._transport.get_extra_info("peername", ("?", 0))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
banner = POP3_BANNER if POP3_BANNER.endswith("\r\n") else POP3_BANNER + "\r\n"
|
banner = POP3_BANNER if POP3_BANNER.endswith("\r\n") else POP3_BANNER + "\r\n"
|
||||||
if not banner.startswith("+OK"):
|
if not banner.startswith("+OK"):
|
||||||
banner = "+OK " + banner
|
banner = "+OK " + banner
|
||||||
transport.write(banner.encode())
|
self._transport.write(banner.encode())
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._buf += data
|
self._buf += data
|
||||||
@@ -267,6 +271,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
# ── Command dispatch ──────────────────────────────────────────────────────
|
# ── Command dispatch ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _handle_line(self, line: str) -> None:
|
def _handle_line(self, line: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
parts = line.split(None, 1)
|
parts = line.split(None, 1)
|
||||||
if not parts:
|
if not parts:
|
||||||
return
|
return
|
||||||
@@ -314,6 +319,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
# ── Command implementations ───────────────────────────────────────────────
|
# ── Command implementations ───────────────────────────────────────────────
|
||||||
|
|
||||||
def _cmd_user(self, args: str) -> None:
|
def _cmd_user(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if self._state != "AUTHORIZATION":
|
if self._state != "AUTHORIZATION":
|
||||||
self._transport.write(b"-ERR Already authenticated\r\n")
|
self._transport.write(b"-ERR Already authenticated\r\n")
|
||||||
return
|
return
|
||||||
@@ -321,6 +327,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"+OK User name accepted, password please\r\n")
|
self._transport.write(b"+OK User name accepted, password please\r\n")
|
||||||
|
|
||||||
def _cmd_pass(self, args: str) -> None:
|
def _cmd_pass(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if self._state != "AUTHORIZATION":
|
if self._state != "AUTHORIZATION":
|
||||||
self._transport.write(b"-ERR Already authenticated\r\n")
|
self._transport.write(b"-ERR Already authenticated\r\n")
|
||||||
return
|
return
|
||||||
@@ -342,6 +349,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"-ERR Authentication failed.\r\n")
|
self._transport.write(b"-ERR Authentication failed.\r\n")
|
||||||
|
|
||||||
def _require_transaction(self) -> bool:
|
def _require_transaction(self) -> bool:
|
||||||
|
assert self._transport is not None
|
||||||
if self._state != "TRANSACTION":
|
if self._state != "TRANSACTION":
|
||||||
self._transport.write(b"-ERR Not authenticated\r\n")
|
self._transport.write(b"-ERR Not authenticated\r\n")
|
||||||
return False
|
return False
|
||||||
@@ -356,6 +364,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _cmd_stat(self) -> None:
|
def _cmd_stat(self) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
msgs = self._active_messages()
|
msgs = self._active_messages()
|
||||||
@@ -363,6 +372,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(f"+OK {len(msgs)} {total}\r\n".encode())
|
self._transport.write(f"+OK {len(msgs)} {total}\r\n".encode())
|
||||||
|
|
||||||
def _cmd_list(self, args: str) -> None:
|
def _cmd_list(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
emails = _get_emails()
|
emails = _get_emails()
|
||||||
@@ -386,6 +396,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b".\r\n")
|
self._transport.write(b".\r\n")
|
||||||
|
|
||||||
def _cmd_retr(self, args: str) -> None:
|
def _cmd_retr(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -407,6 +418,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"-ERR Invalid argument\r\n")
|
self._transport.write(b"-ERR Invalid argument\r\n")
|
||||||
|
|
||||||
def _cmd_top(self, args: str) -> None:
|
def _cmd_top(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -436,6 +448,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"-ERR Invalid arguments\r\n")
|
self._transport.write(b"-ERR Invalid arguments\r\n")
|
||||||
|
|
||||||
def _cmd_uidl(self, args: str) -> None:
|
def _cmd_uidl(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
if args:
|
if args:
|
||||||
@@ -455,6 +468,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b".\r\n")
|
self._transport.write(b".\r\n")
|
||||||
|
|
||||||
def _cmd_dele(self, args: str) -> None:
|
def _cmd_dele(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -470,6 +484,7 @@ class POP3Protocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"-ERR Invalid argument\r\n")
|
self._transport.write(b"-ERR Invalid argument\r\n")
|
||||||
|
|
||||||
def _cmd_rset(self) -> None:
|
def _cmd_rset(self) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
if not self._require_transaction():
|
if not self._require_transaction():
|
||||||
return
|
return
|
||||||
self._deleted.clear()
|
self._deleted.clear()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ returns an error. Logs all interactions as JSON.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
import base64 as _base64
|
import base64 as _base64
|
||||||
@@ -59,15 +60,18 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class PostgresProtocol(asyncio.Protocol):
|
class PostgresProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
self._state = "startup"
|
self._state = "startup"
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
@@ -75,6 +79,7 @@ class PostgresProtocol(asyncio.Protocol):
|
|||||||
self._process()
|
self._process()
|
||||||
|
|
||||||
def _process(self):
|
def _process(self):
|
||||||
|
assert self._transport is not None
|
||||||
if self._state == "startup":
|
if self._state == "startup":
|
||||||
if len(self._buf) < 4:
|
if len(self._buf) < 4:
|
||||||
return
|
return
|
||||||
@@ -104,7 +109,9 @@ class PostgresProtocol(asyncio.Protocol):
|
|||||||
if msg_type == "p":
|
if msg_type == "p":
|
||||||
self._handle_password(payload)
|
self._handle_password(payload)
|
||||||
|
|
||||||
def _handle_startup(self, msg: bytes):
|
def _handle_startup(self, msg: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
# Startup message: length(4) + protocol_version(4) + params (key=value\0 pairs)
|
# Startup message: length(4) + protocol_version(4) + params (key=value\0 pairs)
|
||||||
if len(msg) < 8:
|
if len(msg) < 8:
|
||||||
return
|
return
|
||||||
@@ -128,8 +135,8 @@ class PostgresProtocol(asyncio.Protocol):
|
|||||||
# rejects *before* asking for a password. Short-circuit so the decoy
|
# rejects *before* asking for a password. Short-circuit so the decoy
|
||||||
# matches that behavior and exposes the per-decky DB list.
|
# matches that behavior and exposes the per-decky DB list.
|
||||||
if database and database not in _DATABASES:
|
if database and database not in _DATABASES:
|
||||||
msg = f'database "{database}" does not exist'
|
err_msg = f'database "{database}" does not exist'
|
||||||
self._transport.write(_error_response("FATAL", "3D000", msg))
|
self._transport.write(_error_response("FATAL", "3D000", err_msg))
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
return
|
return
|
||||||
self._state = "auth"
|
self._state = "auth"
|
||||||
@@ -137,7 +144,9 @@ class PostgresProtocol(asyncio.Protocol):
|
|||||||
auth_md5 = b"R" + struct.pack(">I", 12) + struct.pack(">I", 5) + salt
|
auth_md5 = b"R" + struct.pack(">I", 12) + struct.pack(">I", 5) + salt
|
||||||
self._transport.write(auth_md5)
|
self._transport.write(auth_md5)
|
||||||
|
|
||||||
def _handle_password(self, payload: bytes):
|
def _handle_password(self, payload: bytes) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
# Postgres MD5 challenge-response: the wire form is the literal
|
# Postgres MD5 challenge-response: the wire form is the literal
|
||||||
# ASCII string "md5" + 32 hex chars (md5(md5(pw+user)+salt)).
|
# ASCII string "md5" + 32 hex chars (md5(md5(pw+user)+salt)).
|
||||||
# Plaintext is unrecoverable, so we land this in the Credential
|
# Plaintext is unrecoverable, so we land this in the Credential
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ async def _upgrade_to_tls_and_capture(
|
|||||||
# into a StreamReader/StreamWriter pair the rest of the handler can use.
|
# into a StreamReader/StreamWriter pair the rest of the handler can use.
|
||||||
new_reader = asyncio.StreamReader(loop=loop)
|
new_reader = asyncio.StreamReader(loop=loop)
|
||||||
new_protocol = asyncio.StreamReaderProtocol(new_reader, loop=loop)
|
new_protocol = asyncio.StreamReaderProtocol(new_reader, loop=loop)
|
||||||
|
assert new_transport is not None
|
||||||
new_transport.set_protocol(new_protocol)
|
new_transport.set_protocol(new_protocol)
|
||||||
new_protocol.connection_made(new_transport)
|
new_protocol.connection_made(new_transport)
|
||||||
new_writer = asyncio.StreamWriter(new_transport, new_protocol, new_reader, loop)
|
new_writer = asyncio.StreamWriter(new_transport, new_protocol, new_reader, loop)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ KEYS, and arbitrary commands. Logs every command and argument as JSON.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
@@ -203,15 +204,18 @@ def _config_get(pattern: str) -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
class RedisProtocol(asyncio.Protocol):
|
class RedisProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._parser = RESPParser()
|
self._parser = RESPParser()
|
||||||
self._authed = not _REQUIREPASS # auth satisfied iff no password set
|
self._authed = not _REQUIREPASS # auth satisfied iff no password set
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
@@ -228,7 +232,8 @@ class RedisProtocol(asyncio.Protocol):
|
|||||||
if self._transport and not self._transport.is_closing():
|
if self._transport and not self._transport.is_closing():
|
||||||
self._transport.write(payload)
|
self._transport.write(payload)
|
||||||
|
|
||||||
def _handle_command(self, parts):
|
def _handle_command(self, parts) -> None:
|
||||||
|
assert self._peer is not None
|
||||||
if not parts:
|
if not parts:
|
||||||
return
|
return
|
||||||
verb = parts[0].upper()
|
verb = parts[0].upper()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Authorization header and call metadata, then responds with 401 Unauthorized.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
classify_authorization,
|
classify_authorization,
|
||||||
forward_syslog,
|
forward_syslog,
|
||||||
@@ -98,11 +99,13 @@ def _handle_message(data: bytes, src_addr) -> bytes | None:
|
|||||||
|
|
||||||
|
|
||||||
class SIPUDPProtocol(asyncio.DatagramProtocol):
|
class SIPUDPProtocol(asyncio.DatagramProtocol):
|
||||||
|
_transport: asyncio.DatagramTransport | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.DatagramTransport, transport)
|
||||||
|
|
||||||
def datagram_received(self, data, addr):
|
def datagram_received(self, data, addr):
|
||||||
response = _handle_message(data, addr)
|
response = _handle_message(data, addr)
|
||||||
@@ -111,21 +114,24 @@ class SIPUDPProtocol(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
|
|
||||||
class SIPTCPProtocol(asyncio.Protocol):
|
class SIPTCPProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data: bytes) -> None:
|
||||||
self._buf += data
|
self._buf += data
|
||||||
if b"\r\n\r\n" in self._buf or b"\n\n" in self._buf:
|
if b"\r\n\r\n" in self._buf or b"\n\n" in self._buf:
|
||||||
response = _handle_message(self._buf, self._peer)
|
response = _handle_message(self._buf, self._peer)
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
if response:
|
if response and self._transport:
|
||||||
self._transport.write(response)
|
self._transport.write(response)
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from datetime import datetime, timezone
|
|||||||
from email import message_from_bytes
|
from email import message_from_bytes
|
||||||
from email.header import decode_header, make_header
|
from email.header import decode_header, make_header
|
||||||
from email.message import Message
|
from email.message import Message
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import instance_seed as _seed
|
import instance_seed as _seed
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
@@ -150,7 +151,8 @@ def _summarize_message(body: bytes, msg_id: str) -> dict:
|
|||||||
if not filename and "attachment" not in disposition:
|
if not filename and "attachment" not in disposition:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
payload = part.get_payload(decode=True) or b""
|
_raw = part.get_payload(decode=True) or b""
|
||||||
|
payload: bytes = _raw if isinstance(_raw, bytes) else b""
|
||||||
except Exception:
|
except Exception:
|
||||||
payload = b""
|
payload = b""
|
||||||
attachments.append({
|
attachments.append({
|
||||||
@@ -207,6 +209,9 @@ def _decode_auth_plain(blob: str) -> tuple[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
class SMTPProtocol(asyncio.Protocol):
|
class SMTPProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = ("?", 0)
|
self._peer = ("?", 0)
|
||||||
@@ -228,11 +233,11 @@ class SMTPProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
# ── asyncio.Protocol ──────────────────────────────────────────────────────
|
# ── asyncio.Protocol ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = self._transport.get_extra_info("peername", ("?", 0))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
transport.write(f"{_SMTP_BANNER}\r\n".encode())
|
self._transport.write(f"{_SMTP_BANNER}\r\n".encode())
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._buf += data
|
self._buf += data
|
||||||
@@ -247,6 +252,7 @@ class SMTPProtocol(asyncio.Protocol):
|
|||||||
# ── Command dispatch ──────────────────────────────────────────────────────
|
# ── Command dispatch ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _handle_line(self, line: str) -> None:
|
def _handle_line(self, line: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
# ── DATA body accumulation ────────────────────────────────────────────
|
# ── DATA body accumulation ────────────────────────────────────────────
|
||||||
if self._in_data:
|
if self._in_data:
|
||||||
if line == ".":
|
if line == ".":
|
||||||
@@ -444,6 +450,7 @@ class SMTPProtocol(asyncio.Protocol):
|
|||||||
# ── AUTH helpers ──────────────────────────────────────────────────────────
|
# ── AUTH helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _handle_auth(self, args: str) -> None:
|
def _handle_auth(self, args: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
parts = args.split(None, 1)
|
parts = args.split(None, 1)
|
||||||
mech = parts[0].upper() if parts else ""
|
mech = parts[0].upper() if parts else ""
|
||||||
initial = parts[1] if len(parts) > 1 else ""
|
initial = parts[1] if len(parts) > 1 else ""
|
||||||
@@ -468,6 +475,7 @@ class SMTPProtocol(asyncio.Protocol):
|
|||||||
self._transport.write(b"504 5.5.4 Unrecognized authentication mechanism\r\n")
|
self._transport.write(b"504 5.5.4 Unrecognized authentication mechanism\r\n")
|
||||||
|
|
||||||
def _finish_auth(self, username: str, password: str) -> None:
|
def _finish_auth(self, username: str, password: str) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
_log("auth_attempt", src=self._peer[0],
|
_log("auth_attempt", src=self._peer[0],
|
||||||
username=username, principal=username,
|
username=username, principal=username,
|
||||||
severity=SEVERITY_WARNING, **encode_secret(password))
|
severity=SEVERITY_WARNING, **encode_secret(password))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Logs all requests as JSON.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import (
|
from syslog_bridge import (
|
||||||
encode_secret,
|
encode_secret,
|
||||||
forward_syslog,
|
forward_syslog,
|
||||||
@@ -225,11 +226,13 @@ def _build_response(version: int, community: str, request_id: int, oids: list) -
|
|||||||
|
|
||||||
|
|
||||||
class SNMPProtocol(asyncio.DatagramProtocol):
|
class SNMPProtocol(asyncio.DatagramProtocol):
|
||||||
|
_transport: asyncio.DatagramTransport | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.DatagramTransport, transport)
|
||||||
|
|
||||||
def datagram_received(self, data, addr):
|
def datagram_received(self, data, addr):
|
||||||
try:
|
try:
|
||||||
@@ -244,7 +247,8 @@ class SNMPProtocol(asyncio.DatagramProtocol):
|
|||||||
principal=None, secret_kind="snmp_community",
|
principal=None, secret_kind="snmp_community",
|
||||||
**encode_secret(community))
|
**encode_secret(community))
|
||||||
response = _build_response(version, community, request_id, oids)
|
response = _build_response(version, community, request_id, oids)
|
||||||
self._transport.sendto(response, addr)
|
if self._transport is not None:
|
||||||
|
self._transport.sendto(response, addr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log("parse_error", severity=4, src=addr[0], error=str(e), data=data[:64].hex())
|
_log("parse_error", severity=4, src=addr[0], error=str(e), data=data[:64].hex())
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ then responds with an error packet. Logs all requests as JSON.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||||
|
|
||||||
NODE_NAME = os.environ.get("NODE_NAME", "tftpserver")
|
NODE_NAME = os.environ.get("NODE_NAME", "tftpserver")
|
||||||
@@ -33,11 +34,13 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class TFTPProtocol(asyncio.DatagramProtocol):
|
class TFTPProtocol(asyncio.DatagramProtocol):
|
||||||
|
_transport: asyncio.DatagramTransport | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.DatagramTransport, transport)
|
||||||
|
|
||||||
def datagram_received(self, data: bytes, addr):
|
def datagram_received(self, data: bytes, addr):
|
||||||
if len(data) < 4:
|
if len(data) < 4:
|
||||||
@@ -56,7 +59,8 @@ class TFTPProtocol(asyncio.DatagramProtocol):
|
|||||||
filename=filename,
|
filename=filename,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
)
|
)
|
||||||
self._transport.sendto(_error_pkt(2, "Access violation"), addr)
|
if self._transport is not None:
|
||||||
|
self._transport.sendto(_error_pkt(2, "Access violation"), addr)
|
||||||
else:
|
else:
|
||||||
_log("unknown_opcode", src=addr[0], opcode=opcode, data=data[:32].hex())
|
_log("unknown_opcode", src=addr[0], opcode=opcode, data=data[:32].hex())
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ failed". Logs the raw response for offline cracking.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import base64 as _base64
|
import base64 as _base64
|
||||||
|
from typing import cast
|
||||||
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
from syslog_bridge import syslog_line, write_syslog_file, forward_syslog
|
||||||
|
|
||||||
NODE_NAME = os.environ.get("NODE_NAME", "desktop")
|
NODE_NAME = os.environ.get("NODE_NAME", "desktop")
|
||||||
@@ -26,24 +27,29 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class VNCProtocol(asyncio.Protocol):
|
class VNCProtocol(asyncio.Protocol):
|
||||||
|
_transport: asyncio.Transport | None = None
|
||||||
|
_peer: tuple[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._transport = None
|
self._transport = None
|
||||||
self._peer = None
|
self._peer = None
|
||||||
self._buf = b""
|
self._buf = b""
|
||||||
self._state = "version"
|
self._state = "version"
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||||
self._transport = transport
|
self._transport = cast(asyncio.Transport, transport)
|
||||||
self._peer = transport.get_extra_info("peername", ("?", 0))
|
self._peer = cast(tuple[str, int], self._transport.get_extra_info("peername", ("?", 0)))
|
||||||
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
_log("connect", src=self._peer[0], src_port=self._peer[1])
|
||||||
# Send RFB version
|
# Send RFB version
|
||||||
transport.write(b"RFB 003.008\n")
|
self._transport.write(b"RFB 003.008\n")
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._buf += data
|
self._buf += data
|
||||||
self._process()
|
self._process()
|
||||||
|
|
||||||
def _process(self):
|
def _process(self) -> None:
|
||||||
|
assert self._transport is not None
|
||||||
|
assert self._peer is not None
|
||||||
if self._state == "version":
|
if self._state == "version":
|
||||||
if b"\n" not in self._buf:
|
if b"\n" not in self._buf:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -178,4 +178,14 @@ Enable `check_untyped_defs = true` as the final step once the repo is clean.
|
|||||||
- Remove 9 stale `# type: ignore` comments across logging, helpers, credentials
|
- Remove 9 stale `# type: ignore` comments across logging, helpers, credentials
|
||||||
- Fix `telemetry.py` overload `no-redef` + `misc`
|
- Fix `telemetry.py` overload `no-redef` + `misc`
|
||||||
- Fix `logs.py` `datetime/str` operator errors and nullable PK comparison
|
- Fix `logs.py` `datetime/str` operator errors and nullable PK comparison
|
||||||
- [ ] Annotate `transport` in template servers + guard call sites (P3, ~100 errors)
|
- [x] P3 — template servers now have 0 mypy errors (146 fixed):
|
||||||
|
- Add `_transport: asyncio.Transport | None` class-level annotation + `cast()` in `connection_made` for 11 TCP Protocol files (pop3, smtp, mqtt, postgres, mssql, mongodb, imap, ldap, redis, mysql, sip, vnc)
|
||||||
|
- Add `_transport: asyncio.DatagramTransport | None` for 2 UDP DatagramProtocol files (snmp, tftp) + SIPUDPProtocol
|
||||||
|
- `assert self._transport is not None` guards in each method that directly accesses transport
|
||||||
|
- Fix RDP `start_tls()` `Transport | None` narrowing with `assert new_transport is not None`
|
||||||
|
- Fix FTP Twisted stubs: `assert self.transport is not None`, `# type: ignore[misc/override/arg-type/attr-defined]` for imprecise Twisted stubs, `IReactorTCP` cast for `listenTCP`
|
||||||
|
- Fix conpot `proc.stdout is None` guard before iteration
|
||||||
|
- Fix SMTP `get_payload(decode=True)` → explicit `bytes` narrowing
|
||||||
|
- Fix postgres `_handle_startup` param-name shadowing (`msg` bytes → `err_msg` str)
|
||||||
|
- Fix mongodb `base64.binascii.Error` → `import binascii; binascii.Error`
|
||||||
|
- Fix imap `result: list[int] = []` var-annotated
|
||||||
|
|||||||
Reference in New Issue
Block a user