from datetime import datetime from twisted.internet import reactor from twisted.internet.protocol import ReconnectingClientFactory from twisted.internet.ssl import DefaultOpenSSLContextFactory from twisted.words.protocols.irc import IRCClient import main from core.relay import sendRelayNotification from modules import userinfo from utils.get import getRelay from utils.logging.log import error, log from utils.logging.send import sendAll from utils.parsing import parsen # TODO: strip out non-relay functionality class IRCRelay(IRCClient): def __init__(self, num, relayCommands, user, stage2): self.isconnected = False self.buffer = "" if user is None: self.user = main.config["Relay"]["User"] else: self.user = user.lower() password = main.config["Relay"]["Password"] self.nickname = "relay" self.realname = "relay" self.username = self.user self.password = self.user + ":" + password self.relayCommands = relayCommands self.num = num self.stage2 = stage2 self.loop = None def privmsg(self, user, channel, msg): nick, ident, host = parsen(user) for i in main.ZNCErrors: if i in msg: error("ZNC issue:", msg) if nick[0] == main.config["Tweaks"]["ZNC"]["Prefix"]: nick = nick[1:] if nick in self.relayCommands.keys(): sendAll("[%s] %s -> %s" % (self.num, nick, msg)) def irc_ERR_PASSWDMISMATCH(self, prefix, params): log("%s: relay password mismatch" % self.num) sendAll("%s: relay password mismatch" % self.num) def sendStage2(self): # [["user", {"sasl": ["message1", "message2"]}], []] if not len(self.stage2) == 0: user = self.stage2[0].pop(0) commands = self.stage2[0].pop(0) del self.stage2[0] deliverRelayCommands(self.num, commands, user, self.stage2) def signedOn(self): if not self.isconnected: self.isconnected = True # log("signed on as a relay: %s" % self.num) sleeptime = 0 increment = 0.8 for i in self.relayCommands.keys(): for x in self.relayCommands[i]: reactor.callLater( sleeptime, self.msg, main.config["Tweaks"]["ZNC"]["Prefix"] + i, x, ) sleeptime += increment increment += 0.8 if self.stage2 is not None: reactor.callLater(sleeptime, self.sendStage2) reactor.callLater(sleeptime + 5, self.transport.loseConnection) return class IRCRelayFactory(ReconnectingClientFactory): def __init__(self, net, num=None, relayCommands=None, user=None, stage2=None): if net is None: self.num = num self.net = None self.name = "relay - %i" % num self.relay = True else: self.name = net + str(num) self.num = num self.net = net self.relay = False self.client = None self.maxDelay = main.config["Tweaks"]["Delays"]["MaxDelay"] self.initialDelay = main.config["Tweaks"]["Delays"]["InitialDelay"] self.factor = main.config["Tweaks"]["Delays"]["Factor"] self.jitter = main.config["Tweaks"]["Delays"]["Jitter"] self.relayCommands, self.user, self.stage2 = relayCommands, user, stage2 def buildProtocol(self, addr): entry = IRCRelay(self.num, self.relayCommands, self.user, self.stage2) self.client = entry return entry def clientConnectionLost(self, connector, reason): if not self.relay: userinfo.delChannels(self.net, self.client.channels) if self.client is not None: self.client.isconnected = False self.client.authenticated = False self.client.channels = [] error = reason.getErrorMessage() if not self.relay: log("%s - %i: connection lost: %s" % (self.net, self.num, error)) sendAll("%s - %i: connection lost: %s" % (self.net, self.num, error)) ctime = str(datetime.now().isoformat()) sendRelayNotification( { "type": "conn", "net": self.net, "num": self.num, "status": "lost", "message": error, "ts": ctime, } ) self.retry(connector) # ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailed(self, connector, reason): if self.client is not None: self.client.isconnected = False self.client.authenticated = False self.client.channels = [] error = reason.getErrorMessage() log("%s - %i: connection failed: %s" % (self.net, self.num, error)) if not self.relay: sendAll("%s - %s: connection failed: %s" % (self.net, self.num, error)) ctime = str(datetime.now().isoformat()) sendRelayNotification( { "type": "conn", "net": self.net, "num": self.num, "status": "failed", "message": error, "ts": ctime, } ) self.retry(connector) # ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) def deliverRelayCommands(num, relayCommands, user=None, stage2=None): keyFN = main.certPath + main.config["Key"] certFN = main.certPath + main.config["Certificate"] contextFactory = DefaultOpenSSLContextFactory(keyFN.encode("utf-8", "replace"), certFN.encode("utf-8", "replace")) bot = IRCRelayFactory(net=None, num=num, relayCommands=relayCommands, user=user, stage2=stage2) host, port = getRelay(num) reactor.connectSSL(host, port, bot, contextFactory)