Esempio n. 1
0
 def __init__(self, ircd, name):
     if not isValidChannelName(name):
         raise InvalidChannelNameError
     self.ircd = ircd
     self.name = name[:self.ircd.config.get("channel_name_length", 64)]
     self.users = WeakKeyDictionary()
     self.modes = {}
     self.existedSince = now()
     self.topic = ""
     self.topicSetter = ""
     self.topicTime = now()
     self._metadata = CaseInsensitiveDictionary()
     self.cache = {}
Esempio n. 2
0
 def load(self):
     self.targetIndex = CaseInsensitiveDictionary()
     # We'll run a cleaner every minute. The reason we do this is that, since there can be multiple
     # notified users for a target, the index is implemented as a CaseInsensitiveDictionary pointing
     # to WeakSets as opposed to simply a CaseInsensitiveDictionary(WeakValueDictionary). Because of
     # this, we'll need to check for empty WeakSets on occasion and garbage-collect them to prevent
     # memory getting full.
     self.indexCleaner = LoopingCall(self.cleanIndex)
     self.indexCleaner.start(60, now=False)
     if "unloading-monitor" in self.ircd.dataCache:
         del self.ircd.dataCache["unloading-monitor"]
         return
     if "cap-add" in self.ircd.functionCache:
         self.ircd.functionCache["cap-add"]("metadata-notify")
Esempio n. 3
0
	def __init__(self, configFileName):
		self.config = Config(self, configFileName)
		
		self.boundPorts = {}
		self.loadedModules = {}
		self._loadedModuleData = {}
		self._unloadingModules = {}
		self.commonModules = set()
		self.userCommands = {}
		self.serverCommands = {}
		self.channelModes = ({}, {}, {}, {})
		self.channelStatuses = {}
		self.channelStatusSymbols = {}
		self.channelStatusOrder = []
		self.channelModeTypes = {}
		self.userModes = ({}, {}, {}, {})
		self.userModeTypes = {}
		self.actions = {}
		self.storage = None
		self.storageSyncer = None
		self.dataCache = {}
		self.functionCache = {}
		
		self.serverID = None
		self.name = None
		self.isupport_tokens = {
			"CASEMAPPING": "strict-rfc1459",
			"CHANTYPES": "#",
		}
		self._uid = self._genUID()
		
		self.users = {}
		self.userNicks = CaseInsensitiveDictionary()
		self.channels = CaseInsensitiveDictionary(WeakValueDictionary)
		self.servers = {}
		self.serverNames = CaseInsensitiveDictionary()
		self.recentlyQuitUsers = {}
		self.recentlyQuitServers = {}
		self.recentlyDestroyedChannels = CaseInsensitiveDictionary()
		self.pruneRecentlyQuit = None
		self.pruneRecentChannels = None
		
		self._logFilter = LogLevelFilterPredicate()
		filterObserver = FilteringLogObserver(globalLogPublisher, (self._logFilter,))
		self.log = Logger("txircd", observer=filterObserver)
		
		self.startupTime = None
Esempio n. 4
0
	def __init__(self, ircd, name):
		if not isValidChannelName(name):
			raise InvalidChannelNameError
		self.ircd = ircd
		self.name = name[:self.ircd.config.get("channel_name_length", 64)]
		self.users = WeakKeyDictionary()
		self.modes = {}
		self.existedSince = now()
		self.topic = ""
		self.topicSetter = ""
		self.topicTime = now()
		self._metadata = CaseInsensitiveDictionary()
		self.cache = {}
Esempio n. 5
0
    def __init__(self, ircd, ip, uuid=None, host=None):
        self.ircd = ircd
        self.uuid = ircd.createUUID() if uuid is None else uuid

        registrationTimeout = self.ircd.config.get("user_registration_timeout",
                                                   10)

        self.nick = None
        self.ident = None
        if ip[0] == ":":  # Normalize IPv6 address for IRC
            ip = "0{}".format(ip)
        if host is None:
            self.realHost = ip
        else:
            self.realHost = host
        self.ip = ip
        self._hostStack = []
        self._hostsByType = {}
        self.gecos = None
        self._metadata = CaseInsensitiveDictionary()
        self.cache = {}
        self.channels = []
        self.modes = {}
        self.connectedSince = now()
        self.nickSince = now()
        self.idleSince = now()
        self._registerHolds = set(("connection", "dns", "NICK", "USER"))
        self.disconnectedDeferred = Deferred()
        self._messageBatches = {}
        self._errorBatchName = None
        self._errorBatch = []
        self.ircd.users[self.uuid] = self
        self.localOnly = False
        self.secureConnection = False
        self._pinger = LoopingCall(self._ping)
        self._registrationTimeoutTimer = reactor.callLater(
            registrationTimeout, self._timeoutRegistration)
        self._connectHandlerTimer = None
        self._startDNSResolving(registrationTimeout)
Esempio n. 6
0
	def load(self):
		self.targetIndex = CaseInsensitiveDictionary()
		# We'll run a cleaner every minute. The reason we do this is that, since there can be multiple
		# notified users for a target, the index is implemented as a CaseInsensitiveDictionary pointing
		# to WeakSets as opposed to simply a CaseInsensitiveDictionary(WeakValueDictionary). Because of
		# this, we'll need to check for empty WeakSets on occasion and garbage-collect them to prevent
		# memory getting full.
		self.indexCleaner = LoopingCall(self.cleanIndex)
		self.indexCleaner.start(60, now=False)
		if "unloading-monitor" in self.ircd.dataCache:
			del self.ircd.dataCache["unloading-monitor"]
			return
		if "cap-add" in self.ircd.functionCache:
			self.ircd.functionCache["cap-add"]("metadata-notify")
Esempio n. 7
0
File: ircd.py Progetto: ojii/txircd
    def __init__(self, config, options=None):
        reactor.addSystemEventTrigger("before", "shutdown", self.cleanup)
        self.dead = False

        self.config = config
        self.version = "txircd.{}".format(__version__)
        self.created = now()
        self.token = uuid.uuid1()
        self.servers = CaseInsensitiveDictionary()
        self.users = CaseInsensitiveDictionary()
        self.whowas = CaseInsensitiveDictionary()
        self.channels = DefaultCaseInsensitiveDictionary(self.ChannelFactory)
        self.peerConnections = {}
        self.db = None
        # self.stats = None
        # self.stats_timer = LoopingCall(self.flush_stats)
        # self.stats_data = {
        #    "bytes_in": 0,
        #    "bytes_out": 0,
        #    "lines_in": 0,
        #    "lines_out": 0,
        #    "total_bytes_in": 0,
        #    "total_bytes_out": 0,
        #    "total_lines_in": 0,
        #    "total_lines_out": 0,
        #    "connections": 0,
        #    "total_connections": 0
        # }
        self.xlines = {
            "G": CaseInsensitiveDictionary(),
            "K": CaseInsensitiveDictionary(),
            "Z": CaseInsensitiveDictionary(),
            "E": CaseInsensitiveDictionary(),
            "Q": CaseInsensitiveDictionary(),
            "SHUN": CaseInsensitiveDictionary(),
        }
        self.xline_match = {
            "G": ["{ident}@{host}", "{ident}@{ip}"],
            "K": ["{ident}@{host}", "{ident}@{ip}"],
            "Z": ["{ip}"],
            "E": ["{ident}@{host}", "{ident}@{ip}"],
            "Q": ["{nick}"],
            "SHUN": ["{ident}@{host}", "{ident}@{ip}"],
        }

        if not options:
            options = {}
        self.load_options(options)
Esempio n. 8
0
	def __init__(self, ircd, ip, uuid = None, host = None):
		self.ircd = ircd
		self.uuid = ircd.createUUID() if uuid is None else uuid
		self.nick = None
		self.ident = None
		if ip[0] == ":": # Normalize IPv6 address for IRC
			ip = "0{}".format(ip)
		if host is None:
			try:
				resolvedHost = gethostbyaddr(ip)[0]
				# First half of host resolution done, run second half to prevent rDNS spoofing.
				# Refuse hosts that are too long as well.
				if ip == gethostbyname(resolvedHost) and len(resolvedHost) <= self.ircd.config.get("hostname_length", 64) and isValidHost(resolvedHost):
					host = resolvedHost
				else:
					host = ip
			except (herror, gaierror):
				host = ip
		self.realHost = host
		self.ip = ip
		self._hostStack = []
		self._hostsByType = {}
		self.gecos = None
		self._metadata = CaseInsensitiveDictionary()
		self.cache = {}
		self.channels = []
		self.modes = {}
		self.connectedSince = now()
		self.nickSince = now()
		self.idleSince = now()
		self._registerHolds = set(("connection", "NICK", "USER"))
		self.disconnectedDeferred = Deferred()
		self._messageBatches = {}
		self._errorBatchName = None
		self._errorBatch = []
		self.ircd.users[self.uuid] = self
		self.localOnly = False
		self.secureConnection = False
		self._pinger = LoopingCall(self._ping)
		self._registrationTimeoutTimer = reactor.callLater(self.ircd.config.get("user_registration_timeout", 10), self._timeoutRegistration)
Esempio n. 9
0
	def __init__(self, ircd, ip, uuid = None, host = None):
		self.ircd = ircd
		self.uuid = ircd.createUUID() if uuid is None else uuid
		
		registrationTimeout = self.ircd.config.get("user_registration_timeout", 10)
		
		self.nick = None
		self.ident = None
		if ip[0] == ":": # Normalize IPv6 address for IRC
			ip = "0{}".format(ip)
		if host is None:
			self.realHost = ip
		else:
			self.realHost = host
		self.ip = ip
		self._hostStack = []
		self._hostsByType = {}
		self.gecos = None
		self._metadata = CaseInsensitiveDictionary()
		self.cache = {}
		self.channels = []
		self.modes = {}
		self.connectedSince = now()
		self.nickSince = now()
		self.idleSince = now()
		self._registerHolds = set(("connection", "dns", "NICK", "USER"))
		self.disconnectedDeferred = Deferred()
		self._messageBatches = {}
		self._errorBatchName = None
		self._errorBatch = []
		self.ircd.users[self.uuid] = self
		self.localOnly = False
		self.secureConnection = False
		self._pinger = LoopingCall(self._ping)
		self._registrationTimeoutTimer = reactor.callLater(registrationTimeout, self._timeoutRegistration)
		self._connectHandlerTimer = None
		self._startDNSResolving(registrationTimeout)
Esempio n. 10
0
class IRCd(Service):
	def __init__(self, configFileName):
		self.config = Config(self, configFileName)
		
		self.boundPorts = {}
		self.loadedModules = {}
		self._loadedModuleData = {}
		self._unloadingModules = {}
		self.commonModules = set()
		self.userCommands = {}
		self.serverCommands = {}
		self.channelModes = ({}, {}, {}, {})
		self.channelStatuses = {}
		self.channelStatusSymbols = {}
		self.channelStatusOrder = []
		self.channelModeTypes = {}
		self.userModes = ({}, {}, {}, {})
		self.userModeTypes = {}
		self.actions = {}
		self.storage = None
		self.storageSyncer = None
		self.dataCache = {}
		self.functionCache = {}
		
		self.serverID = None
		self.name = None
		self.isupport_tokens = {
			"CASEMAPPING": "strict-rfc1459",
			"CHANTYPES": "#",
		}
		self._uid = self._genUID()
		
		self.users = {}
		self.userNicks = CaseInsensitiveDictionary()
		self.channels = CaseInsensitiveDictionary(WeakValueDictionary)
		self.servers = {}
		self.serverNames = CaseInsensitiveDictionary()
		self.recentlyQuitUsers = {}
		self.recentlyQuitServers = {}
		self.recentlyDestroyedChannels = CaseInsensitiveDictionary()
		self.pruneRecentlyQuit = None
		self.pruneRecentChannels = None
		
		self._logFilter = LogLevelFilterPredicate()
		filterObserver = FilteringLogObserver(globalLogPublisher, (self._logFilter,))
		self.log = Logger("txircd", observer=filterObserver)
		
		self.startupTime = None
	
	def startService(self):
		self.log.info("Starting up...")
		self.startupTime = now()
		self.log.info("Loading configuration...")
		self.config.reload()
		self.name = self.config["server_name"]
		self.serverID = self.config["server_id"]
		self.log.info("Loading storage...")
		self.storage = shelve.open(self.config["datastore_path"], writeback=True)
		self.storageSyncer = LoopingCall(self.storage.sync)
		self.storageSyncer.start(self.config.get("storage_sync_interval", 5), now=False)
		self.log.info("Starting processes...")
		self.pruneRecentlyQuit = LoopingCall(self.pruneQuit)
		self.pruneRecentlyQuit.start(10, now=False)
		self.pruneRecentChannels = LoopingCall(self.pruneChannels)
		self.pruneRecentChannels.start(15, now=False)
		self.log.info("Loading modules...")
		self._loadModules()
		self.log.info("Binding ports...")
		self._bindPorts()
		self.log.info("txircd started!")
		try:
			self._logFilter.setLogLevelForNamespace("txircd", LogLevel.levelWithName(self.config["log_level"]))
		except (KeyError, InvalidLogLevelError):
			self._logFilter.setLogLevelForNamespace("txircd", LogLevel.warn)
		self.runActionStandard("startup")
	
	def stopService(self):
		stopDeferreds = []
		self.log.info("Disconnecting servers...")
		serverList = self.servers.values() # Take the list of server objects
		self.servers = {} # And then destroy the server dict to inhibit server objects generating lots of noise
		for server in serverList:
			if server.nextClosest == self.serverID:
				stopDeferreds.append(server.disconnectedDeferred)
				allUsers = self.users.keys()
				for user in allUsers:
					if user[:3] == server.serverID:
						del self.users[user]
				server.transport.loseConnection()
		self.log.info("Disconnecting users...")
		userList = self.users.values() # Basically do the same thing I just did with the servers
		self.users = {}
		for user in userList:
			if user.transport:
				stopDeferreds.append(user.disconnectedDeferred)
				user.transport.loseConnection()
		self.log.info("Unloading modules...")
		moduleList = self.loadedModules.keys()
		for module in moduleList:
			self._unloadModule(module, False) # Incomplete unload is done to save time and because side effects are destroyed anyway
		self.log.info("Stopping processes...")
		if self.pruneRecentlyQuit.running:
			self.pruneRecentlyQuit.stop()
		if self.pruneRecentChannels.running:
			self.pruneRecentChannels.stop()
		self.log.info("Closing data storage...")
		if self.storageSyncer.running:
			self.storageSyncer.stop()
		self.storage.close() # a close() will sync() also
		self.log.info("Releasing ports...")
		stopDeferreds.extend(self._unbindPorts())
		return DeferredList(stopDeferreds)
	
	def _loadModules(self):
		for module in getPlugins(IModuleData, txircd.modules):
			if module.name in self.loadedModules:
				continue
			if module.core or module.name in self.config["modules"]:
				self._loadModuleData(module)
		for moduleName in self.config["modules"]:
			if moduleName not in self.loadedModules:
				self.log.warn("The module {module} failed to load.", module=moduleName)
	
	def loadModule(self, moduleName):
		"""
		Loads a module of the specified name.
		Raises ModuleLoadError if the module cannot be loaded.
		If the specified module is currently being unloaded, returns the
		DeferredList specified by the module when it was unloading with a
		callback to try to load the module again once it succeeds.
		"""
		if moduleName in self._unloadingModules:
			deferList = self._unloadingModules[moduleName]
			deferList.addCallback(self._tryLoadAgain, moduleName)
			return deferList
		for module in getPlugins(IModuleData, txircd.modules):
			if module.name == moduleName:
				rebuild(importlib.import_module(module.__module__)) # getPlugins doesn't recompile modules, so let's do that ourselves.
				self._loadModuleData(module)
				self.log.info("Loaded module {module}.", module=moduleName)
				break
	
	def _tryLoadAgain(self, _, moduleName):
		self.loadModule(moduleName)
	
	def _loadModuleData(self, module):
		if not IModuleData.providedBy(module):
			raise ModuleLoadError ("???", "Module does not implement module interface")
		if not module.name:
			raise ModuleLoadError ("???", "Module did not provide a name")
		if module.name in self.loadedModules:
			self.log.debug("Not loading {module.name} because it's already loaded", module=module)
			return
		
		self.log.debug("Beginning to load {module.name}...", module=module)
		module.hookIRCd(self)
		try:
			module.verifyConfig(self.config)
		except ConfigError as e:
			raise ModuleLoadError(module.name, e)
		
		self.log.debug("Loading hooks from {module.name}...", module=module)
		moduleData = {
			"channelmodes": module.channelModes(),
			"usermodes": module.userModes(),
			"actions": module.actions(),
			"usercommands": module.userCommands(),
			"servercommands": module.serverCommands()
		}
		newChannelModes = ({}, {}, {}, {})
		newChannelStatuses = {}
		newUserModes = ({}, {}, {}, {})
		newActions = {}
		newUserCommands = {}
		newServerCommands = {}
		common = False
		self.log.debug("Processing hook data from {module.name}...", module=module)
		for mode in moduleData["channelmodes"]:
			if mode[0] in self.channelModeTypes:
				raise ModuleLoadError (module.name, "Tries to implement channel mode +{} when that mode is already implemented.".format(mode[0]))
			if not IMode.providedBy(mode[2]):
				raise ModuleLoadError (module.name, "Returns a channel mode object (+{}) that doesn't implement IMode.".format(mode[0]))
			if mode[1] == ModeType.Status:
				if mode[4] in self.channelStatusSymbols:
					raise ModuleLoadError (module.name, "Tries to create a channel rank with symbol {} when that symbol is already in use.".format(mode[4]))
				try:
					newChannelStatuses[mode[0]] = (mode[4], mode[3], mode[2])
				except IndexError:
					raise ModuleLoadError (module.name, "Specifies channel status mode {} without a rank or symbol".format(mode[0]))
			else:
				newChannelModes[mode[1]][mode[0]] = mode[2]
			common = True
		for mode in moduleData["usermodes"]:
			if mode[0] in self.userModeTypes:
				raise ModuleLoadError (module.name, "Tries to implement user mode +{} when that mode is already implemented.".format(mode[0]))
			if not IMode.providedBy(mode[2]):
				raise ModuleLoadError (module.name, "Returns a user mode object (+{}) that doesn't implement IMode.".format(mode[0]))
			newUserModes[mode[1]][mode[0]] = mode[2]
			common = True
		for action in moduleData["actions"]:
			if action[0] not in newActions:
				newActions[action[0]] = [(action[2], action[1])]
			else:
				newActions[action[0]].append((action[2], action[1]))
		for command in moduleData["usercommands"]:
			if not ICommand.providedBy(command[2]):
				raise ModuleLoadError (module.name, "Returns a user command object ({}) that doesn't implement ICommand.".format(command[0]))
			if command[0] not in newUserCommands:
				newUserCommands[command[0]] = []
			newUserCommands[command[0]].append((command[2], command[1]))
		for command in moduleData["servercommands"]:
			if not ICommand.providedBy(command[2]):
				raise ModuleLoadError (module.name, "Returns a server command object ({}) that doesnt implement ICommand.".format(command[0]))
			if command[0] not in newServerCommands:
				newServerCommands[command[0]] = []
			newServerCommands[command[0]].append((command[2], command[1]))
			common = True
		if not common:
			common = module.requiredOnAllServers

		self.log.debug("Loaded data from {module.name}; committing data and calling hooks...", module=module)
		
		module.load()
		
		self.loadedModules[module.name] = module
		self._loadedModuleData[module.name] = moduleData
		if common:
			self.commonModules.add(module.name)
		
		self.runActionStandard("moduleload", module.name)
		
		for modeType, typeSet in enumerate(newChannelModes):
			for mode, implementation in typeSet.iteritems():
				self.channelModeTypes[mode] = modeType
				self.channelModes[modeType][mode] = implementation
		for mode, data in newChannelStatuses.iteritems():
			self.channelModeTypes[mode] = ModeType.Status
			self.channelStatuses[mode] = data
			self.channelStatusSymbols[data[0]] = mode
			for index, status in enumerate(self.channelStatusOrder):
				if self.channelStatuses[status][1] < data[1]:
					self.channelStatusOrder.insert(index, mode)
					break
			else:
				self.channelStatusOrder.append(mode)
		for modeType, typeSet in enumerate(newUserModes):
			for mode, implementation in typeSet.iteritems():
				self.userModeTypes[mode] = modeType
				self.userModes[modeType][mode] = implementation
		for action, actionList in newActions.iteritems():
			if action not in self.actions:
				self.actions[action] = []
			for actionData in actionList:
				for index, handlerData in enumerate(self.actions[action]):
					if handlerData[1] < actionData[1]:
						self.actions[action].insert(index, actionData)
						break
				else:
					self.actions[action].append(actionData)
		for command, dataList in newUserCommands.iteritems():
			if command not in self.userCommands:
				self.userCommands[command] = []
			for data in dataList:
				for index, cmd in enumerate(self.userCommands[command]):
					if cmd[1] < data[1]:
						self.userCommands[command].insert(index, data)
						break
				else:
					self.userCommands[command].append(data)
		for command, dataList in newServerCommands.iteritems():
			if command not in self.serverCommands:
				self.serverCommands[command] = []
			for data in dataList:
				for index, cmd in enumerate(self.serverCommands[command]):
					if cmd[1] < data[1]:
						self.serverCommands[command].insert(index, data)
						break
				else:
					self.serverCommands[command].append(data)
		
		self.log.debug("Module {module.name} is now fully loaded.", module=module)
	
	def unloadModule(self, moduleName):
		"""
		Unloads the loaded module with the given name. Raises ValueError
		if the module cannot be unloaded because it's a core module.
		"""
		self._unloadModule(moduleName, True)
		self.log.info("Unloaded module {module}.", module=moduleName)
	
	def _unloadModule(self, moduleName, fullUnload):
		unloadDeferreds = []
		if moduleName not in self.loadedModules:
			return
		module = self.loadedModules[moduleName]
		if fullUnload and module.core:
			raise ValueError ("The module you're trying to unload is a core module.")
		moduleData = self._loadedModuleData[moduleName]
		d = module.unload()
		if d is not None:
			unloadDeferreds.append(d)
		
		if fullUnload:
			d = module.fullUnload()
			if d is not None:
				unloadDeferreds.append(d)
		
		for modeData in moduleData["channelmodes"]:
			if fullUnload: # Unset modes on full unload
				if modeData[1] == ModeType.Status:
					for channel in self.channels.itervalues():
						removeFromChannel = []
						for user, userData in channel.user.iteritems():
							if modeData[0] in userData["status"]:
								removeFromChannel.append((False, modeData[0], user.uuid))
						channel.setModes(removeFromChannel, self.serverID)
				elif modeData[1] == ModeType.List:
					for channel in self.channels.itervalues():
						if modeData[0] in channel.modes:
							removeFromChannel = []
							for paramData in channel.modes[modeData[0]]:
								removeFromChannel.append((False, modeData[0], paramData[0]))
							channel.setModes(removeFromChannel, self.serverID)
				else:
					for channel in self.channels.itervalues():
						if modeData[0] in channel.modes:
							channel.setModes([(False, modeData[0], channel.modes[modeData[0]])], self.serverID)
			
			if modeData[1] == ModeType.Status:
				del self.channelStatuses[modeData[0]]
				del self.channelStatusSymbols[modeData[4]]
				self.channelStatusOrder.remove(modeData[0])
			else:
				del self.channelModes[modeData[1]][modeData[0]]
			del self.channelModeTypes[modeData[0]]
		for modeData in moduleData["usermodes"]:
			if fullUnload: # Unset modes on full unload
				if modeData[1] == ModeType.List:
					for user in self.users.itervalues():
						if modeData[0] in user.modes:
							removeFromUser = []
							for paramData in user.modes[modeData[0]]:
								removeFromUser.append((False, modeData[0], paramData[0]))
							user.setModes(removeFromUser, self.serverID)
				else:
					for user in self.users.itervalues():
						if modeData[0] in user.modes:
							user.setModes([(False, modeData[0], user.modes[modeData[0]])], self.serverID)
			
			del self.userModes[modeData[1]][modeData[0]]
			del self.userModeTypes[modeData[0]]
		for actionData in moduleData["actions"]:
			self.actions[actionData[0]].remove((actionData[2], actionData[1]))
			if not self.actions[actionData[0]]:
				del self.actions[actionData[0]]
		for commandData in moduleData["usercommands"]:
			self.userCommands[commandData[0]].remove((commandData[2], commandData[1]))
			if not self.userCommands[commandData[0]]:
				del self.userCommands[commandData[0]]
		for commandData in moduleData["servercommands"]:
			self.serverCommands[commandData[0]].remove((commandData[2], commandData[1]))
			if not self.serverCommands[commandData[0]]:
				del self.serverCommands[commandData[0]]
		
		del self.loadedModules[moduleName]
		del self._loadedModuleData[moduleName]
		
		if fullUnload:
			self.runActionStandard("moduleunload", module.name)
		
		if unloadDeferreds:
			deferList = DeferredList(unloadDeferreds)
			self._unloadingModules[moduleName] = deferList
			deferList.addCallback(self._removeFromUnloadingList, moduleName)
			return deferList
	
	def _removeFromUnloadingList(self, _, moduleName):
		del self._unloadingModules[moduleName]
	
	def reloadModule(self, moduleName):
		"""
		Reloads the module with the given name.
		Returns a DeferredList if the module unloads with one or more Deferreds.
		May raise ModuleLoadError if the module cannot be loaded.
		"""
		deferList = self._unloadModule(moduleName, False)
		if deferList is None:
			deferList = self.loadModule(moduleName)
		else:
			deferList.addCallback(lambda result: self.loadModule(moduleName))
		return deferList

	def verifyConfig(self, config):
		# IRCd
		if "server_name" not in config:
			raise ConfigValidationError("server_name", "required item not found in configuration file.")
		if not isinstance(config["server_name"], basestring):
			raise ConfigValidationError("server_name", "value must be a string")
		if len(config["server_name"]) > 64:
			config["server_name"] = config["server_name"][:64]
			self.logConfigValidationWarning("server_name", "value is too long and has been truncated", config["server_name"])
		if not re.match(r"^[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+$", config["server_name"]):
			raise ConfigValidationError("server_name", "server name must look like a valid hostname.")
		if "server_id" in config:
			if not isinstance(config["server_id"], basestring):
				raise ConfigValidationError("server_id", "value must be a string")
			else:
				config["server_id"] = config["server_id"].upper()
		else:
			randFromName = random.Random(config["server_name"])
			serverID = randFromName.choice(string.digits) + randFromName.choice(string.digits + string.ascii_uppercase) + randFromName.choice(string.digits + string.ascii_uppercase)
			config["server_id"] = serverID
		if len(config["server_id"]) != 3 or not config["server_id"].isalnum() or not config["server_id"][0].isdigit():
			raise ConfigValidationError("server_id", "value must be a 3-character alphanumeric string starting with a number.")
		if "server_description" not in config:
			raise ConfigValidationError("server_description", "required item not found in configuration file.")
		if not isinstance(config["server_description"], basestring):
			raise ConfigValidationError("server_description", "value must be a string")
		if not config["server_description"]:
			raise ConfigValidationError("server_description", "value must not be an empty string")
		if len(config["server_description"]) > 255:
			config["server_description"] = config["server_description"][:255]
			self.logConfigValidationWarning("server_description", "value is too long and has been truncated", config["server_description"])
		if "network_name" not in config:
			raise ConfigValidationError("network_name", "required item not found in configuration file.")
		if not isinstance(config["network_name"], basestring):
			raise ConfigValidationError("network_name", "value must be a string")
		if not config["network_name"]:
			raise ConfigValidationError("network_name", "value must not be an empty string")
		if " " in config["network_name"]:
			raise ConfigValidationError("network_name", "value cannot have spaces")
		if len(config["network_name"]) > 32:
			config["network_name"] = config["network_name"][:32]
			self.logConfigValidationWarning("network_name", "value is too long", config["network_name"])
		if "bind_client" not in config:
			config["bind_client"] = [ "tcp:6667:interface={::}" ]
			self.logConfigValidationWarning("bind_client", "no default client binding specified", "[ \"tcp:6667:interface={::}\" ]")
		if not isinstance(config["bind_client"], list):
			raise ConfigValidationError("bind_client", "value must be a list")
		for bindDesc in config["bind_client"]:
			if not isinstance(bindDesc, basestring):
				raise ConfigValidationError("bind_client", "every entry must be a string")
		if "bind_server" not in config:
			config["bind_server"] = []
		if not isinstance(config["bind_server"], list):
			raise ConfigValidationError("bind_server", "value must be a list")
		for bindDesc in config["bind_server"]:
			if not isinstance(bindDesc, basestring):
				raise ConfigValidationError("bind_server", "every entry must be a string")
		if "modules" not in config:
			config["modules"] = []
		if not isinstance(config["modules"], list):
			raise ConfigValidationError("modules", "value must be a list")
		for module in config["modules"]:
			if not isinstance(module, basestring):
				raise ConfigValidationError("modules", "every entry must be a string")
		if "links" in config:
			if not isinstance(config["links"], dict):
				raise ConfigValidationError("links", "value must be a dictionary")
			for desc, server in config["links"].iteritems():
				if not isinstance(desc, basestring):
					raise ConfigValidationError("links", "\"{}\" is an invalid server description".format(desc))
				if not isinstance(server, dict):
					raise ConfigValidationError("links", "values for \"{}\" must be a dictionary".format(desc))
				if "connect_descriptor" not in server:
					raise ConfigValidationError("links", "server \"{}\" must contain a \"connect_descriptor\" value".format(desc))
				if "in_password" in server:
					if not isinstance(server["in_password"], basestring):
						config["links"][desc]["in_password"] = str(server["in_password"])
				if "out_password" in server:
					if not isinstance(server["out_password"], basestring):
						config["links"][desc]["out_password"] = str(server["out_password"])
		if "datastore_path" not in config:
			config["datastore_path"] = "data.db"
		if "storage_sync_interval" in config and not isinstance(config["storage_sync_interval"], int):
			raise ConfigValidationError(config["storage_sync_interval"], "invalid number")

		# Channels
		if "channel_name_length" in config:
			if not isinstance(config["channel_name_length"], int) or config["channel_name_length"] < 0:
				raise ConfigValidationError("channel_name_length", "invalid number")
			elif config["channel_name_length"] > 64:
				config["channel_name_length"] = 64
				self.logConfigValidationWarning("channel_name_length", "value is too large", 64)
		if "modes_per_line" in config:
			if not isinstance(config["modes_per_line"], int) or config["modes_per_line"] < 0:
				raise ConfigValidationError("modes_per_line", "invalid number")
			elif config["modes_per_line"] > 20:
				config["modes_per_line"] = 20
				self.logConfigValidationWarning("modes_per_line", "value is too large", 20)
		if "channel_listmode_limit" in config:
			if not isinstance(config["channel_listmode_limit"], int) or config["channel_listmode_limit"] < 0:
				raise ConfigValidationError("channel_listmode_limit", "invalid number")
			if config["channel_listmode_limit"] > 256:
				config["channel_listmode_limit"] = 256
				self.logConfigValidationWarning("channel_listmode_limit", "value is too large", 256)

		# Users
		if "user_registration_timeout" in config:
			if not isinstance(config["user_registration_timeout"], int) or config["user_registration_timeout"] < 0:
				raise ConfigValidationError("user_registration_timeout", "invalid number")
			elif config["user_registration_timeout"] < 10:
				config["user_registration_timeout"] = 10
				self.logConfigValidationWarning("user_registration_timeout", "timeout could be too short for clients to register in time", 10)
		if "user_ping_frequency" in config and (not isinstance(config["user_ping_frequency"], int) or config["user_ping_frequency"] < 0):
			raise ConfigValidationError("user_ping_frequency", "invalid number")
		if "hostname_length" in config:
			if not isinstance(config["hostname_length"], int) or config["hostname_length"] < 0:
				raise ConfigValidationError("hostname_length", "invalid number")
			elif config["hostname_length"] > 64:
				config["hostname_length"] = 64
				self.logConfigValidationWarning("hostname_length", "value is too large", 64)
			elif config["hostname_length"] < 4:
				config["hostname_length"] = 4
				self.logConfigValidationWarning("hostname_length", "value is too small", 4)
		if "ident_length" in config:
			if not isinstance(config["ident_length"], int) or config["ident_length"] < 0:
				raise ConfigValidationError("ident_length", "invalid number")
			elif config["ident_length"] > 12:
				config["ident_length"] = 12
				self.logConfigValidationWarning("ident_length", "value is too large", 12)
			elif config["ident_length"] < 1:
				config["ident_length"] = 1
				self.logConfigValidationWarning("ident_length", "value is too small", 1)
		if "gecos_length" in config:
			if not isinstance(config["gecos_length"], int) or config["gecos_length"] < 0:
				raise ConfigValidationError("gecos_length", "invalid number")
			elif config["gecos_length"] > 128:
				config["gecos_length"] = 128
				self.logConfigValidationWarning("gecos_length", "value is too large", 128)
			elif config["gecos_length"] < 1:
				config["gecos_length"] = 1
				self.logConfigValidationWarning("gecos_length", "value is too small", 1)
		if "user_listmode_limit" in config:
			if not isinstance(config["user_listmode_limit"], int) or config["user_listmode_limit"] < 0:
				raise ConfigValidationError("user_listmode_limit", "invalid number")
			if config["user_listmode_limit"] > 256:
				config["user_listmode_limit"] = 256
				self.logConfigValidationWarning("user_listmode_limit", "value is too large", 256)

		# Servers
		if "server_registration_timeout" in config:
			if not isinstance(config["server_registration_timeout"], int) or config["server_registration_timeout"] < 0:
				raise ConfigValidationError("server_registration_timeout", "invalid number")
			elif config["server_registration_timeout"] < 10:
				config["server_registration_timeout"] = 10
				self.logConfigValidationWarning("server_registration_timeout", "timeout could be too short for servers to register in time", 10)
		if "server_ping_frequency" in config and (not isinstance(config["server_ping_frequency"], int) or config["server_ping_frequency"] < 0):
			raise ConfigValidationError("server_ping_frequency", "invalid number")

		for module in self.loadedModules.itervalues():
			module.verifyConfig(config)

	def logConfigValidationWarning(self, key, message, default):
		self.log.warn("Config value \"{configKey}\" is invalid ({message}); the value has been set to a default of \"{default}\".", configKey=key, message=message, default=default)

	def rehash(self):
		"""
		Reloads the configuration file and applies changes.
		"""
		self.log.info("Rehashing...")
		self.config.reload()
		d = self._unbindPorts() # Unbind the ports that are bound
		if d: # And then bind the new ones
			DeferredList(d).addCallback(lambda result: self._bindPorts())
		else:
			self._bindPorts()
		
		try:
			self._logFilter.setLogLevelForNamespace("txircd", LogLevel.levelWithName(self.config["log_level"]))
		except (KeyError, InvalidLogLevelError):
			pass # If we can't set a new log level, we'll keep the old one
		
		for module in self.loadedModules.itervalues():
			module.rehash()
	
	def _bindPorts(self):
		for bindDesc in self.config["bind_client"]:
			try:
				endpoint = serverFromString(reactor, unescapeEndpointDescription(bindDesc))
			except ValueError as e:
				self.log.error(e)
				continue
			listenDeferred = endpoint.listen(UserFactory(self))
			listenDeferred.addCallback(self._savePort, bindDesc, "client")
			listenDeferred.addErrback(self._logNotBound, bindDesc)
		for bindDesc in self.config["bind_server"]:
			try:
				endpoint = serverFromString(reactor, unescapeEndpointDescription(bindDesc))
			except ValueError as e:
				self.log.error(e)
				continue
			listenDeferred = endpoint.listen(ServerListenFactory(self))
			listenDeferred.addCallback(self._savePort, bindDesc, "server")
			listenDeferred.addErrback(self._logNotBound, bindDesc)
	
	def _unbindPorts(self):
		deferreds = []
		for port in self.boundPorts.itervalues():
			d = port.stopListening()
			if d:
				deferreds.append(d)
		return deferreds
	
	def _savePort(self, port, desc, portType):
		self.boundPorts[desc] = port
		self.log.debug("Bound endpoint '{endpointDescription}' for {portType} connections.", endpointDescription=desc, portType=portType)
	
	def _logNotBound(self, err, desc):
		self.log.error("Could not bind '{endpointDescription}': {errorMsg}", endpointDescription=desc, errorMsg=err)
	
	def createUUID(self):
		"""
		Gets the next UUID for a new client.
		"""
		newUUID = self.serverID + self._uid.next()
		while newUUID in self.users: # It'll take over 1.5 billion connections to loop around, but we still
			newUUID = self.serverID + self._uid.next() # want to be extra safe and avoid collisions
		self.log.debug("Generated new UUID {uuid}", uuid=newUUID)
		return newUUID
	
	def _genUID(self):
		uid = "AAAAAA"
		while True:
			yield uid
			uid = self._incrementUID(uid)
	
	def _incrementUID(self, uid):
		if uid == "Z": # The first character must be a letter
			return "A" # So wrap that around
		if uid[-1] == "9":
			return self._incrementUID(uid[:-1]) + "A"
		if uid[-1] == "Z":
			return uid[:-1] + "0"
		return uid[:-1] + chr(ord(uid[-1]) + 1)
	
	def pruneQuit(self):
		compareTime = now() - timedelta(seconds=10)
		remove = []
		for uuid, timeQuit in self.recentlyQuitUsers.iteritems():
			if timeQuit < compareTime:
				remove.append(uuid)
		for uuid in remove:
			del self.recentlyQuitUsers[uuid]
		
		remove = []
		for serverID, timeQuit in self.recentlyQuitServers.iteritems():
			if timeQuit < compareTime:
				remove.append(serverID)
		for serverID in remove:
			del self.recentlyQuitServers[serverID]
	
	def pruneChannels(self):
		removeChannels = []
		for channel, remove in self.recentlyDestroyedChannels.iteritems():
			if remove:
				removeChannels.append(channel)
			elif channel not in self.channels:
				self.recentlyDestroyedChannels[channel] = True
		for channel in removeChannels:
			del self.recentlyDestroyedChannels[channel]
	
	def generateISupportList(self):
		isupport = self.isupport_tokens.copy()
		statusSymbolOrder = "".join([self.channelStatuses[status][0] for status in self.channelStatusOrder])
		isupport["CHANMODES"] = ",".join(["".join(modes) for modes in self.channelModes])
		isupport["CHANNELLEN"] = self.config.get("channel_name_length", 64)
		isupport["NETWORK"] = self.config["network_name"]
		isupport["PREFIX"] = "({}){}".format("".join(self.channelStatusOrder), statusSymbolOrder)
		isupport["STATUSMSG"] = statusSymbolOrder
		isupport["USERMODES"] = ",".join(["".join(modes) for modes in self.userModes])
		self.runActionStandard("buildisupport", isupport)
		isupportList = []
		for key, val in isupport.iteritems():
			if val is None:
				isupportList.append(key)
			else:
				isupportList.append("{}={}".format(key, val))
		return isupportList
	
	def connectServer(self, name):
		"""
		Connect a server with the given name in the configuration.
		Returns a Deferred for the connection when we can successfully connect
		or None if the server is already connected or if we're unable to find
		information for that server in the configuration.
		"""
		if name in self.serverNames:
			return None
		if name not in self.config.get("links", {}):
			return None
		serverConfig = self.config["links"][name]
		endpoint = clientFromString(reactor, unescapeEndpointDescription(serverConfig["connect_descriptor"]))
		d = endpoint.connect(ServerConnectFactory(self))
		d.addCallback(self._completeServerConnection, name)
		return d
	
	def _completeServerConnection(self, result, name):
		self.log.info("Connected to server {serverName}", serverName=name)
		self.runActionStandard("initiateserverconnection", result)
	
	def broadcastToServers(self, fromServer, command, *params, **kw):
		"""
		Broadcasts a message to all connected servers. The fromServer parameter
		should be the server from which the message came; if this server is the
		originating server, specify None for fromServer.
		"""
		for server in self.servers.itervalues():
			if server.nextClosest == self.serverID and server != fromServer:
				server.sendMessage(command, *params, **kw)
	
	def _getActionModes(self, actionName, *params, **kw):
		users = []
		channels = []
		if "users" in kw:
			users = kw["users"]
		if "channels" in kw:
			channels = kw["channels"]
		
		functionList = []
		
		if users:
			genericUserActionName = "modeactioncheck-user-{}".format(actionName)
			genericUserActionNameWithChannel = "modeactioncheck-user-withchannel-{}".format(actionName)
			for modeType in self.userModes:
				for mode, modeObj in modeType.iteritems():
					if actionName not in modeObj.affectedActions:
						continue
					priority = modeObj.affectedActions[actionName]
					actionList = []
					# Because Python doesn't properly capture variables in lambdas, we have to force static capture
					# by wrapping lambdas in more lambdas.
					# I wish Python wasn't this gross.
					for action in self.actions.get("modeactioncheck-user", []):
						actionList.append(((lambda action, actionName, mode: lambda user, *params: action[0](actionName, mode, user, *params))(action, actionName, mode), action[1]))
					for action in self.actions.get("modeactioncheck-user-withchannel", []):
						for channel in channels:
							actionList.append(((lambda action, actionName, mode, channel: lambda user, *params: action[0](actionName, mode, user, channel, *params))(action, actionName, mode, channel), action[1]))
					for action in self.actions.get(genericUserActionName, []):
						actionList.append(((lambda action, mode: lambda user, *params: action[0](mode, user, *params))(action, mode), action[1]))
					for action in self.actions.get(genericUserActionNameWithChannel, []):
						for channel in channels:
							actionList.append(((lambda action, mode, channel: lambda user, *params: action[0](mode, user, channel, *params))(action, mode, channel), action[1]))
					modeUserActionName = "modeactioncheck-user-{}-{}".format(mode, actionName)
					modeUserActionNameWithChannel = "modeactioncheck-user-withchannel-{}-{}".format(mode, actionName)
					for action in self.actions.get(modeUserActionNameWithChannel, []):
						for channel in channels:
							actionList.append(((lambda action, channel: lambda user, *params: action[0](user, channel, *params))(action, channel), action[1]))
					actionList = sorted(self.actions.get(modeUserActionName, []) + actionList, key=lambda action: action[1], reverse=True)
					applyUsers = []
					for user in users:
						for action in actionList:
							param = action[0](user, *params)
							if param is not None:
								if param is not False:
									applyUsers.append((user, param))
								break
					for user, param in applyUsers:
						functionList.append(((lambda modeObj, actionName, user, param: lambda *params: modeObj.apply(actionName, user, param, *params))(modeObj, actionName, user, param), priority))
		
		if channels:
			genericChannelActionName = "modeactioncheck-channel-{}".format(actionName)
			genericChannelActionNameWithUser = "******".format(actionName)
			for modeType in self.channelModes:
				for mode, modeObj in modeType.iteritems():
					if actionName not in modeObj.affectedActions:
						continue
					priority = modeObj.affectedActions[actionName]
					actionList = []
					for action in self.actions.get("modeactioncheck-channel", []):
						actionList.append(((lambda action, actionName, mode: lambda channel, *params: action[0](actionName, mode, channel, *params))(action, actionName, mode), action[1]))
					for action in self.actions.get("modeactioncheck-channel-withuser", []):
						for user in users:
							actionList.append(((lambda action, actionName, mode, user: lambda channel, *params: action[0](actionName, mode, channel, user, *params))(action, actionName, mode, user), action[1]))
					for action in self.actions.get(genericChannelActionName, []):
						actionList.append(((lambda action, mode: lambda channel, *params: action[0](mode, channel, *params))(action, mode), action[1]))
					for action in self.actions.get(genericChannelActionNameWithUser, []):
						for user in users:
							actionList.append(((lambda action, mode, user: lambda channel, *params: action[0](mode, channel, user, *params))(action, mode, user), action[1]))
					modeChannelActionName = "modeactioncheck-channel-{}-{}".format(mode, actionName)
					modeChannelActionNameWithUser = "******".format(mode, actionName)
					for action in self.actions.get(modeChannelActionNameWithUser, []):
						for user in users:
							actionList.append(((lambda action, user: lambda channel, *params: action[0](channel, user, *params))(action, user), action[1]))
					actionList = sorted(self.actions.get(modeChannelActionName, []) + actionList, key=lambda action: action[1], reverse=True)
					applyChannels = []
					for channel in channels:
						for action in actionList:
							param = action[0](channel, *params)
							if param is not None:
								if param is not False:
									applyChannels.append((channel, param))
								break
					for channel, param in applyChannels:
						functionList.append(((lambda modeObj, actionName, channel, param: lambda *params: modeObj.apply(actionName, channel, param, *params))(modeObj, actionName, channel, param), priority))
		return functionList
	
	def _getActionFunctionList(self, actionName, *params, **kw):
		functionList = self.actions.get(actionName, [])
		functionList = functionList + self._getActionModes(actionName, *params, **kw)
		return sorted(functionList, key=lambda action: action[1], reverse=True)
	
	def _combineActionFunctionLists(self, actionLists):
		"""
		Combines multiple lists of action functions into one.
		Assumes all lists are sorted.
		Takes a dict mapping action names to their action function lists.
		Returns a list in priority order (highest to lowest) of (actionName, function) tuples.
		"""
		fullActionList = []
		for actionName, actionList in actionLists.iteritems():
			insertPos = 0
			for action in actionList:
				try:
					while fullActionList[insertPos][1] > action[1]:
						insertPos += 1
					fullActionList.insert(insertPos, (actionName, action[0]))
				except IndexError:
					fullActionList.append((actionName, action[0]))
				insertPos += 1
		return fullActionList
	
	def runActionStandard(self, actionName, *params, **kw):
		"""
		Calls all functions for a given action with the given parameters in
		priority order. Accepts the 'users' and 'channels' keyword arguments to
		determine which mode handlers should be included.
		"""
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			action[0](*params)
	
	def runActionUntilTrue(self, actionName, *params, **kw):
		"""
		Calls functions for a given action with the given parameters in
		priority order until one of them returns a true value. Returns True
		when one of the functions returned True. Accepts the 'users' and
		'channels' keyword arguments to determine which mode handlers should be
		included.
		"""
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			if action[0](*params):
				return True
		return False
	
	def runActionUntilFalse(self, actionName, *params, **kw):
		"""
		Calls functions for a given action with the given parameters in
		priority order until one of them returns a false value. Returns True
		when one of the functions returned False. Accepts the 'users' and
		'channels' keyword arguments to determine which mode handlers should be
		included.
		"""
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			if not action[0](*params):
				return True
		return False
	
	def runActionUntilValue(self, actionName, *params, **kw):
		"""
		Calls functions for a given action with the given parameters in
		priority order until one of them returns a non-None value. Returns the
		value returned by the function that returned a non-None value. Accepts
		the 'users' and 'channels' keyword arguments to determine which mode
		handlers should be included.
		"""
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			value = action[0](*params)
			if value is not None:
				return value
		return None
	
	def runActionFlagTrue(self, actionName, *params, **kw):
		"""
		Calls all functions for a given action with the given parameters in
		priority order. Returns True when one of the functions returns a true
		value. Accepts the 'users' and 'channels' keyword arguments to
		determine which mode handlers should be included.
		"""
		oneIsTrue = False
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			if action[0](*params):
				oneIsTrue = True
		return oneIsTrue
	
	def runActionFlagFalse(self, actionName, *params, **kw):
		"""
		Calls all functions for a given action with the given parameters in
		priority order. Returns True when one of the functions returns a false
		value. Accepts the 'users' and 'channels' keyword arguments to
		determine which mode handlers should be included.
		"""
		oneIsFalse = False
		actionList = self._getActionFunctionList(actionName, *params, **kw)
		for action in actionList:
			if action[0](*params):
				oneIsFalse = True
		return oneIsFalse
	
	def runActionProcessing(self, actionName, data, *params, **kw):
		"""
		Calls functions for a given action with the given parameters in
		priority order until the provided data is all processed (the data
		parameter becomes empty). Accepts 'users' and 'channels' keyword
		arguments to determine which mode handlers should be included.
		"""
		actionList = self._getActionFunctionList(actionName, data, *params, **kw)
		for action in actionList:
			action[0](data, *params)
			if not data:
				return
	
	def runActionProcessingMultiple(self, actionName, dataList, *params, **kw):
		"""
		Calls functions for a given action with the given parameters in
		priority order until the provided data is all processed (all of the
		data structures in the dataList parameter become empty). Accepts
		'users' and 'channels' keyword arguments to determine which mode
		handlers should be included.
		"""
		paramList = dataList + params
		actionList = self._getActionFunctionList(actionName, *paramList, **kw)
		for action in actionList:
			action[0](*paramList)
			for data in dataList:
				if data:
					break
			else:
				return
	
	def runComboActionStandard(self, actionList, **kw):
		"""
		Calls all functions for the given actions with the given parameters in
		priority order. Actions are specifed as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Accepts 'users' and 'channels' keyword arguments to determine which
		mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			actionFunc(*actionParameters[actionName])
	
	def runComboActionUntilTrue(self, actionList, **kw):
		"""
		Calls functions for the given actions with the given parameters in
		priority order until one of the functions returns a true value. Actions
		are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Returns True if one of the functions returned a true value. Accepts
		'users' and 'channels' keyword arguments to determine which mode
		handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			if actionFunc(*actionParameters[actionName]):
				return True
		return False
	
	def runComboActionUntilFalse(self, actionList, **kw):
		"""
		Calls functions for the given actions with the given parameters in
		priority order until one of the functions returns a false value.
		Actions are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Returns True if one of the functions returned a false value. Accepts
		'users' and 'channels' keyword arguments to determine which mode
		handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			if not actionFunc(*actionParameters[actionName]):
				return True
		return False
	
	def runComboActionUntilValue(self, actionList, **kw):
		"""
		Calls functions for the given actions with the given parameters in
		priority order until one of the functions returns a non-None value.
		Actions are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Returns the value returned by the function that returned a non-None
		value. Accepts 'users' and 'channels' keyword arguments to determine
		which mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			value = actionFunc(*actionParameters[actionName])
			if value is not None:
				return value
		return None
	
	def runComboActionFlagTrue(self, actionList, **kw):
		"""
		Calls all functions for the given actions with the given parameters in
		priority order. Actions are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Returns True if any of the functions called returned a true value.
		Accepts 'users' and 'channels' keyword arguments to determine which
		mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		oneIsTrue = False
		for actionName, actionFunc in funcList:
			if actionFunc(*actionParameters[actionName]):
				oneIsTrue = True
		return oneIsTrue
	
	def runComboActionFlagFalse(self, actionList, **kw):
		"""
		Calls all functions for the given actions with the given parameters in
		priority order. Actions are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Returns True if any of the functions called returned a false value.
		Accepts 'users' and 'channels' keyword arguments to determine which
		mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		oneIsFalse = False
		for actionName, actionFunc in funcList:
			if not actionFunc(*actionParameters[actionName]):
				oneIsFalse = True
		return oneIsFalse
	
	def runComboActionProcessing(self, data, actionList, **kw):
		"""
		Calls functions for the given actions with the given parameters in
		priority order until the data given has been processed (the data
		parameter becomes empty). Actions are specified as a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Accepts 'users' and 'channels' keyword arguments to determine which
		mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = [data] + action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			actionFunc(*actionParameters[actionName])
			if not data:
				break
	
	def runComboActionProcessingMultiple(self, dataList, actionList, **kw):
		"""
		Calls functions for the given actions with the given parameters in
		priority order until the data given has been processed (all the data
		items in the dataList parameter become empty). Actions are specified as
		a list of tuples:
		[ ("action1", param1, param2, ...), ("action2", param1, param2, ...) ]
		Accepts 'users' and 'channels' keyword arguments to determine which
		mode handlers should be included.
		"""
		actionFuncLists = {}
		actionParameters = {}
		for action in actionList:
			parameters = dataList + action[1:]
			actionParameters[action[0]] = parameters
			actionFuncLists[action[0]] = self._getActionFunctionList(action[0], *parameters, **kw)
		funcList = self._combineActionFunctionLists(actionFuncLists)
		for actionName, actionFunc in funcList:
			actionFunc(*actionParameters[actionName])
			for data in dataList:
				if data:
					break
			else:
				return
Esempio n. 11
0
class ElineCommand(Command):
    def __init__(self):
        self.exceptList = CaseInsensitiveDictionary()

    def onUse(self, user, data):
        if "reason" in data:
            self.exceptList[data["mask"]] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"],
            }
            user.sendMessage(
                "NOTICE", ":*** E:Line set on {}, to expire in {} seconds".format(data["mask"], data["duration"])
            )
        else:
            mask = data["mask"]
            del self.exceptList[mask]
            user.sendMessage("NOTICE", ":*** E:Line removed on {}".format(mask))
            for u in self.ircd.users.itervalues():
                if self.match_eline(u):
                    u.cache["except_line"] = True
            now_banned = {}
            for uid, udata in self.ircd.users.iteritems():
                for modfunc in self.ircd.actions["xline_rematch"]:
                    reason = modfunc(udata)
                    if reason:
                        now_banned[uid] = reason
                        break  # If the user is banned, the user is banned. We don't need to gather a consensus or something.
            for uid, reason in now_banned.iteritems():
                udata = self.ircd.users[uid]
                udata.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
                udata.disconnect("Banned: Exception Removed ({})".format(reason))

    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTYETREGISTERED, "ELINE", ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(
                irc.ERR_NOPRIVILEGES, ":Permission denied - You do not have the correct operator privileges"
            )
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE", ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            udata = self.ircd.users[banmask]
            banmask = "{}@{}".format(udata.username, udata.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_elines()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE", ":Not enough parameters")
                return {}
            if banmask not in self.exceptList:
                user.sendMessage(
                    "NOTICE",
                    ":*** E:line for {} not found! Check /stats E to view currently set e:lines.".format(banmask),
                )
                return {}
            return {"user": user, "mask": banmask}
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE", ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE", ":Not enough parameters")
                return {}
        if banmask in self.exceptList:
            user.sendMessage(
                "NOTICE",
                ":*** An e:line is already set on {}!  Check /stats E to view currently set e:lines.".format(banmask),
            )
            return {}
        return {"user": user, "mask": banmask, "duration": parse_duration(params[1]), "reason": " ".join(params[2:])}

    def statsList(self, cmd, data):
        if cmd != "STATS":
            return
        if data["statstype"] != "E":
            return
        self.expire_elines()
        user = data["user"]
        for mask, linedata in self.exceptList.iteritems():
            user.sendMessage(
                irc.RPL_STATSELINES,
                "{} {} {} {} :{}".format(
                    mask, linedata["created"], linedata["duration"], linedata["setter"], linedata["reason"]
                ),
            )

    def check_register(self, user):
        if self.match_eline(user):
            user.cache["except_line"] = True
        return True

    def match_eline(self, user):
        self.expire_elines()
        matchMask = irc_lower("{}@{}".format(user.username, user.hostname))
        for mask, linedata in self.exceptList.iteritems():
            if fnmatch(matchMask, mask):
                user.cache["except_line"] = True
                return linedata["reason"]
        matchMask = irc_lower("{}@{}".format(user.username, user.ip))
        for mask, linedata in self.exceptList.iteritems():
            if fnmatch(matchMask, mask):
                user.cache["except_line"] = True
                return linedata["reason"]
        user.cache["except_line"] = False
        return None

    def expire_elines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.exceptList.iteritems():
            if linedata["duration"] and current_time > linedata["created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.exceptList[mask]
Esempio n. 12
0
class KlineCommand(Command):
    def __init__(self):
        self.banList = CaseInsensitiveDictionary()

    def onUse(self, user, data):
        if "reason" in data:
            self.banList[data["mask"]] = {
                "setter": user.prefix(),
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage(
                "NOTICE",
                ":*** K:Line added on {}, to expire in {} seconds".format(
                    data["mask"], data["duration"]))
            now_banned = {}
            for nick, u in self.ircd.users.iteritems():
                if u.server == self.ircd.name:
                    result = self.match_kline(u)
                    if result:
                        now_banned[nick] = result
            for uid, reason in now_banned.iteritems():
                udata = self.ircd.users[uid]
                udata.sendMessage(
                    "NOTICE",
                    ":{}".format(self.ircd.servconfig["client_ban_msg"]))
                udata.disconnect("K:Lined: {}".format(reason))
        else:
            del self.banList[data["mask"]]
            user.sendMessage("NOTICE",
                             ":*** K:Line removed on {}".format(data["mask"]))

    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "KLINE",
                             ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(
                irc.ERR_NOPRIVILEGES,
                ":Permission denied - You do not have the correct operator privileges"
            )
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE",
                             ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            banmask = "{}@{}".format(user.username, user.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_klines()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE",
                                 ":Not enough parameters")
                return {}
            if banmask not in self.banList:
                user.sendMessage(
                    "NOTICE",
                    ":*** K:line for {} does not currently exist; check /stats K for a list of active k:lines"
                    .format(banmask))
                return {}
            return {"user": user, "mask": banmask}
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE",
                             ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE",
                                 ":Not enough parameters")
                return {}
        if banmask in self.banList:
            user.sendMessage(
                "NOTICE",
                ":*** There's already a k:line set on {}!  Check /stats K for a list of active k:lines."
                .format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }

    def statsList(self, user, statsType):
        if statsType != "K":
            return
        self.expire_klines()
        for mask, linedata in self.banList.iteritems():
            user.sendMessage(
                irc.RPL_STATSKLINE,
                ":{} {} {} {} :{}".format(mask, linedata["created"],
                                          linedata["duration"],
                                          linedata["setter"],
                                          linedata["reason"]))

    def register_check(self, user):
        result = self.match_kline(user)
        if not result:
            if result == None:
                return True
            return "again"
        user.sendMessage("NOTICE",
                         ":{}".format(self.ircd.servconfig["client_ban_msg"]))
        user.sendMessage("ERROR",
                         ":Closing Link: {} [K:Lined: {}]".format(
                             user.hostname, result),
                         to=None,
                         prefix=None)
        return False

    def match_kline(self, user):
        if "o" in user.mode:
            return None  # don't allow bans to affect opers
        if user.server != self.ircd.name:
            return None  # only match users on this server
        if "except_line" not in user.cache:
            if "kline_match" in user.cache:
                return user.cache["kline_match"]
            # Determine whether the user matches
            self.expire_klines()
            match_against = irc_lower("{}@{}".format(user.username,
                                                     user.hostname))
            for mask, linedata in self.banList.iteritems():
                if fnmatch(match_against, mask):
                    user.cache["kline_match"] = linedata["reason"]
                    return ""
            match_against = irc_lower("{}@{}".format(user.username, user.ip))
            for mask in self.banList.iterkeys(
            ):  # we just removed expired lines
                if fnmatch(match_against, mask):
                    user.cache["kline_match"] = linedata["reason"]
                    return ""
            return None
        else:
            if user.cache["except_line"]:
                return None
            if "kline_match" in user.cache:
                return user.cache["kline_match"]
            self.expire_klines()
            match_against = irc_lower("{}@{}".format(user.username,
                                                     user.hostname))
            for mask, linedata in self.banList.iteritems():
                if fnmatch(match_against, mask):
                    return linedata["reason"]
            match_against = irc_lower("{}@{}".format(user.username, user.ip))
            for mask in self.banList.iterkeys(
            ):  # we just removed expired lines
                if fnmatch(match_against, mask):
                    return linedata["reason"]
            return None

    def expire_klines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.banList.iteritems():
            if linedata["duration"] and current_time > linedata[
                    "created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.banList[mask]
Esempio n. 13
0
class IRCD(Factory):
    protocol = IRCProtocol

    def __init__(self, config, options = None, sslCert = None):
        reactor.addSystemEventTrigger("before", "shutdown", self.cleanup)
        self.dead = False
        
        self.config = config
        self.version = "txircd-{}".format(__version__)
        self.created = now()
        self.servers = CaseInsensitiveDictionary()
        self.users = CaseInsensitiveDictionary()
        self.userid = {}
        self.channels = CaseInsensitiveDictionary()
        self.peerConnections = {}
        self.ssl_cert = sslCert
        self.client_ports = {}
        self.server_ports = {}
        self.modules = {}
        self.module_abilities = {}
        self.actions = {
            "connect": [],
            "register": [],
            "welcome": [],
            "join": [],
            "joinmessage": [],
            "nick": [],
            "quit": [],
            "topic": [],
            "mode": [],
            "nameslistentry": [],
            "chancreate": [],
            "chandestroy": [],
            "commandextra": [],
            "commandunknown": [],
            "commandpermission": [],
            "metadataupdate": [],
            "recvdata": [],
            "senddata": [],
            "netmerge": [],
            "netsplit": []
        }
        self.commands = {}
        self.channel_modes = [{}, {}, {}, {}]
        self.channel_mode_type = {}
        self.user_modes = [{}, {}, {}, {}]
        self.user_mode_type = {}
        self.prefixes = {}
        self.prefix_symbols = {}
        self.prefix_order = []
        self.server_commands = {}
        self.module_data_cache = {}
        self.server_factory = None
        self.common_modules = set()
        log.msg("Loading module data...")
        try:
            with open("data.yaml", "r") as dataFile:
                self.serialized_data = yaml.safe_load(dataFile)
                if self.serialized_data is None:
                    self.serialized_data = {}
        except IOError:
            self.serialized_data = {}
        self.isupport = {}
        self.usercount = {
            "localmax": 0,
            "globalmax": 0
        }
        log.msg("Loading configuration...")
        self.servconfig = {}
        if not options:
            options = {}
        self.load_options(options)
        self.name = self.servconfig["server_name"]
        log.msg("Loading modules...")
        self.all_module_load()
        self.save_serialized_deferred = None
        self.autoconnect_servers = LoopingCall(self.server_autoconnect)
        self.autoconnect_servers.start(60, now=False) # The server factory isn't added to here yet
        # Fill in the default ISUPPORT dictionary once config and modules are loaded, since some values depend on those
        self.isupport["CASEMAPPING"] = "rfc1459"
        self.isupport["CHANMODES"] = ",".join(["".join(modedict.keys()) for modedict in self.channel_modes])
        self.isupport["CHANNELLEN"] = "64"
        self.isupport["CHANTYPES"] = "#"
        self.isupport["MODES"] = 20
        self.isupport["NETWORK"] = self.servconfig["server_network_name"]
        self.isupport["NICKLEN"] = "32"
        self.isupport["PREFIX"] = "({}){}".format("".join(self.prefix_order), "".join([self.prefixes[mode][0] for mode in self.prefix_order]))
        self.isupport["STATUSMSG"] = "".join([self.prefixes[mode][0] for mode in self.prefix_order])
        self.isupport["TOPICLEN"] = "316"
        self.isupport["USERMODES"] = ",".join(["".join(modedict.keys()) for modedict in self.user_modes])
    
    def all_module_load(self):
        # load RFC-required modules
        rfc_spec = [
                    # commands
                    "cmd_user", "cmd_nick", "cmd_pass", # registration
                    "cmd_ping", "cmd_pong", # connection keep-alive
                    "cmd_join", "cmd_part", "cmd_kick", "cmd_topic", "cmd_mode", "cmd_invite", # channels
                    "cmd_quit", # connection end
                    "cmd_privmsg_notice", # messages
                    "cmd_oper", "umode_o", "cmd_rehash", "cmd_wallops", # oper
                    "cmd_admin", "cmd_info", "cmd_lusers", "cmd_motd", "cmd_stats", "cmd_time", "cmd_version", # server info
                    "cmd_away", "cmd_ison", "cmd_userhost", "cmd_who", "cmd_whois", "cmd_whowas", # user info
                    "cmd_names", "cmd_list", # channel info
                    "cmd_kill", "cmd_eline", "cmd_gline", "cmd_kline", "cmd_qline", "cmd_zline", # user management
                    "cmd_links", "cmd_connect", "cmd_squit", # linked servers
                    
                    # channel modes
                    "cmode_b", "cmode_i", "cmode_k", "cmode_l", "cmode_m", "cmode_n", "cmode_o", "cmode_p", "cmode_s", "cmode_t", "cmode_v",
                    
                    # user modes
                    "umode_i", "umode_s"
                    ]
        ircv3_spec = [ # http://ircv3.atheme.org/
                    "ircv3_cap", # capability mechanism which essentially serves as the base for everything else
                    "ircv3_multi-prefix", "ircv3_sasl", # other IRC 3.1 base extensions
                    "ircv3_account-notify", "ircv3_away-notify", "ircv3_extended-join", "ircv3_tls", # IRC 3.1 optional extensions
                    "ircv3_monitor", "ircv3_metadata" # IRC 3.2 base extensions
                    ]
        for module in rfc_spec:
            check = self.load_module(module)
            if not check:
                log.msg("An RFC-required capability could not be loaded!")
                raise RuntimeError("A module required for RFC compatibility could not be loaded.")
                return
        if self.servconfig["app_irc_spec"] == "ircv3":
            for module in ircv3_spec:
                check = self.load_module(module)
                if not check:
                    log.msg("IRCv3 compatibility was specified, but a required IRCv3 module could not be loaded!")
                    raise RuntimeError("A module required for IRCv3 compatibility could not be loaded.")
                    return
        for module in self.servconfig["server_modules"]:
            self.load_module(module)
    
    def rehash(self):
        log.msg("Rehashing config file and reloading modules")
        try:
            with open(self.config) as f:
                self.load_options(yaml.safe_load(f))
            self.all_module_load()
            self.save_module_data()
            self.rebind_ports()
        except:
            return False
        return True
    
    def load_options(self, options):
        for var, value in options.iteritems():
            self.servconfig[var] = value
        for var, value in default_options.iteritems():
            if var not in self.servconfig:
                self.servconfig[var] = value
    
    def cleanup(self):
        # Track the disconnections so we know they get done
        deferreds = []
        log.msg("Disconnecting servers...")
        for server in self.servers.values():
            if server.nearHop == self.name:
                server.transport.loseConnection()
                deferreds.append(server.disconnected)
        # Cleanly disconnect all clients
        log.msg("Disconnecting clients...")
        for u in self.users.values():
            u.sendMessage("ERROR", ":Closing Link: {} [Server shutting down]".format(u.hostname), to=None, prefix=None)
            u.socket.transport.loseConnection()
            deferreds.append(u.disconnected)
        log.msg("Unloading modules...")
        for name, spawner in self.modules.iteritems():
            try:
                spawner.cleanup()
            except AttributeError:
                pass # If the module has no extra cleanup to do, that's fine
            try:
                data_to_save, free_data = self.modules[name].data_serialize()
                if data_to_save:
                    self.serialized_data[name] = data_to_save
            except AttributeError:
                pass # If the module has no data to save, that's also fine.
        log.msg("Saving serialized data...")
        if not self.save_module_data():
            self.save_serialized_deferred.addCallback(self.save_serialized)
        deferreds.append(self.save_serialized_deferred)
        # Return deferreds
        log.msg("Waiting on deferreds...")
        self.dead = True
        return DeferredList(deferreds)
    
    def connect_server(self, servername):
        def sendServerHandshake(protocol, password):
            protocol.callRemote(IntroduceServer, name=self.name, password=password, description=self.servconfig["server_description"], version=protocol_version, commonmodules=self.common_modules)
            protocol.sentDataBurst = False
        if servername in self.servers:
            raise RuntimeError ("Server {} is already connected".format(servername))
        if servername not in self.servconfig["serverlinks"]:
            raise RuntimeError ("Server {} is not configured".format(servername))
        servinfo = self.servconfig["serverlinks"][servername]
        if "ip" not in servinfo:
            raise RuntimeError ("Server {} is not properly configured: IP address must be specified".format(servername))
        if "connect" not in servinfo:
            raise RuntimeError ("Server {} is not properly configured: Connection description not provided".format(servername))
        if "incoming_password" not in servinfo or "outgoing_password" not in servinfo:
            raise RuntimeError ("Server {} is not properly configured: Passwords not specified".format(servername))
        try:
            endpoint = clientFromString(reactor, resolveEndpointDescription(servinfo["connect"]))
        except ValueError as e:
            raise RuntimeError ("Server {} is not properly configured: Connection description is not valid ({})".format(servername, e))
        connectDeferred = endpoint.connect(self.server_factory)
        connectDeferred.addCallback(sendServerHandshake, servinfo["outgoing_password"])
        reactor.callLater(30, connectDeferred.cancel) # Time out the connection after 30 seconds
    
    def server_autoconnect(self):
        for server in self.servconfig["serverlink_autoconnect"]:
            if server not in self.servers and server in self.servconfig["serverlinks"]:
                log.msg("Initiating autoconnect to server {}".format(server))
                try:
                    self.connect_server(server)
                except RuntimeError as ex:
                    log.msg("Connection to server failed: {}".format(ex))
    
    def load_module(self, name):
        saved_data = {}
        if name in self.modules:
            saved_data = self.unload_module_data(name)
        try:
            mod_find = imp.find_module("txircd/modules/{}".format(name))
        except ImportError as e:
            log.msg("Module not found: {} {}".format(name, e))
            return False
        try:
            mod_load = imp.load_module(name, mod_find[0], mod_find[1], mod_find[2])
        except ImportError as e:
            log.msg("Could not load module: {} ({})".format(name, e))
            mod_find[0].close()
            return False
        mod_find[0].close()
        try:
            mod_spawner = mod_load.Spawner(self)
        except Exception as e:
            log.msg("Module is not a valid txircd module: {} ({})".format(name, e))
            return False
        try:
            mod_contains = mod_spawner.spawn()
        except Exception as e:
            log.msg("Module is not a valid txircd module: {} ({})".format(name, e))
            return False
        self.modules[name] = mod_spawner
        self.module_abilities[name] = mod_contains
        if "commands" in mod_contains:
            for command, implementation in mod_contains["commands"].iteritems():
                if command in self.commands:
                    log.msg("Module {} tries to reimplement command {}".format(name, command))
                    continue
                self.commands[command] = implementation.hook(self)
        if "modes" in mod_contains:
            for mode, implementation in mod_contains["modes"].iteritems():
                if len(mode) < 2:
                    continue
                if mode[1] == "l":
                    modetype = 0
                elif mode[1] == "u":
                    modetype = 1
                elif mode[1] == "p":
                    modetype = 2
                elif mode[1] == "n":
                    modetype = 3
                elif mode[1] == "s":
                    modetype = -1
                else:
                    log.msg("Module {} registers a mode of an invalid type".format(name))
                    continue
                if mode[0] == "c":
                    if mode[2] in self.channel_mode_type:
                        log.msg("Module {} tries to reimplement channel mode {}".format(name, mode))
                        continue
                    if modetype >= 0:
                        self.channel_modes[modetype][mode[2]] = implementation.hook(self)
                    else:
                        if len(mode) < 5:
                            log.msg("Module {} tries to register a prefix without a symbol or level".format(name))
                            continue
                        try:
                            level = int(mode[4:])
                        except:
                            log.msg("Module {} tries to register a prefix without a numeric level".format(name))
                            continue
                        closestLevel = 0
                        closestModeChar = None
                        orderFail = False
                        for levelMode, levelData in self.prefixes.iteritems():
                            if level == levelData[1]:
                                log.msg("Module {} tries to register a prefix with the same rank level as an existing prefix")
                                orderFail = True
                                break
                            if levelData[1] < level and levelData[1] > closestLevel:
                                closestLevel = levelData[1]
                                closestModeChar = levelMode
                        if orderFail:
                            continue
                        if closestModeChar:
                            self.prefix_order.insert(self.prefix_order.index(closestModeChar), mode[2])
                        else:
                            self.prefix_order.append(mode[2])
                        self.prefixes[mode[2]] = [mode[3], level, implementation.hook(self)]
                        self.prefix_symbols[mode[3]] = mode[2]
                    self.channel_mode_type[mode[2]] = modetype
                    self.isupport["PREFIX"] = "({}){}".format("".join(self.prefix_order), "".join([self.prefixes[mode][0] for mode in self.prefix_order]))
                    self.isupport["STATUSMSG"] = "".join([self.prefixes[mode][0] for mode in self.prefix_order])
                elif mode[0] == "u":
                    if modetype == -1:
                        log.msg("Module {} registers a mode of an invalid type".format(name))
                        continue
                    if mode[2] in self.user_mode_type:
                        log.msg("Module {} tries to reimplement user mode {}".format(name, mode))
                        continue
                    self.user_modes[modetype][mode[2]] = implementation.hook(self)
                    self.user_mode_type[mode[2]] = modetype
        if "actions" in mod_contains:
            for actiontype, actionfunc in mod_contains["actions"].iteritems():
                if actiontype not in self.actions:
                    self.actions[actiontype] = []
                self.actions[actiontype].append(actionfunc)
        if "server" in mod_contains:
            for commandtype, commandfunc in mod_contains["server"].iteritems():
                if commandtype not in self.server_commands:
                    self.server_commands[commandtype] = []
                self.server_commands[commandtype].append(commandfunc)
        if "common" in mod_contains and mod_contains["common"]:
            self.common_modules.add(name)
        if not saved_data and name in self.serialized_data:
            saved_data = self.serialized_data[name] # present serialized data on first load of session
        if saved_data:
            try:
                mod_spawner.data_unserialize(saved_data)
            except AttributeError:
                pass
        return True
    
    def unload_module_data(self, name):
        data_to_save = {}
        all_data = {}
        try:
            data_to_save, all_data = self.modules[name].data_serialize()
            if data_to_save:
                self.serialized_data[name] = data_to_save
            elif name in self.serialized_data:
                del self.serialized_data[name]
            # Copy data_to_save (if anything) over to all_data (intentionally overwriting any non-permanent items already in all_data)
            # So that we have one dictionary to pass back to data_unserialize if this is being immediately reloaded
            for item, value in data_to_save.iteritems():
                all_data[item] = value
        except AttributeError:
            pass
        try:
            self.modules[name].cleanup()
        except AttributeError:
            pass
        abilities = self.module_abilities[name]
        del self.module_abilities[name]
        del self.modules[name]
        if "commands" in abilities:
            for command, implementation in abilities["commands"].iteritems():
                if self.commands[command] == implementation:
                    del self.commands[command]
        if "modes" in abilities:
            for mode, implementation in abilities["modes"].iteritems():
                if mode[1] == "l":
                    modetype = 0
                elif mode[1] == "u":
                    modetype = 1
                elif mode[1] == "p":
                    modetype = 2
                elif mode[1] == "n":
                    modetype = 3
                elif mode[1] == "s":
                    modetype = -1
                
                if mode[0] == "c":
                    if modetype == -1:
                        if mode[2] in self.prefixes:
                            del self.prefix_symbols[self.prefixes[mode[2]][0]]
                            del self.prefixes[mode[2]]
                            self.prefix_order.remove(mode[2])
                    else:
                        if mode[2] in self.channel_modes[modetype]:
                            del self.channel_modes[modetype][mode[2]]
                    if mode[2] in self.channel_mode_type:
                        del self.channel_mode_type[mode[2]]
                else:
                    if mode[2] in self.user_modes[modetype]:
                        del self.user_modes[modetype][mode[2]]
                    if mode[2] in self.user_mode_type:
                        del self.user_mode_type[mode[2]]
        if "actions" in abilities:
            for type, function in abilities["actions"].iteritems():
                if type in self.actions and function in self.actions[type]:
                    self.actions[type].remove(function)
        if "server" in abilities:
            for command, function in abilities["server"].iteritems():
                if command in self.server_commands and function in self.server_commands[command]:
                    self.server_commands[command].remove(function)
        return all_data
    
    def save_module_data(self):
        if self.save_serialized_deferred is None or self.save_serialized_deferred.called:
            self.save_serialized_deferred = deferToThread(self.save_serialized)
            return True
        # Otherwise, there's a save currently happening.  This likely means that
        #  1. We don't need to save now; not THAT much has changed
        #  2. Saving now has the potential to cause problems.
        # We could add self.save_serialized as a callback to the Deferred, but there's
        # not a good way to check whether that's done yet without complicating things (and,
        # as mentioned, there's not a need for it).
        # The return value allows us to work around it currently saving already in the
        # cleanup step (when we absolutely must save regardless), as adding a callback
        # in IRCD.cleanup won't hurt anything.
        return False
    
    def save_serialized(self, _ = None):
        with open("data.yaml", "w") as dataFile:
            yaml.safe_dump(self.serialized_data, dataFile, default_flow_style=False)
    
    def saveClientPort(self, desc, port):
        if desc in self.client_ports:
            return
        self.client_ports[desc] = port
    
    def saveServerPort(self, desc, port):
        if desc in self.server_ports:
            return
        self.server_ports[desc] = port

    def rebind_ports(self):
        def addClientPortToIRCd(port, ircd, desc):
            ircd.saveClientPort(desc, port)

        def addServerPortToIRCd(port, ircd, desc):
            ircd.saveServerPort(desc, port)

        def logPortNotBound(error):
            log.msg("An error occurred: {}".format(error))

        # Client ports
        old_ports, new_ports = set(self.client_ports.keys()), set(self.servconfig["server_client_ports"])
        ports_to_unbind, ports_to_bind = old_ports - new_ports, new_ports - old_ports

        for port in ports_to_unbind:
            self.client_ports[port].stopListening()
            del self.client_ports[port]

        for port in ports_to_bind:
            try:
                endpoint = serverFromString(reactor, resolveEndpointDescription(port))
            except ValueError as e:
                log.msg("Could not bind {}: not a valid description ({})".format(port, e))
                continue
            listenDeferred = endpoint.listen(self)
            listenDeferred.addCallback(addClientPortToIRCd, self, port)
            listenDeferred.addErrback(logPortNotBound)

        # Server ports
        old_ports, new_ports = set(self.server_ports.keys()), set(self.servconfig["server_link_ports"])
        ports_to_unbind, ports_to_bind = old_ports - new_ports, new_ports - old_ports

        for port in ports_to_unbind:
            self.server_ports[port].stopListening()
            del self.server_ports[port]

        for port in ports_to_bind:
            try:
                endpoint = serverFromString(reactor, resolveEndpointDescription(port))
            except ValueError as e:
                log.msg("Could not bind {}: not a valid description ({})".format(port, e))
                continue
            listenDeferred = endpoint.listen(self.server_factory)
            listenDeferred.addCallback(addServerPortToIRCd, self, port)
            listenDeferred.addErrback(logPortNotBound)
    
    def buildProtocol(self, addr):
        if self.dead:
            return None
        ip = addr.host
        connections = self.peerConnections.get(ip, 0)
        maxConnections = self.servconfig["client_peer_exempt"][ip] if ip in self.servconfig["client_peer_exempt"] else self.servconfig["client_peer_connections"]
        if maxConnections and connections >= maxConnections:
            log.msg("A client at IP address {} has exceeded the session limit".format(ip))
            return None
        self.peerConnections[ip] = connections + 1
        newProtocol = IRCProtocol(ip)
        newProtocol.factory = self
        return newProtocol

    def unregisterProtocol(self, p):
        self.peerConnections[p.ip] -= 1
Esempio n. 14
0
class QlineCommand(Command):
    def __init__(self):
        self.banList = CaseInsensitiveDictionary()
    
    def onUse(self, user, data):
        mask = data["mask"]
        if "reason" in data:
            self.banList[mask] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage("NOTICE", ":*** Q:Line set on {}, to expire in {} seconds".format(mask, data["duration"]))
            if "*" not in mask and "?" not in mask:
                if mask in self.ircd.users:
                    self.ircd.users[mask].disconnect("Q:Lined: {}".format(data["reason"]))
            else:
                now_banned = {}
                for user in self.ircd.users.itervalues():
                    reason = self.match_qline(user)
                    if reason:
                        now_banned[user] = reason
                for user, reason in now_banned.iteritems():
                    user.disconnect("Q:Lined: {}".format(reason))
        else:
            del self.banList[mask]
            user.sendMessage("NOTICE", ":*** Q:Line removed on {}".format(mask))
    
    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "QLINE", ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(irc.ERR_NOPRIVILEGES, ":Permission denied - You do not have the correct operator privileges")
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE", ":Not enough parameters")
            return {}
        self.expire_qlines()
        banmask = params[0]
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE", ":Not enough parameters")
                return {}
            if banmask not in self.banList:
                user.sendMessage("NOTICE", ":*** There is not a q:line set on {}; check /stats Q for a list of existing q:lines".format(banmask))
                return {}
            return {
                "user": user,
                "mask": banmask
            }
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE", ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE", ":Not enough parameters")
                return {}
        if banmask in self.banList:
            user.sendMessage("NOTICE", ":*** Q:line already exists for {}!  Check /stats Q for a list of existing q:lines.".format(params[0]))
            return {}
        bancheck = banmask.replace("*", "")
        if not bancheck or ("*" in banmask and bancheck == "?"):
            user.sendMessage("NOTICE", ":*** That q:line will match all nicks!  Please check your nick mask and try again.")
            return {}
        if not VALID_NICKNAME.match(params[0].replace("*", "").replace("?", "a")):
            user.sendMessage("NOTICE", ":*** That isn't a valid nick mask and won't match any nicks.  Please check your nick mask and try again.")
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }
    
    def statsList(self, user, statsType):
        if statsType != "Q":
            return
        self.expire_qlines()
        for mask, linedata in self.banList.iteritems():
            user.sendMessage(irc.RPL_STATSQLINE, ":{} {} {} {} :{}".format(mask, linedata["created"], linedata["duration"], linedata["setter"], linedata["reason"]))
    
    def check_register(self, user):
        self.expire_qlines()
        reason = self.match_qline(user)
        if not reason:
            return True
        user.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
        user.sendMessage("ERROR", ":Closing Link: {} [Q:Lined: {}]".format(user.hostname, reason), to=None, prefix=None)
    
    def match_qline(self, user):
        if "o" in user.mode:
            return None
        lowerNick = irc_lower(user.nickname)
        for mask, linedata in self.banList.iteritems():
            if fnmatch(lowerNick, mask):
                return linedata["reason"]
        return None
    
    def expire_qlines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.banList.iteritems():
            if linedata["duration"] and current_time > linedata["created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.banList[mask]
    
    def blockNick(self, user, command, data):
        if command != "NICK":
            return data
        newNick = data["nick"]
        lowerNick = irc_lower(newNick)
        self.expire_qlines()
        for mask, linedata in self.banList.iteritems():
            if fnmatch(lowerNick, mask):
                user.sendMessage(irc.ERR_ERRONEUSNICKNAME, newNick, ":Invalid nickname: {}".format(linedata["reason"]))
                return {}
        return data
Esempio n. 15
0
class QlineCommand(Command):
    def __init__(self):
        self.banList = CaseInsensitiveDictionary()

    def onUse(self, user, data):
        mask = data["mask"]
        if "reason" in data:
            self.banList[mask] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage(
                "NOTICE",
                ":*** Q:Line set on {}, to expire in {} seconds".format(
                    mask, data["duration"]))
            if "*" not in mask and "?" not in mask:
                if mask in self.ircd.users:
                    self.ircd.users[mask].disconnect("Q:Lined: {}".format(
                        data["reason"]))
            else:
                now_banned = {}
                for user in self.ircd.users.itervalues():
                    reason = self.match_qline(user)
                    if reason:
                        now_banned[user] = reason
                for user, reason in now_banned.iteritems():
                    user.disconnect("Q:Lined: {}".format(reason))
        else:
            del self.banList[mask]
            user.sendMessage("NOTICE",
                             ":*** Q:Line removed on {}".format(mask))

    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "QLINE",
                             ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(
                irc.ERR_NOPRIVILEGES,
                ":Permission denied - You do not have the correct operator privileges"
            )
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE",
                             ":Not enough parameters")
            return {}
        self.expire_qlines()
        banmask = params[0]
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE",
                                 ":Not enough parameters")
                return {}
            if banmask not in self.banList:
                user.sendMessage(
                    "NOTICE",
                    ":*** There is not a q:line set on {}; check /stats Q for a list of existing q:lines"
                    .format(banmask))
                return {}
            return {"user": user, "mask": banmask}
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE",
                             ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "QLINE",
                                 ":Not enough parameters")
                return {}
        if banmask in self.banList:
            user.sendMessage(
                "NOTICE",
                ":*** Q:line already exists for {}!  Check /stats Q for a list of existing q:lines."
                .format(params[0]))
            return {}
        bancheck = banmask.replace("*", "")
        if not bancheck or ("*" in banmask and bancheck == "?"):
            user.sendMessage(
                "NOTICE",
                ":*** That q:line will match all nicks!  Please check your nick mask and try again."
            )
            return {}
        if not VALID_NICKNAME.match(params[0].replace("*", "").replace(
                "?", "a")):
            user.sendMessage(
                "NOTICE",
                ":*** That isn't a valid nick mask and won't match any nicks.  Please check your nick mask and try again."
            )
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }

    def statsList(self, user, statsType):
        if statsType != "Q":
            return
        self.expire_qlines()
        for mask, linedata in self.banList.iteritems():
            user.sendMessage(
                irc.RPL_STATSQLINE,
                ":{} {} {} {} :{}".format(mask, linedata["created"],
                                          linedata["duration"],
                                          linedata["setter"],
                                          linedata["reason"]))

    def check_register(self, user):
        self.expire_qlines()
        reason = self.match_qline(user)
        if not reason:
            return True
        user.sendMessage("NOTICE",
                         ":{}".format(self.ircd.servconfig["client_ban_msg"]))
        user.sendMessage("ERROR",
                         ":Closing Link: {} [Q:Lined: {}]".format(
                             user.hostname, reason),
                         to=None,
                         prefix=None)

    def match_qline(self, user):
        if "o" in user.mode:
            return None
        lowerNick = irc_lower(user.nickname)
        for mask, linedata in self.banList.iteritems():
            if fnmatch(lowerNick, mask):
                return linedata["reason"]
        return None

    def expire_qlines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.banList.iteritems():
            if linedata["duration"] and current_time > linedata[
                    "created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.banList[mask]

    def blockNick(self, user, command, data):
        if command != "NICK":
            return data
        newNick = data["nick"]
        lowerNick = irc_lower(newNick)
        self.expire_qlines()
        for mask, linedata in self.banList.iteritems():
            if fnmatch(lowerNick, mask):
                user.sendMessage(
                    irc.ERR_ERRONEUSNICKNAME, newNick,
                    ":Invalid nickname: {}".format(linedata["reason"]))
                return {}
        return data
Esempio n. 16
0
class Monitor(ModuleData, Command):
	implements(IPlugin, IModuleData, ICommand)
	
	name = "Monitor"
	
	def actions(self):
		return [ ("capabilitylist", 10, self.addCapability),
		         ("welcome", 1, self.reportNewUser),
		         ("remoteregister", 1, self.reportNewUser),
		         ("changenick", 1, self.reportNickChangeUser),
		         ("remotechangenick", 1, self.reportNickChangeUser),
		         ("quit", 1, self.reportGoneUser),
		         ("remotequit", 1, self.reportGoneUser),
		         ("addusercap", 1, self.userSubMetadata),
		         ("usermetadataupdate", 1, self.notifyUserMetadataChange),
		         ("channelmetadataupdate", 1, self.notifyChannelMetadataChange),
		         ("buildisupport", 1, self.buildISupport) ]
	
	def userCommands(self):
		return [ ("MONITOR", 1, self) ]
	
	def load(self):
		self.targetIndex = CaseInsensitiveDictionary()
		# We'll run a cleaner every minute. The reason we do this is that, since there can be multiple
		# notified users for a target, the index is implemented as a CaseInsensitiveDictionary pointing
		# to WeakSets as opposed to simply a CaseInsensitiveDictionary(WeakValueDictionary). Because of
		# this, we'll need to check for empty WeakSets on occasion and garbage-collect them to prevent
		# memory getting full.
		self.indexCleaner = LoopingCall(self.cleanIndex)
		self.indexCleaner.start(60, now=False)
		if "unloading-monitor" in self.ircd.dataCache:
			del self.ircd.dataCache["unloading-monitor"]
			return
		if "cap-add" in self.ircd.functionCache:
			self.ircd.functionCache["cap-add"]("metadata-notify")
	
	def unload(self):
		if self.indexCleaner.running:
			self.indexCleaner.stop()
		self.ircd.dataCache["unloading-monitor"] = True
	
	def fullUnload(self):
		del self.ircd.dataCache["unloading-monitor"]
		if "cap-del" in self.ircd.functionCache:
			self.ircd.functionCache["cap-del"]("metadata-notify")
	
	def verifyConfig(self, config):
		if "monitor_limit" not in config:
			config["monitor_limit"] = None
			return
		if not isinstance(config["monitor_limit"], int) or config["monitor_limit"] < 0:
			raise ConfigValidationError("monitor_limit", "invalid number")
	
	def addCapability(self, user, capList):
		capList.append("metadata-notify")
	
	def reportNewUser(self, user):
		self._doNotify(user.nick, irc.RPL_MONONLINE)
	
	def reportNickChangeUser(self, user, oldNick, fromServer):
		self._doNotify(oldNick, irc.RPL_MONOFFLINE)
		self._doNotify(user.nick, irc.RPL_MONONLINE)
	
	def reportGoneUser(self, user, reason):
		if user.isRegistered():
			self._doNotify(user.nick, irc.RPL_MONOFFLINE)
	
	def _doNotify(self, nick, numeric):
		if nick in self.targetIndex:
			for notifyUser in self.targetIndex[nick]:
				notifyUser.sendMessage(numeric, nick)
	
	def userSubMetadata(self, user, capability, value):
		if capability != "metadata-notify":
			return
		self.sendUserMetadata(user, user)
		if "monitor" in user.cache:
			for target in user.cache["monitor"]:
				if target in self.ircd.userNicks:
					targetUser = self.ircd.users[self.ircd.userNicks[target]]
					self.sendUserMetadata(targetUser, user)
	
	def notifyUserMetadataChange(self, user, key, oldValue, value, visibility, setByUser, fromServer):
		sentToUsers = set()
		if not setByUser and ("capabilities" in user.cache and "metadata-notify" in user.cache["capabilities"]) and user.canSeeMetadataVisibility(visibility):
			# Technically, the spec excludes "changes made by the clients themselves" from notification. However,
			# since we don't know WHICH user changed the metadata, we'll exclude all sets by users and hope that
			# nobody's actually changing someone else's metadata (would only be opers).
			if value is None:
				user.sendMessage("METADATA", key, visibility, to=user.nick)
			else:
				user.sendMessage("METADATA", key, visibility, value, to=user.nick)
			sentToUsers.add(user)
		if user.nick in self.targetIndex:
			for monitoringUser in self.targetIndex[user.nick]:
				if monitoringUser in sentToUsers:
					continue
				if "capabilities" in monitoringUser.cache and "metadata-notify" in monitoringUser.cache["capabilities"] and monitoringUser.canSeeMetadataVisibility(visibility):
					if value is None:
						monitoringUser.sendMessage("METADATA", key, visibility, to=user.nick)
					else:
						monitoringUser.sendMessage("METADATA", key, visibility, value, to=user.nick)
					sentToUsers.add(monitoringUser)
		for channel in user.channels:
			for inChannelUser in channel.users.iterkeys():
				if inChannelUser in sentToUsers:
					continue
				if "capabilities" in inChannelUser.cache and "metadata-notify" in inChannelUser.cache["capabilities"] and inChannelUser.canSeeMetadataVisibility(visibility):
					if value is None:
						inChannelUser.sendMessage("METADATA", key, visibility, to=user.nick)
					else:
						inChannelUser.sendMessage("METADATA", key, visibility, value, to=user.nick)
					sentToUsers.add(inChannelUser)
	
	def notifyChannelMetadataChange(self, channel, key, oldValue, value, visibility, setByUser, fromServer):
		for user in channel.users.iterkeys():
			if "capabilities" in user.cache and "metadata-notify" in user.cache["capabilities"] and user.canSeeMetadataVisibility(visibility):
				if value is None:
					user.sendMessage("METADATA", key, visibility, to=channel.name)
				else:
					user.sendMessage("METADATA", key, visibility, value, to=channel.name)
	
	def buildISupport(self, data):
		data["MONITOR"] = self.ircd.config["monitor_limit"]
	
	def cleanIndex(self):
		removeKeys = []
		for target, notifyList in self.targetIndex.iteritems():
			if not notifyList:
				removeKeys.append(target)
		for target in removeKeys:
			del self.targetIndex[target]
	
	def sendUserMetadata(self, user, sendToUser):
		metadataList = user.metadataList()
		for key, value, visibility, setByUser in metadataList:
			if sendToUser.canSeeMetadataVisibility(visibility):
				sendToUser.sendMessage(irc.RPL_KEYVALUE, user.nick, key, visibility, value)
		sendToUser.sendMessage(irc.RPL_METADATAEND, "end of metadata")
	
	def parseParams(self, user, params, prefix, tags):
		if not params:
			user.sendSingleError("MonitorParams", irc.ERR_NEEDMOREPARAMS, "MONITOR", "Not enough parameters")
			return None
		subcmd = params[0]
		if subcmd in ("+", "-"):
			if len(params) < 2:
				user.sendSingleError("Monitor+Params", irc.ERR_NEEDMOREPARAMS, "MONITOR", "Not enough parameters")
				return None
			nickList = params[1].split(",")
			return {
				"subcmd": subcmd,
				"targets": nickList
			}
		if subcmd in ("C", "L", "S"):
			return {
				"subcmd": subcmd
			}
		user.sendSingleError("MonitorBadSubcmd", irc.ERR_UNKNOWNCOMMAND, "MONITOR", "Unknown subcommand: {}".format(subcmd))
		return None
	
	def execute(self, user, data):
		subcmd = data["subcmd"]
		if subcmd == "+":
			monitorLimit = self.ircd.config["monitor_limit"]
			newTargets = data["targets"]
			if "monitor" not in user.cache:
				user.cache["monitor"] = set()
			if monitorLimit is not None and (len(user.cache["monitor"]) + len(newTargets)) > monitorLimit:
				user.sendMessage(irc.ERR_MONLISTFULL, monitorLimit, ",".join(newTargets), "Monitor list is full.")
				return True
			onlineList = []
			onlineUserList = []
			offlineList = []
			userMonitorList = user.cache["monitor"]
			for target in newTargets:
				if target in userMonitorList:
					continue
				userMonitorList.add(target)
				if target in self.ircd.userNicks:
					onlineList.append(target)
					onlineUserList.append(self.ircd.users[self.ircd.userNicks[target]])
				else:
					offlineList.append(target)
			if onlineList:
				onlineLines = splitMessage(",".join(onlineList), 400, ",")
				for line in onlineLines:
					user.sendMessage(irc.RPL_MONONLINE, line)
			if offlineList:
				offlineLines = splitMessage(",".join(offlineList), 400, ",")
				for line in offlineLines:
					user.sendMessage(irc.RPL_MONOFFLINE, line)
			if "capabilities" in user.cache and "metadata-notify" in user.cache["capabilities"]:
				for targetUser in onlineUserList:
					self.sendUserMetadata(targetUser, user)
			return True
		if subcmd == "-":
			if "monitor" not in user.cache:
				return True
			userMonitorList = user.cache["monitor"]
			for target in data["targets"]:
				userMonitorList.discard(target)
			return True
		if subcmd == "C":
			if "monitor" in user.cache:
				del user.cache["monitor"]
			return True
		if subcmd == "L":
			if "monitor" not in user.cache:
				user.sendMessage(irc.RPL_ENDOFMONLIST, "End of MONITOR list")
				return True
			listLines = splitMessage(",".join(user.cache["monitor"]), 400, ",")
			for line in listLines:
				user.sendMessage(irc.RPL_MONLIST, line)
			user.sendMessage(irc.RPL_ENDOFMONLIST, "End of MONITOR list")
			return True
		if subcmd == "S":
			if "monitor" not in user.cache:
				return True
			onlineList = []
			offlineList = []
			for target in user.cache["monitor"]:
				if target in self.ircd.userNicks:
					onlineList.append(target)
				else:
					offlineList.append(target)
			onlineLines = splitMessage(",".join(onlineList), 400, ",")
			for line in onlineLines:
				user.sendMessage(irc.RPL_MONONLINE, line)
			offlineLines = splitMessage(",".join(offlineList), 400, ",")
			for line in offlineLines:
				user.sendMessage(irc.RPL_MONOFFLINE, line)
			return True
		return None
Esempio n. 17
0
class Monitor(ModuleData, Command):
    implements(IPlugin, IModuleData, ICommand)

    name = "Monitor"

    def actions(self):
        return [("capabilitylist", 10, self.addCapability),
                ("welcome", 1, self.reportNewUser),
                ("remoteregister", 1, self.reportNewUser),
                ("changenick", 1, self.reportNickChangeUser),
                ("remotechangenick", 1, self.reportNickChangeUser),
                ("quit", 1, self.reportGoneUser),
                ("remotequit", 1, self.reportGoneUser),
                ("addusercap", 1, self.userSubMetadata),
                ("usermetadataupdate", 1, self.notifyUserMetadataChange),
                ("channelmetadataupdate", 1, self.notifyChannelMetadataChange),
                ("buildisupport", 1, self.buildISupport)]

    def userCommands(self):
        return [("MONITOR", 1, self)]

    def load(self):
        self.targetIndex = CaseInsensitiveDictionary()
        # We'll run a cleaner every minute. The reason we do this is that, since there can be multiple
        # notified users for a target, the index is implemented as a CaseInsensitiveDictionary pointing
        # to WeakSets as opposed to simply a CaseInsensitiveDictionary(WeakValueDictionary). Because of
        # this, we'll need to check for empty WeakSets on occasion and garbage-collect them to prevent
        # memory getting full.
        self.indexCleaner = LoopingCall(self.cleanIndex)
        self.indexCleaner.start(60, now=False)
        if "unloading-monitor" in self.ircd.dataCache:
            del self.ircd.dataCache["unloading-monitor"]
            return
        if "cap-add" in self.ircd.functionCache:
            self.ircd.functionCache["cap-add"]("metadata-notify")

    def unload(self):
        if self.indexCleaner.running:
            self.indexCleaner.stop()
        self.ircd.dataCache["unloading-monitor"] = True

    def fullUnload(self):
        del self.ircd.dataCache["unloading-monitor"]
        if "cap-del" in self.ircd.functionCache:
            self.ircd.functionCache["cap-del"]("metadata-notify")

    def verifyConfig(self, config):
        if "monitor_limit" not in config:
            config["monitor_limit"] = None
            return
        if not isinstance(config["monitor_limit"],
                          int) or config["monitor_limit"] < 0:
            raise ConfigValidationError("monitor_limit", "invalid number")

    def addCapability(self, user, capList):
        capList.append("metadata-notify")

    def reportNewUser(self, user):
        self._doNotify(user.nick, irc.RPL_MONONLINE)

    def reportNickChangeUser(self, user, oldNick, fromServer):
        self._doNotify(oldNick, irc.RPL_MONOFFLINE)
        self._doNotify(user.nick, irc.RPL_MONONLINE)

    def reportGoneUser(self, user, reason):
        if user.isRegistered():
            self._doNotify(user.nick, irc.RPL_MONOFFLINE)

    def _doNotify(self, nick, numeric):
        if nick in self.targetIndex:
            for notifyUser in self.targetIndex[nick]:
                notifyUser.sendMessage(numeric, nick)

    def userSubMetadata(self, user, capability, value):
        if capability != "metadata-notify":
            return
        self.sendUserMetadata(user, user)
        if "monitor" in user.cache:
            for target in user.cache["monitor"]:
                if target in self.ircd.userNicks:
                    targetUser = self.ircd.users[self.ircd.userNicks[target]]
                    self.sendUserMetadata(targetUser, user)

    def notifyUserMetadataChange(self, user, key, oldValue, value, visibility,
                                 setByUser, fromServer):
        sentToUsers = set()
        if not setByUser and ("capabilities" in user.cache and
                              "metadata-notify" in user.cache["capabilities"]
                              ) and user.canSeeMetadataVisibility(visibility):
            # Technically, the spec excludes "changes made by the clients themselves" from notification. However,
            # since we don't know WHICH user changed the metadata, we'll exclude all sets by users and hope that
            # nobody's actually changing someone else's metadata (would only be opers).
            if value is None:
                user.sendMessage("METADATA", key, visibility, to=user.nick)
            else:
                user.sendMessage("METADATA",
                                 key,
                                 visibility,
                                 value,
                                 to=user.nick)
            sentToUsers.add(user)
        if user.nick in self.targetIndex:
            for monitoringUser in self.targetIndex[user.nick]:
                if monitoringUser in sentToUsers:
                    continue
                if "capabilities" in monitoringUser.cache and "metadata-notify" in monitoringUser.cache[
                        "capabilities"] and monitoringUser.canSeeMetadataVisibility(
                            visibility):
                    if value is None:
                        monitoringUser.sendMessage("METADATA",
                                                   key,
                                                   visibility,
                                                   to=user.nick)
                    else:
                        monitoringUser.sendMessage("METADATA",
                                                   key,
                                                   visibility,
                                                   value,
                                                   to=user.nick)
                    sentToUsers.add(monitoringUser)
        for channel in user.channels:
            for inChannelUser in channel.users.iterkeys():
                if inChannelUser in sentToUsers:
                    continue
                if "capabilities" in inChannelUser.cache and "metadata-notify" in inChannelUser.cache[
                        "capabilities"] and inChannelUser.canSeeMetadataVisibility(
                            visibility):
                    if value is None:
                        inChannelUser.sendMessage("METADATA",
                                                  key,
                                                  visibility,
                                                  to=user.nick)
                    else:
                        inChannelUser.sendMessage("METADATA",
                                                  key,
                                                  visibility,
                                                  value,
                                                  to=user.nick)
                    sentToUsers.add(inChannelUser)

    def notifyChannelMetadataChange(self, channel, key, oldValue, value,
                                    visibility, setByUser, fromServer):
        for user in channel.users.iterkeys():
            if "capabilities" in user.cache and "metadata-notify" in user.cache[
                    "capabilities"] and user.canSeeMetadataVisibility(
                        visibility):
                if value is None:
                    user.sendMessage("METADATA",
                                     key,
                                     visibility,
                                     to=channel.name)
                else:
                    user.sendMessage("METADATA",
                                     key,
                                     visibility,
                                     value,
                                     to=channel.name)

    def buildISupport(self, data):
        data["MONITOR"] = self.ircd.config["monitor_limit"]

    def cleanIndex(self):
        removeKeys = []
        for target, notifyList in self.targetIndex.iteritems():
            if not notifyList:
                removeKeys.append(target)
        for target in removeKeys:
            del self.targetIndex[target]

    def sendUserMetadata(self, user, sendToUser):
        metadataList = user.metadataList()
        for key, value, visibility, setByUser in metadataList:
            if sendToUser.canSeeMetadataVisibility(visibility):
                sendToUser.sendMessage(irc.RPL_KEYVALUE, user.nick, key,
                                       visibility, value)
        sendToUser.sendMessage(irc.RPL_METADATAEND, "end of metadata")

    def parseParams(self, user, params, prefix, tags):
        if not params:
            user.sendSingleError("MonitorParams", irc.ERR_NEEDMOREPARAMS,
                                 "MONITOR", "Not enough parameters")
            return None
        subcmd = params[0]
        if subcmd in ("+", "-"):
            if len(params) < 2:
                user.sendSingleError("Monitor+Params", irc.ERR_NEEDMOREPARAMS,
                                     "MONITOR", "Not enough parameters")
                return None
            nickList = params[1].split(",")
            return {"subcmd": subcmd, "targets": nickList}
        if subcmd in ("C", "L", "S"):
            return {"subcmd": subcmd}
        user.sendSingleError("MonitorBadSubcmd", irc.ERR_UNKNOWNCOMMAND,
                             "MONITOR",
                             "Unknown subcommand: {}".format(subcmd))
        return None

    def execute(self, user, data):
        subcmd = data["subcmd"]
        if subcmd == "+":
            monitorLimit = self.ircd.config["monitor_limit"]
            newTargets = data["targets"]
            if "monitor" not in user.cache:
                user.cache["monitor"] = set()
            if monitorLimit is not None and (len(user.cache["monitor"]) +
                                             len(newTargets)) > monitorLimit:
                user.sendMessage(irc.ERR_MONLISTFULL, monitorLimit,
                                 ",".join(newTargets), "Monitor list is full.")
                return True
            onlineList = []
            onlineUserList = []
            offlineList = []
            userMonitorList = user.cache["monitor"]
            for target in newTargets:
                if target in userMonitorList:
                    continue
                userMonitorList.add(target)
                if target in self.ircd.userNicks:
                    onlineList.append(target)
                    onlineUserList.append(
                        self.ircd.users[self.ircd.userNicks[target]])
                else:
                    offlineList.append(target)
            if onlineList:
                onlineLines = splitMessage(",".join(onlineList), 400, ",")
                for line in onlineLines:
                    user.sendMessage(irc.RPL_MONONLINE, line)
            if offlineList:
                offlineLines = splitMessage(",".join(offlineList), 400, ",")
                for line in offlineLines:
                    user.sendMessage(irc.RPL_MONOFFLINE, line)
            if "capabilities" in user.cache and "metadata-notify" in user.cache[
                    "capabilities"]:
                for targetUser in onlineUserList:
                    self.sendUserMetadata(targetUser, user)
            return True
        if subcmd == "-":
            if "monitor" not in user.cache:
                return True
            userMonitorList = user.cache["monitor"]
            for target in data["targets"]:
                userMonitorList.discard(target)
            return True
        if subcmd == "C":
            if "monitor" in user.cache:
                del user.cache["monitor"]
            return True
        if subcmd == "L":
            if "monitor" not in user.cache:
                user.sendMessage(irc.RPL_ENDOFMONLIST, "End of MONITOR list")
                return True
            listLines = splitMessage(",".join(user.cache["monitor"]), 400, ",")
            for line in listLines:
                user.sendMessage(irc.RPL_MONLIST, line)
            user.sendMessage(irc.RPL_ENDOFMONLIST, "End of MONITOR list")
            return True
        if subcmd == "S":
            if "monitor" not in user.cache:
                return True
            onlineList = []
            offlineList = []
            for target in user.cache["monitor"]:
                if target in self.ircd.userNicks:
                    onlineList.append(target)
                else:
                    offlineList.append(target)
            onlineLines = splitMessage(",".join(onlineList), 400, ",")
            for line in onlineLines:
                user.sendMessage(irc.RPL_MONONLINE, line)
            offlineLines = splitMessage(",".join(offlineList), 400, ",")
            for line in offlineLines:
                user.sendMessage(irc.RPL_MONOFFLINE, line)
            return True
        return None
Esempio n. 18
0
class IRCUser(IRCBase):
	def __init__(self, ircd, ip, uuid = None, host = None):
		self.ircd = ircd
		self.uuid = ircd.createUUID() if uuid is None else uuid
		self.nick = None
		self.ident = None
		if ip[0] == ":": # Normalize IPv6 address for IRC
			ip = "0{}".format(ip)
		if host is None:
			try:
				resolvedHost = gethostbyaddr(ip)[0]
				# First half of host resolution done, run second half to prevent rDNS spoofing.
				# Refuse hosts that are too long as well.
				if ip == gethostbyname(resolvedHost) and len(resolvedHost) <= self.ircd.config.get("hostname_length", 64) and isValidHost(resolvedHost):
					host = resolvedHost
				else:
					host = ip
			except (herror, gaierror):
				host = ip
		self.realHost = host
		self.ip = ip
		self._hostStack = []
		self._hostsByType = {}
		self.gecos = None
		self._metadata = CaseInsensitiveDictionary()
		self.cache = {}
		self.channels = []
		self.modes = {}
		self.connectedSince = now()
		self.nickSince = now()
		self.idleSince = now()
		self._registerHolds = set(("connection", "NICK", "USER"))
		self.disconnectedDeferred = Deferred()
		self._messageBatches = {}
		self._errorBatchName = None
		self._errorBatch = []
		self.ircd.users[self.uuid] = self
		self.localOnly = False
		self.secureConnection = False
		self._pinger = LoopingCall(self._ping)
		self._registrationTimeoutTimer = reactor.callLater(self.ircd.config.get("user_registration_timeout", 10), self._timeoutRegistration)
	
	def connectionMade(self):
		# We need to callLater the connect action call because the connection isn't fully set up yet,
		# nor is it fully set up even with a delay of zero, which causes the message buffer not to be sent
		# when the connection is closed.
		# The "connection" register hold is used basically solely for the purposes of this to prevent potential
		# race conditions with registration.
		reactor.callLater(0.1, self._callConnectAction)
		if ISSLTransport.providedBy(self.transport):
			self.secureConnection = True
	
	def _callConnectAction(self):
		if self.ircd.runActionUntilFalse("userconnect", self, users=[self]):
			self.transport.loseConnection()
		else:
			self.register("connection")
	
	def dataReceived(self, data):
		self.ircd.runActionStandard("userrecvdata", self, data, users=[self])
		try:
			IRCBase.dataReceived(self, data)
		except Exception:
			self.ircd.log.failure("An error occurred while processing incoming data.")
			if self.uuid in self.ircd.users:
				self.disconnect("Error occurred")
	
	def sendLine(self, line):
		self.ircd.runActionStandard("usersenddata", self, line, users=[self])
		IRCBase.sendLine(self, line)
	
	def sendMessage(self, command, *args, **kw):
		"""
		Sends the given message to this user.
		Accepts the following keyword arguments:
		- prefix: The message prefix or None to suppress the default prefix
		    If not given, defaults to the server name.
		- to: The destination of the message or None if the message has no
		    destination. The implicit destination is this user if this
		    argument isn't specified.
		- tags: Dict of message tags to send.
		- alwaysPrefixLastParam: For compatibility with some broken clients,
		    you might want some messages to always have the last parameter
		    prefixed with a colon. To do that, pass this as True.
		"""
		if "prefix" not in kw:
			kw["prefix"] = self.ircd.name
		if kw["prefix"] is None:
			del kw["prefix"]
		to = self.nick if self.nick else "*"
		if "to" in kw:
			to = kw["to"]
			del kw["to"]
		if to:
			args = [to] + list(args)
		self.ircd.runActionStandard("modifyoutgoingmessage", self, command, args, kw)
		IRCBase.sendMessage(self, command, *args, **kw)
	
	def handleCommand(self, command, params, prefix, tags):
		if self.uuid not in self.ircd.users:
			return # we have been disconnected - ignore all further commands
		if command in self.ircd.userCommands:
			handlers = self.ircd.userCommands[command]
			if not handlers:
				return
			data = None
			spewRegWarning = True
			affectedUsers = []
			affectedChannels = []
			for handler in handlers:
				if handler[0].forRegistered is not None:
					if (handler[0].forRegistered is True and not self.isRegistered()) or (handler[0].forRegistered is False and self.isRegistered()):
						continue
				spewRegWarning = False
				data = handler[0].parseParams(self, params, prefix, tags)
				if data is not None:
					affectedUsers = handler[0].affectedUsers(self, data)
					affectedChannels = handler[0].affectedChannels(self, data)
					if self not in affectedUsers:
						affectedUsers.append(self)
					break
			if data is None:
				if spewRegWarning:
					if self.isRegistered():
						self.sendMessage(irc.ERR_ALREADYREGISTERED, "You may not reregister")
					else:
						self.sendMessage(irc.ERR_NOTREGISTERED, command, "You have not registered")
				elif self._hasBatchedErrors():
					self._dispatchErrorBatch()
				return
			self._clearErrorBatch()
			if self.ircd.runComboActionUntilValue((("commandpermission-{}".format(command), self, data), ("commandpermission", self, command, data)), users=affectedUsers, channels=affectedChannels) is False:
				return
			self.ircd.runComboActionStandard((("commandmodify-{}".format(command), self, data), ("commandmodify", self, command, data)), users=affectedUsers, channels=affectedChannels) # This allows us to do processing without the "stop on empty" feature of runActionProcessing
			for handler in handlers:
				if handler[0].execute(self, data):
					if handler[0].resetsIdleTime:
						self.idleSince = now()
					break # If the command executor returns True, it was handled
			else:
				return # Don't process commandextra if it wasn't handled
			self.ircd.runComboActionStandard((("commandextra-{}".format(command), self, data), ("commandextra", self, command, data)), users=affectedUsers, channels=affectedChannels)
		else:
			if not self.ircd.runActionFlagTrue("commandunknown", self, command, params, {}):
				self.sendMessage(irc.ERR_UNKNOWNCOMMAND, command, "Unknown command")
	
	def createMessageBatch(self, batchName, batchType, batchParameters = None):
		"""
		Start a new message batch with the given batch name, type, and list of parameters.
		If a batch with the given name already exists, that batch will be overwritten.
		"""
		self._messageBatches[batchName] = { "type": batchType, "parameters": batchParameters, "messages": [] }
	
	def sendMessageInBatch(self, batchName, command, *args, **kw):
		"""
		Adds a message to the batch with the given name.
		"""
		if batchName not in self._messageBatches:
			return
		self._messageBatches[batchName]["messages"].append((command, args, kw))
	
	def sendBatch(self, batchName):
		"""
		Sends the messages in the given batch to the user.
		"""
		if batchName not in self._messageBatches:
			return
		batchType = self._messageBatches[batchName]["type"]
		batchParameters = self._messageBatches[batchName]["parameters"]
		self.ircd.runActionStandard("startbatchsend", self, batchName, batchType, batchParameters)
		for messageData in self._messageBatches[batchName]["messages"]:
			self.sendMessage(messageData[0], *messageData[1], **messageData[2])
		self.ircd.runActionStandard("endbatchsend", self, batchName, batchType, batchParameters)
	
	def startErrorBatch(self, batchName):
		"""
		Used to start an error batch when sending multiple error messages to a
		user from a command's parseParams or from the commandpermission action.
		"""
		if not self._errorBatchName or not self._errorBatch: # Only the first batch should apply
			self._errorBatchName = batchName
		
	def sendBatchedError(self, batchName, command, *args, **kw):
		"""
		Adds an error to the current error batch if the specified error batch
		is the current error batch.
		"""
		if batchName and self._errorBatchName == batchName:
			self._errorBatch.append((command, args, kw))
	
	def sendSingleError(self, batchName, command, *args, **kw):
		"""
		Creates a batch containing a single error and adds the specified error
		to it.
		"""
		if not self._errorBatchName:
			self._errorBatchName = batchName
			self._errorBatch.append((command, args, kw))
	
	def _hasBatchedErrors(self):
		if self._errorBatch:
			return True
		return False
	
	def _clearErrorBatch(self):
		self._errorBatchName = None
		self._errorBatch = []
	
	def _dispatchErrorBatch(self):
		for error in self._errorBatch:
			self.sendMessage(error[0], *error[1], **error[2])
		self._clearErrorBatch()
	
	def filterConditionalTags(self, conditionalTags):
		applyTags = {}
		for tag, data in conditionalTags.iteritems():
			value, check = data
			if check(self):
				applyTags[tag] = value
		return applyTags
	
	def connectionLost(self, reason):
		if self.uuid in self.ircd.users:
			self.disconnect("Connection reset")
		self.disconnectedDeferred.callback(None)
	
	def disconnect(self, reason):
		"""
		Disconnects the user from the server.
		"""
		self.ircd.log.debug("Disconnecting user {user.uuid} ({user.hostmask()}): {reason}", user=self, reason=reason)
		if self._pinger:
			if self._pinger.running:
				self._pinger.stop()
			self._pinger = None
		if self._registrationTimeoutTimer:
			if self._registrationTimeoutTimer.active():
				self._registrationTimeoutTimer.cancel()
			self._registrationTimeoutTimer = None
		self.ircd.recentlyQuitUsers[self.uuid] = now()
		del self.ircd.users[self.uuid]
		if self.isRegistered():
			del self.ircd.userNicks[self.nick]
		userSendList = [self]
		while self.channels:
			channel = self.channels[0]
			userSendList.extend(channel.users.keys())
			self._leaveChannel(channel)
		userSendList = [u for u in set(userSendList) if u.uuid[:3] == self.ircd.serverID]
		userSendList.remove(self)
		self.ircd.runActionProcessing("quitmessage", userSendList, self, reason, users=[self] + userSendList)
		self.ircd.runActionStandard("quit", self, reason, users=self)
		self.transport.loseConnection()
	
	def _timeoutRegistration(self):
		if self.isRegistered():
			self._pinger.start(self.ircd.config.get("user_ping_frequency", 60), False)
			return
		self.disconnect("Registration timeout")
	
	def _ping(self):
		self.ircd.runActionStandard("pinguser", self)
	
	def isRegistered(self):
		"""
		Returns True if this user session is fully registered.
		"""
		return not self._registerHolds
	
	def register(self, holdName):
		"""
		Removes the specified hold on a user's registratrion. If this is the
		last hold on a user, completes registration on the user.
		"""
		if holdName not in self._registerHolds:
			return
		self._registerHolds.remove(holdName)
		if not self._registerHolds:
			if not self.nick or self.nick in self.ircd.userNicks:
				self._registerHolds.add("NICK")
			if not self.ident or not self.gecos:
				self._registerHolds.add("USER")
			if self._registerHolds:
				return
			self._registerHolds.add("registercheck") # The user shouldn't be considered registered until we complete these final checks
			if self.ircd.runActionUntilFalse("register", self, users=[self]):
				self.transport.loseConnection()
				return
			self._registerHolds.remove("registercheck")
			self.ircd.userNicks[self.nick] = self.uuid
			self.ircd.log.debug("Registering user {user.uuid} ({user.hostmask()})", user=self)
			versionWithName = "txircd-{}".format(version)
			self.sendMessage(irc.RPL_WELCOME, "Welcome to the {} Internet Relay Chat Network {}".format(self.ircd.config["network_name"], self.hostmask()))
			self.sendMessage(irc.RPL_YOURHOST, "Your host is {}, running version {}".format(self.ircd.name, versionWithName))
			self.sendMessage(irc.RPL_CREATED, "This server was created {}".format(self.ircd.startupTime.replace(microsecond=0)))
			chanModes = "".join(["".join(modes.keys()) for modes in self.ircd.channelModes])
			chanModes += "".join(self.ircd.channelStatuses.keys())
			self.sendMessage(irc.RPL_MYINFO, self.ircd.name, versionWithName, "".join(["".join(modes.keys()) for modes in self.ircd.userModes]), chanModes)
			self.sendISupport()
			self.ircd.runActionStandard("welcome", self, users=[self])
	
	def addRegisterHold(self, holdName):
		"""
		Adds a register hold to this user if the user is not yet registered.
		"""
		if not self._registerHolds:
			return
		self._registerHolds.add(holdName)
	
	def sendISupport(self):
		"""
		Sends ISUPPORT to this user."""
		isupportList = self.ircd.generateISupportList()
		isupportMsgList = splitMessage(" ".join(isupportList), 350)
		for line in isupportMsgList:
			lineArgs = line.split(" ")
			lineArgs.append("are supported by this server")
			self.sendMessage(irc.RPL_ISUPPORT, *lineArgs)
	
	def hostmask(self):
		"""
		Returns the user's hostmask.
		"""
		return "{}!{}@{}".format(self.nick, self.ident, self.host())
	
	def hostmaskWithRealHost(self):
		"""
		Returns the user's hostmask using the user's real host rather than any
		vhost that may have been applied.
		"""
		return "{}!{}@{}".format(self.nick, self.ident, self.realHost)
	
	def hostmaskWithIP(self):
		"""
		Returns the user's hostmask using the user's IP address instead of the
		host.
		"""
		return "{}!{}@{}".format(self.nick, self.ident, self.ip)
	
	def changeNick(self, newNick, fromServer = None):
		"""
		Changes this user's nickname. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		"""
		if newNick == self.nick:
			return
		if newNick in self.ircd.userNicks and self.ircd.userNicks[newNick] != self.uuid:
			return
		oldNick = self.nick
		if oldNick and oldNick in self.ircd.userNicks:
			del self.ircd.userNicks[self.nick]
		self.nick = newNick
		self.nickSince = now()
		if self.isRegistered():
			self.ircd.userNicks[self.nick] = self.uuid
			userSendList = [self]
			for channel in self.channels:
				userSendList.extend(channel.users.keys())
			userSendList = [u for u in set(userSendList) if u.uuid[:3] == self.ircd.serverID]
			self.ircd.runActionProcessing("changenickmessage", userSendList, self, oldNick, users=userSendList)
			self.ircd.runActionStandard("changenick", self, oldNick, fromServer, users=[self])
	
	def changeIdent(self, newIdent, fromServer = None):
		"""
		Changes this user's ident. If initiated by a remote server, that server
		should be specified in the fromServer parameter.
		"""
		if newIdent == self.ident:
			return
		if len(newIdent) > self.ircd.config.get("ident_length", 12):
			return
		oldIdent = self.ident
		self.ident = newIdent
		if self.isRegistered():
			self.ircd.runActionStandard("changeident", self, oldIdent, fromServer, users=[self])
	
	def host(self):
		if not self._hostStack:
			return self.realHost
		return self._hostsByType[self._hostStack[-1]]
	
	def changeHost(self, hostType, newHost, fromServer = None):
		"""
		Changes a user's host. If initiated by a remote server, that server
		should be specified in the fromServer parameter.
		"""
		if hostType == "*":
			return
		if len(newHost) > self.ircd.config.get("hostname_length", 64):
			return
		if hostType in self._hostsByType and self._hostsByType[hostType] == newHost:
			return
		oldHost = self.host()
		self._hostsByType[hostType] = newHost
		if hostType in self._hostStack:
			self._hostStack.remove(hostType)
		self._hostStack.append(hostType)
		if self.isRegistered():
			self.ircd.runComboActionStandard((("changehost", self, hostType, oldHost, fromServer), ("updatehost", self, hostType, oldHost, newHost, fromServer)), users=[self])
	
	def updateHost(self, hostType, newHost, fromServer = None):
		"""
		Updates the host of a given host type for the user. If initiated by
		a remote server, that server should be specified in the fromServer
		parameter.
		"""
		if hostType not in self._hostStack:
			self.changeHost(hostType, newHost, fromServer)
			return
		if hostType == "*":
			return
		if len(newHost) > self.ircd.config.get("hostname_length", 64):
			return
		if hostType in self._hostsByType and self._hostsByType[hostType] == newHost:
			return
		oldHost = self.host()
		oldHostOfType = None
		if hostType in self._hostsByType:
			oldHostOfType = self._hostsByType[hostType]
		self._hostsByType[hostType] = newHost
		changedUserHost = (oldHost != self.host())
		changedHostOfType = (oldHostOfType != newHost)
		if self.isRegistered():
			if changedUserHost and changedHostOfType:
				self.ircd.runComboActionStandard((("changehost", self, hostType, oldHost, fromServer), ("updatehost", self, hostType, oldHost, newHost, fromServer)), users=[self])
			elif changedHostOfType:
				self.ircd.runActionStandard("updatehost", self, hostType, oldHost, newHost, fromServer, users=[self])
	
	def resetHost(self, hostType, fromServer = None):
		"""
		Resets the user's host to the real host.
		"""
		if hostType not in self._hostsByType:
			return
		oldHost = self.host()
		if hostType in self._hostStack:
			self._hostStack.remove(hostType)
		del self._hostsByType[hostType]
		currentHost = self.host()
		if currentHost != oldHost:
			self.ircd.runComboActionStandard((("changehost", self, hostType, oldHost, fromServer), ("updatehost", self, hostType, oldHost, None, fromServer)), users=[self])
		else:
			self.ircd.runActionStandard("updatehost", self, hostType, oldHost, None, fromServer, users=[self])
	
	def currentHostType(self):
		if self._hostStack:
			return self._hostStack[-1]
		return "*"
	
	def changeGecos(self, newGecos, fromServer = None):
		"""
		Changes a user's real name. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		"""
		if len(newGecos) > self.ircd.config.get("gecos_length", 128):
			return
		if newGecos == self.gecos:
			return
		oldGecos = self.gecos
		self.gecos = newGecos
		if self.isRegistered():
			self.ircd.runActionStandard("changegecos", self, oldGecos, fromServer, users=[self])
	
	def metadataKeyExists(self, key):
		"""
		Checks whether the specified key exists in the user's metadata.
		"""
		return key in self._metadata
	
	def metadataKeyCase(self, key):
		"""
		Returns the specified key in the user's metadata in its original case.
		Returns None if the given key is not in the user's metadata.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][0]
	
	def metadataValue(self, key):
		"""
		Returns the value of the given key in the user's metadata or None if
		the given key is not in the user's metadata.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][1]
	
	def metadataVisibility(self, key):
		"""
		Returns the visibility value of the given key in the user's metadata or
		None if the given key is not in the user's metadata.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][2]
	
	def metadataSetByUser(self, key):
		"""
		Returns whether the given key in the user's metadata was set by a user
		or None if the given key is not in the user's metadata.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][3]
	
	def metadataList(self):
		"""
		Returns the list of metadata keys/values for the user as a list of
		tuples in the format
		[ (key, value, visibility, setByUser) ]
		"""
		return self._metadata.values()
	
	def setMetadata(self, key, value, visibility, setByUser, fromServer = None):
		"""
		Sets metadata for the user. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		If the value is None, deletes the metadata at the provided key.
		"""
		if not isValidMetadataKey(key):
			return False
		oldData = None
		if key in self._metadata:
			oldData = self._metadata[key]
		if setByUser and oldData and not oldData[3]:
			return False
		if setByUser and self.ircd.runActionUntilValue("usercansetmetadata", key, users=[self]) is False:
			return False
		if value is None:
			if key in self._metadata:
				del self._metadata[key]
		elif not visibility:
			return False
		else:
			self._metadata[key] = (key, value, visibility, setByUser)
		oldValue = oldData[1] if oldData else None
		self.ircd.runActionStandard("usermetadataupdate", self, key, oldValue, value, visibility, setByUser, fromServer, users=[self])
		return True
	
	def canSeeMetadataVisibility(self, visibility):
		if visibility == "*":
			return True
		return self.ircd.runActionUntilValue("usercanseemetadata", self, visibility) is not False
	
	def joinChannel(self, channel, override = False):
		"""
		Joins the user to a channel. Specify the override parameter only if all
		permission checks should be bypassed.
		"""
		if channel in self.channels:
			return
		if not override:
			if self.ircd.runActionUntilValue("joinpermission", channel, self, users=[self], channels=[channel]) is False:
				return
		channel.users[self] = { "status": "" }
		self.channels.append(channel)
		newChannel = False
		if channel.name not in self.ircd.channels:
			newChannel = True
			self.ircd.channels[channel.name] = channel
			self.ircd.recentlyDestroyedChannels[channel.name] = False
		# We need to send the JOIN message before doing other processing, as chancreate will do things like
		# mode defaulting, which will send messages about the channel before the JOIN message, which is bad.
		messageUsers = [u for u in channel.users.iterkeys() if u.uuid[:3] == self.ircd.serverID]
		self.ircd.runActionProcessing("joinmessage", messageUsers, channel, self, users=messageUsers, channels=[channel])
		if newChannel:
			self.ircd.runActionStandard("channelcreate", channel, self, channels=[channel])
		self.ircd.runActionStandard("join", channel, self, users=[self], channels=[channel])
	
	def leaveChannel(self, channel, partType = "PART", typeData = {}, fromServer = None):
		"""
		Removes the user from a channel. The partType and typeData are used for
		the leavemessage action to send the parting message. If the channel
		leaving is initiated by a remote server, that server should be
		specified in the fromServer parameter.
		"""
		if channel not in self.channels:
			return
		messageUsers = [u for u in channel.users.iterkeys() if u.uuid[:3] == self.ircd.serverID]
		self.ircd.runActionProcessing("leavemessage", messageUsers, channel, self, partType, typeData, fromServer, users=[self], channels=[channel])
		self._leaveChannel(channel)
	
	def _leaveChannel(self, channel):
		self.ircd.runActionStandard("leave", channel, self, users=[self], channels=[channel])
		self.channels.remove(channel)
		del channel.users[self]
	
	def setModes(self, modes, defaultSource):
		"""
		Sets modes on the user. Accepts modes as a list of tuples in the
		format:
		[ (adding, mode, param, setBy, setTime) ]
		- adding: True if we're setting the mode; False if unsetting
		- mode: The mode letter
		- param: The mode's parameter; None if no parameter is needed for that
		    mode
		- setBy: Optional, only used for list modes; a human-readable string
		    (typically server name or nick!user@host) for who/what set this
		    mode)
		- setTime: Optional, only used for list modes; a datetime object
		    containing when the mode was set
		
		The defaultSource is a valid user ID or server ID of someone who set
		the modes. It is used as the source for announcements about the mode
		change and as the default setter for any list modes who do not have the
		setBy parameter specified.
		The default time for list modes with no setTime specified is now().
		"""
		modeChanges = []
		defaultSourceName = self._sourceName(defaultSource)
		if defaultSourceName is None:
			raise ValueError ("Source must be a valid user or server ID.")
		nowTime = now()
		for modeData in modes:
			mode = modeData[1]
			if mode not in self.ircd.userModeTypes:
				continue
			setBy = defaultSourceName
			setTime = nowTime
			modeType = self.ircd.userModeTypes[mode]
			adding = modeData[0]
			if modeType in (ModeType.List, ModeType.ParamOnUnset, ModeType.Param):
				param = modeData[2]
			else:
				param = None
			if modeType == ModeType.List:
				dataCount = len(modeData)
				if dataCount >= 4:
					setBy = modeData[3]
				if dataCount >= 5:
					setTime = modeData[4]
			if adding:
				paramList = self.ircd.userModes[modeType][mode].checkSet(self, param)
			else:
				paramList = self.ircd.userModes[modeType][mode].checkUnset(self, param)
			if paramList is None:
				continue
			
			for parameter in paramList:
				if self._applyMode(adding, modeType, mode, parameter, setBy, setTime):
					modeChanges.append((adding, mode, parameter, setBy, setTime))
		
		self._notifyModeChanges(modeChanges, defaultSource, defaultSourceName)
		return modeChanges
	
	def setModesByUser(self, user, modes, params, override = False):
		"""
		Parses a mode string specified by a user and sets those modes on the
		user.
		The user parameter should be the user who set the modes (usually, but
		not always, this user).
		The modes parameter is the actual modes string; parameters specified by
		the user should be as a list of strings in params.
		The override parameter should be used only when all permission checks
		should be overridden.
		"""
		adding = True
		changes = []
		setBy = self._sourceName(user.uuid)
		setTime = now()
		for mode in modes:
			if len(changes) >= self.ircd.config.get("modes_per_line", 20):
				break
			if mode == "+":
				adding = True
				continue
			if mode == "-":
				adding = False
				continue
			if mode not in self.ircd.userModeTypes:
				user.sendMessage(irc.ERR_UNKNOWNMODE, mode, "is unknown mode char to me")
				continue
			modeType = self.ircd.userModeTypes[mode]
			param = None
			if modeType in (ModeType.List, ModeType.ParamOnUnset) or (adding and modeType == ModeType.Param):
				try:
					param = params.pop(0)
				except IndexError:
					if modeType == ModeType.List:
						self.ircd.userModes[modeType][mode].showListParams(user, self)
					continue
			if adding:
				paramList = self.ircd.userModes[modeType][mode].checkSet(self, param)
			else:
				paramList = self.ircd.userModes[modeType][mode].checkUnset(self, param)
			if paramList is None:
				continue
			
			for parameter in paramList:
				if len(changes) >= self.ircd.config.get("modes_per_line", 20):
					break
				if not override and self.ircd.runActionUntilValue("modepermission-user-{}".format(mode), self, user, adding, parameter, users=[self, user]) is False:
					continue
				if adding:
					if modeType == ModeType.List:
						if mode in self.modes and len(self.modes[mode]) > self.ircd.config.get("user_listmode_limit", 128):
							user.sendMessage(irc.ERR_BANLISTFULL, self.name, parameter, "Channel +{} list is full".format(mode))
							continue
				if self._applyMode(adding, modeType, mode, parameter, setBy, setTime):
					changes.append((adding, mode, parameter, setBy, setTime))
		self._notifyModeChanges(changes, user.uuid, setBy)
		return changes
	
	def _applyMode(self, adding, modeType, mode, parameter, setBy, setTime):
		if parameter:
			if len(parameter) > 255:
				return False
			if " " in parameter:
				return False
		
		if adding:
			if modeType == ModeType.List:
				if mode in self.modes:
					if len(self.modes[mode]) > self.ircd.config.get("user_listmode_limit", 128):
						return False
					for paramData in self.modes[mode]:
						if parameter == paramData[0]:
							return False
				else:
					self.modes[mode] = []
				self.modes[mode].append((parameter, setBy, setTime))
				return True
			if mode in self.modes and self.modes[mode] == parameter:
				return False
			self.modes[mode] = parameter
			return True
		
		if modeType == ModeType.List:
			if mode not in self.modes:
				return False
			for index, paramData in enumerate(self.modes[mode]):
				if paramData[0] == parameter:
					del self.modes[mode][index]
					break
			else:
				return False
			if not self.modes[mode]:
				del self.modes[mode]
			return True
		if mode not in self.modes:
			return False
		if modeType == ModeType.ParamOnUnset and parameter != self.modes[mode]:
			return False
		del self.modes[mode]
		return True
	
	def _notifyModeChanges(self, modeChanges, source, sourceName):
		if not modeChanges:
			return 
		for change in modeChanges:
			self.ircd.runActionStandard("modechange-user-{}".format(change[1]), self, change[3], change[0], change[2], users=[self])
		
		users = []
		if source in self.ircd.users and source[:3] == self.ircd.serverID:
			users.append(self.ircd.users[source])
		if self.uuid[:3] == self.ircd.serverID:
			users.append(self)
		if users:
			self.ircd.runActionProcessing("modemessage-user", users, self, source, sourceName, modeChanges, users=users)
		self.ircd.runActionStandard("modechanges-user", self, source, sourceName, modeChanges, users=[self])
	
	def _sourceName(self, source):
		if source in self.ircd.users:
			return self.ircd.users[source].hostmask()
		if source == self.ircd.serverID:
			return self.ircd.name
		if source in self.ircd.servers:
			return self.ircd.servers[source].name
		return None
	
	def modeString(self, toUser):
		"""
		Get a user-reportable mode string for the modes set on the user.
		"""
		modeStr = ["+"]
		params = []
		for mode in self.modes:
			modeType = self.ircd.userModeTypes[mode]
			if modeType not in (ModeType.ParamOnUnset, ModeType.Param, ModeType.NoParam):
				continue
			if modeType != ModeType.NoParam:
				param = None
				if toUser:
					param = self.ircd.userModes[modeType][mode].showParam(toUser, self)
				if not param:
					param = self.modes[mode]
			else:
				param = None
			modeStr.append(mode)
			if param:
				params.append(param)
		if params:
			return "{} {}".format("".join(modeStr), " ".join(params))
		return "".join(modeStr)
Esempio n. 19
0
class Logger(Module):
    # This is to help save from excessive disk I/O by holding the log files instead of constantly opening/flushing/closing them
    logfiles = CaseInsensitiveDictionary()

    def timePrefix(self):
        nowtime = now()
        return "[{:02d}:{:02d}:{:02d}]".format(nowtime.hour, nowtime.minute,
                                               nowtime.second)

    def writeLog(self, chan, line):
        line = "{} {}\n".format(self.timePrefix(), line)
        if chan.name not in self.logfiles:
            self.logfiles[chan.name] = DailyLogFile(
                chan.name, self.ircd.servconfig["app_log_dir"])
        logFile = self.logfiles[chan.name]
        if logFile.shouldRotate():
            logFile.rotate()
        logFile.write(line)

    def logMsg(self, cmd, data):
        if cmd in ["PRIVMSG", "NOTICE"]:
            if "targetchan" not in data or not data["targetchan"]:
                return
            user = data["user"]
            message = data["message"]
            if cmd == "PRIVMSG":
                if message[0:7] == "\x01ACTION":
                    message = data["message"][8:]
                    if message[-1] == "\x01":
                        message = message[:-1]
                    for index, chan in enumerate(data["targetchan"]):
                        prefix = self.ircd.prefixes[
                            chan.users[user]
                            [0]][0] if user in chan.users and chan.users[
                                user] else ""
                        if data["chanmod"][index]:
                            self.writeLog(
                                chan, "*({}#) {}{} {}".format(
                                    data["chanmod"][index], prefix,
                                    user.nickname, message))
                        else:
                            self.writeLog(
                                chan,
                                "* {}{} {}".format(prefix, user.nickname,
                                                   message))
                else:
                    for index, chan in enumerate(data["targetchan"]):
                        prefix = self.ircd.prefixes[
                            chan.users[user]
                            [0]][0] if user in chan.users and chan.users[
                                user] else ""
                        if data["chanmod"][index]:
                            self.writeLog(
                                chan,
                                "<{}#:{}{}> {}".format(data["chanmod"][index],
                                                       prefix, user.nickname,
                                                       message))
                        else:
                            self.writeLog(
                                chan,
                                "<{}{}> {}".format(prefix, user.nickname,
                                                   message))
            elif cmd == "NOTICE":
                for index, chan in enumerate(data["targetchan"]):
                    prefix = self.ircd.prefixes[chan.users[user][0]][
                        0] if user in chan.users and chan.users[user] else ""
                    if data["chanmod"][index]:
                        self.writeLog(
                            chan,
                            "-{}#:{}{}- {}".format(data["chanmod"][index],
                                                   prefix, user.nickname,
                                                   message))
                    else:
                        self.writeLog(
                            chan, "-{}{}- {}".format(prefix, user.nickname,
                                                     message))
        elif cmd == "PART":
            if "reason" in data:
                for chan in data["targetchan"]:
                    self.writeLog(
                        chan, "< {} has left the channel: {}".format(
                            data["user"].nickname, data["reason"]))
            else:
                for chan in data["targetchan"]:
                    self.writeLog(
                        chan, "< {} has left the channel".format(
                            data["user"].nickname))
        elif cmd == "KICK":
            self.writeLog(
                data["targetchan"], "< {} was kicked by {} ({})".format(
                    data["targetuser"].nickname, data["user"].nickname,
                    data["reason"]))

    def logJoin(self, user, channel):
        self.writeLog(channel,
                      "> {} has joined the channel".format(user.nickname))

    def logNick(self, user, oldNick):
        for cdata in self.ircd.channels.itervalues():
            if user in cdata.users:
                self.writeLog(
                    cdata,
                    "! {} is now known as {}".format(oldNick, user.nickname))

    def logQuit(self, user, reason):
        for cdata in self.ircd.channels.itervalues():
            if user in cdata.users:
                self.writeLog(
                    cdata, "< {} has quit: {}".format(user.nickname, reason))

    def logTopic(self, channel, topic, setter):
        self.writeLog(
            channel,
            "! {} has set the channel topic: {}".format(setter, topic))

    def logMode(self, channel, source, modeLine, modesChanged):
        # Filter out users so that we only work on channels
        try:
            channel.name
        except AttributeError:
            return
        self.writeLog(channel,
                      "! {} has set modes {}".format(source, modeLine))

    def onDestroy(self, channel):
        if channel.name in self.logfiles:
            self.logfiles[channel.name].close()
            del self.logfiles[channel.name]

    def closeAllFiles(self):
        for logFile in self.logfiles.itervalues():
            logFile.close()
        self.logfiles.clear()
Esempio n. 20
0
class IRCD(Factory):
    protocol = IRCProtocol

    def __init__(self, config, options = None, sslCert = None):
        reactor.addSystemEventTrigger("before", "shutdown", self.cleanup)
        self.dead = False
        
        self.config = config
        self.version = "txircd-{}".format(__version__)
        self.created = now()
        self.servers = CaseInsensitiveDictionary()
        self.users = CaseInsensitiveDictionary()
        self.userid = {}
        self.channels = CaseInsensitiveDictionary()
        self.peerConnections = {}
        self.ssl_cert = sslCert
        self.modules = {}
        self.actions = {
            "connect": [],
            "register": [],
            "welcome": [],
            "join": [],
            "joinmessage": [],
            "nick": [],
            "quit": [],
            "topic": [],
            "nameslistentry": [],
            "chancreate": [],
            "chandestroy": [],
            "commandextra": [],
            "commandunknown": [],
            "commandpermission": [],
            "metadataupdate": [],
            "recvdata": [],
            "senddata": [],
            "netmerge": [],
            "netsplit": []
        }
        self.commands = {}
        self.channel_modes = [{}, {}, {}, {}]
        self.channel_mode_type = {}
        self.user_modes = [{}, {}, {}, {}]
        self.user_mode_type = {}
        self.prefixes = {}
        self.prefix_symbols = {}
        self.prefix_order = []
        self.server_commands = {}
        self.module_data_cache = {}
        self.server_factory = None
        self.common_modules = set()
        log.msg("Loading module data...")
        try:
            with open("data.yaml", "r") as dataFile:
                self.serialized_data = yaml.safe_load(dataFile)
                if self.serialized_data is None:
                    self.serialized_data = {}
        except IOError:
            self.serialized_data = {}
        self.serialize_timer = LoopingCall(self.save_serialized)
        self.isupport = {}
        self.usercount = {
            "localmax": 0,
            "globalmax": 0
        }
        log.msg("Loading configuration...")
        self.servconfig = {}
        if not options:
            options = {}
        self.load_options(options)
        self.name = self.servconfig["server_name"]
        log.msg("Loading modules...")
        self.all_module_load()
        self.autoconnect_servers = LoopingCall(self.server_autoconnect)
        self.autoconnect_servers.start(60, now=False) # The server factory isn't added to here yet
        # Fill in the default ISUPPORT dictionary once config and modules are loaded, since some values depend on those
        self.isupport["CASEMAPPING"] = "rfc1459"
        self.isupport["CHANMODES"] = ",".join(["".join(modedict.keys()) for modedict in self.channel_modes])
        self.isupport["CHANNELLEN"] = "64"
        self.isupport["CHANTYPES"] = "#"
        self.isupport["MODES"] = 20
        self.isupport["NETWORK"] = self.servconfig["server_network_name"]
        self.isupport["NICKLEN"] = "32"
        self.isupport["PREFIX"] = "({}){}".format("".join(self.prefix_order), "".join([self.prefixes[mode][0] for mode in self.prefix_order]))
        self.isupport["STATUSMSG"] = "".join([self.prefixes[mode][0] for mode in self.prefix_order])
        self.isupport["TOPICLEN"] = "316"
        
        self.serialize_timer.start(300, now=False) # run every 5 minutes
    
    def all_module_load(self):
        # load RFC-required modules
        rfc_spec = [
                    # commands
                    "cmd_user", "cmd_nick", "cmd_pass", # registration
                    "cmd_ping", "cmd_pong", # connection keep-alive
                    "cmd_join", "cmd_part", "cmd_kick", "cmd_topic", "cmd_mode", "cmd_invite", # channels
                    "cmd_quit", # connection end
                    "cmd_privmsg_notice", # messages
                    "cmd_oper", "umode_o", "cmd_rehash", "cmd_wallops", # oper
                    "cmd_admin", "cmd_info", "cmd_lusers", "cmd_motd", "cmd_stats", "cmd_time", "cmd_version", # server info
                    "cmd_away", "cmd_ison", "cmd_userhost", "cmd_who", "cmd_whois", "cmd_whowas", # user info
                    "cmd_names", "cmd_list", # channel info
                    "cmd_kill", "cmd_eline", "cmd_gline", "cmd_kline", "cmd_qline", "cmd_zline", # user management
                    "cmd_links", "cmd_connect", "cmd_squit", # linked servers
                    
                    # channel modes
                    "cmode_b", "cmode_i", "cmode_k", "cmode_l", "cmode_m", "cmode_n", "cmode_o", "cmode_p", "cmode_s", "cmode_t", "cmode_v",
                    
                    # user modes
                    "umode_i", "umode_s"
                    ]
        ircv3_spec = [ # http://ircv3.atheme.org/
                    "ircv3_cap", # capability mechanism which essentially serves as the base for everything else
                    "ircv3_multi-prefix", "ircv3_sasl", # other IRC 3.1 base extensions
                    "ircv3_account-notify", "ircv3_away-notify", "ircv3_extended-join", "ircv3_tls", # IRC 3.1 optional extensions
                    "ircv3_monitor", "ircv3_metadata" # IRC 3.2 base extensions
                    ]
        for module in rfc_spec:
            check = self.load_module(module)
            if not check:
                log.msg("An RFC-required capability could not be loaded!")
                raise RuntimeError("A module required for RFC compatibility could not be loaded.")
                return
        if self.servconfig["app_irc_spec"] == "ircv3":
            for module in ircv3_spec:
                check = self.load_module(module)
                if not check:
                    log.msg("IRCv3 compatibility was specified, but a required IRCv3 module could not be loaded!")
                    raise RuntimeError("A module required for IRCv3 compatibility could not be loaded.")
                    return
        for module in self.servconfig["server_modules"]:
            self.load_module(module)
    
    def rehash(self):
        log.msg("Rehashing config file and reloading modules")
        try:
            with open(self.config) as f:
                self.load_options(yaml.safe_load(f))
            self.all_module_load()
        except:
            return False
        return True
    
    def load_options(self, options):
        for var, value in options.iteritems():
            self.servconfig[var] = value
        for var, value in default_options.iteritems():
            if var not in self.servconfig:
                self.servconfig[var] = value
    
    def cleanup(self):
        # Track the disconnections so we know they get done
        deferreds = []
        log.msg("Disconnecting servers...")
        for server in self.servers.values():
            if server.nearHop == self.name:
                server.transport.loseConnection()
                deferreds.append(server.disconnected)
        # Cleanly disconnect all clients
        log.msg("Disconnecting clients...")
        for u in self.users.values():
            u.sendMessage("ERROR", ":Closing Link: {} [Server shutting down]".format(u.hostname), to=None, prefix=None)
            u.socket.transport.loseConnection()
            deferreds.append(u.disconnected)
        log.msg("Unloading modules...")
        for name, spawner in self.modules.iteritems():
            spawner.cleanup()
            try:
                data_to_save, free_data = self.modules[name].data_serialize()
                if data_to_save:
                    self.serialized_data[name] = data_to_save
            except AttributeError:
                pass
        log.msg("Saving serialized data...")
        self.save_serialized()
        # Return deferreds
        log.msg("Waiting on deferreds...")
        self.dead = True
        return DeferredList(deferreds)
    
    def server_autoconnect(self):
        def sendServerHandshake(protocol, password):
            protocol.callRemote(IntroduceServer, name=self.name, password=password, description=self.servconfig["server_description"], version=protocol_version, commonmodules=self.common_modules)
            protocol.sentDataBurst = False
        for server in self.servconfig["serverlink_autoconnect"]:
            if server not in self.servers and server in self.servconfig["serverlinks"]:
                log.msg("Initiating autoconnect to server {}".format(server))
                servinfo = self.servconfig["serverlinks"][server]
                if "ip" not in servinfo or "port" not in servinfo:
                    continue
                if "bindaddress" in servinfo and "bindport" in servinfo:
                    bind = (servinfo["bindaddress"], servinfo["bindport"])
                else:
                    bind = None
                creator = ClientCreator(reactor, ServerProtocol, self)
                if "ssl" in servinfo and servinfo["ssl"]:
                    d = creator.connectSSL(servinfo["ip"], servinfo["port"], self.ssl_cert, bindAddress=bind)
                else:
                    d = creator.connectTCP(servinfo["ip"], servinfo["port"], bindAddress=bind)
                d.addCallback(sendServerHandshake, servinfo["outgoing_password"])
    
    def load_module(self, name):
        saved_data = {}
        if name in self.modules:
            try:
                data_to_save, free_data = self.modules[name].data_serialize()
                if data_to_save:
                    self.serialized_data[name] = data_to_save
                elif name in self.serialized_data:
                    del self.serialized_data[name]
                for key, value in free_data.iteritems():
                    saved_data[key] = value
                for key, value in data_to_save.iteritems():
                    saved_data[key] = value
            except AttributeError:
                pass
            try:
                self.modules[name].cleanup()
            except:
                log.msg("Cleanup failed for module {}: some pieces may still be remaining!".format(name))
            del self.modules[name]
        try:
            mod_find = imp.find_module("txircd/modules/{}".format(name))
        except ImportError as e:
            log.msg("Module not found: {} {}".format(name, e))
            return False
        try:
            mod_load = imp.load_module(name, mod_find[0], mod_find[1], mod_find[2])
        except ImportError as e:
            log.msg("Could not load module: {} ({})".format(name, e))
            mod_find[0].close()
            return False
        mod_find[0].close()
        try:
            mod_spawner = mod_load.Spawner(self)
        except Exception as e:
            log.msg("Module is not a valid txircd module: {} ({})".format(name, e))
            return False
        try:
            mod_contains = mod_spawner.spawn()
        except Exception as e:
            log.msg("Module is not a valid txircd module: {} ({})".format(name, e))
            return False
        self.modules[name] = mod_spawner
        if "commands" in mod_contains:
            for command, implementation in mod_contains["commands"].iteritems():
                if command in self.commands:
                    log.msg("Module {} tries to reimplement command {}".format(name, command))
                    continue
                self.commands[command] = implementation.hook(self)
        if "modes" in mod_contains:
            for mode, implementation in mod_contains["modes"].iteritems():
                if len(mode) < 2:
                    continue
                if mode[1] == "l":
                    modetype = 0
                elif mode[1] == "u":
                    modetype = 1
                elif mode[1] == "p":
                    modetype = 2
                elif mode[1] == "n":
                    modetype = 3
                elif mode[1] == "s":
                    modetype = -1
                else:
                    log.msg("Module {} registers a mode of an invalid type".format(name))
                    continue
                if mode[0] == "c":
                    if mode[2] in self.channel_mode_type:
                        log.msg("Module {} tries to reimplement channel mode {}".format(name, mode))
                        continue
                    if modetype >= 0:
                        self.channel_modes[modetype][mode[2]] = implementation.hook(self)
                    else:
                        if len(mode) < 5:
                            log.msg("Module {} tries to register a prefix without a symbol or level".format(name))
                            continue
                        try:
                            level = int(mode[4:])
                        except:
                            log.msg("Module {} tries to register a prefix without a numeric level".format(name))
                            continue
                        closestLevel = 0
                        closestModeChar = None
                        orderFail = False
                        for levelMode, levelData in self.prefixes.iteritems():
                            if level == levelData[1]:
                                log.msg("Module {} tries to register a prefix with the same rank level as an existing prefix")
                                orderFail = True
                                break
                            if levelData[1] < level and levelData[1] > closestLevel:
                                closestLevel = levelData[1]
                                closestModeChar = levelMode
                        if orderFail:
                            continue
                        if closestModeChar:
                            self.prefix_order.insert(self.prefix_order.index(closestModeChar), mode[2])
                        else:
                            self.prefix_order.append(mode[2])
                        self.prefixes[mode[2]] = [mode[3], level, implementation.hook(self)]
                        self.prefix_symbols[mode[3]] = mode[2]
                    self.channel_mode_type[mode[2]] = modetype
                    self.isupport["PREFIX"] = "({}){}".format("".join(self.prefix_order), "".join([self.prefixes[mode][0] for mode in self.prefix_order]))
                    self.isupport["STATUSMSG"] = "".join([self.prefixes[mode][0] for mode in self.prefix_order])
                elif mode[0] == "u":
                    if modetype == -1:
                        log.msg("Module {} registers a mode of an invalid type".format(name))
                        continue
                    if mode[2] in self.user_mode_type:
                        log.msg("Module {} tries to reimplement user mode {}".format(name, mode))
                        continue
                    self.user_modes[modetype][mode[2]] = implementation.hook(self)
                    self.user_mode_type[mode[2]] = modetype
        if "actions" in mod_contains:
            for actiontype, actionfuncs in mod_contains["actions"].iteritems():
                if actiontype in self.actions:
                    for func in actionfuncs:
                        self.actions[actiontype].append(func)
                else:
                    self.actions[actiontype] = actionfuncs
        if "server" in mod_contains:
            for commandtype, commandfunc in mod_contains["server"].iteritems():
                if commandtype not in self.server_commands:
                    self.server_commands[commandtype] = []
                self.server_commands[commandtype].append(commandfunc)
        if "common" in mod_contains and mod_contains["common"]:
            self.common_modules.add(name)
        if not saved_data and name in self.serialized_data:
            saved_data = self.serialized_data[name] # present serialized data on first load of session
        if saved_data:
            try:
                mod_spawner.data_unserialize(saved_data)
            except AttributeError:
                pass
        return True
    
    def removeMode(self, modedesc):
        # This function is heavily if'd in case we get passed invalid data.
        if modedesc[1] == "l":
            modetype = 0
        elif modedesc[1] == "u":
            modetype = 1
        elif modedesc[1] == "p":
            modetype = 2
        elif modedesc[1] == "n":
            modetype = 3
        elif modedesc[1] == "s":
            modetype = -1
        else:
            return
        
        if modedesc[0] == "c":
            if modetype != -1 and modedesc[2] in self.channel_modes[modetype]:
                del self.channel_modes[modetype][modedesc[2]]
            if modedesc[2] in self.channel_mode_type:
                del self.channel_mode_type[modedesc[2]]
            if modetype == -1 and modedesc[2] in self.prefixes:
                del self.prefix_symbols[self.prefixes[modedesc[2]][0]]
                if modedesc[2] in self.prefixes:
                    del self.prefixes[modedesc[2]]
                if modedesc[2] in self.prefix_order:
                    self.prefix_order.remove(modedesc[2])
        else:
            if modedesc[2] in self.user_modes[modetype]:
                del self.user_modes[modetype][modedesc[2]]
            if modedesc[2] in self.user_mode_type:
                del self.user_mode_type[modedesc[2]]
    
    def save_serialized(self):
        with open("data.yaml", "w") as dataFile:
            yaml.dump(self.serialized_data, dataFile, default_flow_style=False)
    
    def buildProtocol(self, addr):
        if self.dead:
            return None
        ip = addr.host
        connections = self.peerConnections.get(ip, 0)
        maxConnections = self.servconfig["client_peer_exempt"][ip] if ip in self.servconfig["client_peer_exempt"] else self.servconfig["client_peer_connections"]
        if maxConnections and connections >= maxConnections:
            log.msg("A client at IP address {} has exceeded the session limit".format(ip))
            return None
        self.peerConnections[ip] = connections + 1
        return Factory.buildProtocol(self, addr)

    def unregisterProtocol(self, p):
        ip = p.transport.getPeer().host
        self.peerConnections[ip] -= 1
Esempio n. 21
0
class IRCChannel(object):
	def __init__(self, ircd, name):
		if not isValidChannelName(name):
			raise InvalidChannelNameError
		self.ircd = ircd
		self.name = name[:self.ircd.config.get("channel_name_length", 64)]
		self.users = WeakKeyDictionary()
		self.modes = {}
		self.existedSince = now()
		self.topic = ""
		self.topicSetter = ""
		self.topicTime = now()
		self._metadata = CaseInsensitiveDictionary()
		self.cache = {}
	
	def sendUserMessage(self, command, *params, **kw):
		"""
		Sends a message to all local users in a channel.
		Accepts a command and some parameters for that command to send.
		Accepts any keyword arguments accepted by IRCUser.sendMessage.
		Also accepts the following keyword arguments:
		- skip: list of users in the channel to skip when sending the message
		"""
		if "to" not in kw:
			kw["to"] = self.name
		if kw["to"] is None:
			del kw["to"]
		userList = [u for u in self.users.iterkeys() if u.uuid[:3] == self.ircd.serverID]
		if "skip" in kw:
			for u in kw["skip"]:
				if u in userList:
					userList.remove(u)
		kw["users"] = userList
		kw["channels"] = [self]
		baseTags = {}
		if "tags" in kw:
			baseTags = kw["tags"]
			del kw["tags"]
		conditionalTags = {}
		if "conditionalTags" in kw:
			conditionalTags = kw["conditionalTags"]
			del kw["conditionalTags"]
		for user in userList:
			if conditionalTags:
				tags = baseTags.copy()
				addTags = user.filterConditionalTags(conditionalTags)
				tags.update(addTags)
			else:
				tags = baseTags
			kw["tags"] = tags
			user.sendMessage(command, *params, **kw)
	
	def sendServerMessage(self, command, *params, **kw):
		"""
		Sends a message to all remote servers to which any user in this channel
		is connected. Accepts a command and some parameters for that command to
		send. Also accepts the following keyword arguments:
		- skipall: list of servers to skip from the network
		- skiplocal: list of locally-connected servers to which to skip sending
		    after we've determined the closest hop of all the servers to which
		    we're sending
		"""
		servers = set()
		for user in self.users.iterkeys():
			if user.uuid[:3] != self.ircd.serverID:
				servers.add(self.ircd.servers[user.uuid[:3]])
		if "skipall" in kw:
			for s in kw["skipall"]:
				servers.discard(s)
		localServers = set()
		for server in servers:
			nearHop = server
			while nearHop.nextClosest != self.ircd.serverID:
				nearHop = self.ircd.servers[nearHop.nextClosest]
			localServers.add(nearHop)
		if "skiplocal" in kw:
			for s in kw["skiplocal"]:
				localServers.discard(s)
		for server in localServers:
			server.sendMessage(command, *params, **kw)
	
	def setTopic(self, topic, setter):
		"""
		Sets the channel topic.
		"""
		if setter in self.ircd.users:
			source = self.ircd.users[setter].hostmask()
		elif setter == self.ircd.serverID:
			source = self.ircd.name
		elif setter in self.ircd.servers:
			source = self.ircd.servers[setter].name
		else:
			return False
		if topic == self.topic:
			return True
		oldTopic = self.topic
		self.topic = topic
		self.topicSetter = source
		self.topicTime = now()
		self.ircd.runActionStandard("topic", self, setter, oldTopic, channels=[self])
		return True
	
	def metadataKeyExists(self, key):
		"""
		Checks whether a specific key exists in the channel's metadata.
		"""
		return key in self._metadata
	
	def metadataKeyCase(self, key):
		"""
		Gets the key from the channel's metadata in its original case.
		Returns None if the key is not present.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][0]
	
	def metadataValue(self, key):
		"""
		Gets the value for the given key in the channel's metadata.
		Returns None if the key is not present.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][1]
	
	def metadataVisibility(self, key):
		"""
		Gets the visibility value for the given key in the channel's metadata.
		Returns None if the key is not present.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][2]
	
	def metadataSetByUser(self, key):
		"""
		Gets whether the given metadata key/value was set by a user.
		Returns None if the key is not present.
		"""
		if key not in self._metadata:
			return None
		return self._metadata[key][3]
	
	def metadataList(self):
		"""
		Returns the list of metadata keys/values for the channel as a list of
		tuples in the format
		[ (key, value, visibility, setByUser) ]
		"""
		return self._metadata.values()
	
	def setMetadata(self, key, value, visibility, setByUser, fromServer = None):
		"""
		Sets metadata for the channel. Returns True if the set is successful or
		False if it is not. If the metadata set is caused by a message from a
		remote server, pass the server object as the fromServer parameter.
		If value is None, deletes the key provided.
		"""
		if not isValidMetadataKey(key):
			return False
		oldData = None
		if key in self._metadata:
			oldData = self._metadata[key]
		if setByUser and oldData and not oldData[3]:
			return False
		if setByUser and self.ircd.runActionUntilValue("usercansetmetadata", key, channels=[self]) is False:
			return False
		if value is None:
			del self._metadata[key]
		elif not visibility:
			return False
		else:
			self._metadata[key] = (key, value, visibility, setByUser)
		oldValue = oldData[1] if oldData else None
		self.ircd.runActionStandard("channelmetadataupdate", self, key, oldValue, value, visibility, setByUser, fromServer, channels=[self])
		return True
	
	def setModes(self, modes, defaultSource):
		"""
		Sets modes on the channel. Accepts modes as a list of tuples in the
		format:
		[ (adding, mode, param, setBy, setTime) ]
		- adding: True if we're setting the mode; False if unsetting
		- mode: The mode letter
		- param: The mode's parameter; None if no parameter is needed for that
		    mode
		- setBy: Optional, only used for list modes; a human-readable string
		    (typically server name or nick!user@host) for who/what set this
		    mode)
		- setTime: Optional, only used for list modes; a datetime object
		    containing when the mode was set
		
		The defaultSource is a valid user ID or server ID of someone who set
		the modes. It is used as the source for announcements about the mode
		change and as the default setter for any list modes who do not have the
		setBy parameter specified.
		The default time for list modes with no setTime specified is now().
		"""
		modeChanges = []
		defaultSourceName = self._sourceName(defaultSource)
		if defaultSourceName is None:
			raise ValueError ("Source must be a valid user or server ID.")
		nowTime = now()
		for modeData in modes:
			mode = modeData[1]
			if mode not in self.ircd.channelModeTypes:
				continue
			setBy = defaultSourceName
			setTime = nowTime
			modeType = self.ircd.channelModeTypes[mode]
			adding = modeData[0]
			if modeType in (ModeType.List, ModeType.ParamOnUnset, ModeType.Param, ModeType.Status):
				param = modeData[2]
			else:
				param = None
			if modeType == ModeType.List:
				dataCount = len(modeData)
				if dataCount >= 4:
					setBy = modeData[3]
				if dataCount >= 5:
					setTime = modeData[4]
			if modeType == ModeType.Status:
				if adding:
					paramList = self.ircd.channelStatuses[mode][2].checkSet(self, param)
				else:
					paramList = self.ircd.channelStatuses[mode][2].checkUnset(self, param)
			else:
				if adding:
					paramList = self.ircd.channelModes[modeType][mode].checkSet(self, param)
				else:
					paramList = self.ircd.channelModes[modeType][mode].checkUnset(self, param)
			if paramList is None:
				continue
			
			for parameter in paramList:
				if self._applyMode(adding, modeType, mode, parameter, setBy, setTime):
					modeChanges.append((adding, mode, parameter, setBy, setTime))
		
		self._notifyModeChanges(modeChanges, defaultSource, defaultSourceName)
		return modeChanges
	
	def setModesByUser(self, user, modes, params, override = False):
		"""
		Parses a mode string specified by a user and sets those modes on the
		channel.
		The user parameter should be the user who set the modes.
		The modes parameter is the actual modes string; parameters specified by
		the user should be as a list of strings in params.
		The override parameter should be used only when all permission checks
		should be overridden.
		"""
		adding = True
		changes = []
		setBy = self._sourceName(user.uuid)
		setTime = now()
		for mode in modes:
			if len(changes) >= self.ircd.config.get("modes_per_line", 20):
				break
			if mode == "+":
				adding = True
				continue
			if mode == "-":
				adding = False
				continue
			if mode not in self.ircd.channelModeTypes:
				user.sendMessage(irc.ERR_UNKNOWNMODE, mode, "is unknown mode char to me")
				continue
			modeType = self.ircd.channelModeTypes[mode]
			param = None
			if modeType in (ModeType.List, ModeType.ParamOnUnset, ModeType.Status) or (adding and modeType == ModeType.Param):
				try:
					param = params.pop(0)
				except IndexError:
					if modeType == ModeType.List:
						self.ircd.channelModes[modeType][mode].showListParams(user, self)
					continue
			if modeType == ModeType.Status:
				if adding:
					paramList = self.ircd.channelStatuses[mode][2].checkSet(self, param)
				else:
					paramList = self.ircd.channelStatuses[mode][2].checkUnset(self, param)
			else:
				if adding:
					paramList = self.ircd.channelModes[modeType][mode].checkSet(self, param)
				else:
					paramList = self.ircd.channelModes[modeType][mode].checkUnset(self, param)
			if paramList is None:
				continue
			
			for parameter in paramList:
				if len(changes) >= self.ircd.config.get("modes_per_line", 20):
					break
				if not override and self.ircd.runActionUntilValue("modepermission-channel-{}".format(mode), self, user, adding, parameter, users=[user], channels=[self]) is False:
					continue
				if adding:
					if modeType == ModeType.Status:
						try:
							targetUser = self.ircd.users[self.ircd.userNicks[parameter]]
						except KeyError:
							continue
						if targetUser not in self.users:
							continue
						if mode in self.users[targetUser]["status"]:
							continue
						statusLevel = self.ircd.channelStatuses[mode][1]
						if not override and self.userRank(user) < statusLevel and not self.ircd.runActionUntilValue("channelstatusoverride", self, user, mode, parameter, users=[user], channels=[self]):
							user.sendMessage(irc.ERR_CHANOPRIVSNEEDED, self.name, "You do not have permission to set channel mode +{}".format(mode))
							continue
						parameter = targetUser.uuid
					elif modeType == ModeType.List:
						if mode in self.modes and len(self.modes[mode]) > self.ircd.config.get("channel_listmode_limit", 128):
							user.sendMessage(irc.ERR_BANLISTFULL, self.name, parameter, "Channel +{} list is full".format(mode))
							continue
				else:
					if modeType == ModeType.Status:
						try:
							targetUser = self.ircd.users[self.ircd.userNicks[parameter]]
						except KeyError:
							continue
						if mode not in self.users[targetUser]["status"]:
							continue
						statusLevel = self.ircd.channelStatuses[mode][1]
						if not override and self.userRank(user) < statusLevel and not self.ircd.runActionUntilValue("channelstatusoverride", self, user, mode, parameter, users=[user], channels=[self]):
							user.sendMessage(irc.ERR_CHANOPRIVSNEEDED, self.name, "You do not have permission to set channel mode -{}".format(mode))
							continue
						parameter = targetUser.uuid
				if self._applyMode(adding, modeType, mode, parameter, setBy, setTime):
					changes.append((adding, mode, parameter, setBy, setTime))
		self._notifyModeChanges(changes, user.uuid, setBy)
		return changes
	
	def _applyMode(self, adding, modeType, mode, parameter, setBy, setTime):
		if parameter:
			if len(parameter) > 255:
				return False
			if " " in parameter:
				return False
		
		if adding:
			if modeType == ModeType.Status:
				try:
					targetUser = self.ircd.users[parameter]
				except KeyError:
					return False
				if targetUser not in self.users:
					return False
				if mode in self.users[targetUser]:
					return False
				statusLevel = self.ircd.channelStatuses[mode][1]
				targetStatus = self.users[targetUser]["status"]
				if mode in targetStatus:
					return False
				for index, rank in enumerate(targetStatus):
					if self.ircd.channelStatuses[rank][1] < statusLevel:
						statusList = list(targetStatus)
						statusList.insert(index, mode)
						self.users[targetUser]["status"] = "".join(statusList)
						return True
				self.users[targetUser]["status"] += mode
				return True
			if modeType == ModeType.List:
				if mode in self.modes:
					if len(self.modes[mode]) > self.ircd.config.get("channel_listmode_limit", 128):
						return False
					for paramData in self.modes[mode]:
						if parameter == paramData[0]:
							return False
				else:
					self.modes[mode] = []
				self.modes[mode].append((parameter, setBy, setTime))
				return True
			if mode in self.modes and self.modes[mode] == parameter:
				return False
			self.modes[mode] = parameter
			return True
		
		if modeType == ModeType.Status:
			try:
				targetUser = self.ircd.users[parameter]
			except KeyError:
				return False
			if targetUser not in self.users:
				return False
			if mode not in self.users[targetUser]["status"]:
				return False
			self.users[targetUser]["status"] = self.users[targetUser]["status"].replace(mode, "")
			return True
		if modeType == ModeType.List:
			if mode not in self.modes:
				return False
			for index, paramData in enumerate(self.modes[mode]):
				if paramData[0] == parameter:
					del self.modes[mode][index]
					break
			else:
				return False
			if not self.modes[mode]:
				del self.modes[mode]
			return True
		if mode not in self.modes:
			return False
		if modeType == ModeType.ParamOnUnset and parameter != self.modes[mode]:
			return False
		del self.modes[mode]
		return True
	
	def _notifyModeChanges(self, modeChanges, source, sourceName):
		if not modeChanges:
			return
		channelUsers = []
		for user in self.users.iterkeys():
			if user.uuid[:3] == self.ircd.serverID:
				channelUsers.append(user)
		for change in modeChanges:
			self.ircd.runActionStandard("modechange-channel-{}".format(change[1]), self, change[3], change[0], change[2], channels=[self])
		self.ircd.runActionProcessing("modemessage-channel", channelUsers, self, source, sourceName, modeChanges, users=channelUsers, channels=[self])
		self.ircd.runActionStandard("modechanges-channel", self, source, sourceName, modeChanges, channels=[self])
	
	def _sourceName(self, source):
		if source in self.ircd.users:
			return self.ircd.users[source].hostmask()
		if source == self.ircd.serverID:
			return self.ircd.name
		if source in self.ircd.servers:
			return self.ircd.servers[source].name
		return None
	
	def modeString(self, toUser):
		"""
		Get a user-reportable mode string for the modes set on the channel.
		"""
		modeStr = ["+"]
		params = []
		for mode in self.modes:
			modeType = self.ircd.channelModeTypes[mode]
			if modeType not in (ModeType.ParamOnUnset, ModeType.Param, ModeType.NoParam):
				continue
			if modeType != ModeType.NoParam:
				param = self.ircd.channelModes[modeType][mode].showParam(toUser, self)
				if not param:
					param = self.modes[mode]
			else:
				param = None
			modeStr.append(mode)
			if param:
				params.append(param)
		if params:
			return "{} {}".format("".join(modeStr), " ".join(params))
		return "".join(modeStr)
	
	def userRank(self, user):
		"""
		Gets the user's numeric rank in the channel.
		"""
		if user not in self.users:
			return -1
		status = self.users[user]["status"]
		if not status:
			return 0
		return self.ircd.channelStatuses[status[0]][1]
Esempio n. 22
0
 def __init__(self):
     self.history = CaseInsensitiveDictionary()
Esempio n. 23
0
 def __init__(self):
     self.exceptList = CaseInsensitiveDictionary()
Esempio n. 24
0
class ShunCommand(Command):
    def __init__(self):
        self.shunList = CaseInsensitiveDictionary()

    def onUse(self, user, data):
        if "reason" in data:
            self.shunList[data["mask"]] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage(
                "NOTICE",
                ":*** Shun set on {}, to expire in {} seconds".format(
                    data["mask"], data["duration"]))
        else:
            del self.shunList[data["mask"]]
            user.sendMessage("NOTICE",
                             ":*** Shun removed on {}".format(data["mask"]))
        for udata in self.ircd.users.itervalues():
            if self.match_shun(udata):
                udata.cache["shunned"] = True
            else:
                udata.cache["shunned"] = False

    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "SHUN",
                             ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(
                irc.ERR_NOPRIVILEGES,
                ":Permission denied - You do not have the correct operator privileges"
            )
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN",
                             ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            udata = self.ircd.users[banmask]
            banmask = "{}@{}".format(udata.username, udata.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_shuns()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN",
                                 ":Not enough parameters")
                return {}
            if banmask not in self.shunList:
                user.sendMessage(
                    "NOTICE",
                    ":*** Shun {} not found; check /stats S for a list of active shuns."
                    .format(banmask))
                return {}
            return {"user": user, "mask": banmask}
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN",
                             ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN",
                                 ":Not enough parameters")
                return {}
        if banmask in self.shunList:
            user.sendMessage(
                "NOTICE",
                ":*** Shun {} is already set!  Check /stats S for a list of active shuns."
                .format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }

    def statsList(self, user, statsType):
        if statsType != "S":
            return
        self.expire_shuns()
        for mask, linedata in self.shunList.iteritems():
            user.sendMessage(
                irc.RPL_STATSSHUN,
                "{} {} {} {} :{}".format(mask, linedata["created"],
                                         linedata["duration"],
                                         linedata["setter"],
                                         linedata["reason"]))

    def check_register(self, user):
        reason = self.match_shun(user)
        if reason:
            user.cache["shunned"] = True
        elif reason == None:
            user.cache["shunned"] = False
        else:
            return "again"
        return True

    def reassign_shun(self, user):
        reason = self.match_shun(user)
        if reason:
            user.cache["shunned"] = True
        else:
            user.cache["shunned"] = False
        return None  # the xline_rematch hook shouldn't automatically operate on these, so let's make it not.

    def match_shun(self, user):
        self.expire_shuns()
        if "except_line" in user.cache:
            if user.cache["except_line"]:
                return None
            matchMask = "{}@{}".format(user.username, user.hostname)
            for mask, linedata in self.shunList.iteritems():
                if fnmatch(matchMask, mask):
                    return linedata["reason"]
            matchMask = "{}@{}".format(user.username, user.ip)
            for mask, linedata in self.shunList.iteritems():
                if fnmatch(matchMask, mask):
                    return linedata["reason"]
            return None
        elif "shunned" in user.cache:
            if user.cache["shunned"]:
                return "Shunned"
            return None
        else:
            matchMask = "{}@{}".format(user.username, user.hostname)
            for mask in self.shunList.iterkeys():
                if fnmatch(matchMask, mask):
                    user.cache["shunned"] = True
                    return ""
            matchMask = "{}@{}".format(user.username, user.ip)
            for mask in self.shunList.iterkeys():
                if fnmatch(matchMask, mask):
                    user.cache["shunned"] = True
                    return ""
            user.cache["shunned"] = False
            return None

    def expire_shuns(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.shunList.iteritems():
            if linedata["duration"] and current_time > linedata[
                    "created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.shunList[mask]

    def check_command(self, user, command, data):
        if "shunned" not in user.cache or not user.cache["shunned"]:
            return data
        if command not in self.ircd.servconfig["shun_command_list"]:
            return {}
        return data
Esempio n. 25
0
class KlineCommand(Command):
    def __init__(self):
        self.banList = CaseInsensitiveDictionary()
    
    def onUse(self, user, data):
        if "reason" in data:
            self.banList[data["mask"]] = {
                "setter": user.prefix(),
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage("NOTICE", ":*** K:Line added on {}, to expire in {} seconds".format(data["mask"], data["duration"]))
            now_banned = {}
            for nick, u in self.ircd.users.iteritems():
                if u.server == self.ircd.name:
                    result = self.match_kline(u)
                    if result:
                        now_banned[nick] = result
            for uid, reason in now_banned.iteritems():
                udata = self.ircd.users[uid]
                udata.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
                udata.disconnect("K:Lined: {}".format(reason))
        else:
            del self.banList[data["mask"]]
            user.sendMessage("NOTICE", ":*** K:Line removed on {}".format(data["mask"]))
    
    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "KLINE", ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(irc.ERR_NOPRIVILEGES, ":Permission denied - You do not have the correct operator privileges")
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE", ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            banmask = "{}@{}".format(user.username, user.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_klines()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE", ":Not enough parameters")
                return {}
            if banmask not in self.banList:
                user.sendMessage("NOTICE", ":*** K:line for {} does not currently exist; check /stats K for a list of active k:lines".format(banmask))
                return {}
            return {
                "user": user,
                "mask": banmask
            }
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE", ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "KLINE", ":Not enough parameters")
                return {}
        if banmask in self.banList:
            user.sendMessage("NOTICE", ":*** There's already a k:line set on {}!  Check /stats K for a list of active k:lines.".format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }
    
    def statsList(self, user, statsType):
        if statsType != "K":
            return
        self.expire_klines()
        for mask, linedata in self.banList.iteritems():
            user.sendMessage(irc.RPL_STATSKLINE, ":{} {} {} {} :{}".format(mask, linedata["created"], linedata["duration"], linedata["setter"], linedata["reason"]))
    
    def register_check(self, user):
        result = self.match_kline(user)
        if not result:
            if result == None:
                return True
            return "again"
        user.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
        user.sendMessage("ERROR", ":Closing Link: {} [K:Lined: {}]".format(user.hostname, result), to=None, prefix=None)
        return False
    
    def match_kline(self, user):
        if "o" in user.mode:
            return None # don't allow bans to affect opers
        if user.server != self.ircd.name:
            return None # only match users on this server
        if "except_line" not in user.cache:
            if "kline_match" in user.cache:
                return user.cache["kline_match"]
            # Determine whether the user matches
            self.expire_klines()
            match_against = irc_lower("{}@{}".format(user.username, user.hostname))
            for mask, linedata in self.banList.iteritems():
                if fnmatch(match_against, mask):
                    user.cache["kline_match"] = linedata["reason"]
                    return ""
            match_against = irc_lower("{}@{}".format(user.username, user.ip))
            for mask in self.banList.iterkeys(): # we just removed expired lines
                if fnmatch(match_against, mask):
                    user.cache["kline_match"] = linedata["reason"]
                    return ""
            return None
        else:
            if user.cache["except_line"]:
                return None
            if "kline_match" in user.cache:
                return user.cache["kline_match"]
            self.expire_klines()
            match_against = irc_lower("{}@{}".format(user.username, user.hostname))
            for mask, linedata in self.banList.iteritems():
                if fnmatch(match_against, mask):
                    return linedata["reason"]
            match_against = irc_lower("{}@{}".format(user.username, user.ip))
            for mask in self.banList.iterkeys(): # we just removed expired lines
                if fnmatch(match_against, mask):
                    return linedata["reason"]
            return None
    
    def expire_klines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.banList.iteritems():
            if linedata["duration"] and current_time > linedata["created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.banList[mask]
Esempio n. 26
0
class ZlineCommand(Command):
    def __init__(self):
        self.banList = CaseInsensitiveDictionary()
    
    def onUse(self, user, data):
        if "reason" in data:
            self.banList[data["mask"]] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage("NOTICE", ":*** Z:Line set on {}, to expire in {} seconds".format(data["mask"], data["duration"]))
            now_banned = {}
            for uid, udata in self.ircd.users.iteritems():
                reason = self.match_zline(udata)
                if reason:
                    now_banned[uid] = reason
            for uid, reason in now_banned.iteritems():
                udata = self.ircd.users[uid]
                udata.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
                udata.disconnect("Z:Lined: {}".format(reason))
        else:
            del self.banList[data["mask"]]
            user.sendMessage("NOTICE", ":*** Z:Line removed on {}".format(data["mask"]))
    
    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "ZLINE", ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(irc.ERR_NOPRIVILEGES, ":Permission denied - You do not have the correct operator privileges")
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ZLINE", ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            banmask = self.ircd.users[banmask].ip
        self.expire_zlines()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ZLINE", ":Not enough parameters")
                return {}
            if banmask not in self.banList:
                user.sendMessage("NOTICE", ":*** Z:line on {} not found!  Check /stats Z for a list of active z:lines.".format(banmask))
                return {}
            return {
                "user": user,
                "mask": banmask
            }
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ZLINE", ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ZLINE", ":Not enough parameters")
                return {}
        if banmask in self.banList:
            user.sendMessage("NOTICE", ":*** There is already a z:line set on {}!  Check /stats Z for a list of active z:lines.".format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }
    
    def stats_list(self, user, statsType):
        if statsType != "Z":
            return
        self.expire_zlines()
        for mask, linedata in self.banList.iteritems():
            user.sendMessage(irc.RPL_STATSZLINE, ":{} {} {} {} :{}".format(mask, linedata["created"], linedata["duration"], linedata["setter"], linedata["reason"]))
    
    def check_connect(self, user):
        reason = self.match_zline(user)
        if not reason:
            return True
        user.sendMessage("NOTICE", ":{}".format(self.ircd.servconfig["client_ban_msg"]))
        user.sendMessage("ERROR", ":Closing Link: {} [Z:Lined: {}]".format(user.ip, reason), to=None, prefix=None)
        return False
    
    def match_zline(self, user):
        if "o" in user.mode:
            return None
        self.expire_zlines()
        for mask, linedata in self.banList.iteritems():
            if fnmatch(user.ip, mask):
                return linedata["reason"]
        return None
    
    def expire_zlines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.banList.iteritems():
            if linedata["duration"] and current_time > linedata["created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.banList[mask]
Esempio n. 27
0
class ElineCommand(Command):
    def __init__(self):
        self.exceptList = CaseInsensitiveDictionary()

    def onUse(self, user, data):
        if "reason" in data:
            self.exceptList[data["mask"]] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage(
                "NOTICE",
                ":*** E:Line set on {}, to expire in {} seconds".format(
                    data["mask"], data["duration"]))
        else:
            mask = data["mask"]
            del self.exceptList[mask]
            user.sendMessage("NOTICE",
                             ":*** E:Line removed on {}".format(mask))
            for u in self.ircd.users.itervalues():
                if self.match_eline(u):
                    u.cache["except_line"] = True
            now_banned = {}
            for uid, udata in self.ircd.users.iteritems():
                for modfunc in self.ircd.actions["xline_rematch"]:
                    reason = modfunc(udata)
                    if reason:
                        now_banned[uid] = reason
                        break  # If the user is banned, the user is banned. We don't need to gather a consensus or something.
            for uid, reason in now_banned.iteritems():
                udata = self.ircd.users[uid]
                udata.sendMessage(
                    "NOTICE",
                    ":{}".format(self.ircd.servconfig["client_ban_msg"]))
                udata.disconnect(
                    "Banned: Exception Removed ({})".format(reason))

    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "ELINE",
                             ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(
                irc.ERR_NOPRIVILEGES,
                ":Permission denied - You do not have the correct operator privileges"
            )
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE",
                             ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            udata = self.ircd.users[banmask]
            banmask = "{}@{}".format(udata.username, udata.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_elines()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE",
                                 ":Not enough parameters")
                return {}
            if banmask not in self.exceptList:
                user.sendMessage(
                    "NOTICE",
                    ":*** E:line for {} not found! Check /stats E to view currently set e:lines."
                    .format(banmask))
                return {}
            return {"user": user, "mask": banmask}
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE",
                             ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "ELINE",
                                 ":Not enough parameters")
                return {}
        if banmask in self.exceptList:
            user.sendMessage(
                "NOTICE",
                ":*** An e:line is already set on {}!  Check /stats E to view currently set e:lines."
                .format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }

    def statsList(self, user, statsType):
        if statsType != "E":
            return
        self.expire_elines()
        for mask, linedata in self.exceptList.iteritems():
            user.sendMessage(
                irc.RPL_STATSELINES,
                "{} {} {} {} :{}".format(mask, linedata["created"],
                                         linedata["duration"],
                                         linedata["setter"],
                                         linedata["reason"]))

    def check_register(self, user):
        if self.match_eline(user):
            user.cache["except_line"] = True
        return True

    def match_eline(self, user):
        self.expire_elines()
        matchMask = irc_lower("{}@{}".format(user.username, user.hostname))
        for mask, linedata in self.exceptList.iteritems():
            if fnmatch(matchMask, mask):
                user.cache["except_line"] = True
                return linedata["reason"]
        matchMask = irc_lower("{}@{}".format(user.username, user.ip))
        for mask, linedata in self.exceptList.iteritems():
            if fnmatch(matchMask, mask):
                user.cache["except_line"] = True
                return linedata["reason"]
        user.cache["except_line"] = False
        return None

    def expire_elines(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.exceptList.iteritems():
            if linedata["duration"] and current_time > linedata[
                    "created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.exceptList[mask]
Esempio n. 28
0
 def __init__(self):
     self.banList = CaseInsensitiveDictionary()
Esempio n. 29
0
 def __init__(self):
     self.exceptList = CaseInsensitiveDictionary()
Esempio n. 30
0
class IRCChannel(object):
    def __init__(self, ircd, name):
        if not isValidChannelName(name):
            raise InvalidChannelNameError
        self.ircd = ircd
        self.name = name[:self.ircd.config.get("channel_name_length", 64)]
        self.users = WeakKeyDictionary()
        self.modes = {}
        self.existedSince = now()
        self.topic = ""
        self.topicSetter = ""
        self.topicTime = now()
        self._metadata = CaseInsensitiveDictionary()
        self.cache = {}

    def sendUserMessage(self, command, *params, **kw):
        """
		Sends a message to all local users in a channel.
		Accepts a command and some parameters for that command to send.
		Accepts any keyword arguments accepted by IRCUser.sendMessage.
		Also accepts the following keyword arguments:
		- skip: list of users in the channel to skip when sending the message
		"""
        if "to" not in kw:
            kw["to"] = self.name
        if kw["to"] is None:
            del kw["to"]
        userList = [
            u for u in self.users.iterkeys()
            if u.uuid[:3] == self.ircd.serverID
        ]
        if "skip" in kw:
            for u in kw["skip"]:
                if u in userList:
                    userList.remove(u)
        kw["users"] = userList
        kw["channels"] = [self]
        baseTags = {}
        if "tags" in kw:
            baseTags = kw["tags"]
            del kw["tags"]
        conditionalTags = {}
        if "conditionalTags" in kw:
            conditionalTags = kw["conditionalTags"]
            del kw["conditionalTags"]
        for user in userList:
            if conditionalTags:
                tags = baseTags.copy()
                addTags = user.filterConditionalTags(conditionalTags)
                tags.update(addTags)
            else:
                tags = baseTags
            kw["tags"] = tags
            user.sendMessage(command, *params, **kw)

    def sendServerMessage(self, command, *params, **kw):
        """
		Sends a message to all remote servers to which any user in this channel
		is connected. Accepts a command and some parameters for that command to
		send. Also accepts the following keyword arguments:
		- skipall: list of servers to skip from the network
		- skiplocal: list of locally-connected servers to which to skip sending
		    after we've determined the closest hop of all the servers to which
		    we're sending
		"""
        servers = set()
        for user in self.users.iterkeys():
            if user.uuid[:3] != self.ircd.serverID:
                servers.add(self.ircd.servers[user.uuid[:3]])
        if "skipall" in kw:
            for s in kw["skipall"]:
                servers.discard(s)
        localServers = set()
        for server in servers:
            nearHop = server
            while nearHop.nextClosest != self.ircd.serverID:
                nearHop = self.ircd.servers[nearHop.nextClosest]
            localServers.add(nearHop)
        if "skiplocal" in kw:
            for s in kw["skiplocal"]:
                localServers.discard(s)
        for server in localServers:
            server.sendMessage(command, *params, **kw)

    def setTopic(self, topic, setter):
        """
		Sets the channel topic.
		"""
        if setter in self.ircd.users:
            source = self.ircd.users[setter].hostmask()
        elif setter == self.ircd.serverID:
            source = self.ircd.name
        elif setter in self.ircd.servers:
            source = self.ircd.servers[setter].name
        else:
            return False
        if topic == self.topic:
            return True
        oldTopic = self.topic
        self.topic = topic
        self.topicSetter = source
        self.topicTime = now()
        self.ircd.runActionStandard("topic",
                                    self,
                                    setter,
                                    oldTopic,
                                    channels=[self])
        return True

    def metadataKeyExists(self, key):
        """
		Checks whether a specific key exists in the channel's metadata.
		"""
        return key in self._metadata

    def metadataKeyCase(self, key):
        """
		Gets the key from the channel's metadata in its original case.
		Returns None if the key is not present.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][0]

    def metadataValue(self, key):
        """
		Gets the value for the given key in the channel's metadata.
		Returns None if the key is not present.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][1]

    def metadataVisibility(self, key):
        """
		Gets the visibility value for the given key in the channel's metadata.
		Returns None if the key is not present.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][2]

    def metadataSetByUser(self, key):
        """
		Gets whether the given metadata key/value was set by a user.
		Returns None if the key is not present.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][3]

    def metadataList(self):
        """
		Returns the list of metadata keys/values for the channel as a list of
		tuples in the format
		[ (key, value, visibility, setByUser) ]
		"""
        return self._metadata.values()

    def setMetadata(self, key, value, visibility, setByUser, fromServer=None):
        """
		Sets metadata for the channel. Returns True if the set is successful or
		False if it is not. If the metadata set is caused by a message from a
		remote server, pass the server object as the fromServer parameter.
		If value is None, deletes the key provided.
		"""
        if not isValidMetadataKey(key):
            return False
        oldData = None
        if key in self._metadata:
            oldData = self._metadata[key]
        if setByUser and oldData and not oldData[3]:
            return False
        if setByUser and self.ircd.runActionUntilValue(
                "usercansetmetadata", key, channels=[self]) is False:
            return False
        if value is None:
            del self._metadata[key]
        elif not visibility:
            return False
        else:
            self._metadata[key] = (key, value, visibility, setByUser)
        oldValue = oldData[1] if oldData else None
        self.ircd.runActionStandard("channelmetadataupdate",
                                    self,
                                    key,
                                    oldValue,
                                    value,
                                    visibility,
                                    setByUser,
                                    fromServer,
                                    channels=[self])
        return True

    def setModes(self, modes, defaultSource):
        """
		Sets modes on the channel. Accepts modes as a list of tuples in the
		format:
		[ (adding, mode, param, setBy, setTime) ]
		- adding: True if we're setting the mode; False if unsetting
		- mode: The mode letter
		- param: The mode's parameter; None if no parameter is needed for that
		    mode
		- setBy: Optional, only used for list modes; a human-readable string
		    (typically server name or nick!user@host) for who/what set this
		    mode)
		- setTime: Optional, only used for list modes; a datetime object
		    containing when the mode was set
		
		The defaultSource is a valid user ID or server ID of someone who set
		the modes. It is used as the source for announcements about the mode
		change and as the default setter for any list modes who do not have the
		setBy parameter specified.
		The default time for list modes with no setTime specified is now().
		"""
        modeChanges = []
        defaultSourceName = self._sourceName(defaultSource)
        if defaultSourceName is None:
            raise ValueError("Source must be a valid user or server ID.")
        nowTime = now()
        for modeData in modes:
            mode = modeData[1]
            if mode not in self.ircd.channelModeTypes:
                continue
            setBy = defaultSourceName
            setTime = nowTime
            modeType = self.ircd.channelModeTypes[mode]
            adding = modeData[0]
            if modeType in (ModeType.List, ModeType.ParamOnUnset,
                            ModeType.Param, ModeType.Status):
                param = modeData[2]
            else:
                param = None
            if modeType == ModeType.List:
                dataCount = len(modeData)
                if dataCount >= 4:
                    setBy = modeData[3]
                if dataCount >= 5:
                    setTime = modeData[4]
            if modeType == ModeType.Status:
                if adding:
                    paramList = self.ircd.channelStatuses[mode][2].checkSet(
                        self, param)
                else:
                    paramList = self.ircd.channelStatuses[mode][2].checkUnset(
                        self, param)
            else:
                if adding:
                    paramList = self.ircd.channelModes[modeType][
                        mode].checkSet(self, param)
                else:
                    paramList = self.ircd.channelModes[modeType][
                        mode].checkUnset(self, param)
            if paramList is None:
                continue

            for parameter in paramList:
                if self._applyMode(adding, modeType, mode, parameter, setBy,
                                   setTime):
                    modeChanges.append(
                        (adding, mode, parameter, setBy, setTime))

        self._notifyModeChanges(modeChanges, defaultSource, defaultSourceName)
        return modeChanges

    def setModesByUser(self, user, modes, params, override=False):
        """
		Parses a mode string specified by a user and sets those modes on the
		channel.
		The user parameter should be the user who set the modes.
		The modes parameter is the actual modes string; parameters specified by
		the user should be as a list of strings in params.
		The override parameter should be used only when all permission checks
		should be overridden.
		"""
        adding = True
        changes = []
        setBy = self._sourceName(user.uuid)
        setTime = now()
        for mode in modes:
            if len(changes) >= self.ircd.config.get("modes_per_line", 20):
                break
            if mode == "+":
                adding = True
                continue
            if mode == "-":
                adding = False
                continue
            if mode not in self.ircd.channelModeTypes:
                user.sendMessage(irc.ERR_UNKNOWNMODE, mode,
                                 "is unknown mode char to me")
                continue
            modeType = self.ircd.channelModeTypes[mode]
            param = None
            if modeType in (ModeType.List, ModeType.ParamOnUnset,
                            ModeType.Status) or (adding and modeType
                                                 == ModeType.Param):
                try:
                    param = params.pop(0)
                except IndexError:
                    if modeType == ModeType.List:
                        self.ircd.channelModes[modeType][mode].showListParams(
                            user, self)
                    continue
            if modeType == ModeType.Status:
                if adding:
                    paramList = self.ircd.channelStatuses[mode][2].checkSet(
                        self, param)
                else:
                    paramList = self.ircd.channelStatuses[mode][2].checkUnset(
                        self, param)
            else:
                if adding:
                    paramList = self.ircd.channelModes[modeType][
                        mode].checkSet(self, param)
                else:
                    paramList = self.ircd.channelModes[modeType][
                        mode].checkUnset(self, param)
            if paramList is None:
                continue

            for parameter in paramList:
                if len(changes) >= self.ircd.config.get("modes_per_line", 20):
                    break
                if not override and self.ircd.runActionUntilValue(
                        "modepermission-channel-{}".format(mode),
                        self,
                        user,
                        adding,
                        parameter,
                        users=[user],
                        channels=[self]) is False:
                    continue
                if adding:
                    if modeType == ModeType.Status:
                        try:
                            targetUser = self.ircd.users[
                                self.ircd.userNicks[parameter]]
                        except KeyError:
                            continue
                        if targetUser not in self.users:
                            continue
                        if mode in self.users[targetUser]["status"]:
                            continue
                        statusLevel = self.ircd.channelStatuses[mode][1]
                        if not override and self.userRank(
                                user
                        ) < statusLevel and not self.ircd.runActionUntilValue(
                                "channelstatusoverride",
                                self,
                                user,
                                mode,
                                parameter,
                                users=[user],
                                channels=[self]):
                            user.sendMessage(
                                irc.ERR_CHANOPRIVSNEEDED, self.name,
                                "You do not have permission to set channel mode +{}"
                                .format(mode))
                            continue
                        parameter = targetUser.uuid
                    elif modeType == ModeType.List:
                        if mode in self.modes and len(
                                self.modes[mode]) > self.ircd.config.get(
                                    "channel_listmode_limit", 128):
                            user.sendMessage(
                                irc.ERR_BANLISTFULL, self.name, parameter,
                                "Channel +{} list is full".format(mode))
                            continue
                else:
                    if modeType == ModeType.Status:
                        try:
                            targetUser = self.ircd.users[
                                self.ircd.userNicks[parameter]]
                        except KeyError:
                            continue
                        if mode not in self.users[targetUser]["status"]:
                            continue
                        statusLevel = self.ircd.channelStatuses[mode][1]
                        if not override and self.userRank(
                                user
                        ) < statusLevel and not self.ircd.runActionUntilValue(
                                "channelstatusoverride",
                                self,
                                user,
                                mode,
                                parameter,
                                users=[user],
                                channels=[self]):
                            user.sendMessage(
                                irc.ERR_CHANOPRIVSNEEDED, self.name,
                                "You do not have permission to set channel mode -{}"
                                .format(mode))
                            continue
                        parameter = targetUser.uuid
                if self._applyMode(adding, modeType, mode, parameter, setBy,
                                   setTime):
                    changes.append((adding, mode, parameter, setBy, setTime))
        self._notifyModeChanges(changes, user.uuid, setBy)
        return changes

    def _applyMode(self, adding, modeType, mode, parameter, setBy, setTime):
        if parameter:
            if len(parameter) > 255:
                return False
            if " " in parameter:
                return False

        if adding:
            if modeType == ModeType.Status:
                try:
                    targetUser = self.ircd.users[parameter]
                except KeyError:
                    return False
                if targetUser not in self.users:
                    return False
                if mode in self.users[targetUser]:
                    return False
                statusLevel = self.ircd.channelStatuses[mode][1]
                targetStatus = self.users[targetUser]["status"]
                if mode in targetStatus:
                    return False
                for index, rank in enumerate(targetStatus):
                    if self.ircd.channelStatuses[rank][1] < statusLevel:
                        statusList = list(targetStatus)
                        statusList.insert(index, mode)
                        self.users[targetUser]["status"] = "".join(statusList)
                        return True
                self.users[targetUser]["status"] += mode
                return True
            if modeType == ModeType.List:
                if mode in self.modes:
                    if len(self.modes[mode]) > self.ircd.config.get(
                            "channel_listmode_limit", 128):
                        return False
                    for paramData in self.modes[mode]:
                        if parameter == paramData[0]:
                            return False
                else:
                    self.modes[mode] = []
                self.modes[mode].append((parameter, setBy, setTime))
                return True
            if mode in self.modes and self.modes[mode] == parameter:
                return False
            self.modes[mode] = parameter
            return True

        if modeType == ModeType.Status:
            try:
                targetUser = self.ircd.users[parameter]
            except KeyError:
                return False
            if targetUser not in self.users:
                return False
            if mode not in self.users[targetUser]["status"]:
                return False
            self.users[targetUser]["status"] = self.users[targetUser][
                "status"].replace(mode, "")
            return True
        if modeType == ModeType.List:
            if mode not in self.modes:
                return False
            for index, paramData in enumerate(self.modes[mode]):
                if paramData[0] == parameter:
                    del self.modes[mode][index]
                    break
            else:
                return False
            if not self.modes[mode]:
                del self.modes[mode]
            return True
        if mode not in self.modes:
            return False
        if modeType == ModeType.ParamOnUnset and parameter != self.modes[mode]:
            return False
        del self.modes[mode]
        return True

    def _notifyModeChanges(self, modeChanges, source, sourceName):
        if not modeChanges:
            return
        channelUsers = []
        for user in self.users.iterkeys():
            if user.uuid[:3] == self.ircd.serverID:
                channelUsers.append(user)
        for change in modeChanges:
            self.ircd.runActionStandard("modechange-channel-{}".format(
                change[1]),
                                        self,
                                        change[3],
                                        change[0],
                                        change[2],
                                        channels=[self])
        self.ircd.runActionProcessing("modemessage-channel",
                                      channelUsers,
                                      self,
                                      source,
                                      sourceName,
                                      modeChanges,
                                      users=channelUsers,
                                      channels=[self])
        self.ircd.runActionStandard("modechanges-channel",
                                    self,
                                    source,
                                    sourceName,
                                    modeChanges,
                                    channels=[self])

    def _sourceName(self, source):
        if source in self.ircd.users:
            return self.ircd.users[source].hostmask()
        if source == self.ircd.serverID:
            return self.ircd.name
        if source in self.ircd.servers:
            return self.ircd.servers[source].name
        return None

    def modeString(self, toUser):
        """
		Get a user-reportable mode string for the modes set on the channel.
		"""
        modeStr = ["+"]
        params = []
        for mode in self.modes:
            modeType = self.ircd.channelModeTypes[mode]
            if modeType not in (ModeType.ParamOnUnset, ModeType.Param,
                                ModeType.NoParam):
                continue
            if modeType != ModeType.NoParam:
                param = self.ircd.channelModes[modeType][mode].showParam(
                    toUser, self)
                if not param:
                    param = self.modes[mode]
            else:
                param = None
            modeStr.append(mode)
            if param:
                params.append(param)
        if params:
            return "{} {}".format("".join(modeStr), " ".join(params))
        return "".join(modeStr)

    def userRank(self, user):
        """
		Gets the user's numeric rank in the channel.
		"""
        if user not in self.users:
            return -1
        status = self.users[user]["status"]
        if not status:
            return 0
        return self.ircd.channelStatuses[status[0]][1]
Esempio n. 31
0
class IRCUser(IRCBase):
    def __init__(self, ircd, ip, uuid=None, host=None):
        self.ircd = ircd
        self.uuid = ircd.createUUID() if uuid is None else uuid

        registrationTimeout = self.ircd.config.get("user_registration_timeout",
                                                   10)

        self.nick = None
        self.ident = None
        if ip[0] == ":":  # Normalize IPv6 address for IRC
            ip = "0{}".format(ip)
        if host is None:
            self.realHost = ip
        else:
            self.realHost = host
        self.ip = ip
        self._hostStack = []
        self._hostsByType = {}
        self.gecos = None
        self._metadata = CaseInsensitiveDictionary()
        self.cache = {}
        self.channels = []
        self.modes = {}
        self.connectedSince = now()
        self.nickSince = now()
        self.idleSince = now()
        self._registerHolds = set(("connection", "dns", "NICK", "USER"))
        self.disconnectedDeferred = Deferred()
        self._messageBatches = {}
        self._errorBatchName = None
        self._errorBatch = []
        self.ircd.users[self.uuid] = self
        self.localOnly = False
        self.secureConnection = False
        self._pinger = LoopingCall(self._ping)
        self._registrationTimeoutTimer = reactor.callLater(
            registrationTimeout, self._timeoutRegistration)
        self._connectHandlerTimer = None
        self._startDNSResolving(registrationTimeout)

    def _startDNSResolving(self, timeout):
        ip = self.ip
        if ipIsV4(ip):
            addr = "{}.in-addr.arpa".format(".".join(reversed(ip.split("."))))
        else:
            addr = reversed(expandIPv6Address(ip).replace(":", ""))
            addr = "{}.ip6.arpa".format(".".join(addr))
        resolveDeferred = dnsClient.lookupPointer(addr, ((timeout / 2), ))
        resolveDeferred.addCallbacks(callback=self._verifyDNSResolution,
                                     callbackArgs=(timeout, ),
                                     errback=self._cancelDNSResolution)

    def _verifyDNSResolution(self, result, timeout):
        name = result[0][0].payload.name.name
        if len(name) > self.ircd.config.get("hostname_length", 64):
            self._cancelDNSResolution()
            return
        if not isValidHost(name):
            self._cancelDNSResolution()
            return
        resolveDeferred = dnsClient.getHostByName(name, ((timeout / 2), ))
        resolveDeferred.addCallbacks(callback=self._completeDNSResolution,
                                     errback=self._cancelDNSResolution,
                                     callbackArgs=(name, ))

    def _completeDNSResolution(self, result, name):
        if result == self.ip:
            self.realHost = name
        self.register("dns")

    def _cancelDNSResolution(self, error=None):
        self.register("dns")

    def connectionMade(self):
        # We need to callLater the connect action call because the connection isn't fully set up yet,
        # nor is it fully set up even with a delay of zero, which causes the message buffer not to be sent
        # when the connection is closed.
        # The "connection" register hold is used basically solely for the purposes of this to prevent potential
        # race conditions with registration.
        self._connectHandlerTimer = reactor.callLater(0.1,
                                                      self._callConnectAction)
        if ISSLTransport.providedBy(self.transport):
            self.secureConnection = True

    def _callConnectAction(self):
        self._connectHandlerTimer = None
        if self.ircd.runActionUntilFalse("userconnect", self, users=[self]):
            self.transport.loseConnection()
        else:
            self.register("connection")

    def dataReceived(self, data):
        self.ircd.runActionStandard("userrecvdata", self, data, users=[self])
        try:
            IRCBase.dataReceived(self, data)
        except Exception:
            self.ircd.log.failure(
                "An error occurred while processing incoming data.")
            if self.uuid in self.ircd.users:
                self.disconnect("Error occurred")

    def sendLine(self, line):
        self.ircd.runActionStandard("usersenddata", self, line, users=[self])
        IRCBase.sendLine(self, line)

    def sendMessage(self, command, *args, **kw):
        """
		Sends the given message to this user.
		Accepts the following keyword arguments:
		- prefix: The message prefix or None to suppress the default prefix
		    If not given, defaults to the server name.
		- to: The destination of the message or None if the message has no
		    destination. The implicit destination is this user if this
		    argument isn't specified.
		- tags: Dict of message tags to send.
		- alwaysPrefixLastParam: For compatibility with some broken clients,
		    you might want some messages to always have the last parameter
		    prefixed with a colon. To do that, pass this as True.
		"""
        if "prefix" not in kw:
            kw["prefix"] = self.ircd.name
        if kw["prefix"] is None:
            del kw["prefix"]
        to = self.nick if self.nick else "*"
        if "to" in kw:
            to = kw["to"]
            del kw["to"]
        if to:
            args = [to] + list(args)
        self.ircd.runActionStandard("modifyoutgoingmessage", self, command,
                                    args, kw)
        IRCBase.sendMessage(self, command, *args, **kw)

    def handleCommand(self, command, params, prefix, tags):
        if self.uuid not in self.ircd.users:
            return  # we have been disconnected - ignore all further commands
        if command in self.ircd.userCommands:
            handlers = self.ircd.userCommands[command]
            if not handlers:
                return
            data = None
            spewRegWarning = True
            affectedUsers = []
            affectedChannels = []
            for handler in handlers:
                if handler[0].forRegistered is not None:
                    if (handler[0].forRegistered is True
                            and not self.isRegistered()) or (
                                handler[0].forRegistered is False
                                and self.isRegistered()):
                        continue
                spewRegWarning = False
                data = handler[0].parseParams(self, params, prefix, tags)
                if data is not None:
                    affectedUsers = handler[0].affectedUsers(self, data)
                    affectedChannels = handler[0].affectedChannels(self, data)
                    if self not in affectedUsers:
                        affectedUsers.append(self)
                    break
            if data is None:
                if spewRegWarning:
                    if self.isRegistered():
                        self.sendMessage(irc.ERR_ALREADYREGISTERED,
                                         "You may not reregister")
                    else:
                        self.sendMessage(irc.ERR_NOTREGISTERED, command,
                                         "You have not registered")
                elif self._hasBatchedErrors():
                    self._dispatchErrorBatch()
                return
            self._clearErrorBatch()
            if self.ircd.runComboActionUntilValue(
                (("commandpermission-{}".format(command), self, data),
                 ("commandpermission", self, command, data)),
                    users=affectedUsers,
                    channels=affectedChannels) is False:
                return
            self.ircd.runComboActionStandard(
                (("commandmodify-{}".format(command), self, data),
                 ("commandmodify", self, command, data)),
                users=affectedUsers,
                channels=affectedChannels
            )  # This allows us to do processing without the "stop on empty" feature of runActionProcessing
            for handler in handlers:
                if handler[0].execute(self, data):
                    if handler[0].resetsIdleTime:
                        self.idleSince = now()
                    break  # If the command executor returns True, it was handled
            else:
                return  # Don't process commandextra if it wasn't handled
            self.ircd.runComboActionStandard(
                (("commandextra-{}".format(command), self, data),
                 ("commandextra", self, command, data)),
                users=affectedUsers,
                channels=affectedChannels)
        else:
            if not self.ircd.runActionFlagTrue("commandunknown", self, command,
                                               params, {}):
                self.sendMessage(irc.ERR_UNKNOWNCOMMAND, command,
                                 "Unknown command")

    def createMessageBatch(self, batchName, batchType, batchParameters=None):
        """
		Start a new message batch with the given batch name, type, and list of parameters.
		If a batch with the given name already exists, that batch will be overwritten.
		"""
        self._messageBatches[batchName] = {
            "type": batchType,
            "parameters": batchParameters,
            "messages": []
        }

    def sendMessageInBatch(self, batchName, command, *args, **kw):
        """
		Adds a message to the batch with the given name.
		"""
        if batchName not in self._messageBatches:
            return
        self._messageBatches[batchName]["messages"].append((command, args, kw))

    def sendBatch(self, batchName):
        """
		Sends the messages in the given batch to the user.
		"""
        if batchName not in self._messageBatches:
            return
        batchType = self._messageBatches[batchName]["type"]
        batchParameters = self._messageBatches[batchName]["parameters"]
        self.ircd.runActionStandard("startbatchsend", self, batchName,
                                    batchType, batchParameters)
        for messageData in self._messageBatches[batchName]["messages"]:
            self.sendMessage(messageData[0], *messageData[1], **messageData[2])
        self.ircd.runActionStandard("endbatchsend", self, batchName, batchType,
                                    batchParameters)

    def startErrorBatch(self, batchName):
        """
		Used to start an error batch when sending multiple error messages to a
		user from a command's parseParams or from the commandpermission action.
		"""
        if not self._errorBatchName or not self._errorBatch:  # Only the first batch should apply
            self._errorBatchName = batchName

    def sendBatchedError(self, batchName, command, *args, **kw):
        """
		Adds an error to the current error batch if the specified error batch
		is the current error batch.
		"""
        if batchName and self._errorBatchName == batchName:
            self._errorBatch.append((command, args, kw))

    def sendSingleError(self, batchName, command, *args, **kw):
        """
		Creates a batch containing a single error and adds the specified error
		to it.
		"""
        if not self._errorBatchName:
            self._errorBatchName = batchName
            self._errorBatch.append((command, args, kw))

    def _hasBatchedErrors(self):
        if self._errorBatch:
            return True
        return False

    def _clearErrorBatch(self):
        self._errorBatchName = None
        self._errorBatch = []

    def _dispatchErrorBatch(self):
        for error in self._errorBatch:
            self.sendMessage(error[0], *error[1], **error[2])
        self._clearErrorBatch()

    def filterConditionalTags(self, conditionalTags):
        applyTags = {}
        for tag, data in conditionalTags.iteritems():
            value, check = data
            if check(self):
                applyTags[tag] = value
        return applyTags

    def connectionLost(self, reason):
        if self.uuid in self.ircd.users:
            self.disconnect("Connection reset")
        self.disconnectedDeferred.callback(None)

    def disconnect(self, reason):
        """
		Disconnects the user from the server.
		"""
        self.ircd.log.debug(
            "Disconnecting user {user.uuid} ({user.hostmask()}): {reason}",
            user=self,
            reason=reason)
        # Sometimes, actions deferred from initial connection may cause registration to occur after disconnection if
        # disconnection happens before registration completes. If the user is unregistered on disconnection, this prevents
        # the user from completing registration.
        self.addRegisterHold("QUIT")
        if self._pinger:
            if self._pinger.running:
                self._pinger.stop()
            self._pinger = None
        if self._registrationTimeoutTimer:
            if self._registrationTimeoutTimer.active():
                self._registrationTimeoutTimer.cancel()
            self._registrationTimeoutTimer = None
        if self._connectHandlerTimer and self._connectHandlerTimer.active():
            self._connectHandlerTimer.cancel()
            self._connectHandlerTimer = None
        self.ircd.recentlyQuitUsers[self.uuid] = now()
        del self.ircd.users[self.uuid]
        if self.isRegistered():
            del self.ircd.userNicks[self.nick]
        userSendList = [self]
        while self.channels:
            channel = self.channels[0]
            userSendList.extend(channel.users.keys())
            self._leaveChannel(channel)
        userSendList = [
            u for u in set(userSendList) if u.uuid[:3] == self.ircd.serverID
        ]
        userSendList.remove(self)
        self.ircd.runActionProcessing("quitmessage",
                                      userSendList,
                                      self,
                                      reason,
                                      users=[self] + userSendList)
        self.ircd.runActionStandard("quit", self, reason, users=self)
        self.transport.loseConnection()

    def _timeoutRegistration(self):
        if self.isRegistered():
            self._pinger.start(self.ircd.config.get("user_ping_frequency", 60),
                               False)
            return
        self.disconnect("Registration timeout")

    def _ping(self):
        self.ircd.runActionStandard("pinguser", self)

    def isRegistered(self):
        """
		Returns True if this user session is fully registered.
		"""
        return not self._registerHolds

    def register(self, holdName):
        """
		Removes the specified hold on a user's registration. If this is the
		last hold on a user, completes registration on the user.
		"""
        if holdName not in self._registerHolds:
            return
        self._registerHolds.remove(holdName)
        if not self._registerHolds:
            if not self.nick or self.nick in self.ircd.userNicks:
                self._registerHolds.add("NICK")
            if not self.ident or not self.gecos:
                self._registerHolds.add("USER")
            if self._registerHolds:
                return
            self._registerHolds.add(
                "registercheck"
            )  # The user shouldn't be considered registered until we complete these final checks
            if self.ircd.runActionUntilFalse("register", self, users=[self]):
                self.transport.loseConnection()
                return
            self._registerHolds.remove("registercheck")
            self.ircd.userNicks[self.nick] = self.uuid
            self.ircd.log.debug(
                "Registering user {user.uuid} ({user.hostmask()})", user=self)
            versionWithName = "txircd-{}".format(version)
            self.sendMessage(
                irc.RPL_WELCOME,
                "Welcome to the {} Internet Relay Chat Network {}".format(
                    self.ircd.config["network_name"], self.hostmask()))
            self.sendMessage(
                irc.RPL_YOURHOST, "Your host is {}, running version {}".format(
                    self.ircd.name, versionWithName))
            self.sendMessage(
                irc.RPL_CREATED, "This server was created {}".format(
                    self.ircd.startupTime.replace(microsecond=0)))
            chanModes = "".join(
                ["".join(modes.keys()) for modes in self.ircd.channelModes])
            chanModes += "".join(self.ircd.channelStatuses.keys())
            self.sendMessage(
                irc.RPL_MYINFO, self.ircd.name, versionWithName, "".join(
                    ["".join(modes.keys()) for modes in self.ircd.userModes]),
                chanModes)
            self.sendISupport()
            self.ircd.runActionStandard("welcome", self, users=[self])

    def addRegisterHold(self, holdName):
        """
		Adds a register hold to this user if the user is not yet registered.
		"""
        if not self._registerHolds:
            return
        self._registerHolds.add(holdName)

    def sendISupport(self):
        """
		Sends ISUPPORT to this user."""
        isupportList = self.ircd.generateISupportList()
        isupportMsgList = splitMessage(" ".join(isupportList), 350)
        for line in isupportMsgList:
            lineArgs = line.split(" ")
            lineArgs.append("are supported by this server")
            self.sendMessage(irc.RPL_ISUPPORT, *lineArgs)

    def hostmask(self):
        """
		Returns the user's hostmask.
		"""
        return "{}!{}@{}".format(self.nick, self.ident, self.host())

    def hostmaskWithRealHost(self):
        """
		Returns the user's hostmask using the user's real host rather than any
		vhost that may have been applied.
		"""
        return "{}!{}@{}".format(self.nick, self.ident, self.realHost)

    def hostmaskWithIP(self):
        """
		Returns the user's hostmask using the user's IP address instead of the
		host.
		"""
        return "{}!{}@{}".format(self.nick, self.ident, self.ip)

    def changeNick(self, newNick, fromServer=None):
        """
		Changes this user's nickname. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		"""
        if newNick == self.nick:
            return
        if newNick in self.ircd.userNicks and self.ircd.userNicks[
                newNick] != self.uuid:
            return
        oldNick = self.nick
        if oldNick and oldNick in self.ircd.userNicks:
            del self.ircd.userNicks[self.nick]
        self.nick = newNick
        self.nickSince = now()
        if self.isRegistered():
            self.ircd.userNicks[self.nick] = self.uuid
            userSendList = [self]
            for channel in self.channels:
                userSendList.extend(channel.users.keys())
            userSendList = [
                u for u in set(userSendList)
                if u.uuid[:3] == self.ircd.serverID
            ]
            self.ircd.runActionProcessing("changenickmessage",
                                          userSendList,
                                          self,
                                          oldNick,
                                          users=userSendList)
            self.ircd.runActionStandard("changenick",
                                        self,
                                        oldNick,
                                        fromServer,
                                        users=[self])

    def changeIdent(self, newIdent, fromServer=None):
        """
		Changes this user's ident. If initiated by a remote server, that server
		should be specified in the fromServer parameter.
		"""
        if newIdent == self.ident:
            return
        if len(newIdent) > self.ircd.config.get("ident_length", 12):
            return
        oldIdent = self.ident
        self.ident = newIdent
        if self.isRegistered():
            self.ircd.runActionStandard("changeident",
                                        self,
                                        oldIdent,
                                        fromServer,
                                        users=[self])

    def host(self):
        if not self._hostStack:
            return self.realHost
        return self._hostsByType[self._hostStack[-1]]

    def changeHost(self, hostType, newHost, fromServer=None):
        """
		Changes a user's host. If initiated by a remote server, that server
		should be specified in the fromServer parameter.
		"""
        if hostType == "*":
            return
        if len(newHost) > self.ircd.config.get("hostname_length", 64):
            return
        if hostType in self._hostsByType and self._hostsByType[
                hostType] == newHost:
            return
        oldHost = self.host()
        self._hostsByType[hostType] = newHost
        if hostType in self._hostStack:
            self._hostStack.remove(hostType)
        self._hostStack.append(hostType)
        if self.isRegistered():
            self.ircd.runComboActionStandard(
                (("changehost", self, hostType, oldHost, fromServer),
                 ("updatehost", self, hostType, oldHost, newHost, fromServer)),
                users=[self])

    def updateHost(self, hostType, newHost, fromServer=None):
        """
		Updates the host of a given host type for the user. If initiated by
		a remote server, that server should be specified in the fromServer
		parameter.
		"""
        if hostType not in self._hostStack:
            self.changeHost(hostType, newHost, fromServer)
            return
        if hostType == "*":
            return
        if len(newHost) > self.ircd.config.get("hostname_length", 64):
            return
        if hostType in self._hostsByType and self._hostsByType[
                hostType] == newHost:
            return
        oldHost = self.host()
        oldHostOfType = None
        if hostType in self._hostsByType:
            oldHostOfType = self._hostsByType[hostType]
        self._hostsByType[hostType] = newHost
        changedUserHost = (oldHost != self.host())
        changedHostOfType = (oldHostOfType != newHost)
        if self.isRegistered():
            if changedUserHost and changedHostOfType:
                self.ircd.runComboActionStandard(
                    (("changehost", self, hostType, oldHost, fromServer),
                     ("updatehost", self, hostType, oldHost, newHost,
                      fromServer)),
                    users=[self])
            elif changedHostOfType:
                self.ircd.runActionStandard("updatehost",
                                            self,
                                            hostType,
                                            oldHost,
                                            newHost,
                                            fromServer,
                                            users=[self])

    def resetHost(self, hostType, fromServer=None):
        """
		Resets the user's host to the real host.
		"""
        if hostType not in self._hostsByType:
            return
        oldHost = self.host()
        if hostType in self._hostStack:
            self._hostStack.remove(hostType)
        del self._hostsByType[hostType]
        currentHost = self.host()
        if currentHost != oldHost:
            self.ircd.runComboActionStandard(
                (("changehost", self, hostType, oldHost, fromServer),
                 ("updatehost", self, hostType, oldHost, None, fromServer)),
                users=[self])
        else:
            self.ircd.runActionStandard("updatehost",
                                        self,
                                        hostType,
                                        oldHost,
                                        None,
                                        fromServer,
                                        users=[self])

    def currentHostType(self):
        if self._hostStack:
            return self._hostStack[-1]
        return "*"

    def changeGecos(self, newGecos, fromServer=None):
        """
		Changes a user's real name. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		"""
        if len(newGecos) > self.ircd.config.get("gecos_length", 128):
            return
        if newGecos == self.gecos:
            return
        oldGecos = self.gecos
        self.gecos = newGecos
        if self.isRegistered():
            self.ircd.runActionStandard("changegecos",
                                        self,
                                        oldGecos,
                                        fromServer,
                                        users=[self])

    def metadataKeyExists(self, key):
        """
		Checks whether the specified key exists in the user's metadata.
		"""
        return key in self._metadata

    def metadataKeyCase(self, key):
        """
		Returns the specified key in the user's metadata in its original case.
		Returns None if the given key is not in the user's metadata.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][0]

    def metadataValue(self, key):
        """
		Returns the value of the given key in the user's metadata or None if
		the given key is not in the user's metadata.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][1]

    def metadataVisibility(self, key):
        """
		Returns the visibility value of the given key in the user's metadata or
		None if the given key is not in the user's metadata.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][2]

    def metadataSetByUser(self, key):
        """
		Returns whether the given key in the user's metadata was set by a user
		or None if the given key is not in the user's metadata.
		"""
        if key not in self._metadata:
            return None
        return self._metadata[key][3]

    def metadataList(self):
        """
		Returns the list of metadata keys/values for the user as a list of
		tuples in the format
		[ (key, value, visibility, setByUser) ]
		"""
        return self._metadata.values()

    def setMetadata(self, key, value, visibility, setByUser, fromServer=None):
        """
		Sets metadata for the user. If initiated by a remote server, that
		server should be specified in the fromServer parameter.
		If the value is None, deletes the metadata at the provided key.
		"""
        if not isValidMetadataKey(key):
            return False
        oldData = None
        if key in self._metadata:
            oldData = self._metadata[key]
        if setByUser and oldData and not oldData[3]:
            return False
        if setByUser and self.ircd.runActionUntilValue(
                "usercansetmetadata", key, users=[self]) is False:
            return False
        if value is None:
            if key in self._metadata:
                del self._metadata[key]
        elif not visibility:
            return False
        else:
            self._metadata[key] = (key, value, visibility, setByUser)
        oldValue = oldData[1] if oldData else None
        self.ircd.runActionStandard("usermetadataupdate",
                                    self,
                                    key,
                                    oldValue,
                                    value,
                                    visibility,
                                    setByUser,
                                    fromServer,
                                    users=[self])
        return True

    def canSeeMetadataVisibility(self, visibility):
        if visibility == "*":
            return True
        return self.ircd.runActionUntilValue("usercanseemetadata", self,
                                             visibility) is not False

    def joinChannel(self, channel, override=False):
        """
		Joins the user to a channel. Specify the override parameter only if all
		permission checks should be bypassed.
		"""
        if channel in self.channels:
            return
        if not override:
            if self.ircd.runActionUntilValue("joinpermission",
                                             channel,
                                             self,
                                             users=[self],
                                             channels=[channel]) is False:
                return
        channel.users[self] = {"status": ""}
        self.channels.append(channel)
        newChannel = False
        if channel.name not in self.ircd.channels:
            newChannel = True
            self.ircd.channels[channel.name] = channel
            self.ircd.recentlyDestroyedChannels[channel.name] = False
        # We need to send the JOIN message before doing other processing, as chancreate will do things like
        # mode defaulting, which will send messages about the channel before the JOIN message, which is bad.
        messageUsers = [
            u for u in channel.users.iterkeys()
            if u.uuid[:3] == self.ircd.serverID
        ]
        self.ircd.runActionProcessing("joinmessage",
                                      messageUsers,
                                      channel,
                                      self,
                                      users=messageUsers,
                                      channels=[channel])
        if newChannel:
            self.ircd.runActionStandard("channelcreate",
                                        channel,
                                        self,
                                        channels=[channel])
        self.ircd.runActionStandard("join",
                                    channel,
                                    self,
                                    users=[self],
                                    channels=[channel])

    def leaveChannel(self,
                     channel,
                     partType="PART",
                     typeData={},
                     fromServer=None):
        """
		Removes the user from a channel. The partType and typeData are used for
		the leavemessage action to send the parting message. If the channel
		leaving is initiated by a remote server, that server should be
		specified in the fromServer parameter.
		"""
        if channel not in self.channels:
            return
        messageUsers = [
            u for u in channel.users.iterkeys()
            if u.uuid[:3] == self.ircd.serverID
        ]
        self.ircd.runActionProcessing("leavemessage",
                                      messageUsers,
                                      channel,
                                      self,
                                      partType,
                                      typeData,
                                      fromServer,
                                      users=[self],
                                      channels=[channel])
        self._leaveChannel(channel)

    def _leaveChannel(self, channel):
        self.ircd.runActionStandard("leave",
                                    channel,
                                    self,
                                    users=[self],
                                    channels=[channel])
        self.channels.remove(channel)
        del channel.users[self]

    def setModes(self, modes, defaultSource):
        """
		Sets modes on the user. Accepts modes as a list of tuples in the
		format:
		[ (adding, mode, param, setBy, setTime) ]
		- adding: True if we're setting the mode; False if unsetting
		- mode: The mode letter
		- param: The mode's parameter; None if no parameter is needed for that
		    mode
		- setBy: Optional, only used for list modes; a human-readable string
		    (typically server name or nick!user@host) for who/what set this
		    mode)
		- setTime: Optional, only used for list modes; a datetime object
		    containing when the mode was set
		
		The defaultSource is a valid user ID or server ID of someone who set
		the modes. It is used as the source for announcements about the mode
		change and as the default setter for any list modes who do not have the
		setBy parameter specified.
		The default time for list modes with no setTime specified is now().
		"""
        modeChanges = []
        defaultSourceName = self._sourceName(defaultSource)
        if defaultSourceName is None:
            raise ValueError("Source must be a valid user or server ID.")
        nowTime = now()
        for modeData in modes:
            mode = modeData[1]
            if mode not in self.ircd.userModeTypes:
                continue
            setBy = defaultSourceName
            setTime = nowTime
            modeType = self.ircd.userModeTypes[mode]
            adding = modeData[0]
            if modeType in (ModeType.List, ModeType.ParamOnUnset,
                            ModeType.Param):
                param = modeData[2]
            else:
                param = None
            if modeType == ModeType.List:
                dataCount = len(modeData)
                if dataCount >= 4:
                    setBy = modeData[3]
                if dataCount >= 5:
                    setTime = modeData[4]
            if adding:
                paramList = self.ircd.userModes[modeType][mode].checkSet(
                    self, param)
            else:
                paramList = self.ircd.userModes[modeType][mode].checkUnset(
                    self, param)
            if paramList is None:
                continue

            for parameter in paramList:
                if self._applyMode(adding, modeType, mode, parameter, setBy,
                                   setTime):
                    modeChanges.append(
                        (adding, mode, parameter, setBy, setTime))

        self._notifyModeChanges(modeChanges, defaultSource, defaultSourceName)
        return modeChanges

    def setModesByUser(self, user, modes, params, override=False):
        """
		Parses a mode string specified by a user and sets those modes on the
		user.
		The user parameter should be the user who set the modes (usually, but
		not always, this user).
		The modes parameter is the actual modes string; parameters specified by
		the user should be as a list of strings in params.
		The override parameter should be used only when all permission checks
		should be overridden.
		"""
        adding = True
        changes = []
        setBy = self._sourceName(user.uuid)
        setTime = now()
        for mode in modes:
            if len(changes) >= self.ircd.config.get("modes_per_line", 20):
                break
            if mode == "+":
                adding = True
                continue
            if mode == "-":
                adding = False
                continue
            if mode not in self.ircd.userModeTypes:
                user.sendMessage(irc.ERR_UNKNOWNMODE, mode,
                                 "is unknown mode char to me")
                continue
            modeType = self.ircd.userModeTypes[mode]
            param = None
            if modeType in (ModeType.List, ModeType.ParamOnUnset) or (
                    adding and modeType == ModeType.Param):
                try:
                    param = params.pop(0)
                except IndexError:
                    if modeType == ModeType.List:
                        self.ircd.userModes[modeType][mode].showListParams(
                            user, self)
                    continue
            if adding:
                paramList = self.ircd.userModes[modeType][mode].checkSet(
                    self, param)
            else:
                paramList = self.ircd.userModes[modeType][mode].checkUnset(
                    self, param)
            if paramList is None:
                continue

            for parameter in paramList:
                if len(changes) >= self.ircd.config.get("modes_per_line", 20):
                    break
                if not override and self.ircd.runActionUntilValue(
                        "modepermission-user-{}".format(mode),
                        self,
                        user,
                        adding,
                        parameter,
                        users=[self, user]) is False:
                    continue
                if adding:
                    if modeType == ModeType.List:
                        if mode in self.modes and len(
                                self.modes[mode]) > self.ircd.config.get(
                                    "user_listmode_limit", 128):
                            user.sendMessage(
                                irc.ERR_BANLISTFULL, self.name, parameter,
                                "Channel +{} list is full".format(mode))
                            continue
                if self._applyMode(adding, modeType, mode, parameter, setBy,
                                   setTime):
                    changes.append((adding, mode, parameter, setBy, setTime))
        self._notifyModeChanges(changes, user.uuid, setBy)
        return changes

    def _applyMode(self, adding, modeType, mode, parameter, setBy, setTime):
        if parameter:
            if len(parameter) > 255:
                return False
            if " " in parameter:
                return False

        if adding:
            if modeType == ModeType.List:
                if mode in self.modes:
                    if len(self.modes[mode]) > self.ircd.config.get(
                            "user_listmode_limit", 128):
                        return False
                    for paramData in self.modes[mode]:
                        if parameter == paramData[0]:
                            return False
                else:
                    self.modes[mode] = []
                self.modes[mode].append((parameter, setBy, setTime))
                return True
            if mode in self.modes and self.modes[mode] == parameter:
                return False
            self.modes[mode] = parameter
            return True

        if modeType == ModeType.List:
            if mode not in self.modes:
                return False
            for index, paramData in enumerate(self.modes[mode]):
                if paramData[0] == parameter:
                    del self.modes[mode][index]
                    break
            else:
                return False
            if not self.modes[mode]:
                del self.modes[mode]
            return True
        if mode not in self.modes:
            return False
        if modeType == ModeType.ParamOnUnset and parameter != self.modes[mode]:
            return False
        del self.modes[mode]
        return True

    def _notifyModeChanges(self, modeChanges, source, sourceName):
        if not modeChanges:
            return
        for change in modeChanges:
            self.ircd.runActionStandard("modechange-user-{}".format(change[1]),
                                        self,
                                        change[3],
                                        change[0],
                                        change[2],
                                        users=[self])

        users = []
        if source in self.ircd.users and source[:3] == self.ircd.serverID:
            users.append(self.ircd.users[source])
        if self.uuid[:3] == self.ircd.serverID:
            users.append(self)
        if users:
            self.ircd.runActionProcessing("modemessage-user",
                                          users,
                                          self,
                                          source,
                                          sourceName,
                                          modeChanges,
                                          users=users)
        self.ircd.runActionStandard("modechanges-user",
                                    self,
                                    source,
                                    sourceName,
                                    modeChanges,
                                    users=[self])

    def _sourceName(self, source):
        if source in self.ircd.users:
            return self.ircd.users[source].hostmask()
        if source == self.ircd.serverID:
            return self.ircd.name
        if source in self.ircd.servers:
            return self.ircd.servers[source].name
        return None

    def modeString(self, toUser):
        """
		Get a user-reportable mode string for the modes set on the user.
		"""
        modeStr = ["+"]
        params = []
        for mode in self.modes:
            modeType = self.ircd.userModeTypes[mode]
            if modeType not in (ModeType.ParamOnUnset, ModeType.Param,
                                ModeType.NoParam):
                continue
            if modeType != ModeType.NoParam:
                param = None
                if toUser:
                    param = self.ircd.userModes[modeType][mode].showParam(
                        toUser, self)
                if not param:
                    param = self.modes[mode]
            else:
                param = None
            modeStr.append(mode)
            if param:
                params.append(param)
        if params:
            return "{} {}".format("".join(modeStr), " ".join(params))
        return "".join(modeStr)
Esempio n. 32
0
 def __init__(self, config, options = None, sslCert = None):
     reactor.addSystemEventTrigger("before", "shutdown", self.cleanup)
     self.dead = False
     
     self.config = config
     self.version = "txircd-{}".format(__version__)
     self.created = now()
     self.servers = CaseInsensitiveDictionary()
     self.users = CaseInsensitiveDictionary()
     self.userid = {}
     self.channels = CaseInsensitiveDictionary()
     self.peerConnections = {}
     self.ssl_cert = sslCert
     self.client_ports = {}
     self.server_ports = {}
     self.modules = {}
     self.module_abilities = {}
     self.actions = {
         "connect": [],
         "register": [],
         "welcome": [],
         "join": [],
         "joinmessage": [],
         "nick": [],
         "quit": [],
         "topic": [],
         "mode": [],
         "nameslistentry": [],
         "chancreate": [],
         "chandestroy": [],
         "commandextra": [],
         "commandunknown": [],
         "commandpermission": [],
         "metadataupdate": [],
         "recvdata": [],
         "senddata": [],
         "netmerge": [],
         "netsplit": []
     }
     self.commands = {}
     self.channel_modes = [{}, {}, {}, {}]
     self.channel_mode_type = {}
     self.user_modes = [{}, {}, {}, {}]
     self.user_mode_type = {}
     self.prefixes = {}
     self.prefix_symbols = {}
     self.prefix_order = []
     self.server_commands = {}
     self.module_data_cache = {}
     self.server_factory = None
     self.common_modules = set()
     log.msg("Loading module data...")
     try:
         with open("data.yaml", "r") as dataFile:
             self.serialized_data = yaml.safe_load(dataFile)
             if self.serialized_data is None:
                 self.serialized_data = {}
     except IOError:
         self.serialized_data = {}
     self.isupport = {}
     self.usercount = {
         "localmax": 0,
         "globalmax": 0
     }
     log.msg("Loading configuration...")
     self.servconfig = {}
     if not options:
         options = {}
     self.load_options(options)
     self.name = self.servconfig["server_name"]
     log.msg("Loading modules...")
     self.all_module_load()
     self.save_serialized_deferred = None
     self.autoconnect_servers = LoopingCall(self.server_autoconnect)
     self.autoconnect_servers.start(60, now=False) # The server factory isn't added to here yet
     # Fill in the default ISUPPORT dictionary once config and modules are loaded, since some values depend on those
     self.isupport["CASEMAPPING"] = "rfc1459"
     self.isupport["CHANMODES"] = ",".join(["".join(modedict.keys()) for modedict in self.channel_modes])
     self.isupport["CHANNELLEN"] = "64"
     self.isupport["CHANTYPES"] = "#"
     self.isupport["MODES"] = 20
     self.isupport["NETWORK"] = self.servconfig["server_network_name"]
     self.isupport["NICKLEN"] = "32"
     self.isupport["PREFIX"] = "({}){}".format("".join(self.prefix_order), "".join([self.prefixes[mode][0] for mode in self.prefix_order]))
     self.isupport["STATUSMSG"] = "".join([self.prefixes[mode][0] for mode in self.prefix_order])
     self.isupport["TOPICLEN"] = "316"
     self.isupport["USERMODES"] = ",".join(["".join(modedict.keys()) for modedict in self.user_modes])
Esempio n. 33
0
class ShunCommand(Command):
    def __init__(self):
        self.shunList = CaseInsensitiveDictionary()
    
    def onUse(self, user, data):
        if "reason" in data:
            self.shunList[data["mask"]] = {
                "setter": user.nickname,
                "created": epoch(now()),
                "duration": data["duration"],
                "reason": data["reason"]
            }
            user.sendMessage("NOTICE", ":*** Shun set on {}, to expire in {} seconds".format(data["mask"], data["duration"]))
        else:
            del self.shunList[data["mask"]]
            user.sendMessage("NOTICE", ":*** Shun removed on {}".format(data["mask"]))
        for udata in self.ircd.users.itervalues():
            if self.match_shun(udata):
                udata.cache["shunned"] = True
            else:
                udata.cache["shunned"] = False
    
    def processParams(self, user, params):
        if user.registered > 0:
            user.sendMessage(irc.ERR_NOTREGISTERED, "SHUN", ":You have not registered")
            return {}
        if "o" not in user.mode:
            user.sendMessage(irc.ERR_NOPRIVILEGES, ":Permission denied - You do not have the correct operator privileges")
            return {}
        if not params:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN", ":Not enough parameters")
            return {}
        banmask = params[0]
        if banmask in self.ircd.users:
            udata = self.ircd.users[banmask]
            banmask = "{}@{}".format(udata.username, udata.hostname)
        elif "@" not in banmask:
            banmask = "*@{}".format(banmask)
        self.expire_shuns()
        if banmask[0] == "-":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN", ":Not enough parameters")
                return {}
            if banmask not in self.shunList:
                user.sendMessage("NOTICE", ":*** Shun {} not found; check /stats S for a list of active shuns.".format(banmask))
                return {}
            return {
                "user": user,
                "mask": banmask
            }
        if len(params) < 3 or not params[2]:
            user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN", ":Not enough parameters")
            return {}
        if banmask[0] == "+":
            banmask = banmask[1:]
            if not banmask:
                user.sendMessage(irc.ERR_NEEDMOREPARAMS, "SHUN", ":Not enough parameters")
                return {}
        if banmask in self.shunList:
            user.sendMessage("NOTICE", ":*** Shun {} is already set!  Check /stats S for a list of active shuns.".format(banmask))
            return {}
        return {
            "user": user,
            "mask": banmask,
            "duration": parse_duration(params[1]),
            "reason": " ".join(params[2:])
        }
    
    def statsList(self, user, statsType):
        if statsType != "S":
            return
        self.expire_shuns()
        for mask, linedata in self.shunList.iteritems():
            user.sendMessage(irc.RPL_STATSSHUN, "{} {} {} {} :{}".format(mask, linedata["created"], linedata["duration"], linedata["setter"], linedata["reason"]))
    
    def check_register(self, user):
        reason = self.match_shun(user)
        if reason:
            user.cache["shunned"] = True
        elif reason == None:
            user.cache["shunned"] = False
        else:
            return "again"
        return True
    
    def reassign_shun(self, user):
        reason = self.match_shun(user)
        if reason:
            user.cache["shunned"] = True
        else:
            user.cache["shunned"] = False
        return None # the xline_rematch hook shouldn't automatically operate on these, so let's make it not.
    
    def match_shun(self, user):
        self.expire_shuns()
        if "except_line" in user.cache:
            if user.cache["except_line"]:
                return None
            matchMask = "{}@{}".format(user.username, user.hostname)
            for mask, linedata in self.shunList.iteritems():
                if fnmatch(matchMask, mask):
                    return linedata["reason"]
            matchMask = "{}@{}".format(user.username, user.ip)
            for mask, linedata in self.shunList.iteritems():
                if fnmatch(matchMask, mask):
                    return linedata["reason"]
            return None
        elif "shunned" in user.cache:
            if user.cache["shunned"]:
                return "Shunned"
            return None
        else:
            matchMask = "{}@{}".format(user.username, user.hostname)
            for mask in self.shunList.iterkeys():
                if fnmatch(matchMask, mask):
                    user.cache["shunned"] = True
                    return ""
            matchMask = "{}@{}".format(user.username, user.ip)
            for mask in self.shunList.iterkeys():
                if fnmatch(matchMask, mask):
                    user.cache["shunned"] = True
                    return ""
            user.cache["shunned"] = False
            return None
    
    def expire_shuns(self):
        current_time = epoch(now())
        expired = []
        for mask, linedata in self.shunList.iteritems():
            if linedata["duration"] and current_time > linedata["created"] + linedata["duration"]:
                expired.append(mask)
        for mask in expired:
            del self.shunList[mask]
    
    def check_command(self, user, command, data):
        if "shunned" not in user.cache or not user.cache["shunned"]:
            return data
        if command not in self.ircd.servconfig["shun_command_list"]:
            return {}
        return data
Esempio n. 34
0
 def __init__(self):
     self.banList = CaseInsensitiveDictionary()
Esempio n. 35
0
File: ircd.py Progetto: ojii/txircd
class IRCD(Factory):
    protocol = IRCProtocol
    channel_prefixes = "#"
    types = {"user": DBUser, "server": IRCServer, "service": IRCService}
    prefix_order = "qaohv"  # Hardcoded into modes :(
    prefix_symbols = {"q": "~", "a": "&", "o": "@", "h": "%", "v": "+"}

    def __init__(self, config, options=None):
        reactor.addSystemEventTrigger("before", "shutdown", self.cleanup)
        self.dead = False

        self.config = config
        self.version = "txircd.{}".format(__version__)
        self.created = now()
        self.token = uuid.uuid1()
        self.servers = CaseInsensitiveDictionary()
        self.users = CaseInsensitiveDictionary()
        self.whowas = CaseInsensitiveDictionary()
        self.channels = DefaultCaseInsensitiveDictionary(self.ChannelFactory)
        self.peerConnections = {}
        self.db = None
        # self.stats = None
        # self.stats_timer = LoopingCall(self.flush_stats)
        # self.stats_data = {
        #    "bytes_in": 0,
        #    "bytes_out": 0,
        #    "lines_in": 0,
        #    "lines_out": 0,
        #    "total_bytes_in": 0,
        #    "total_bytes_out": 0,
        #    "total_lines_in": 0,
        #    "total_lines_out": 0,
        #    "connections": 0,
        #    "total_connections": 0
        # }
        self.xlines = {
            "G": CaseInsensitiveDictionary(),
            "K": CaseInsensitiveDictionary(),
            "Z": CaseInsensitiveDictionary(),
            "E": CaseInsensitiveDictionary(),
            "Q": CaseInsensitiveDictionary(),
            "SHUN": CaseInsensitiveDictionary(),
        }
        self.xline_match = {
            "G": ["{ident}@{host}", "{ident}@{ip}"],
            "K": ["{ident}@{host}", "{ident}@{ip}"],
            "Z": ["{ip}"],
            "E": ["{ident}@{host}", "{ident}@{ip}"],
            "Q": ["{nick}"],
            "SHUN": ["{ident}@{host}", "{ident}@{ip}"],
        }

        if not options:
            options = {}
        self.load_options(options)

        # if self.app_ip_log:
        #    try:
        #        with open(self.app_ip_log) as f:
        #            self.unique_ips = set(json.loads(f.read()))
        #            self.stats_data["total_connections"] = len(self.unique_ips)
        #    except:
        #        self.unique_ips = set()
        # else:
        #    self.unique_ips = set()

        # logfile = "{}/{}".format(self.app_log_dir,"stats")
        # if not os.path.exists(logfile):
        #    os.makedirs(logfile)
        # self.stats_log = DailyLogFile("log",logfile)
        # self.stats_timer.start(1)

    def rehash(self):
        try:
            with open(self.config) as f:
                self.load_options(yaml.safe_load(f))
        except:
            return False
        return True

    def load_options(self, options):
        # Populate attributes with options
        for var in default_options.iterkeys():
            setattr(self, var, options[var] if var in options else default_options[var])
        # Unserialize xlines
        for key in self.xlines.iterkeys():
            self.xlines[key] = CaseInsensitiveDictionary()
            xlines = getattr(self, "server_xlines_{}".format(key.lower()), None)
            if not xlines:
                continue
            for user, data in xlines.iteritems():
                self.xlines[key][user] = {
                    "created": datetime.datetime.strptime(data["created"], "%Y-%m-%d %H:%M:%S"),
                    "duration": parse_duration(data["duration"]),
                    "setter": data["setter"],
                    "reason": data["reason"],
                }
        # Create database connection
        if self.db:
            self.db.close()
        if self.db_library:
            self.db = adbapi.ConnectionPool(
                self.db_library,
                host=self.db_host,
                port=self.db_port,
                db=self.db_database,
                user=self.db_username,
                passwd=self.db_password,
                cp_reconnect=True,
            )
        # Turn on stats factory if needed, or shut it down if needed
        # if self.stats_enabled and not self.stats:
        #    self.stats = StatFactory()
        #    if self.stats_port_tcp:
        #        try:
        #            reactor.listenTCP(int(self.stats_port_tcp), self.stats)
        #        except:
        #            pass # Wasn't a number
        #    if self.stats_port_web:
        #        try:
        #            reactor.listenTCP(int(self.stats_port_web), SockJSFactory(self.stats))
        #        except:
        #            pass # Wasn't a number
        # elif not self.stats_enabled and self.stats:
        #    self.stats.shutdown()
        #    self.stats = None
        # Load geoip data
        # self.geo_db = pygeoip.GeoIP(self.app_geoip_database, pygeoip.MEMORY_CACHE) if self.app_geoip_database else None

    def save_options(self):
        # Serialize xlines
        for key, lines in self.xlines.iteritems():
            xlines = {}
            for user, data in lines.iteritems():
                xlines[user] = {
                    "created": str(data["created"]),
                    "duration": build_duration(data["duration"]),
                    "setter": data["setter"],
                    "reason": data["reason"],
                }
            setattr(self, "server_xlines_{}".format(key.lower()), xlines)
        # Load old options
        options = {}
        try:
            with open(self.config) as f:
                options = yaml.safe_load(f)
        except:
            return False
        # Overwrite with the new stuff
        for var in default_options.iterkeys():
            options[var] = getattr(self, var, None)
        # Save em
        try:
            with open(self.config, "w") as f:
                yaml.dump(options, f, default_flow_style=False)
        except:
            return False
        return True

    def cleanup(self):
        # Track the disconnections so we know they get done
        deferreds = []
        # Cleanly disconnect all clients
        log.msg("Disconnecting clients...")
        for u in self.users.values():
            u.irc_QUIT(None, ["Server shutting down"])
            deferreds.append(u.disconnected)
        # Without any clients, all channels should be gone
        # But make sure the logs are closed, just in case
        log.msg("Closing logs...")
        for c in self.channels.itervalues():
            c.log.close()
        # self.stats_log.close()
        # Finally, save the config. Just in case.
        log.msg("Saving options...")
        self.save_options()
        # Return deferreds
        log.msg("Waiting on deferreds...")
        self.dead = True
        return DeferredList(deferreds)

    def buildProtocol(self, addr):
        if self.dead:
            return None
        ip = addr.host
        # self.unique_ips.add(ip)
        # self.stats_data["total_connections"] = len(self.unique_ips)
        # if self.app_ip_log:
        #    with open(self.app_ip_log,"w") as f:
        #        f.write(json.dumps(list(self.unique_ips), separators=(',',':')))
        conn = self.peerConnections.get(ip, 0)
        max = self.client_peer_exempt[ip] if ip in self.client_peer_exempt else self.client_peer_connections
        if max and conn >= max:
            return None
        # self.stats_data["connections"] += 1
        self.peerConnections[ip] = conn + 1
        return Factory.buildProtocol(self, addr)

    def unregisterProtocol(self, p):
        # self.stats_data["connections"] -= 1
        peerHost = p.transport.getPeer().host
        self.peerConnections[peerHost] -= 1
        if self.peerConnections[peerHost] == 0:
            del self.peerConnections[peerHost]

    def ChannelFactory(self, name):
        logfile = "{}/{}".format(self.app_log_dir, irc_lower(name))
        if not os.path.exists(logfile):
            os.makedirs(logfile)
        c = Channel(
            name,
            now(),
            {"message": None, "author": "", "created": now()},
            CaseInsensitiveDictionary(),
            ChannelModes(self, None),
            DailyLogFile("log", logfile),
        )
        c.mode.parent = c
        c.mode.combine("nt", [], name)
        return c

    def flush_stats(self):
        return
        users = {}
        countries = {}
        uptime = now() - self.created
        for u in self.users.itervalues():
            users[u.nickname] = [u.latitude, u.longitude]
            if u.country not in countries:
                countries[u.country] = 0
            countries[u.country] += 1
        line = json.dumps(
            {
                "io": self.stats_data,
                "users": users,
                "countries": countries,
                "uptime": "{}".format(uptime if uptime.days > 0 else "0 days, {}".format(uptime)),
            },
            separators=(",", ":"),
        )
        self.stats_data["bytes_in"] = 0
        self.stats_data["bytes_out"] = 0
        self.stats_data["lines_in"] = 0
        self.stats_data["lines_out"] = 0
        # if not self.stats_log.closed:
        #    self.stats_log.write(line+"\n")
        if self.stats:
            self.stats.broadcast(line + "\r\n")