diff --git a/help.json b/help.json index e320efa..e69d776 100644 --- a/help.json +++ b/help.json @@ -4,6 +4,8 @@ "add": "add
", "del": "del ", "mod": "mod [] []", + "enable": "enable ", "list": "list", "rehash": "rehash" } diff --git a/threshold b/threshold index 5f1790c..0ae261b 100755 --- a/threshold +++ b/threshold @@ -2,11 +2,15 @@ from twisted.internet import reactor from twisted.internet.ssl import DefaultOpenSSLContextFactory from twisted.internet.protocol import Protocol, Factory +from twisted.internet.endpoints import SSL4ClientEndpoint, TCP4ClientEndpoint, connectProtocol +from twisted.words.protocols.irc import IRCClient + from json import load, dump, loads from sys import exit listener = None connections = {} +IRCPool = {} def log(data): print("[LOG]", data) @@ -33,6 +37,136 @@ def sendFailure(addr, data): def sendInfo(addr, data): sendData(addr, "[i] " + data) +class IRCBot(IRCClient): + def __init__(self, name): + self.name = name + instance = pool[name] + + self.nickname = instance["nickname"] + self.realname = instance["realname"] + self.username = instance["username"] + self.userinfo = instance["userinfo"] + self.fingerReply = instance["finger"] + self.versionName = instance["version"] + self.versionNum = None + self.versionEnv = None + self.sourceURL = instance["source"] + + self._who = {} + self._getWho = {} + self.wholist = {} + + self.authtype = instance["authtype"] + if self.authtype == "ns": + self.authpass = instance["password"] + self.authentity = instance["authentity"] + else: + self.password = instance["password"] + + def refresh(self): + instance = pool[name] + if not instance["nickname"] == self.nickname: + self.nickname = instance["nickname"] + self.setNick(self.nickname) + + self.userinfo = instance["userinfo"] + self.fingerReply = instance["finger"] + self.versionName = instance["version"] + self.versionNum = None + self.versionEnv = None + self.sourceURL = instance["source"] + + def sanitize(self, user): + user = user.replace("!", "") + user = user.replace("~", "") + user = user.replace("&", "") + user = user.replace("@", "") + user = user.replace("%", "") + user = user.replace("+", "") + return user + + def setNick(self, nickname): + self._attemptedNick = nickname + self.sendLine("NICK %s" % nickname) + self.nickname = nickname + + def alterCollidedNick(self, nickname): + newnick = nickname + "_" + return newnick + + def irc_ERR_NICKNAMEINUSE(self, prefix, params): + self._attemptedNick = self.alterCollidedNick(self._attemptedNick) + self.setNick(self._attemptedNick) + + def irc_ERR_PASSWDMISMATCH(self, prefix, params): + ircLog(self.ident, "Password mismatch") + manager.handleWrongPassword(self.ident, prefix, params) + + def irc_RPL_NAMREPLY(self, prefix, params): + channel = params[2] + nicklist = params[3].split(' ') + + if channel not in self._names: + return + n = self._names[channel][1] + n += nicklist + + def irc_RPL_ENDOFNAMES(self, prefix, params): + channel = params[1] + if channel not in self._names: + return + callbacks, namelist = self._names[channel] + for cb in callbacks: + cb.callback((channel, namelist)) + del self._names[channel] + + def got_names(self, nicklist): + ncklist = [] + for i in nicklist[1]: + ncklist.append(self.sanitize(i)) + self.nameslist[nicklist[0]] = ncklist + + def who(self, channel): + channel = channel + d = defer.Deferred() + if channel not in self._who: + self._who[channel] = ([], {}) + self._who[channel][0].append(d) + self.sendLine("WHO %s" % channel) + return d + + def irc_RPL_WHOREPLY(self, prefix, params): + channel = params[1] + user = params[2] + host = params[3] + server = params[4] + nick = params[5] + status = params[6] + realname = params[7] + if channel not in self._who: + return + n = self._who[channel][1] + n[nick] = [nick, user, host, server, status, realname] + + def irc_RPL_ENDOFWHO(self, prefix, params): + channel = params[1] + if channel not in self._who: + return + callbacks, info = self._who[channel] + for cb in callbacks: + cb.callback((channel, info)) + del self._who[channel] + + def got_who(self, whoinfo): + self.wholist[whoinfo[0]] = whoinfo[1] + + def signedOn(self): + if self.authtype == "ns": + self.msg(self.authentity, "IDENTIFY %s" % self.nspass) + + def joined(self, channel): + self.who(channel).addCallback(self.got_who) + class Base(Protocol): def __init__(self, addr): self.addr = addr @@ -124,6 +258,37 @@ class Helper(object): sendFailure(addr, "Usage: " + help[mode]) return + def addBot(self, name): + global IRCPool + instance = pool[name] + if instance["protocol"] == "plain": + if instance["bind"] == None: + point = TCP4ClientEndpoint(reactor, instance["host"], int(instance["port"]), timeout=int(instance["timeout"])) + bot = IRCBot(name) + IRCPool[name] = bot + d = connectProtocol(point, bot) + return + else: + point = TCP4ClientEndpoint(reactor, instance["host"], int(instance["port"]), timeout=int(instance["timeout"]), bindAddress=instance["bind"]) + bot = IRCBot(name) + IRCPool[name] = bot + d = connectProtocol(point, bot) + return + elif instance["protocol"] == "ssl": + contextFactory = DefaultOpenSSLContextFactory(instance["key"].encode("utf-8", "replace"), instance["certificate"].encode("utf-8", "replace")) + if instance["bind"] == None: + point = SSL4ClientEndpoint(reactor, instance["host"], int(instance["port"]), contextFactory, timeout=int(instance["timeout"])) + bot = IRCBot(name) + IRCPool[name] = bot + d = connectProtocol(point, bot) + return + else: + point = SSL4ClientEndpoint(reactor, instance["host"], int(instance["port"]), contextFactory, timeout=int(instance["timeout"]), bindAddress=instance["bind"]) + bot = IRCBot(name) + IRCPool[name] = bot + d = connectProtocol(point, bot) + return + def parseCommand(self, addr, authed, data): global pool data = data.strip() @@ -174,6 +339,33 @@ class Helper(object): info("\n".join(poolMap)) return + elif cmd == "enable": + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + pool[spl[1]]["enabled"] = True + self.addBot(spl[1]) + success("Successfully enabled bot %s" % spl[1]) + return + else: + incUsage("enable") + return + + elif cmd == "disable": + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + pool[spl[1]]["enabled"] = False + if spl[1] in IRCPool.keys(): + IRCPool[spl[1]].transport.loseConnection() + success("Successfully disabled bot %s" % spl[1]) + return + else: + incUsage("disable") + return + elif cmd == "add": if length == 6: @@ -193,9 +385,11 @@ class Helper(object): failure("Port must be an integer, not %s" % spl[3]) return - pool[spl[1]] = { "address": spl[2], + pool[spl[1]] = { "host": spl[2], "port": spl[3], "protocol": protocol, + "bind": None, + "timeout": 30, "nickname": spl[5], "username": None, "realname": None, @@ -205,10 +399,13 @@ class Helper(object): "source": None, "authtype": None, "password": None, - "authentity": None, - "key": None, - "certificate": None, + "authentity": "NickServ", + "key": config["ListenerKey"], + "certificate": config["ListenerCertificate"], + "enabled": config["ConnectOnCreate"], } + if config["ConnectOnCreate"] == True: + self.addBot(spl[1]) success("Successfully created bot") self.savePool() return @@ -223,6 +420,9 @@ class Helper(object): failure("Name does not exist: %s" % spl[1]) return del pool[spl[1]] + if spl[1] in IRCPool.keys(): + IRCPool[spl[1]].transport.loseConnection() + del IRCPool[spl[1]] success("Successfully removed bot") self.savePool() return @@ -231,6 +431,7 @@ class Helper(object): return elif cmd == "mod": + toUnset = False if length == 2: if not spl[1] in pool.keys(): failure("Name does not exist: %s" % spl[1]) @@ -258,11 +459,11 @@ class Helper(object): if not spl[2] in pool[spl[1]].keys(): failure("No such key: %s" % spl[2]) return - if spl[2] == "port": + if spl[2] in ["port", "timeout"]: try: int(spl[3]) except: - failure("Port must be an integer, not %s" % spl[3]) + failure("Value must be an integer, not %s" % spl[3]) return if spl[2] == "protocol": if not spl[3] in ["ssl", "plain"]: @@ -271,8 +472,25 @@ class Helper(object): if spl[3] == pool[spl[1]][spl[2]]: failure("Value already exists: %s" % spl[3]) return + + if spl[3].lower() in ["none", "nil"]: + spl[3] = None + toUnset = True + + if spl[2] == "authtype": + if not spl[3] in ["sp", "ns"]: + failure("Authtype must be sp or ns, not %s" % spl[3]) + return + if spl[2] == "enabled": + failure("Use the enable and disable commands to manage this") + return + pool[spl[1]][spl[2]] = spl[3] - success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1])) + self.savePool() + if toUnset: + success("Successfully unset key %s on %s" % (spl[2], spl[3], spl[1])) + else: + success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1])) return else: @@ -300,6 +518,9 @@ if __name__ == "__main__": config = helper.getConfig() pool = helper.getPool() help = helper.getHelp() + for i in pool.keys(): + if pool[i]["enabled"] == True: + helper.addBot(i) listener = BaseFactory() if config["UseSSL"] == True: