from twisted.plugin import IPlugin
from twisted.words.protocols import irc
from txircd.module_interface import Command, ICommand, IMode, IModuleData, Mode, ModuleData
from txircd.utils import ircLower, ModeType
from zope.interface import implements
from fnmatch import fnmatch

class Oper(ModuleData, Mode):
    implements(IPlugin, IModuleData, IMode)
    
    name = "Oper"
    core = True
    
    def hookIRCd(self, ircd):
        self.ircd = ircd
    
    def userCommands(self):
        return [ ("OPER", 1, UserOper(self.ircd)) ]
    
    def actions(self):
        return [ ("userhasoperpermission", 1, self.operPermission),
         ("modepermission-user-o", 1, self.nope) ]
    
    def userModes(self):
        return [ ("o", ModeType.NoParam, self) ]
    
    def operPermission(self, user, permissionType):
        # For now, we're just going to check for usermode +o
        # Later, when we add a permission system, this can be expanded
        return "o" in user.modes
    
    def nope(self, user, settingUser, adding, param):
        if adding:
            user.sendMessage(irc.ERR_NOPRIVILEGES, ":Permission denied - User mode o may not be set")
            return False
        return None

class UserOper(Command):
    implements(ICommand)
    
    def __init__(self, ircd):
        self.ircd = ircd
    
    def parseParams(self, user, params, prefix, tags):
        if len(params) < 2:
            user.sendSingleError("OperCmd", irc.ERR_NEEDMOREPARAMS, "OPER", ":Not enough parameters")
            return None
        return {
            "username": params[0],
            "password": params[1]
        }
    
    def execute(self, user, data):
        configuredOpers = self.ircd.config.getWithDefault("opers", {})
        username = data["username"]
        if username not in configuredOpers:
            user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
            return True
        operData = configuredOpers[username]
        if "password" not in operData:
            user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
            return True
        password = data["password"]
        if "hash" in operData:
            compareFunc = "compare-{}".format(operData["hash"])
            if compareFunc not in self.ircd.functionCache:
                user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
                return True
            passwordMatch = self.ircd.functionCache[compareFunc](password, operData["password"])
        else:
            passwordMatch = (password == operData["password"])
        if not passwordMatch:
            user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
            return True
        if "host" in operData:
            operHost = ircLower(operData["host"])
            userHost = ircLower("{}@{}".format(user.ident, user.host))
            if not fnmatch(userHost, operHost):
                userHost = ircLower("{}@{}".format(user.ident, user.realhost))
                if not fnmatch(userHost, operHost):
                    userHost = ircLower("{}@{}".format(user.ident, user.ip))
                    if not fnmatch(userHost, operHost):
                        user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
                        return True
        if self.ircd.runActionUntilFalse("opercheck", user, username, password, operData): # Allow other modules to implement additional checks
            user.sendMessage(irc.ERR_NOOPERHOST, ":Invalid oper credentials")
            return True
        user.setModes(self.ircd.serverID, "+o", [])
        user.sendMessage(irc.RPL_YOUREOPER, ":You are now an IRC operator")
        return True

oper = Oper()
@@ -28,16 +28,47 @@
 IPlugin%0A
+from twisted.python import log%0A
 from twi
@@ -99,16 +99,16 @@
 ort irc%0A
-
 from txi
@@ -300,16 +300,31 @@
  fnmatch
+%0Aimport logging
 %0A%0Aclass 
@@ -870,164 +870,367 @@
     
-# For now, we're just going to check for usermode +o%0A        # Later, when we add a permission system, this can be expanded%0A        return %22o%22 in user.modes
+if %22o%22 not in user.modes:%0A            # Maybe the user de-opered or something, but if they did they're clearly not an oper now%0A            return False%0A        # Check for oper permissions in the user's permission storage%0A        if %22oper-permissions%22 not in user.cache:%0A            return False%0A        return permissionType in user.cache%5B%22oper-permissions%22%5D
 %0A   
@@ -3921,16 +3921,419 @@
 rator%22)%0A
+        if %22types%22 in operData:%0A            configuredOperTypes = self.ircd.config.getWithDefault(%22oper_types%22, %7B%7D)%0A            operPermissions = set()%0A            for type in operData%5B%22types%22%5D:%0A                if type not in configuredOperTypes:%0A                    continue%0A                operPermissions.update(configuredOperTypes%5Btype%5D)%0A            user.cache%5B%22oper-permissions%22%5D = operPermissions%0A
         

Implement the oper permissions mechanism