diff --git a/zulip/integrations/bridge_with_irc/README.md b/zulip/integrations/bridge_with_irc/README.md index 936ae1f41..112f5b427 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 2f30a13fa..31943a537 100755 --- a/zulip/integrations/bridge_with_irc/irc-mirror.py +++ b/zulip/integrations/bridge_with_irc/irc-mirror.py @@ -30,9 +30,10 @@ 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="") @@ -54,6 +55,9 @@ 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, @@ -64,5 +68,6 @@ 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 5242ca0d6..73384eaf4 100644 --- a/zulip/integrations/bridge_with_irc/irc_mirror_backend.py +++ b/zulip/integrations/bridge_with_irc/irc_mirror_backend.py @@ -1,14 +1,26 @@ import multiprocessing as mp +import ssl from typing import Any, Dict import irc.bot -import irc.strings +import irc.connection +from irc import schedule from irc.client import Event, ServerConnection, ip_numstr_to_quad from irc.client_aio import AioReactor +class AioReactorWithScheduler(AioReactor): + scheduler_class = schedule.DefaultScheduler + + def __init__(self, *args: Any, **kwargs: Any) -> None: + 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, @@ -20,6 +32,8 @@ def __init__( server: str, nickserv_password: str = "", port: int = 6667, + use_ssl: bool = True, + ssl_connection_factory: irc.connection.Factory = None, ) -> None: self.channel = channel # type: irc.bot.Channel self.zulip_client = zulip_client @@ -27,10 +41,28 @@ def __init__( 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] @@ -40,6 +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 self.c = self.reactor.loop.run_until_complete(self.connection.connect(*args, **kwargs)) print("Listening now. Please send an IRC message to verify operation")