class Command(object):
    def hook(self, base):
        self.ircd = base
        return self
    def onUse(self, user, data):
        pass
    def processParams(self, user, params):
        return {
            "user": user,
            "params": params
        }
    def updateActivity(self, user):
        user.lastactivity = now() class RegisterUser(Command):
    arguments = [
        ("uuid", String()),
        ("nick", String()),
        ("ident", String()),
        ("host", String()),
        ("realhost", String()),
        ("gecos", String()),
        ("ip", String()),
        ("password", String()),
        ("server", String()),
        ("secure", Boolean()),
        ("signon", Integer()),
        ("nickts", Integer())
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE",
        NoSuchServer: "NO_SUCH_SERVER",
        UserAlreadyConnected: "UUID_ALREADY_CONNECTED"
    }
    requiresAnswer = False class RemoveUser(Command):
    arguments = [
        ("user", String()),
        ("reason", String())
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE",
        NoSuchUser: "NO_SUCH_USER"
    }
    requiresAnswer = False class ModuleMessage(Command):
    arguments = [
        ("destserver", String()),
        ("type", String()),
        ("args", ListOf(String()))
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE"
    } class SetIdent(Command):
    arguments = [
        ("user", String()),
        ("ident", String())
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE",
        NoSuchUser: "NO_SUCH_USER"
    }
    requiresAnswer = False class SetHost(Command):
    arguments = [
        ("user", String()),
        ("host", String())
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE",
        NoSuchUser: "NO_SUCH_USER"
    }
    requiresAnswer = False class SetName(Command):
    arguments = [
        ("user", String()),
        ("gecos", String())
    ]
    errors = {
        HandshakeNotYetComplete: "HANDSHAKE_NOT_COMPLETE",
        NoSuchUser: "NO_SUCH_USER"
    }
    requiresAnswer = False def chunk_message(msg, chunk_size):
    chunks = []
    msg += "\n"
    while msg:
        index = msg.find("\n",0,chunk_size+1)
        if index < 0:
            index = msg.rfind(" ",0,chunk_size+1)
        if index < 0:
            index = chunk_size
        chunks.append(msg[:index])
        msg = msg[index+1:] if msg[index] in " \n" else msg[index:]
    return chunks def crypt(word, salt=None, iterations=1000, algorithm="sha256", bytes=24):
    """PBKDF2-based unix crypt(3) replacement.

    The number of iterations specified in the salt overrides the 'iterations'
    parameter.
    """
    
    # Reserve algorithms
    algos = {
        "md5": md5,
        "sha1": sha1,
        "sha224": sha224,
        "sha256": sha256,
        "sha384": sha384,
        "sha512": sha512
    }
    
    # Generate a (pseudo-)random salt if the user hasn't provided one.
    if salt is None:
        salt = _makesalt()

    # salt must be a string or the us-ascii subset of unicode
    if isunicode(salt):
        salt = salt.encode('us-ascii').decode('us-ascii')
    elif isbytes(salt):
        salt = salt.decode('us-ascii')
    else:
        raise TypeError("salt must be a string")

    # word must be a string or unicode (in the latter case, we convert to UTF-8)
    if isunicode(word):
        word = word.encode("UTF-8")
    elif not isbytes(word):
        raise TypeError("word must be a string or unicode")

    # Try to extract the real salt and iteration count from the salt
    if ":" in salt:
        (algorithm, iterations, salt, oldhash) = salt.split(":")
        if iterations != "":
            iterations = int(iterations)
            if iterations < 1:
                raise ValueError("Invalid salt")
        bytes = len(b64decode(oldhash))

    # Make sure the salt matches the allowed character set
    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="
    for ch in salt:
        if ch not in allowed:
            raise ValueError("Illegal character {!r} in salt".format(ch))
    
    if algorithm not in algos:
        raise ValueError("Invalid algorithm: {}".format(algorithm))

    hash = b64encode(PBKDF2(word, salt, iterations, algos[algorithm]).read(bytes))
    return "{}:{!s}:{}:{}".format(algorithm, iterations, salt, hash) def irc_lower(str):
    return str.lower().replace("[","{").replace("]","}").replace("/","|").replace("~","^") def now():
    return datetime.datetime.utcnow().replace(microsecond=0) class CaseInsensitiveDictionary(MutableMapping):
    def __init__(self):
        self._data = {}

    def __repr__(self):
        return repr(self._data)

    def __delitem__(self, key):
        try:
            del self._data[irc_lower(key)]
        except KeyError:
            raise KeyError(key)

    def __getitem__(self, key):
        try:
            return self._data[irc_lower(key)]
        except KeyError:
            raise KeyError(key)

    def __iter__(self):
        return iter(self._data)

    def __len__(self):
        return len(self._data)

    def __setitem__(self, key, value):
        self._data[irc_lower(key)] = value
from twisted.enterprise import adbapi
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.words.protocols import irc
from txircd.modbase import Command
from txircd.server import RegisterUser, RemoveUser, ModuleMessage, SetIdent, SetHost, SetName
from txircd.utils import chunk_message, crypt, irc_lower, now, CaseInsensitiveDictionary
from base64 import b64decode, b64encode
from Crypto.Random.random import getrandbits
from Crypto.Cipher import AES
from Crypto.Cipher import Blowfish
from datetime import datetime
from random import choice
import math, os, random, uuid, yaml
        class ServiceTransport(object):
            def loseConnection(self):
                pass
        
        def __init__(self):
            self.transport = self.ServiceTransport()
            self.secure = True
    
    def __init__(self, ircd, nick, ident, host, gecos, helpTexts, module):
        # We're going to try to keep Service fairly consistent with IRCUser, even if most of these variables will never be used
        # in order to prevent exceptions all over the place
        self.ircd = ircd
        self.socket = self.ServiceSocket()
        self.uuid = str(uuid.uuid1())
        self.password = None
        self.nickname = nick
        self.username = ident
        self.hostname = host
        self.realhost = host
        self.realname = gecos
        self.ip = "127.0.0.1"
        self.server = self.ircd.name
        self.signon = datetime.utcfromtimestamp(1) # Give these pseudoclients a really old time so that they won't be disconnected by remote servers
        self.lastactivity = now()
        self.lastpong = now()
        self.nicktime = datetime.utcfromtimestamp(1)
        self.mode = {}
        self.disconnected = Deferred()
        self.disconnected.callback(None)
        self.registered = 0
        self.metadata = {
            "server": {},
            "user": {},
            "client": {},
            "ext": {},
            "private": {}
        }
        self.cache = {} # Not only do various other modules potentially play with the cache, but we can do what we want with it to store auction data, etc.
        self.help = helpTexts
        self.module = module
    
    def addToServers(self):
        for server in self.ircd.servers.itervalues():
            if server.nearHop == self.ircd.name:
                server.callRemote(RegisterUser, uuid=self.uuid, nick=self.nickname, ident=self.username, host=self.hostname, realhost=self.realhost, gecos=self.realname, ip=self.ip, password="", server=self.server, secure=self.socket.secure, signon=1, nickts=1)
    
    def removeFromServers(self):
        for server in self.ircd.servers.itervalues():
            if server.nearHop == self.ircd.name:
                server.callRemote(RemoveUser, user=self.uuid, reason="Unloading module")
    
    def register(self):
        pass
    
    def send_isupport(self):
        pass
    
    def disconnect(self, reason, sourceServer = None):
        if sourceServer is None:
            return
        self.ircd.servers[sourceServer].callRemote(RegisterUser, uuid=self.uuid, nick=self.nickname, ident=self.username, host=self.hostname, realhost=self.realhost, gecos=self.realname, ip=self.ip, password="", server=self.server, secure=self.socket.secure, signon=1, nickts=1)
    
    def sendMessage(self, command, *parameter_list, **kw):
        if command == "PRIVMSG" and "prefix" in kw:
            nick = kw["prefix"][0:kw["prefix"].find("!")]
            user = self.ircd.users[nick]
            params = parameter_list[0].split(" ")
            serviceCommand = params.pop(0).upper().lstrip(":") # Messages sent this way start with a colon
            if serviceCommand == "HELP":
                if not params:
                    helpOut = chunk_message(self.help[0], 80)
                    for line in helpOut:
                        user.sendMessage("NOTICE", ":{}".format(line), prefix=self.prefix())
                    user.sendMessage("NOTICE", ": ", prefix=self.prefix())
                    commands = sorted(self.help[1].keys())
                    for cmd in commands:
                        info = self.help[1][cmd]
                        if info[2] and not self.module.isServiceAdmin(user, self):
                            continue
                        user.sendMessage("NOTICE", ":\x02{}\x02\t{}".format(cmd.upper(), info[0]), prefix=self.prefix())
                    user.sendMessage("NOTICE", ": ", prefix=self.prefix())
                    user.sendMessage("NOTICE", ":*** End of help", prefix=self.prefix())
                else:
                    helpCmd = params[0]
                    if helpCmd not in self.help[1]:
                        user.sendMessage("NOTICE", ":No help available for \x02{}\x02.".format(helpCmd), prefix=self.prefix())
                    else:
                        info = self.help[1][helpCmd]
                        if info[2] and not self.module.isServiceAdmin(user, self):
                            user.sendMessage("NOTICE", ":No help available for \x02{}\x02.".format(helpCmd), prefix=self.prefix())
                        else:
                            helpOut = chunk_message(info[1], 80)
                            for line in helpOut:
                                user.sendMessage("NOTICE", ":{}".format(line), prefix=self.prefix())
                            user.sendMessage("NOTICE", ":*** End of \x02{}\x02 help".format(helpCmd), prefix=self.prefix())
            elif serviceCommand in self.help[1] and (not self.help[1][serviceCommand][2] or self.module.isServiceAdmin(user, self)):
                self.ircd.users[nick].handleCommand(serviceCommand, None, params)
            else:
                self.ircd.users[nick].sendMessage("NOTICE", ":Unknown command \x02{}\x02.  Use \x1F/msg {} HELP\x1F for help.".format(serviceCommand, self.nickname), prefix=self.prefix())
    
    def setMetadata(self, namespace, key, value):
        oldValue = self.metadata[namespace][key] if key in self.metadata[namespace] else ""
        self.metadata[namespace][key] = value
        for modfunc in self.ircd.actions["metadataupdate"]:
            modfunc(self, namespace, key, oldValue, value)
    
    def delMetadata(self, namespace, key):
        oldValue = self.metadata[namespace][key]
        del self.metadata[namespace][key]
        for modfunc in self.ircd.actions["metadataupdate"]:
            modfunc(self, namespace, key, oldValue, "")
    
    def prefix(self):
        return "{}!{}@{}".format(self.nickname, self.username, self.hostname)
    
    def hasAccess(self, channel, level):
        return True # access to change anything in all channels
    
    def setUsername(self, newUsername, sourceServer = None):
        if sourceServer:

            self.ircd.servers[sourceServer].callRemote(SetIdent, user=self.uuid, ident=self.username)