Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

run MASKTRACE at startup to have everyone's details #55

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions beryllia/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,34 @@
from json import loads as json_loads
from re import compile as re_compile
from shlex import split as shlex_split
from typing import Dict, List, Optional, Sequence, Tuple
from typing import Dict, List, Optional, Sequence, Set, Tuple

from irctokens import build, hostmask as hostmask_parse, Hostmask, Line
from ircrobots import Bot as BaseBot
from ircrobots import Server as BaseServer

from ircstates.numerics import RPL_ENDOFMOTD, ERR_NOMOTD, RPL_WELCOME, RPL_YOUREOPER
from ircrobots.ircv3 import Capability
from ircstates.numerics import RPL_ENDOFMOTD, ERR_NOMOTD, RPL_WELCOME, RPL_YOUREOPER

from .common import NickUserHost, User
from .config import Config
from .database import Database
from .database.common import NickUserHost
from .database.kline import DBKLine
from .normalise import RFC1459SearchNormaliser

from .util import oper_up, pretty_delta, get_statsp, get_klines
from .util import try_parse_cidr, try_parse_ip, try_parse_ts
from .util import looks_like_glob, colourise
from .util import (
colourise,
get_klines,
get_links,
get_masktrace,
get_statsp,
looks_like_glob,
oper_up,
pretty_delta,
try_parse_cidr,
try_parse_ip,
try_parse_ts,
)

from .parse.nickserv import NickServParser
from .parse.snote import SnoteParser
Expand Down Expand Up @@ -55,6 +65,8 @@ def __init__(self, bot: BaseBot, name: str, config: Config):
self._config = config

self._database_init: bool = False
self._links: Dict[str, Set[str]] = {}
self._users: Dict[str, User] = {}

def set_throttle(self, rate: int, time: float):
# turn off throttling
Expand Down Expand Up @@ -130,9 +142,22 @@ async def line_read(self, line: Line):
self._database_init = True

self._nickserv = NickServParser(database)
self._snote = SnoteParser(database, self._config.rejects, self._kline_new)
self._snote = SnoteParser(
self,
database,
self._users,
self._links,
self._config.rejects,
self._kline_new,
)

elif line.command == RPL_YOUREOPER:
self._links.clear()
self._links.update(await get_links(self))

self._users.clear()
self._users.update(await get_masktrace(self))

# B connections rejected due to k-line
# F far cliconn
# c near cliconn
Expand Down
23 changes: 23 additions & 0 deletions beryllia/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from dataclasses import dataclass
from ipaddress import IPv4Address, IPv6Address
from typing import Optional, Union


class NickUserHost:
# nick user host
def nuh(self) -> str:
raise NotImplementedError()


@dataclass
class User(NickUserHost):
nickname: str
username: str
realname: str
hostname: str
account: Optional[str]
ip: Optional[Union[IPv4Address, IPv6Address]]
server: str

def nuh(self) -> str:
return f"{self.nickname}!{self.username}@{self.hostname}"
5 changes: 3 additions & 2 deletions beryllia/database/cliconn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
from ipaddress import IPv4Network, IPv6Network
from typing import Any, Optional, Sequence, Tuple, Union

from .common import NickUserHost, Table
from .common import Table
from ..common import User
from ..normalise import SearchType
from ..util import glob_to_sql, lex_glob_pattern


@dataclass
class Cliconn(NickUserHost):
class Cliconn(User):
nickname: str
username: str
realname: str
Expand Down
6 changes: 0 additions & 6 deletions beryllia/database/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
from ..util import CompositeString, CompositeStringText


class NickUserHost:
# nick user host
def nuh(self) -> str:
raise NotImplementedError()


@dataclass
class Table(object):
pool: Pool
Expand Down
3 changes: 2 additions & 1 deletion beryllia/database/kline_kill.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from ipaddress import IPv4Network, IPv6Network
from typing import Any, Collection, Optional, Sequence, Tuple, Union

from .common import NickUserHost, Table
from .common import Table
from ..common import NickUserHost
from ..normalise import SearchType
from ..util import lex_glob_pattern, glob_to_sql

Expand Down
85 changes: 79 additions & 6 deletions beryllia/parse/snote.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
Match,
Optional,
Pattern,
Set,
Tuple,
Union,
)

from irctokens import Line
from ircrobots import Server

from .common import IRCParser
from ..common import User
from ..database import Database
from ..database.cliconn import Cliconn
from ..util import get_links, get_masktrace

_TYPE_HANDLER = Callable[[Any, str, Match], Awaitable[None]]
_HANDLERS: List[Tuple[Pattern, _TYPE_HANDLER]] = []
Expand All @@ -37,17 +41,23 @@ def _inner(func: _TYPE_HANDLER) -> _TYPE_HANDLER:
class SnoteParser(IRCParser):
def __init__(
self,
server: Server,
database: Database,
users: Dict[str, User],
links: Dict[str, Set[str]],
kline_reject_max: int,
kline_new: Callable[[int], Awaitable[None]],
):
super().__init__()

self._server = server
self._database = database
self._users = users
self._links = links
self._kline_reject_max = kline_reject_max
self._kline_new = kline_new

self._cliconns: Dict[str, Tuple[int, Cliconn]] = {}
self._cliconns: Dict[str, int] = {}
self._kline_waiting_exit: Dict[str, str] = {}

async def handle(self, line: Line) -> None:
Expand Down Expand Up @@ -104,7 +114,8 @@ async def _handle_cliconn(self, server: str, match: Match) -> None:
datetime.utcnow(),
)
cliconn_id = await self._database.cliconn.add(cliconn)
self._cliconns[nickname] = (cliconn_id, cliconn)
self._cliconns[nickname] = cliconn_id
self._users[nickname] = cliconn

@_handler(
r"""
Expand Down Expand Up @@ -134,7 +145,10 @@ async def _handle_cliexit(self, server: str, match: Match) -> None:

cliconn_id: Optional[int] = None
if nickname in self._cliconns:
cliconn_id, _ = self._cliconns.pop(nickname)
cliconn_id = self._cliconns.pop(nickname)

if nickname in self._users:
del self._users[nickname]

await self._database.cliexit.add(
cliconn_id, nickname, username, hostname, ip, reason
Expand Down Expand Up @@ -210,12 +224,16 @@ async def _handle_klinerej(self, server: str, match: Match) -> None:
)
async def _handle_nickchg(self, server: str, match: Match) -> None:
old_nick = match.group("old_nick")
new_nick = match.group("new_nick")

user = self._users.pop(old_nick)
self._users[new_nick] = user

if not old_nick in self._cliconns:
return

new_nick = match.group("new_nick")
cliconn_id, cliconn = self._cliconns.pop(old_nick)
self._cliconns[new_nick] = (cliconn_id, cliconn)
cliconn_id = self._cliconns.pop(old_nick)
self._cliconns[new_nick] = cliconn_id
await self._database.nick_change.add(cliconn_id, new_nick)

@_handler(
Expand Down Expand Up @@ -310,3 +328,58 @@ async def _handle_klinedel(self, server: str, match: Match) -> None:
return

await self._database.kline_remove.add(id, source, oper)

@_handler(
r"""
^
# "*** Notice --"
\*{3}\ Notice\ --
# " Netsplit silver.libera.chat <-> tungsten.libera.chat"
\ Netsplit\ (?P<near>\S+)\ <->\ (?P<far>\S+)
# " (1S 2000C) (by jess: jess)"
\ .*
$
"""
)
async def _handle_netsplit(self, server: str, match: Match) -> None:
server_near = match.group("near")
if not server_near in self._links:
# this should only happen when something splits during burst
return

server_far = match.group("far")

self._links[server_near].remove(server_far)

servers_gone_list = [server_far]
server_i = 0
while server_i < len(servers_gone_list):
server_gone = servers_gone_list[server_i]
servers_gone_list.extend(self._links.pop(server_gone))
server_i += 1

servers_gone = set(servers_gone_list)
for nickname, user in list(self._users.items()):
if not user.server in servers_gone:
continue

del self._users[nickname]

@_handler(
r"""
^
# "*** Notice --"
\*{3}\ Notice\ --
# " Netjoin"
\ Netjoin
# " silver.libera.chat <-> tungsten.libera.chat (1S 2000C)
\ .*
$
"""
)
async def _handle_netjoin(self, server: str, match: Match) -> None:
self._links.clear()
self._links.update(await get_links(self._server))

self._users.clear()
self._users.update(await get_masktrace(self._server))
61 changes: 60 additions & 1 deletion beryllia/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ipaddress import ip_address, IPv4Address, IPv6Address
from ipaddress import ip_network, IPv4Network, IPv6Network

from typing import Deque, List, Optional, Sequence, Set, Tuple, Union
from typing import Dict, Deque, List, Optional, Sequence, Set, Tuple, Union

from ircrobots import Server
from irctokens import build
Expand All @@ -16,9 +16,15 @@
from aiodns import DNSResolver
from aiodns.error import DNSError

from .common import User

# not in ircstates.numerics
RPL_STATS = "249"
RPL_ENDOFSTATS = "219"
RPL_LINKS = "364"
RPL_ENDOFLINKS = "365"
RPL_TRACEEND = "262"
RPL_ETRACE = "709"

RE_OPERNAME = re.compile(r"^is opered as (\S+)(?:,|$)")

Expand Down Expand Up @@ -150,6 +156,59 @@ async def get_klines(server: Server) -> Optional[Set[str]]:
return masks


async def get_masktrace(server: Server) -> Dict[str, User]:
users: Dict[str, User] = {}

await server.send(build("MASKTRACE", ["!*@*"]))
while True:
line = await server.wait_for(
{
Response(RPL_ETRACE, [SELF, ANY, ANY, ANY, ANY, ANY, ANY, ANY]),
Response(RPL_TRACEEND, [SELF]),
}
)
if line.command == RPL_TRACEEND:
break

nickname = line.params[3]
ip: Optional[Union[IPv4Address, IPv6Address]] = None
if not (ip_str := line.params[6]) == "0":
ip = ip_address(ip_str)

user = User(
nickname,
line.params[4],
line.params[7],
line.params[5],
None,
ip,
line.params[2],
)
users[nickname] = user
return users


async def get_links(server: Server) -> Dict[str, Set[str]]:
links: Dict[str, Set[str]] = {}

await server.send(build("LINKS"))
while True:
line = await server.wait_for(
{
Response(RPL_LINKS, [SELF, ANY, ANY]),
Response(RPL_ENDOFLINKS, [SELF]),
}
)
if line.command == RPL_ENDOFLINKS:
break

server_far, server_near = line.params[1:3]
links[server_far] = set()
if not server_far == server_near:
links[server_near].add(server_far)
return links


def try_parse_ip(ip: str) -> Optional[Union[IPv4Address, IPv6Address]]:

try:
Expand Down