From 5ff38909be6170f674ac6daab9363fbaf42175be Mon Sep 17 00:00:00 2001 From: Reto Brunner Date: Tue, 14 Jun 2022 20:38:55 +0200 Subject: [PATCH] Allow the bot to auth to nickserv Note that this uses the insecure nickserv auth, rather than sasl as the underlying IRC lib doesn't support sasl and hence this is the quick and dirty way to solve it until upstream gets the support. --- README.md | 4 +++ neo-bot.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4541ce4..3fece20 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ optional arguments: -m MAX_AGE, --max_age MAX_AGE only show issues less than MAX_AGE days old --cooldown_min do not repeat lookups within the given number of minutes + --nickserv_cloak NICKSERV_CLOAK + Only try to identify if nickserv has this host (as we aren't using sasl) + --nickserv_password NICKSERV_PASSWORD + Nickserv password to use with the given nickname as account ``` ### Example: diff --git a/neo-bot.py b/neo-bot.py index 2ecdb4e..495d238 100755 --- a/neo-bot.py +++ b/neo-bot.py @@ -17,7 +17,18 @@ class GitHubBot(irc.bot.SingleServerIRCBot): def __init__( - self, api, channel, nickname, server, port, user, repo, max_age, cooldown_min + self, + api, + channel, + nickname, + server, + port, + user, + repo, + max_age, + cooldown_min, + nickserv_cloak, + nickserv_password, ): super().__init__(((server, port),), nickname, nickname) self.api = api @@ -37,6 +48,13 @@ def __init__( self.user = user self.repo = repo self.nickname = nickname + + # nickserv stuff + self.account = nickname # save our original, non extended nick for auth + self.nickserv_password = nickserv_password + self.nickserv_cloak = nickserv_cloak + self._is_doing_auth = False + self.max_age = timedelta(days=max_age) self.policies = [ self.reject_if_too_old(), @@ -48,6 +66,7 @@ def on_nicknameinuse(self, c, e): self.nickname = c.get_nickname() def on_welcome(self, c, e): + self._start_auth(c) c.join(self.channel) def on_privmsg(self, c, e): @@ -64,6 +83,54 @@ def _apply_report_policies(self, msg, entity): return resp return None + _auth_events = ("whoreply", "nosuchserver") + + def _start_auth(self, c): + if self.nickserv_password is None: + return + + self._is_doing_auth = True + # we want to know that we are talking to services, so we first ask whom we are talking to. + # Note this is inherently racy but the best we can do without sasl + for ev in self._auth_events: + c.add_global_handler(ev, self._do_auth) + c.who("nickserv") + + def _validate_should_auth(self, e): + """Raises a ValueError if we shouldn't try to auth""" + if not self._is_doing_auth: + raise ValueError("ERROR: auth callback called but not initiated by us") + + if e.type == "nosuchserver": + raise ValueError("ERROR: nickserv is unknown") + + if e.type != "whoreply": + raise ValueError(f"ERROR: got invalid event {e}") + + def _check_nickserv(self, e): + if len(e.arguments) < 3: + raise ValueError(f"ERROR: got invalid event {e}") + username = e.arguments[1] + host = e.arguments[2] + if username.lower() != "nickserv" or host != self.nickserv_cloak: + raise ValueError( + f"ERROR: username <{username}> not nickserv " + + f"or host {host} doesn't match {self.nickserv_cloak}" + ) + + def _do_auth(self, c, e): + """Fired when we should try to auth""" + try: + self._validate_should_auth(e) + self._check_nickserv(e) + c.privmsg("nickserv", f"IDENTIFY {self.account} {self.nickserv_password}") + except ValueError as err: + print(err) + finally: + for ev in self._auth_events: + c.remove_global_handler(ev, self._do_auth) + self._is_doing_auth = False + def _process_message(self, c, answer_to, e): msgs = e.arguments for msg in msgs: @@ -184,6 +251,17 @@ def parse_args(): type=int, default=5, ) + parser.add_argument( + "--nickserv_cloak", + help="Only try to identify if nickserv has this host (as we aren't using sasl)", + type=str, + default="services.libera.chat", + ) + parser.add_argument( + "--nickserv_password", + help="Nickserv password to use with the given nickname as account", + type=str, + ) return parser.parse_args() @@ -202,6 +280,8 @@ def main(): args.repo, args.max_age, args.cooldown_min, + args.nickserv_cloak, + args.nickserv_password, ) bot.start()