Example #1
0
    def help_for_command(self, irc, command):
        acl = ACLController()
        callbacks = self.event.get_command_callbacks(command)
        found_help = False

        if callbacks is None:
            irc.reply("No such command")
            return

        for plugin, callback, privileged in callbacks:
            # Don't show help for privileged commands if the user doesn't have access
            if privileged and not acl.check_access(irc.message.source, command):
                continue

            doc = callback.__doc__
            if doc:
                found_help = True

                if hasattr(plugin, "name"):
                    irc.notice(irc.message.source.nick, plugin.name + " - " + command)

                # Filter out empty lines
                lines = [line for line in doc.splitlines() if len(line) > 0]

                # Find minimum whitespace so we can normalize doc-comment
                min_whitespace = min([len(x) - len(x.lstrip()) for x in lines])

                for line in lines:
                    # Tweak whitespace and replace tabs with spaces
                    line = line[min_whitespace:].replace("\t", "    ")
                    irc.notice(irc.message.source.nick, line)

        if not found_help:
            irc.reply("No help available")
Example #2
0
    def list_commands(self, irc):
        acl = ACLController()
        commands = self.event.get_all_commands()
        num_columns = 6

        cmdlist = []

        for command in commands:
            callbacks = self.event.get_command_callbacks(command)

            masters = self.config.get('masters')
            if masters:
                masterAccess = reduce(lambda x, y : x or y, [irc.message.source.is_matching(hostmask) for hostmask in masters])
            else:
                masterAccess = False

            for _, _, privileged in callbacks:
                if privileged and not (masterAccess or acl.check_access(irc.message.source, command)):
                    pass
                else:
                    if privileged:
                        cmdlist.append("*" + command)
                    else:
                        cmdlist.append(" " + command)

        table = TableFormatter(['']*num_columns, 'Available commands')

        while len(cmdlist) > 0:
            row = cmdlist[0:num_columns]
            row += [' '] * (num_columns - len(row))
            table.add_row(row)
            del cmdlist[0:num_columns]

        for row in table.get_table(column_headers = False):
            irc.notice(irc.message.source.nick, row)
Example #3
0
    def __init__(self):
        self.author = "Jim Persson"
        self.name = "ACL Commands (Core)"

        self.acl = ACLController()
        self.parseTree = {
            "add": {
                "user":  {"$username"  : self.add_user},
                "group": {"$groupname" : self.add_group},
            },

            "del": {
                "user":  {"$username"  : self.del_user},
                "group": {"$groupname" : self.del_group},
            },

            "user": {
                "$username": {
                    "show": self.show_user,
                    "addmask": {"$hostmask": self.user_add_hostmask},
                    "delmask": {"$hostmask": self.user_del_hostmask}
                }
            },

            "group": {
                "$groupname": {
                    "show": self.show_group,
                    "adduser": {"$username": self.group_add_user},
                    "deluser": {"$username": self.group_del_user}
                }
            },

            "access": {
                "$context": {
                    "allow":  {"$aro": self.access_allow},
                    "deny" :  {"$aro": self.access_deny},
                    "remove": {"$aro": self.access_remove},
                    "clear": self.access_clear
                }
            },

            "show": {
                "users": self.get_users,
                "groups": self.get_groups
            }
        }

        self.event.register_command("acl", self.cmd_acl, True)
Example #4
0
    def construct(self):
        self.commandCallbacks = {}  # "command" : [(object_instance, callback, privileged), ...]
        self.eventCallbacks = {}    # "event"   : [(object_instance, callback), ...]
        self.syseventCallbacks = {} # "sysevent": [(object_instance, callback), ...]

        # {obj: timer}
        self.moduleTimers = {}

        self.config = ConfigController()
        self.acl = ACLController()

        self.command_prefix = "!"

        # Number of active dispatched event threads
        self._pending_events = 0
        self._pending_events_lock = threading.RLock()
        self._pending_events_event = threading.Event()
Example #5
0
class EventController(Singleton):
    ## @brief Constructor
    def construct(self):
        self.commandCallbacks = {}  # "command" : [(object_instance, callback, privileged), ...]
        self.eventCallbacks = {}    # "event"   : [(object_instance, callback), ...]
        self.syseventCallbacks = {} # "sysevent": [(object_instance, callback), ...]

        # {obj: timer}
        self.moduleTimers = {}

        self.config = ConfigController()
        self.acl = ACLController()

        self.command_prefix = "!"

        # Number of active dispatched event threads
        self._pending_events = 0
        self._pending_events_lock = threading.RLock()
        self._pending_events_event = threading.Event()

    ##
    # Increment the number of active event threads
    def _increment_event_threads(self):
        with self._pending_events_lock:
            self._pending_events += 1
            self._pending_events_event.clear()

    ##
    # Decrement the number of active event threads
    # and signal any waiters if the number of event
    # threads reaches zero
    def _decrement_event_threads(self):
        with self._pending_events_lock:
            self._pending_events -= 1
            if self._pending_events == 0:
                self._pending_events_event.set()

    ##
    # Block until all dispatched events have finished.
    # This is primarily used by the simulator.
    def wait_for_pending_events(self):
        self._pending_events_event.wait()

    ## @brief Release callbacks registered by a module
    # @param obj Module instance for which callbacks should be released
    # @warning This should NEVER be called directly by a module
    def release_related(self, obj):
        for table in [self.commandCallbacks,
                      self.eventCallbacks,
                      self.syseventCallbacks]:

            for key in table.keys():
                entries = [entry for entry in table[key] if entry[0] == obj]

                for entry in entries:
                    table[key].remove(entry)

                if len(table[key]) == 0:
                    del table[key]

        # Stop timers
        if self.moduleTimers.has_key(obj):
            for timer in self.moduleTimers[obj]:
                timer.cancel()

            del self.moduleTimers[obj]

    ## @brief Run a specified function after a specified time
    # @param callback Function to call
    # @param interval Time to wait before calling the callback function
    # @param args (Optional) Tuple containing arguments in the order they should be passed
    # @param kwargs (Optional) Dict containing named arguments to pass to the callback
    def register_timer(self, callback, interval, args = (), kwargs = {}):
        def do_timeout(data):
            callback = data["callback"]
            try:
                callback(*data["args"], **data["kwargs"])
            except Exception as e:
                Logger.warning("Timer caught exception: %s" % e)

            # Free timer
            if self.moduleTimers.has_key(callback.im_self):
                for timer in self.moduleTimers[callback.im_self]:
                    if timer == threading.currentThread():
                        self.moduleTimers[callback.im_self].remove(timer)

                if len(self.moduleTimers[callback.im_self]) == 0:
                    del self.moduleTimers[callback.im_self]

        data = {
            "callback": callback,
            "args"  : args,
            "kwargs": kwargs,
        }

        if not hasattr(callback, "im_self"):
            raise Exception("Only class methods can be registered as timer callbacks")

        # Create timer object with associated data
        timer = threading.Timer(interval, do_timeout, kwargs = {"data": data})

        # Insert timer in list of active timers
        if not self.moduleTimers.has_key(callback.im_self):
            self.moduleTimers[callback.im_self] = []
        self.moduleTimers[callback.im_self].append(timer)

        # Dispatch timer
        timer.start()

    ## @brief Register command callback
    # @param command Command excluding command prefix. Example: "mycommand"
    # @param callback Function to execute when this command is dispatched
    # @param privileged Whether or not the command is privileged or not and subject to ACL
    #
    # @note
    # <pre>
    # Callback functions should have the form
    # def callback(ModuleInterface, params)
    # Where params will be the parameters of the command. Example: "!mycommand param1 param2 param3" will yield "param1 param2 param3"
    # </pre>
    def register_command(self, command, callback, privileged = False):
        key = command.lower()

        if not self.commandCallbacks.has_key(key):
            self.commandCallbacks[key] = []

        self.commandCallbacks[key].append((callback.im_self, callback, privileged))

    ##
    # Retrieve all callbacks associated with a command
    # @param command Command to find associated callbacks for
    # @return List of tuples (classinstance, callback, privileged?)
    def get_command_callbacks(self, command):
        key = command.lower()

        if not self.commandCallbacks.has_key(key):
            return []

        return self.commandCallbacks[key]

    ##
    # Retrieve a list of all available commands
    def get_all_commands(self):
        return self.commandCallbacks.keys()

    ## @brief Dispatch a command
    # @param irc IRCController instance
    # @param msg IRCMessage instance
    # @param command The command. Example: "mycommand"
    # @param params Parameters to the command. Example: "param1 param2 param3"
    def dispatch_command(self, irc, msg, command, params):
        def dispatcher_command_thread(callback, interface, params):
            try:
                callback(interface, params)
            except:
                Logger.log_traceback(callback.im_self)
            finally:
                self._decrement_event_threads()

        params = Arguments(params)

        callbacks = self.get_command_callbacks(command)

        Logger.debug1("Found %d receiver(s) for command '%s'" % (len(callbacks), command))

        if callbacks:
            interface = IRCMessageController(irc, msg)

            for (obj, callback, privileged) in callbacks:
                try:
                    # This will abort instantly as soon as a command without access is found
                    if privileged:
                        if not self.acl.check_access(msg.source, command.lower()):
                            interface.reply("Access denied")
                            return

                    thread = threading.Thread(target = dispatcher_command_thread,
                                              kwargs = {"callback": callback, "interface": interface, "params": params})
                    self._increment_event_threads()
                    thread.start()
                except Exception as e:
                    Logger.log_traceback(callback.im_self)

    ## @brief Register event callback
    # Register callback for an IRC-event, this could be any command the server sends, including numerics.
    # @param callback Function to execute when this event is dispatched
    # @param event Event to register. Example: "PRIVMSG", "PART", "QUIT", "001"
    #
    # @note
    # <pre>
    # Callback functions should have the form
    # def callback(IRCMessageController)
    # </pre>
    def register_event(self, event, callback):
        key = str(event).upper()
        if not self.eventCallbacks.has_key(key):
            self.eventCallbacks[key] = []

        self.eventCallbacks[key].append((callback.im_self, callback))

    ##
    # Register a system event callback
    # @param event Event code
    # @param callback Callback function to receive the event
    def register_system_event(self, event, callback):
        key = str(event).upper()
        if not self.syseventCallbacks.has_key(key):
            self.syseventCallbacks[key] = []

        self.syseventCallbacks[key].append((callback.im_self, callback))

    ##
    # Dispatch an bot internal event
    # @param event Event code
    # @param params Parameters to send to the event handler (optional)
    def dispatch_system_event(self, event, params = []):
        key = str(event).upper()
        def dispatcher_event_thread(callback, params):
            try:
                callback(params)
            except:
                Logger.log_traceback(callback.im_self)

        if self.syseventCallbacks.has_key(key):
            for (_, callback) in self.syseventCallbacks[key]:
                thread = threading.Thread(target = dispatcher_event_thread, kwargs = {"callback": callback, "params": params})
                thread.start()

    ## @brief Dispatch an event
    # @param irc IRCController instance
    # @param msg IRCMessage instance. IRCMessage.command contains the event.
    def dispatch_event(self, irc, msg):
        def dispatcher_event_thread(callback, interface):
            try:
                callback(interface)
            except:
                Logger.log_traceback(callback.im_self)
            finally:
                self._decrement_event_threads()

        if not msg.command:
            return

        # Dispatch the event
        key = msg.command.upper()
        if self.eventCallbacks.has_key(key):
            interface = IRCMessageController(irc, msg)

            for (obj, callback) in self.eventCallbacks[key]:
                thread = threading.Thread(target = dispatcher_event_thread,
                                          kwargs = {"callback": callback, "interface": interface})
                self._increment_event_threads()
                thread.start()

        # Check if it may be a command
        if msg.command == "PRIVMSG":
            match = re.match("^%s([^ ]+)(?:$| (.*)$)" % self.command_prefix, msg.params)
            if match:
                command, params = match.groups()
                self.dispatch_command(irc, msg, command, params)
Example #6
0
 def setUp(self):
     self.tempdir = tempfile.mkdtemp()
     self.acl = ACLController(self.tempdir + "/acl.db")
Example #7
0
class TestACLController(unittest.TestCase):
    def setUp(self):
        self.tempdir = tempfile.mkdtemp()
        self.acl = ACLController(self.tempdir + "/acl.db")

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def testCreateDeleteUsers(self):
        self.acl.add_user('bill')
        self.acl.add_user('bob')
        self.acl.add_user('anna')
        self.acl.add_user('linda')

        self.assertRaises(Exception, self.acl.add_user, 'linda')
        self.assertRaises(Exception, self.acl.del_user, 'user_that_doesnt_exist')

    def testAddDelHostMask(self):
        self.acl.add_user('theuser')
        self.acl.user_add_hostmask('theuser', '*[email protected]')
        self.acl.user_add_hostmask('theuser', '*[email protected]')

        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '*!*invalid hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid!hostmask')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '')
        self.assertRaises(Exception, self.acl.user_add_hostmask, 'user_that_doesnt_exist', 'anyhostmask')
        self.assertRaises(Exception, self.acl.user_del_hostmask, 'user_that_doesnt_exist', 'anyhostmask')
        self.assertRaises(Exception, self.acl.user_del_hostmask, 'theuser', 'hostmask_that_doesnt_exist')

        self.acl.user_del_hostmask('theuser', '*[email protected]')

    def testAddDelGroups(self):
        self.acl.add_group("mygroup")
        self.acl.del_group("mygroup")

        self.acl.add_group("duplicate")
        self.assertRaises(Exception, self.acl.add_group, "duplicate")

        self.assertRaises(Exception, self.acl.del_group, "nonexistant")

    def testMatchHostmasks(self):
        self.acl.add_user('theuser')
        self.acl.user_add_hostmask('theuser', '[email protected]')

        self.acl.add_user('superuser')
        self.acl.user_add_hostmask('superuser', '[email protected]')

        self.acl.add_user('baduser')
        self.acl.user_add_hostmask('baduser', '[email protected]')

        identityNormal = IRCUser('[email protected]')
        identitySuper  = IRCUser('[email protected]')
        identityBad    = IRCUser('[email protected]')

        self.assertRaises(Exception, self.acl.access_allow, 'secret', 'nosuchuser')
        self.acl.access_allow('secret', 'superuser')

        self.assertFalse(self.acl.check_access(identityNormal, 'secret'))
        self.assertTrue(self.acl.check_access(identitySuper, 'secret'))
        self.assertFalse(self.acl.check_access(identitySuper, 'SECRET'))
        self.assertFalse(self.acl.check_access(identitySuper, 'secret2'))

        # Test group access
        self.acl.add_group('superusers')
        self.acl.group_add_user('superuser', 'superusers')

        self.acl.add_group('normalusers')
        self.acl.group_add_user('theuser', 'normalusers')

        # "any"-group
        self.acl.access_allow('foreveryone', 'any')
        self.assertTrue(self.acl.check_access(identityNormal, 'foreveryone'))

        # Exclusion (Ticket #1)
        #self.assertTrue(self.acl.check_access(identityBad, 'foreveryone'))
        #self.acl.access_deny('foreveryone', 'baduser')
        #self.assertFalse(self.acl.check_access(identityBad, 'foreveryone'))

        # Group membership
        self.acl.access_allow('supersecret', 'superusers')
        self.assertTrue(self.acl.check_access(identitySuper, 'supersecret'))
        self.assertFalse(self.acl.check_access(identityNormal, 'supersecret'))
        self.assertFalse(self.acl.check_access(identityBad, 'supersecret'))

    def testMasterAccess(self):
        config = ConfigController()
        config.load_defaults()
        config.get('masters').append("*[email protected]")

        identityMaster = IRCUser('[email protected]')
        identityNormal = IRCUser('[email protected]')

        self.assertTrue(self.acl.check_access(identityMaster, 'something'))
        self.assertFalse(self.acl.check_access(identityNormal, 'something'))
Example #8
0
class ACLCommands(Plugin):
    def __init__(self):
        self.author = "Jim Persson"
        self.name = "ACL Commands (Core)"

        self.acl = ACLController()
        self.parseTree = {
            "add": {
                "user":  {"$username"  : self.add_user},
                "group": {"$groupname" : self.add_group},
            },

            "del": {
                "user":  {"$username"  : self.del_user},
                "group": {"$groupname" : self.del_group},
            },

            "user": {
                "$username": {
                    "show": self.show_user,
                    "addmask": {"$hostmask": self.user_add_hostmask},
                    "delmask": {"$hostmask": self.user_del_hostmask}
                }
            },

            "group": {
                "$groupname": {
                    "show": self.show_group,
                    "adduser": {"$username": self.group_add_user},
                    "deluser": {"$username": self.group_del_user}
                }
            },

            "access": {
                "$context": {
                    "allow":  {"$aro": self.access_allow},
                    "deny" :  {"$aro": self.access_deny},
                    "remove": {"$aro": self.access_remove},
                    "clear": self.access_clear
                }
            },

            "show": {
                "users": self.get_users,
                "groups": self.get_groups
            }
        }

        self.event.register_command("acl", self.cmd_acl, True)

    def add_user(self, username):
        self.acl.add_user(username)
        return ["Added user %s" % username]

    def add_group(self, groupname):
        self.acl.add_group(groupname)
        return ["Added group %s" % groupname]

    def del_user(self, username):
        self.acl.del_user(username)
        return ["Deleted user %s" % username]

    def del_group(self, groupname):
        self.acl.del_group(groupname)
        return ["Deleted group %s" % groupname]

    def user_add_hostmask(self, username, hostmask):
        self.acl.user_add_hostmask(username, hostmask)
        return ["Added hostmask %s to user %s" % (hostmask, username)]

    def user_del_hostmask(self, username, hostmask):
        self.acl.user_del_hostmask(username, hostmask)
        return ["Removed hostmask %s to user %s" % (hostmask, username)]

    def group_add_user(self, username, groupname):
        self.acl.group_add_user(username, groupname)
        return ["User %s is now a member of group %s" % (username, groupname)]

    def group_del_user(self, username, groupname):
        self.acl.group_del_user(username, groupname)
        return ["User %s is no longer a member of group %s" % (username, groupname)]

    def show_user(self, username):
        info = self.acl.get_user_info(username)

        data = []

        data += ["User: %s" % username]

        data += ["Associated Hostmasks:"]
        result = info["hostmasks"]
        if len(result) == 0:
            haveHostmask = False
            data += ["  None"]
        else:
            haveHostmask = True
            for row in result:
                data += ["  " + row]

        data += ["Group Memberships:"]
        result = info["groups"]
        if result is None:
            data += ["  None"]
        else:
            for row in result:
                data += ["  " + row]

        data += ["Command Access:"]
        if haveHostmask:
            result = info["access"]
            for (context, group) in result:
                if group is None:
                    data += ["  %s (direct access)" % (context)]
                else:
                    data += ["  %s (member in '%s')" % (context, group)]
        else:
            data += ["  No hostmasks for this user, unable to grant access"]

        return data

    def show_group(self, groupname):
        members = self.acl.get_group_members(groupname)

        users = TableFormatter([""]*5, "Group Members")
        members.sort()
        members.reverse()
        while len(members) != 0:
            row = []
            for _ in range(0, 5):
                if len(members) != 0:
                    user = members.pop()
                    row.append(user)

            row += [""]*(5-len(row))
            users.add_row(row)

        return users.get_table()

    def get_users(self):
        users = self.acl.get_users()

        return ["Found %d users:" % len(users)] + users

    def get_groups(self):
        groups = self.acl.get_groups()

        return ["Found %d groups" % len(groups)] + groups

    def access_allow(self, context, aro):
        self.acl.access_allow(context, aro)

        return ["%s now have access to '%s'" % (aro, context)]

    def access_deny(self, context, aro):
        self.acl.access_deny(context, aro)

        return ["Denied access to '%s' for %s'" % (context, aro)]

    def access_remove(self, context, aro):
        self.acl.access_remove(context, aro)

        return ["Ok"]

    def access_clear(self, context):
        self.acl.access_clear(context)

        return ["Ok"]

    def cmd_acl(self, irc, params):
        """
        Available operations
            add [user|group] <name>
            del [user|group] <name>
            user <username> [show | addmask <hostmask> | delmask <hostmask>]
            group <groupname> [show | adduser <username> | deluser <username>]
            access <context> [allow|deny|remove] <group_or_user>
            access <context> clear
            show [users|groups]"""

        try:
            result = CommandParser(self.parseTree).parse(str(params))
            if result:
                for line in result:
                    irc.reply(line)

        except CommandError as e:
            irc.reply(e)

        except Exception as e:
            irc.reply(e)