def handleCmd_KILL(self, prefix, args): l33t = prefix n00b = args[0] reason = irc_strip(args[1]) # In most cases, n00b is a UUID, but Anope seems to still use a nick. # Thus, we have to try both everywhere :-/ # Reconnect the bot if it's killed. if self.ism.isMyBot(n00b): if self.ism.syncd: self.pushBotJoin(do_nick=True) return l33t = self.ism.findUser(l33t).inick # If n00b is a Dtella node, kick 'em. n = self.ism.findDtellaNode(inick=n00b) if n: self.ism.kickDtellaNode( n, l33t, "KILL: " + reason, send_quit=False) return # If n00b is an IRC user, treat it like a QUIT. try: n00b_u = self.ism.findUser(n00b) except KeyError: LOG.warning("Tried to KILL unknown user: %s" % n00b) return message = ( "%s has KILL'd %s: %s" % (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason)) self.ism.removeUser(n00b_u, message)
def handleCmd_ENDBURST(self, prefix, args): if self.ism.syncd: # FIXME LOG.warning("Ignoring ENDBURST") return CHECK(self.server_name) LOG.info("Finished receiving IRC sync data.") self.showirc = True # Check for conflicting bridges. if self.ism.findConflictingBridge(): LOG.error("My nick prefix is in use! Terminating.") self.transport.loseConnection() reactor.stop() return # Set up nick reservation scfg = getServiceConfig() self.ism.killConflictingUsers() self.sendLine( ":%s ADDLINE Q %s* %s %d 0 :%s" % (self.sid, cfg.dc_to_irc_prefix, scfg.my_host, time.time(), self.qline_reason)) # Send my own bridge nick self.pushBotJoin(do_nick=True) # When we enter the syncd state, register this instance with Dtella. # This will eventually trigger event_DtellaUp, where we send our state. self.schedulePing() self.ism.addMeToMain()
def handleCmd_ENDBURST(self, prefix, args): if self.ism.syncd: # FIXME LOG.warning("Ignoring ENDBURST") return CHECK(self.server_name) LOG.info("Finished receiving IRC sync data.") self.showirc = True # Check for conflicting bridges. if self.ism.findConflictingBridge(): LOG.error("My nick prefix is in use! Terminating.") self.transport.loseConnection() reactor.stop() return # Set up nick reservation scfg = getServiceConfig() self.ism.killConflictingUsers() self.sendLine(":%s ADDLINE Q %s* %s %d 0 :%s" % (self.sid, cfg.dc_to_irc_prefix, scfg.my_host, time.time(), self.qline_reason)) # Send my own bridge nick self.pushBotJoin(do_nick=True) # When we enter the syncd state, register this instance with Dtella. # This will eventually trigger event_DtellaUp, where we send our state. self.schedulePing() self.ism.addMeToMain()
def handleCmd_KILL(self, prefix, args): l33t = prefix n00b = args[0] reason = irc_strip(args[1]) # In most cases, n00b is a UUID, but Anope seems to still use a nick. # Thus, we have to try both everywhere :-/ # Reconnect the bot if it's killed. if self.ism.isMyBot(n00b): if self.ism.syncd: self.pushBotJoin(do_nick=True) return l33t = self.ism.findUser(l33t).inick # If n00b is a Dtella node, kick 'em. n = self.ism.findDtellaNode(inick=n00b) if n: self.ism.kickDtellaNode(n, l33t, "KILL: " + reason, send_quit=False) return # If n00b is an IRC user, treat it like a QUIT. try: n00b_u = self.ism.findUser(n00b) except KeyError: LOG.warning("Tried to KILL unknown user: %s" % n00b) return message = ("%s has KILL'd %s: %s" % (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason)) self.ism.removeUser(n00b_u, message)
def handleCmd_QUIT(self, prefix, args): uuid = prefix try: u = self.ism.findUser(uuid) except KeyError: LOG.warning("Can't quit user: %s" % uuid) else: self.ism.removeUser(u)
def handleCmd_NICK(self, prefix, args): # :268AAAAAF NICK Paul 1238303566 old_uuid = prefix new_nick = args[0] try: u = self.ism.findUser(old_uuid) except KeyError: # This might be an echo from our KICK. LOG.warning("NICK: can't find source: %s" % old_uuid) return self.ism.changeNick(u, new_nick)
def receivedBlockRequest(self, src_ipp, bhash): try: b = self.cached_blocks[bhash] except KeyError: LOG.warning("Requested block not found") return b.scheduleExpire(self.cached_blocks, bhash) packet = ['bB'] packet.append(self.main.osm.me.ipp) packet.append(struct.pack('!H', len(b.data))) packet.append(b.data) ad = Ad().setRawIPPort(src_ipp) self.main.ph.sendPacket(''.join(packet), ad.getAddrTuple())
def cb(first): try: reactor.listenTCP(dc_port, dfactory, interface='127.0.0.1') except twisted.internet.error.CannotListenError: if first: LOG.warning("TCP bind failed. Killing old process...") if terminate(dc_port): LOG.info("Ok. Sleeping...") reactor.callLater(2.0, cb, False) else: LOG.error("Kill failed. Giving up.") reactor.stop() else: LOG.error("Bind failed again. Giving up.") reactor.stop() else: LOG.info("Listening on 127.0.0.1:%d" % dc_port) dtMain.startConnecting()
def updateFailed(self, why): self.busy = False LOG.warning("Dconfig Update Failed: %s" % why) self.scheduleUpdate(cfg.dconfig_push_interval)
class IRCStateManager(object): implements(IDtellaStateObserver) def __init__(self, main, ircs=None, uuid_generator=None): self.main = main self.ircs = ircs self.syncd = False self.uuid_generator = uuid_generator # inick.lower() -> User object # *also* indexed by uuid.lower(), if those are enabled. self.users = {} # If we're using UUIDs, create dicts to keep track of them. if self.uuid_generator: # uuid.lower() -> Node() self.dt_uuids = {} # Set of all User()s in the Dtella channel. self.chanusers = set() self.topic = "" self.topic_whoset = "" # always a dnick self.topic_locked = False self.moderated = False # string -> compiled regex self.chanbans = {} # string -> (compiled regex, reason) self.qlines = {} # Network bans: (ip, mask), both ints. self.bans = set() # Set up the dc->irc bot. if self.uuid_generator: bot_uuid = self.uuid_generator() else: bot_uuid = None self.bot_user = User(cfg.dc_to_irc_bot, bot_uuid) # --- These methods are called from the IRC server --- def addMeToMain(self): CHECK(not self.syncd) self.syncd = True self.main.addIRCStateManager(self) def removeMeFromMain(self): # After calling this, I'm basically a dead object. self.main.removeIRCStateManager(self) def addUser(self, inick, uuid=None): # Start tracking a new IRC user. CHECK(inick.lower() not in self.users) if self.uuid_generator: CHECK(number_prefix(uuid)) CHECK(uuid.lower() not in self.users) # If the nick is a uuid, it must be its own uuid. if number_prefix(inick): CHECK(inick.lower() == uuid.lower()) else: CHECK(uuid is None) u = User(inick, uuid) # Don't allow nicks which match my prefix. if self.syncd and matches_dc_to_irc_prefix(u.inick): if self.ircs: self.ircs.event_KillUser(u) return self.users[inick.lower()] = u if self.uuid_generator: self.users[uuid.lower()] = u return u def removeUser(self, u, message=None): self.partChannel(u, message) # If inick is not a uuid, remove from index. if not (self.uuid_generator and number_prefix(u.inick)): CHECK(self.users.pop(u.inick.lower()) is u) # If a uuid is defined, remove from index. if self.uuid_generator: CHECK(self.users.pop(u.uuid.lower()) is u) def findUser(self, inick): # Note: inick may also be a uuid. return self.users[inick.lower()] def changeNick(self, u, new_inick): old_inick = u.inick if old_inick.lower() == new_inick.lower(): # TODO: report case changes to Dtella? u.inick = new_inick return if self.uuid_generator and number_prefix(new_inick): # If nick is changing to a uuid, it must be its own uuid. CHECK(new_inick.lower() == u.uuid.lower()) else: # Otherwise, the nick must not already exist. CHECK(new_inick.lower() not in self.users) if not (self.uuid_generator and number_prefix(u.inick)): # If old nick is not a uuid, remove it from the index. CHECK(self.users.pop(u.inick.lower()) is u) # Don't allow IRC nicks which match my prefix. conflicted = (self.syncd and matches_dc_to_irc_prefix(new_inick)) if conflicted: # Report exit from Dtella before updating nick. self.partChannel(u) # Update nick. Note that UUID is unchanged. u.inick = new_inick self.users[new_inick.lower()] = u if conflicted: # Nick matches my prefix, diediedie! if self.ircs: self.ircs.event_KillUser(u) self.removeUser(u) return scfg = getServiceConfig() # Report the change to Dtella. if u in self.chanusers: try: osm = self.getOnlineStateManager() except NotOnline: return infoindex = scfg.chan_umodes.getUserInfoIndex(u) chunks = [] osm.bsm.addChatChunk( chunks, cfg.irc_to_dc_bot, "%s is now known as %s" % (irc_to_dc(old_inick), irc_to_dc(new_inick))) osm.bsm.addNickChunk(chunks, irc_to_dc(old_inick), 0xFF) osm.bsm.addNickChunk(chunks, irc_to_dc(new_inick), infoindex) osm.bsm.sendBridgeChange(chunks) def isMyBot(self, inick): # Identify the dc_to_irc_bot, by either nick or uuid. return (inick.lower() == self.bot_user.inick.lower() or inick.lower() == self.bot_user.uuid.lower()) def joinChannel(self, u): if u in self.chanusers: LOG.error("joinChannel: %r already in channel." % u) return self.chanusers.add(u) u.chanmodes.clear() try: osm = self.getOnlineStateManager() except NotOnline: return scfg = getServiceConfig() infoindex = scfg.chan_umodes.getUserInfoIndex(u) chunks = [] osm.bsm.addNickChunk(chunks, irc_to_dc(u.inick), infoindex) osm.bsm.sendBridgeChange(chunks) def partChannel(self, u, message=None): # Remove user from the Dtella channel. Return True if successful. try: self.chanusers.remove(u) except KeyError: return False try: osm = self.getOnlineStateManager() except NotOnline: # Removal was successful, even if it's not broadcasted. return True chunks = [] if message: osm.bsm.addChatChunk(chunks, cfg.irc_to_dc_bot, message) osm.bsm.addNickChunk(chunks, irc_to_dc(u.inick), 0xFF) osm.bsm.sendBridgeChange(chunks) return True def findDtellaNode(self, inick=None, dnick=None): # Try to find a user on Dtella. # note: inick may also be a UUID. try: osm = self.getOnlineStateManager() except NotOnline: return None if inick: # First, try parsing inick as a UUID. if self.uuid_generator: try: return self.dt_uuids[inick.lower()] except KeyError: pass try: dnick = dc_from_irc(inick) except NickError: pass if not dnick: return None try: return osm.nkm.lookupNick(dnick) except KeyError: return None def kickDtellaNode(self, n, l33t_inick, reason, send_quit=True): # Handler for KICK/KILL of an existing Dtella user. # Caller should get 'n' from findDtellaNode() # Exception shouldn't happen here; don't catch. osm = self.getOnlineStateManager() # Send a kick message. chunks = [] osm.bsm.addKickChunk(chunks, n, irc_to_dc(l33t_inick), reason, rejoin=True, silent=False) osm.bsm.sendBridgeChange(chunks) # Forget this nick. if not send_quit: del n.inick osm.nkm.removeNode(n, "Kicked") n.setNoUser() def sendPrivateMessage(self, n, src_inick, text, flags): # Send a private message to a Dtella node. # Caller should get 'n' from findDtellaNode() # Exception shouldn't happen here; don't catch. osm = self.getOnlineStateManager() chunks = [] osm.bsm.addMessageChunk(chunks, irc_to_dc(src_inick), text, flags) osm.bsm.sendPrivateBridgeChange(n, chunks) def sendChannelMessage(self, src_inick, text, flags): # Send text to all Dtella nodes. try: osm = self.getOnlineStateManager() except NotOnline: return chunks = [] osm.bsm.addChatChunk(chunks, irc_to_dc(src_inick), text, flags) osm.bsm.sendBridgeChange(chunks) def setTopic(self, whoset, topic): try: # DC nick dnick = dc_from_irc(whoset) except NickError: # IRC nick dnick = irc_to_dc(whoset) self.topic = topic self.topic_whoset = dnick try: osm = self.getOnlineStateManager() except NotOnline: return chunks = [] osm.bsm.addTopicChunk(chunks, dnick, topic, changed=True) osm.bsm.sendBridgeChange(chunks) def setModerated(self, whoset, on_off): self.moderated = on_off try: osm = self.getOnlineStateManager() except NotOnline: return if on_off: action = "enabled" else: action = "disabled" chunks = [] osm.bsm.addModeratedChunk(chunks, on_off) osm.bsm.addChatChunk(chunks, cfg.irc_to_dc_bot, "%s %s moderation." % (irc_to_dc(whoset), action)) osm.bsm.sendBridgeChange(chunks) def setTopicLocked(self, whoset, on_off): self.topic_locked = on_off try: osm = self.getOnlineStateManager() except NotOnline: return if on_off: action = "locked" else: action = "unlocked" chunks = [] osm.bsm.addChatChunk(chunks, cfg.irc_to_dc_bot, "%s %s the topic." % (irc_to_dc(whoset), action)) osm.bsm.sendBridgeChange(chunks) def setChannelBan(self, whoset, on_off, banmask): if on_off: self.chanbans[banmask] = wild_to_regex(banmask) action = "added" else: self.chanbans.pop(banmask, None) action = "removed" LOG.debug("bans= %s" % self.chanbans.keys()) try: osm = self.getOnlineStateManager() except NotOnline: return chunks = [] osm.bsm.addChatChunk( chunks, cfg.irc_to_dc_bot, "%s %s ban: %s" % (irc_to_dc(whoset), action, banmask)) osm.bsm.sendBridgeChange(chunks) def setChannelUserModes(self, whoset, u, changes): # changes: dict of {mode -> on_off} if u not in self.chanusers: LOG.error("setChannelUserModes: %r not in channel." % u) return scfg = getServiceConfig() # Save old index, apply changes, and get new index. old_infoindex = scfg.chan_umodes.getUserInfoIndex(u) for mode, on_off in changes.iteritems(): if on_off: u.chanmodes.add(mode) else: u.chanmodes.discard(mode) new_infoindex = scfg.chan_umodes.getUserInfoIndex(u) try: osm = self.getOnlineStateManager() except NotOnline: return chunks = [] if new_infoindex == old_infoindex: friendly_change = "well that was pointless" else: friendly_change = "%s -> %s" % ( scfg.chan_umodes.friendly[old_infoindex], scfg.chan_umodes.friendly[new_infoindex]) osm.bsm.addNickChunk(chunks, irc_to_dc(u.inick), new_infoindex) osm.bsm.addChatChunk( chunks, cfg.irc_to_dc_bot, "%s set mode %s for %s: %s" % (irc_to_dc(whoset), self.formatChannelUserModes(changes), irc_to_dc(u.inick), friendly_change)) osm.bsm.sendBridgeChange(chunks) def addQLine(self, nickmask, reason): nick_re = wild_to_regex(nickmask) # After EOS, auto-remove any Q-lines which conflict with mine. # This may cause a conflicting bridge to abort. if self.syncd and nick_re.match(cfg.dc_to_irc_prefix): if self.ircs: self.ircs.pushRemoveQLine(nickmask) LOG.info("Conflicted Q-line: " + nickmask) return self.qlines[nickmask] = (nick_re, reason) LOG.info("Added Q-line: " + nickmask) def removeQLine(self, nickmask): self.qlines.pop(nickmask, None) LOG.info("Removed Q-line: " + nickmask) # If some other bridge removes our reservation, abort. if self.syncd and (nickmask == cfg.dc_to_irc_prefix + "*"): LOG.error("My own Q-line was removed! Terminating.") if self.ircs: self.ircs.transport.loseConnection() reactor.stop() def setNetworkBan(self, cidr, on_off): # See if this is a valid 1.2.3.4/5 CIDR string. try: ipmask = ipv4.CidrStringToIPMask(cidr) except ValueError, e: LOG.error("Bad CIDR string: %s") return # Convert back, to get a normalized string. cidr = ipv4.IPMaskToCidrString(ipmask) if on_off: if ipmask in self.bans: LOG.warning("Duplicate ban: %s" % cidr) return self.bans.add(ipmask) LOG.info("Added ban: %s" % cidr) else: try: self.bans.remove(ipmask) except KeyError: LOG.warning("Ban not found: %s" % cidr) return LOG.info("Removed ban: %s" % cidr) # If we're online, broadcast the ban. try: osm = self.getOnlineStateManager() except NotOnline: pass else: ip, mask = ipmask chunks = [] osm.bsm.addBanChunk(chunks, ip, mask, on_off) osm.bsm.sendBridgeChange(chunks) # If we're even sort-of online, update local bans. if self.main.osm: self.main.osm.banm.scheduleRebuildBans()