From c2094f83f2ffcc783843557ab41e4284009ad355 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sun, 4 Oct 2020 17:21:38 +0200 Subject: [PATCH 1/2] WIP: SSL on IRC connection --- zulip/integrations/bridge_with_irc/README.md | 19 ++++++++++ .../bridge_with_irc/irc-mirror.py | 9 ++++- .../bridge_with_irc/irc_mirror_backend.py | 37 +++++++++++++++++-- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/zulip/integrations/bridge_with_irc/README.md b/zulip/integrations/bridge_with_irc/README.md index 936ae1f41f..112f5b4277 100644 --- a/zulip/integrations/bridge_with_irc/README.md +++ b/zulip/integrations/bridge_with_irc/README.md @@ -1,5 +1,23 @@ # IRC <--> Zulip bridge +For a how-to guide, please see: + +https://zulipchat.com/integrations/doc/irc + +## SSL warning + +It can be tricky to get SSL to work, but if you are not using it, everyone's +chats may be leaking through the bridge for any MitM attack. + +## Adding SSL certificates for IRC servers + +This section could need a bit of additional experience and elaboration. + +If your IRC server uses a CA that your system doesn't trust or some other +mechanism of self-signing, please consider adding that as a basic CA in your +system-wide platform. We have not written command-line options for storing +and trusting extra certificates for the bridge. + ## Usage ``` @@ -9,6 +27,7 @@ `--stream` is a Zulip stream. `--topic` is a Zulip topic, is optionally specified, defaults to "IRC". `--nickserv-pw` is the IRC nick password. +`--no-ssl` leaks everything in free text IMPORTANT: Make sure the bot is subscribed to the relevant Zulip stream!! diff --git a/zulip/integrations/bridge_with_irc/irc-mirror.py b/zulip/integrations/bridge_with_irc/irc-mirror.py index 0d767ff984..02559af1d9 100755 --- a/zulip/integrations/bridge_with_irc/irc-mirror.py +++ b/zulip/integrations/bridge_with_irc/irc-mirror.py @@ -27,9 +27,10 @@ if __name__ == "__main__": parser = zulip.add_default_arguments(argparse.ArgumentParser(usage=usage), allow_provisioning=True) parser.add_argument('--irc-server', default=None) - parser.add_argument('--port', default=6667) + parser.add_argument('--port', default=6697) parser.add_argument('--nick-prefix', default=None) parser.add_argument('--channel', default=None) + parser.add_argument('--no-ssl', default=False) parser.add_argument('--stream', default="general") parser.add_argument('--topic', default="IRC") parser.add_argument('--nickserv-pw', default='') @@ -49,7 +50,11 @@ if options.irc_server is None or options.nick_prefix is None or options.channel is None: parser.error("Missing required argument") + if options.no_ssl: + print("You are not using SSL.") + nickname = options.nick_prefix + "_zulip" bot = IRCBot(zulip_client, options.stream, options.topic, options.channel, - nickname, options.irc_server, options.nickserv_pw, options.port) + nickname, options.irc_server, options.nickserv_pw, options.port, + use_ssl=not options.no_ssl) bot.start() diff --git a/zulip/integrations/bridge_with_irc/irc_mirror_backend.py b/zulip/integrations/bridge_with_irc/irc_mirror_backend.py index 9b2ce002f9..786ca41e42 100644 --- a/zulip/integrations/bridge_with_irc/irc_mirror_backend.py +++ b/zulip/integrations/bridge_with_irc/irc_mirror_backend.py @@ -1,26 +1,54 @@ +import ssl + +import irc.connection import irc.bot -import irc.strings from irc.client import Event, ServerConnection, ip_numstr_to_quad from irc.client_aio import AioReactor +from irc import schedule import multiprocessing as mp from typing import Any, Dict +class AioReactorWithScheduler(AioReactor): + scheduler_class = schedule.DefaultScheduler + + def __init__(self, *args, **kwargs): + super(AioReactorWithScheduler, self).__init__() + scheduler = self.scheduler_class() + assert isinstance(scheduler, schedule.IScheduler) + self.scheduler = scheduler + + class IRCBot(irc.bot.SingleServerIRCBot): - reactor_class = AioReactor + reactor_class = AioReactorWithScheduler def __init__(self, zulip_client: Any, stream: str, topic: str, channel: irc.bot.Channel, - nickname: str, server: str, nickserv_password: str = '', port: int = 6667) -> None: + nickname: str, server: str, nickserv_password: str = '', port: int = 6697, + use_ssl: bool = True, ssl_connection_factory: irc.connection.Factory = None) -> None: self.channel = channel # type: irc.bot.Channel self.zulip_client = zulip_client self.stream = stream self.topic = topic self.IRC_DOMAIN = server self.nickserv_password = nickserv_password + + # Use SSL for IRC server + self.use_ssl = use_ssl + if use_ssl: + if ssl_connection_factory: + self.connection_factory = ssl_connection_factory + else: + self.connection_factory = irc.connection.AioFactory(ssl=ssl.create_default_context()) + else: + self.connection_factory = irc.connection.AioFactory() + + connect_params = {} + connect_params['connect_factory'] = self.connection_factory + # Make sure the bot is subscribed to the stream self.check_subscription_or_die() # Initialize IRC bot after proper connection to Zulip server has been confirmed. - irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) + irc.bot.SingleServerIRCBot.__init__(self, [irc.bot.ServerSpec(server, port)], nickname, nickname, **connect_params) def zulip_sender(self, sender_string: str) -> str: nick = sender_string.split("!")[0] @@ -30,6 +58,7 @@ def connect(self, *args: Any, **kwargs: Any) -> None: # Taken from # https://github.com/jaraco/irc/blob/master/irc/client_aio.py, # in particular the method of AioSimpleIRCClient + kwargs['connect_factory'] = self.connection_factory self.c = self.reactor.loop.run_until_complete( self.connection.connect(*args, **kwargs) ) From 62087230781e6c8bc81520112098f68ca419aeb6 Mon Sep 17 00:00:00 2001 From: Benjamin Bach Date: Sat, 28 Aug 2021 11:53:32 +0200 Subject: [PATCH 2/2] Black is back + mypy hi hi --- .../bridge_with_irc/irc-mirror.py | 18 +++++++-------- .../bridge_with_irc/irc_mirror_backend.py | 23 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/zulip/integrations/bridge_with_irc/irc-mirror.py b/zulip/integrations/bridge_with_irc/irc-mirror.py index 3657fe699a..31943a537e 100755 --- a/zulip/integrations/bridge_with_irc/irc-mirror.py +++ b/zulip/integrations/bridge_with_irc/irc-mirror.py @@ -29,14 +29,14 @@ parser = zulip.add_default_arguments( argparse.ArgumentParser(usage=usage), allow_provisioning=True ) - parser.add_argument('--irc-server', default=None) - parser.add_argument('--port', default=6697) - parser.add_argument('--nick-prefix', default=None) - parser.add_argument('--channel', default=None) - parser.add_argument('--no-ssl', default=False) - parser.add_argument('--stream', default="general") - parser.add_argument('--topic', default="IRC") - parser.add_argument('--nickserv-pw', default='') + parser.add_argument("--irc-server", default=None) + parser.add_argument("--port", default=6697) + parser.add_argument("--nick-prefix", default=None) + parser.add_argument("--channel", default=None) + parser.add_argument("--no-ssl", default=False) + parser.add_argument("--stream", default="general") + parser.add_argument("--topic", default="IRC") + parser.add_argument("--nickserv-pw", default="") options = parser.parse_args() # Setting the client to irc_mirror is critical for this to work @@ -68,6 +68,6 @@ options.irc_server, options.nickserv_pw, options.port, - use_ssl=not options.no_ssl + use_ssl=not options.no_ssl, ) bot.start() diff --git a/zulip/integrations/bridge_with_irc/irc_mirror_backend.py b/zulip/integrations/bridge_with_irc/irc_mirror_backend.py index e37319bc9c..73384eaf4a 100644 --- a/zulip/integrations/bridge_with_irc/irc_mirror_backend.py +++ b/zulip/integrations/bridge_with_irc/irc_mirror_backend.py @@ -1,17 +1,18 @@ +import multiprocessing as mp import ssl +from typing import Any, Dict + import irc.bot import irc.connection -import multiprocessing as mp +from irc import schedule from irc.client import Event, ServerConnection, ip_numstr_to_quad from irc.client_aio import AioReactor -from irc import schedule -from typing import Any, Dict class AioReactorWithScheduler(AioReactor): scheduler_class = schedule.DefaultScheduler - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(AioReactorWithScheduler, self).__init__() scheduler = self.scheduler_class() assert isinstance(scheduler, schedule.IScheduler) @@ -32,7 +33,7 @@ def __init__( nickserv_password: str = "", port: int = 6667, use_ssl: bool = True, - ssl_connection_factory: irc.connection.Factory = None + ssl_connection_factory: irc.connection.Factory = None, ) -> None: self.channel = channel # type: irc.bot.Channel self.zulip_client = zulip_client @@ -47,17 +48,21 @@ def __init__( if ssl_connection_factory: self.connection_factory = ssl_connection_factory else: - self.connection_factory = irc.connection.AioFactory(ssl=ssl.create_default_context()) + self.connection_factory = irc.connection.AioFactory( + ssl=ssl.create_default_context() + ) else: self.connection_factory = irc.connection.AioFactory() connect_params = {} - connect_params['connect_factory'] = self.connection_factory + connect_params["connect_factory"] = self.connection_factory # Make sure the bot is subscribed to the stream self.check_subscription_or_die() # Initialize IRC bot after proper connection to Zulip server has been confirmed. - irc.bot.SingleServerIRCBot.__init__(self, [irc.bot.ServerSpec(server, port)], nickname, nickname, **connect_params) + irc.bot.SingleServerIRCBot.__init__( + self, [irc.bot.ServerSpec(server, port)], nickname, nickname, **connect_params + ) def zulip_sender(self, sender_string: str) -> str: nick = sender_string.split("!")[0] @@ -67,7 +72,7 @@ def connect(self, *args: Any, **kwargs: Any) -> None: # Taken from # https://github.com/jaraco/irc/blob/main/irc/client_aio.py, # in particular the method of AioSimpleIRCClient - kwargs['connect_factory'] = self.connection_factory + kwargs["connect_factory"] = self.connection_factory self.c = self.reactor.loop.run_until_complete(self.connection.connect(*args, **kwargs)) print("Listening now. Please send an IRC message to verify operation")