Esempio n. 1
0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = API(wrapper, "Web", internal=True)
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.socket = False
        self.data = Storage("web")

        if "keys" not in self.data.Data:
            self.data.Data["keys"] = []
        # if not self.config["Web"]["web-password"] == None:
        #   self.log.info("Changing web-mode password because web-password was changed in wrapper.properties")
        #  ***** change code to hashlib if this gets uncommented
        #   self.data.Data["password"] = md5.md5(self.config["Web"]["web-password"]).hexdigest()
        #   self.config["Web"]["web-password"] = None
        #   self.wrapper.configManager.save()

        self.api.registerEvent("server.consoleMessage", self.onServerConsole)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.login", self.onPlayerJoin)
        self.api.registerEvent("player.logout", self.onPlayerLeave)
        self.api.registerEvent("irc.message", self.onChannelMessage)
        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0
Esempio n. 2
0
    def __init__(self, wrapper):
        self.api = API(wrapper, "Scripts", internal=True)
        self.wrapper = wrapper
        self.config = wrapper.config

        # Register the events
        self.api.registerEvent("server.start", self._startserver)
        self.api.registerEvent("server.stopped", self._stopserver)
        self.api.registerEvent("wrapper.backupBegin", self._backupbegin)
        self.api.registerEvent("wrapper.backupEnd", self._backupend)

        self.createdefaultscripts()
Esempio n. 3
0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        self.interval = 0
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.time = time.time()
        self.backups = []
        self.enabled = self.config["Backups"]["enabled"]  # allow plugins to shutdown backups via api
        self.timerstarted = False
        if self.enabled and self.dotarchecks():  # only register event if used and tar installed!
            self.api.registerEvent("timer.second", self.eachsecond)
            self.timerstarted = True
            self.log.debug("Backups Enabled..")
Esempio n. 4
0
    def __init__(self, mcserver, log, wrapper):
        self.socket = False
        self.state = False
        self.javaserver = mcserver
        self.config = wrapper.config
        self.configmgr = wrapper.configManager
        self.wrapper = wrapper
        self.pass_handler = self.wrapper.cipher
        self.address = self.config["IRC"]["server"]
        self.port = self.config["IRC"]["port"]
        self.nickname = self.config["IRC"]["nick"]
        self.originalNickname = self.nickname[0:]
        self.nickAttempts = 0
        self.channels = self.config["IRC"]["channels"]
        self.encoding = self.config["General"]["encoding"]
        self.log = log
        self.timeout = False
        self.ready = False
        self.msgQueue = []
        self.authorized = {}

        self.api = API(self.wrapper, "IRC", internal=True)

        self.api.registerEvent("irc.message", self.onchannelmessage)
        self.api.registerEvent("irc.action", self.onchannelaction)
        self.api.registerEvent("irc.join", self.onchanneljoin)
        self.api.registerEvent("irc.part", self.onchannelpart)
        self.api.registerEvent("irc.quit", self.onchannelquit)

        self.api.registerEvent("server.starting", self.onServerStarting)
        self.api.registerEvent("server.started", self.onServerStarted)
        self.api.registerEvent("server.stopping", self.onServerStopping)
        self.api.registerEvent("server.stopped", self.onServerStopped)
        self.api.registerEvent("player.login", self.onPlayerLogin)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.action", self.onPlayerAction)
        self.api.registerEvent("player.logout", self.onPlayerLogout)
        self.api.registerEvent("player.achievement", self.onPlayerAchievement)
        self.api.registerEvent("player.death", self.onPlayerDeath)
        self.api.registerEvent("wrapper.backupBegin", self.onBackupBegin)
        self.api.registerEvent("wrapper.backupEnd", self.onBackupEnd)
        self.api.registerEvent("wrapper.backupFailure", self.onBackupFailure)
        self.api.registerEvent("server.say", self.onPlayerSay)
Esempio n. 5
0
    def __init__(self, wrapper):
        self.api = API(wrapper, "Scripts", internal=True)
        self.wrapper = wrapper
        self.config = wrapper.config

        # Register the events
        self.api.registerEvent("server.start", self._startserver)
        self.api.registerEvent("server.stopped", self._stopserver)
        self.api.registerEvent("wrapper.backupBegin", self._backupbegin)
        self.api.registerEvent("wrapper.backupEnd", self._backupend)

        self.createdefaultscripts()
Esempio n. 6
0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = API(wrapper, "Web", internal=True)
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.socket = False
        self.data = Storage("web")
        self.pass_handler = self.wrapper.cipher

        if "keys" not in self.data.Data:
            self.data.Data["keys"] = []

        self.api.registerEvent("server.consoleMessage", self.onServerConsole)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.login", self.onPlayerJoin)
        self.api.registerEvent("player.logout", self.onPlayerLeave)
        self.api.registerEvent("irc.message", self.onChannelMessage)
        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        self.interval = 0
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.time = time.time()
        self.backups = []
        self.enabled = self.config["Backups"]["enabled"]  # allow plugins to shutdown backups via api
        self.timerstarted = False
        if self.enabled and self.dotarchecks():  # only register event if used and tar installed!
            self.api.registerEvent("timer.second", self.eachsecond)
            self.timerstarted = True
            self.log.debug("Backups Enabled..")
Esempio n. 8
0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        # self.wrapper.backups.idle
        self.idle = True
        self.inprogress = False
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.timer = time.time()
        self.backups = []

        # allow plugins to shutdown backups via api
        self.enabled = self.config["Backups"]["enabled"]

        self._start()
Esempio n. 9
0
    def __init__(self, mcserver, log, wrapper):
        self.socket = False
        self.state = False
        self.javaserver = mcserver
        self.config = wrapper.config
        self.configmgr = wrapper.configManager
        self.wrapper = wrapper
        self.pass_handler = self.wrapper.cipher
        self.address = self.config["IRC"]["server"]
        self.port = self.config["IRC"]["port"]
        self.nickname = self.config["IRC"]["nick"]
        self.originalNickname = self.nickname[0:]
        self.nickAttempts = 0
        self.channels = self.config["IRC"]["channels"]
        self.encoding = self.config["General"]["encoding"]
        self.log = log
        self.timeout = False
        self.ready = False
        self.msgQueue = []
        self.authorized = {}

        self.api = API(self.wrapper, "IRC", internal=True)

        self.api.registerEvent("irc.message", self.onchannelmessage)
        self.api.registerEvent("irc.action", self.onchannelaction)
        self.api.registerEvent("irc.join", self.onchanneljoin)
        self.api.registerEvent("irc.part", self.onchannelpart)
        self.api.registerEvent("irc.quit", self.onchannelquit)

        self.api.registerEvent("server.starting", self.onServerStarting)
        self.api.registerEvent("server.started", self.onServerStarted)
        self.api.registerEvent("server.stopping", self.onServerStopping)
        self.api.registerEvent("server.stopped", self.onServerStopped)
        self.api.registerEvent("player.login", self.onPlayerLogin)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.action", self.onPlayerAction)
        self.api.registerEvent("player.logout", self.onPlayerLogout)
        self.api.registerEvent("player.achievement", self.onPlayerAchievement)
        self.api.registerEvent("player.death", self.onPlayerDeath)
        self.api.registerEvent("wrapper.backupBegin", self.onBackupBegin)
        self.api.registerEvent("wrapper.backupEnd", self.onBackupEnd)
        self.api.registerEvent("wrapper.backupFailure", self.onBackupFailure)
        self.api.registerEvent("server.say", self.onPlayerSay)
Esempio n. 10
0
class Scripts(object):

    def __init__(self, wrapper):
        self.api = API(wrapper, "Scripts", internal=True)
        self.wrapper = wrapper
        self.config = wrapper.config

        # Register the events
        self.api.registerEvent("server.start", self._startserver)
        self.api.registerEvent("server.stopped", self._stopserver)
        self.api.registerEvent("wrapper.backupBegin", self._backupbegin)
        self.api.registerEvent("wrapper.backupEnd", self._backupend)

        self.createdefaultscripts()

    def createdefaultscripts(self):
        if not os.path.exists("wrapper-data"):
            mkdir_p("wrapper-data")
        if not os.path.exists("wrapper-data/scripts"):
            mkdir_p("wrapper-data/scripts")
        for script in scripts:
            path = "wrapper-data/scripts/%s" % script
            if not os.path.exists(path):
                with open(path, "w") as f:
                    f.write(scripts[script])
                os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)
    # Events

    def _startserver(self, payload):
        os.system("wrapper-data/scripts/server-start.sh")

    def _stopserver(self, payload):
        os.system("wrapper-data/scripts/server-stop.sh")

    def _backupbegin(self, payload):
        os.system("wrapper-data/scripts/backup-begin.sh %s" % payload["file"])

    def _backupend(self, payload):
        os.system("wrapper-data/scripts/backup-finish.sh %s" % payload["file"])
Esempio n. 11
0
class Scripts(object):
    def __init__(self, wrapper):
        self.api = API(wrapper, "Scripts", internal=True)
        self.wrapper = wrapper
        self.config = wrapper.config

        # Register the events
        self.api.registerEvent("server.start", self._startserver)
        self.api.registerEvent("server.stopped", self._stopserver)
        self.api.registerEvent("wrapper.backupBegin", self._backupbegin)
        self.api.registerEvent("wrapper.backupEnd", self._backupend)

        self.createdefaultscripts()

    def createdefaultscripts(self):
        if not os.path.exists("wrapper-data"):
            mkdir_p("wrapper-data")
        if not os.path.exists("wrapper-data/scripts"):
            mkdir_p("wrapper-data/scripts")
        for script in scripts:
            path = "wrapper-data/scripts/%s" % script
            if not os.path.exists(path):
                with open(path, "w") as f:
                    f.write(scripts[script])
                os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC)

    # Events

    def _startserver(self, payload):
        os.system("wrapper-data/scripts/server-start.sh")

    def _stopserver(self, payload):
        os.system("wrapper-data/scripts/server-stop.sh")

    def _backupbegin(self, payload):
        os.system("wrapper-data/scripts/backup-begin.sh %s" % payload["file"])

    def _backupend(self, payload):
        os.system("wrapper-data/scripts/backup-finish.sh %s" % payload["file"])
Esempio n. 12
0
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = API(wrapper, "Web", internal=True)
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.socket = False
        self.data = Storage("web")
        self.pass_handler = self.wrapper.cipher

        if "keys" not in self.data.Data:
            self.data.Data["keys"] = []

        self.api.registerEvent("server.consoleMessage", self.onServerConsole)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.login", self.onPlayerJoin)
        self.api.registerEvent("player.logout", self.onPlayerLeave)
        self.api.registerEvent("irc.message", self.onChannelMessage)
        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0
Esempio n. 13
0
class Wrapper(object):
    def __init__(self, secret_passphrase):
        # setup log and config
        # needs a false setting on first in case config does not
        # load (like after changes).
        self.storage = False
        self.log = logging.getLogger('Wrapper.py')
        self.configManager = Config()
        self.configManager.loadconfig()
        self.config = self.configManager.config

        # Read Config items
        # hard coded cursor for non-readline mode
        self.cursor = ">"
        # This was to allow alternate encodings
        self.encoding = self.config["General"]["encoding"]
        self.serverpath = self.config["General"]["server-directory"]
        self.proxymode = self.config["Proxy"]["proxy-enabled"]
        self.wrapper_onlinemode = self.config["Proxy"]["online-mode"]
        self.halt_message = self.config["Misc"]["halt-message"]

        # encryption items (for passwords and sensitive user data)
        # salt is generated and stored in wrapper.properties.json
        config_changes = False
        salt = self.config["General"]["salt"]
        if not salt:
            salt = gensalt(self.encoding)
            self.config["General"]["salt"] = salt
            config_changes = True
        # passphrase is provided at startup by the wrapper operator or script (not stored)
        passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding,
                                             salt)
        self.cipher = Crypt(passphrase, self.encoding)

        # Update passwords (hash any plaintext passwords)
        for groups in self.config:
            for cfg_items in self.config[groups]:
                if cfg_items[-10:] == "-plaintext":
                    # i.e., cfg_items ===> like ["web-password-plaintext"]
                    hash_item = cfg_items[:-10]
                    # hash_item ===> i.e., ["web-password"]
                    if hash_item in self.config[groups] and self.config[
                            groups][cfg_items]:
                        # encrypt contents of (i.e.) ["web-password-plaintext"]
                        hashed_item = self.cipher.encrypt(
                            self.config[groups][cfg_items])
                        # store in "" ["Web"]["web-password"]
                        self.config[groups][hash_item] = hashed_item
                        # set plaintext item to false (successful digest)
                        self.config[groups][cfg_items] = False
                        config_changes = True

        # Patch any old update paths "..wrapper/development/build/version.json"
        # new paths are: "..wrapper/development"
        for entries in self.config["Updates"]:
            if "/build/version.json" in str(self.config["Updates"][entries]):
                oldentry = copy.copy(self.config["Updates"][entries])
                self.config["Updates"][entries] = oldentry.split(
                    "/build/version.json")[0]
                config_changes = True

        # save changes made to config file
        if config_changes:
            self.configManager.save()

        # reload branch update info.
        self.auto_update_wrapper = self.config["Updates"][
            "auto-update-wrapper"]
        self.auto_update_branch = self.config["Updates"]["auto-update-branch"]
        if not self.auto_update_branch:
            self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development"
        else:
            self.update_url = self.config["Updates"][self.auto_update_branch]

        self.use_timer_tick_event = self.config["Gameplay"][
            "use-timer-tick-event"]
        self.use_readline = not (self.config["Misc"]["use-betterconsole"])

        # Storages
        self.wrapper_storage = Storage("wrapper", encoding=self.encoding)
        self.wrapper_permissions = Storage("permissions",
                                           encoding=self.encoding,
                                           pickle=False)
        self.wrapper_usercache = Storage("usercache",
                                         encoding=self.encoding,
                                         pickle=False)

        # storage Data objects
        self.storage = self.wrapper_storage.Data
        self.usercache = self.wrapper_usercache.Data
        # self.wrapper_permissions accessed only by permissions module

        # core functions and datasets
        self.perms = Permissions(self)
        self.uuids = UUIDS(self.log, self.usercache)
        self.plugins = Plugins(self)
        self.commands = Commands(self)
        self.events = Events(self)
        self.players = {}
        self.registered_permissions = {}
        self.help = {}
        self.input_buff = ""
        self.sig_int = False
        self.command_hist = ['/help', 'help']
        self.command_index = 1

        # init items that are set up later (or opted out of/ not set up.)
        self.javaserver = None
        self.api = None
        self.irc = None
        self.scripts = None
        self.web = None
        self.proxy = None
        self.backups = None

        #  HaltSig - Why? ... because if self.halt was just `False`, passing
        #  a self.halt would simply be passing `False` (immutable).  Changing
        # the value of self.halt would not necessarily change the value of the
        # passed parameter (unless it was specifically referenced back as
        # `wrapper.halt`). Since the halt signal needs to be passed, possibly
        # several layers deep, and into modules that it may be desireable to
        # not have direct access to wrapper, using a HaltSig object is
        # more desireable and reliable in behavior.
        self.halt = HaltSig()

        self.updated = False
        # future plan to expose this to api
        self.xplayer = ConsolePlayer(self)

        # Error messages for non-standard import failures.
        if not readline and self.use_readline:
            self.log.warning(
                "'readline' not imported.  This is needed for proper"
                " console functioning. Press <Enter> to acknowledge...")
            sys.stdin.readline()

        # requests is just being used in too many places to try
        # and track its usages piece-meal.
        if not requests:
            self.log.error(
                "You must have the requests module installed to use wrapper!"
                " console functioning. Press <Enter> to Exit...")
            sys.stdin.readline()
            self._halt()

        # create server/proxy vitals and config objects
        self.servervitals = ServerVitals(self.players)

        # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS:
        # The ServerVitals class gets passed the player object list now, but
        # player objects are now housed in wrapper.  This is how we are
        # passing information between proxy and wrapper.

        self.servervitals.serverpath = self.config["General"][
            "server-directory"]
        self.servervitals.state = OFF
        self.servervitals.command_prefix = self.config["Misc"][
            "command-prefix"]

        self.proxyconfig = ProxyConfig()
        self.proxyconfig.proxy = self.config["Proxy"]
        self.proxyconfig.entity = self.config["Entities"]

    def __del__(self):
        """prevent error message on very first wrapper starts when
        wrapper exits after creating new wrapper.properties file.
        """
        if self.storage:
            self.wrapper_storage.close()
            self.wrapper_permissions.close()
            self.wrapper_usercache.close()

    def start(self):
        """wrapper execution starts here"""

        self.signals()

        self.backups = Backups(self)

        self.api = API(self, "Wrapper.py")
        self._registerwrappershelp()

        # This is not the actual server... the MCServer
        # class is a console wherein the server is started
        self.javaserver = MCServer(self, self.servervitals)
        self.javaserver.init()

        # load plugins
        self.plugins.loadplugins()

        if self.config["IRC"]["irc-enabled"]:  # this should be a plugin
            self.irc = IRC(self.javaserver, self.log, self)
            t = threading.Thread(target=self.irc.init, args=())
            t.daemon = True
            t.start()

        if self.config["Web"]["web-enabled"]:  # this should be a plugin
            if manageweb.pkg_resources and manageweb.requests:
                self.log.warning(
                    "Our apologies!  Web mode is currently broken.  Wrapper"
                    " will start web mode anyway, but it will not likely "
                    "function well (or at all).  For now, you should turn "
                    "off web mode in wrapper.properties.json.")
                self.web = manageweb.Web(self)
                t = threading.Thread(target=self.web.wrap, args=())
                t.daemon = True
                t.start()
            else:
                self.log.error(
                    "Web remote could not be started because you do not have"
                    " the required modules installed: pkg_resources\n"
                    "Hint: http://stackoverflow.com/questions/7446187")

        # Console Daemon runs while not wrapper.halt.halt
        consoledaemon = threading.Thread(target=self.parseconsoleinput,
                                         args=())
        consoledaemon.daemon = True
        consoledaemon.start()

        # Timer runs while not wrapper.halt.halt
        ts = threading.Thread(target=self.event_timer_second, args=())
        ts.daemon = True
        ts.start()

        if self.use_timer_tick_event:
            # Timer runs while not wrapper.halt.halt
            tt = threading.Thread(target=self.event_timer_tick, args=())
            tt.daemon = True
            tt.start()

        if self.config["General"]["shell-scripts"]:
            if os.name in ("posix", "mac"):
                self.scripts = Scripts(self)
            else:
                self.log.error(
                    "Sorry, but shell scripts only work on *NIX-based systems!"
                    " If you are using a  *NIX-based system, please file a "
                    "bug report.")

        if self.proxymode:
            t = threading.Thread(target=self._startproxy, args=())
            t.daemon = True
            t.start()

        if self.auto_update_wrapper:
            t = threading.Thread(target=self._auto_update_process, args=())
            t.daemon = True
            t.start()

        self.javaserver.handle_server()
        # handle_server always runs, even if the actual server is not started

        self.plugins.disableplugins()
        self.log.info("Plugins disabled")
        self.wrapper_storage.close()
        self.wrapper_permissions.close()
        self.wrapper_usercache.close()
        self.log.info("Wrapper Storages closed and saved.")

        # wrapper execution ends here.  handle_server ends when
        # wrapper.halt.halt is True.
        if self.sig_int:
            self.log.info("Ending threads, please wait...")
            time.sleep(5)
            os.system("reset")

    def signals(self):
        signal.signal(signal.SIGINT, self.sigint)
        signal.signal(signal.SIGTERM, self.sigterm)
        # noinspection PyBroadException
        try:
            # lacking in Windows
            signal.signal(signal.SIGTSTP, self.sigtstp)
        except:
            pass

    def sigint(*args):
        self = args[0]  # We are only interested in the self component
        self.log.info("Wrapper.py received SIGINT; halting...\n")
        self.sig_int = True
        self._halt()

    def sigterm(*args):
        self = args[0]  # We are only interested in the self component
        self.log.info("Wrapper.py received SIGTERM; halting...\n")
        self._halt()

    def sigtstp(*args):
        # We are only interested in the 'self' component
        self = args[0]
        self.log.info("Wrapper.py received SIGTSTP; NO sleep support!"
                      " Wrapper halting...\n")
        os.system("kill -CONT %d" % self.javaserver.proc.pid)
        self._halt()

    def _halt(self):
        self.javaserver.stop(self.halt_message, restart_the_server=False)
        self.halt.halt = True

    def shutdown(self):
        self._halt()

    def write_stdout(self, message="", source="print"):
        """
        :param message: desired output line.  Default is wrapper.
        :param source: "server", "wrapper", "print" or "log".  Default is
         print.

        """
        cursor = self.cursor

        if self.use_readline:
            print(message)
            return

        def _wrapper(msg):
            """_wrapper is normally displaying a live typing buffer.
            Therefore, there is no cr/lf at end because it is 
            constantly being re-printed in the same spot as the
            user types."""
            if msg != "":
                # re-print what the console user was typing right below that.
                # /wrapper commands receive special magenta coloring
                if msg[0:1] == '/':
                    print("{0}{1}{2}{3}{4}{5}".format(UP_LINE, cursor,
                                                      FG_YELLOW, msg, RESET,
                                                      CLEAR_EOL))
                else:
                    print("{0}{1}{2}{3}".format(BACKSPACE, cursor, msg,
                                                CLEAR_EOL))

        def _server(msg):
            # print server lines
            print("{0}{1}{2}\r\n".format(UP_LINE, CLEAR_LINE, msg, CLEAR_EOL))

        def _print(msg):
            _server(msg)

        parse = {
            "server": _server,
            "wrapper": _wrapper,
            "print": _print,
        }

        # if this fails due to key error, we WANT that raised, as it is
        #  a program code error, not a run-time error.
        parse[source](message)

    def getconsoleinput(self):
        """If wrapper is NOT using readline (self.use_readline == False),
        then getconsoleinput manually implements our own character 
        reading, parsing, arrow keys, command history, etc.  This 
        is desireable because it allows the user to continue to see
        their input and modify it, even if the server is producing
        console line messages that would normally "carry away" the 
        user's typing.

        Implemented in response to issue 326:
        'Command being typed gets carried off by console every time
         server generates output #326' by @Darkness3840:
        https://github.com/benbaptist/minecraft-wrapper/issues/326
        """
        if self.use_readline:
            # Obtain a line of console input
            try:
                consoleinput = sys.stdin.readline().strip()
            except Exception as e:
                self.log.error(
                    "[continue] variable 'consoleinput' in 'console()' did"
                    " not evaluate \n%s" % e)
                consoleinput = ""

        else:
            arrow_index = 0
            # working buffer allows arrow use to restore what they
            # were typing but did not enter as a command yet
            working_buff = ''
            while not self.halt.halt:
                keypress = readkey.getcharacter()
                keycode = readkey.convertchar(keypress)
                length = len(self.input_buff)

                if keycode == "right":
                    arrow_index += 1
                    if arrow_index > length:
                        arrow_index = length

                if keycode == "left":
                    arrow_index -= 1
                    if arrow_index < 1:
                        arrow_index = 0

                if keycode == "up":
                    # goes 'back' in command history time
                    self.command_index -= 1
                    if self.command_index < 1:
                        self.command_index = 0
                    self.input_buff = self.command_hist[self.command_index]
                    arrow_index = len(self.input_buff)

                if keycode == "down":
                    # goes forward in command history time
                    self.command_index += 1

                    if self.command_index + 1 > len(self.command_hist):
                        # These actions happen when at most recent typing
                        self.command_index = len(self.command_hist)
                        self.input_buff = '%s' % working_buff
                        self.write_stdout("%s " % self.input_buff,
                                          source="wrapper")
                        arrow_index = len(self.input_buff)
                        continue

                    self.input_buff = self.command_hist[self.command_index]
                    arrow_index = len(self.input_buff)

                buff_left = "%s" % self.input_buff[:arrow_index]
                buff_right = "%s" % self.input_buff[arrow_index:]

                if keycode == "backspace":
                    if len(buff_left) > 0:
                        buff_left = buff_left[:-1]
                        self.input_buff = "%s%s" % (buff_left, buff_right)
                        working_buff = "%s" % self.input_buff
                        arrow_index -= 1

                if keycode == "delete":
                    if len(buff_right) > 0:
                        buff_right = buff_right[1:]
                    self.input_buff = "%s%s" % (buff_left, buff_right)
                    working_buff = "%s" % self.input_buff

                if keycode in ("enter", "cr", "lf"):
                    # scroll up (because cr is not added to buffer)
                    # print("")
                    break

                if keycode in ("ctrl-c", "ctrl-x"):
                    self.sigterm()
                    break

                # hide special key codes like PAGE_UP, etc if not used
                if not keycode:
                    buff_left = "%s%s" % (buff_left, keypress)
                    self.input_buff = "%s%s" % (buff_left, buff_right)
                    working_buff = "%s" % self.input_buff
                    arrow_index += 1

                # with open('readout.txt', "w") as f:
                #     f.write("left: '%s'\nright: '%s'\nbuff: '%s'" % (
                #         buff_left, buff_right, self.input_buff))

                if len(buff_right) > 0:
                    self.write_stdout(
                        "{0}{1}{2}{3}".format(REVERSED, buff_left, RESET,
                                              buff_right), "wrapper")
                else:
                    self.write_stdout("%s " % self.input_buff,
                                      source="wrapper")

            consoleinput = "%s" % self.input_buff
            self.input_buff = ""

            if consoleinput in self.command_hist:
                # if the command is already in the history somewhere,
                # remove it and re-append to the end (most recent)
                self.command_hist.remove(consoleinput)
                self.command_hist.append(consoleinput)
            else:
                # or just add it.
                self.command_hist.append(consoleinput)
            self.command_index = len(self.command_hist)

            # print the finished command to console
            self.write_stdout("%s\r\n" % self.input_buff, source="wrapper")

        return consoleinput

    def parseconsoleinput(self):
        while not self.halt.halt:
            consoleinput = self.getconsoleinput()
            # No command (perhaps just a line feed or spaces?)
            if len(consoleinput) < 1:
                continue

            # for use with runwrapperconsolecommand() command
            wholecommandline = consoleinput[0:].split(" ")
            command = str(getargs(wholecommandline, 0)).lower()

            # this can be passed to runwrapperconsolecommand() command for args
            allargs = wholecommandline[1:]

            # Console only commands (not accessible in-game)
            if command in ("/halt", "halt"):
                self._halt()
            elif command in ("/stop", "stop"):
                self.javaserver.stop_server_command()
            # "kill" (with no slash) is a server command.
            elif command == "/kill":
                self.javaserver.kill("Server killed at Console...")
            elif command in ("/start", "start"):
                self.javaserver.start()
            elif command in ("/restart", "restart"):
                self.javaserver.restart()
            elif command in ("/update-wrapper", "update-wrapper"):
                self._checkforupdate(True)
            # "plugins" command (with no slash) reserved for server commands
            elif command == "/plugins":
                self.listplugins()
            elif command in ("/mem", "/memory", "mem", "memory"):
                self._memory()
            elif command in ("/raw", "raw"):
                self._raw(consoleinput)
            elif command in ("/freeze", "freeze"):
                self._freeze()
            elif command in ("/unfreeze", "unfreeze"):
                self._unfreeze()
            elif command == "/version":
                readout("/version",
                        self.getbuildstring(),
                        usereadline=self.use_readline)
            elif command in ("/mute", "/pause", "/cm", "/m", "/p"):
                self._mute_console(allargs)

            # Commands that share the commands.py in-game interface

            # "reload" (with no slash) may be used by bukkit servers
            elif command == "/reload":
                self.runwrapperconsolecommand("reload", [])

            # proxy mode ban system
            elif self.proxymode and command == "/ban":
                self.runwrapperconsolecommand("ban", allargs)

            elif self.proxymode and command == "/ban-ip":
                self.runwrapperconsolecommand("ban-ip", allargs)

            elif self.proxymode and command == "/pardon-ip":
                self.runwrapperconsolecommand("pardon-ip", allargs)

            elif self.proxymode and command == "/pardon":
                self.runwrapperconsolecommand("pardon", allargs)

            elif command in ("/perm", "/perms", "/super", "/permissions",
                             "perm", "perms", "super", "permissions"):
                self.runwrapperconsolecommand("perms", allargs)

            elif command in ("/playerstats", "/stats", "playerstats", "stats"):
                self.runwrapperconsolecommand("playerstats", allargs)

            elif command in ("/ent", "/entity", "/entities", "ent", "entity",
                             "entities"):
                self.runwrapperconsolecommand("ent", allargs)

            elif command in ("/config", "/con", "/prop", "/property",
                             "/properties"):
                self.runwrapperconsolecommand("config", allargs)

            elif command in ("op", "/op"):
                self.runwrapperconsolecommand("op", allargs)

            elif command in ("deop", "/deop"):
                self.runwrapperconsolecommand("deop", allargs)

            elif command in ("pass", "/pass", "pw", "/pw", "password",
                             "/password"):
                self.runwrapperconsolecommand("password", allargs)

            # TODO Add more commands below here, below the original items:
            # TODO __________________

            # more commands here...

            # TODO __________________
            # TODO add more commands above here, above the help-related items:

            # minecraft help command
            elif command == "help":
                readout("/help",
                        "Get wrapper.py help.",
                        separator=" (with a slash) - ",
                        usereadline=self.use_readline)
                self.javaserver.console(consoleinput)

            # wrapper's help (console version)
            elif command == "/help":
                self._show_help_console()

            # wrapper ban help
            elif command == "/bans":
                self._show_help_bans()

            # Commmand not recognized by wrapper
            else:
                try:
                    self.javaserver.console(consoleinput)
                except Exception as e:
                    self.log.error("[BREAK] Console input exception"
                                   " (nothing passed to server) \n%s" % e)
                    break
                continue

    def _registerwrappershelp(self):
        # All commands listed herein are accessible in-game
        # Also require player.isOp()
        new_usage = "<player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]"
        self.api.registerHelp(
            "Wrapper",
            "Internal Wrapper.py commands ",
            [
                ("/wrapper [update/memory/halt]",
                 "If no subcommand is provided, it will"
                 " show the Wrapper version.", None),
                ("/playerstats [all]",
                 "Show the most active players. If no subcommand"
                 " is provided, it'll show the top 10 players.", None),
                ("/plugins", "Show a list of the installed plugins", None),
                ("/reload", "Reload all plugins.", None),
                ("/op %s" % new_usage, "This and deop are Wrapper commands.",
                 None),
                ("/permissions <groups/users/RESET>",
                 "Command used to manage permission groups and"
                 " users, add permission nodes, etc.", None),
                ("/entity <count/kill> [eid] [count]",
                 "/entity help/? for more help.. ", None),
                ("/config", "Change wrapper.properties (type"
                 " /config help for more..)", None),
                ("/password", "Sample usage: /pw IRC control-irc-pass <new"
                 "password>", None),

                # Minimum server version for commands to appear is
                # 1.7.6 (registers perm later in serverconnection.py)
                # These won't appear if proxy mode is not on (since
                # serverconnection is part of proxy).
                ("/ban <name> [reason..] [d:<days>/h:<hours>]",
                 "Ban a player. Specifying h:<hours> or d:<days>"
                 " creates a temp ban.", "mc1.7.6"),
                ("/ban-ip <ip> [<reason..> <d:<number of days>]",
                 "- Ban an IP address. Reason and days"
                 " (d:) are optional.", "mc1.7.6"),
                ("/pardon <player> [False]",
                 " - pardon a player. Default is byuuidonly."
                 "  To unban a specific "
                 "name (without checking uuid), use `pardon"
                 " <player> False`", "mc1.7.6"),
                ("/pardon-ip <address>", "Pardon an IP address.", "mc1.7.6"),
                ("/banlist [players|ips] [searchtext]",
                 "search and display the banlist (warning -"
                 " displays on single page!)", "mc1.7.6")
            ])

    def runwrapperconsolecommand(self, wrappercommand, argslist):
        xpayload = {
            'player': self.xplayer,
            'command': wrappercommand,
            'args': argslist
        }
        self.commands.playercommand(xpayload)

    def isonlinemode(self):
        """
        :returns: Whether the server OR (for proxy mode) wrapper
        is in online mode.  This should normally 'always' render
        True. Under rare circumstances it could be false, such
        as when this wrapper and its server are the target for
        a wrapper lobby with player.connect().
        """
        if self.proxymode:
            # if wrapper is using proxy mode (which should be set to online)
            return self.config["Proxy"]["online-mode"]
        if self.javaserver is not None:
            if self.servervitals.onlineMode:
                # if local server is online-mode
                return True
        return False

    def listplugins(self):
        readout("",
                "List of Wrapper.py plugins installed:",
                separator="",
                pad=4,
                usereadline=self.use_readline)
        for plid in self.plugins:
            plugin = self.plugins[plid]
            if plugin["good"]:
                name = plugin["name"]
                summary = plugin["summary"]
                if summary is None:
                    summary = "No description available for this plugin"

                version = plugin["version"]
                readout(name,
                        summary,
                        separator=(" - v%s - " %
                                   ".".join([str(_) for _ in version])),
                        usereadline=self.use_readline)
            else:
                readout("failed to load plugin",
                        plugin,
                        pad=25,
                        usereadline=self.use_readline)

    def _start_emailer(self):
        alerts = self.config["Alerts"]["enabled"]
        if alerts:
            self.config["Alerts"] = "alerts true"

    def _startproxy(self):

        # error will raise if requests or cryptography is missing.
        self.proxy = Proxy(self.halt, self.proxyconfig, self.servervitals,
                           self.log, self.usercache, self.events)

        # wait for server to start
        timer = 0
        while self.servervitals.state < STARTED:
            timer += 1
            time.sleep(.1)
            if timer > 1200:
                self.log.warning(
                    "Proxy mode did not detect a started server within 2"
                    " minutes.  Disabling proxy mode because something is"
                    " wrong.")
                self.disable_proxymode()
                return

        if self.proxy.proxy_port == self.servervitals.server_port:
            self.log.warning("Proxy mode cannot start because the wrapper"
                             " port is identical to the server port.")
            self.disable_proxymode()
            return

        proxythread = threading.Thread(target=self.proxy.host, args=())
        proxythread.daemon = True
        proxythread.start()

    def disable_proxymode(self):
        self.proxymode = False
        self.config["Proxy"]["proxy-enabled"] = False
        self.configManager.save()
        self.log.warning(
            "\nProxy mode is now turned off in wrapper.properties.json.\n")

    @staticmethod
    def getbuildstring():
        if core_buildinfo_version.__branch__ == "dev":
            return "%s (development build #%d)" % (
                core_buildinfo_version.__version__,
                core_buildinfo_version.__build__)

        elif core_buildinfo_version.__branch__ == "stable":
            return "%s (stable)" % core_buildinfo_version.__version__
        else:
            return "Version: %s (%s build #%d)" % (
                core_buildinfo_version.__version__,
                core_buildinfo_version.__branch__,
                core_buildinfo_version.__build__)

    def _auto_update_process(self):
        while not self.halt.halt:
            time.sleep(3600)
            if self.updated:
                self.log.info("An update for wrapper has been loaded,"
                              " Please restart wrapper.")
            else:
                self._checkforupdate()

    def _checkforupdate(self, update_now=False):
        """ checks for update """
        self.log.info("Checking for new builds...")
        update = self.get_wrapper_update_info()
        if update:
            version, build, repotype, reponame = update
            self.log.info(
                "New Wrapper.py %s build #%d is available!"
                " (current build is #%d)", repotype, build,
                core_buildinfo_version.__build__)

            if self.auto_update_wrapper or update_now:
                self.log.info("Updating...")
                self.performupdate(version, build)
            else:
                self.log.info(
                    "Because you have 'auto-update-wrapper' set to False,"
                    " you must manually update Wrapper.py. To update"
                    " Wrapper.py manually, please type /update-wrapper.")
        else:
            self.log.info("No new versions available.")

    def get_wrapper_update_info(self, repotype=None):
        """get the applicable branch wrapper update"""
        # read the installed branch info
        if repotype is None:
            repotype = core_buildinfo_version.__branch__
        if self.auto_update_branch:
            branch_key = self.auto_update_branch
        else:
            branch_key = "%s-branch" % repotype
        r = requests.get("%s/build/version.json" %
                         self.config["Updates"][branch_key])
        if r.status_code == 200:
            data = r.json()
            if data["__build__"] > core_buildinfo_version.__build__:
                if repotype == "dev":
                    reponame = "development"
                elif repotype == "stable":
                    reponame = "master"
                else:
                    reponame = data["__branch__"]
                if "__version__" not in data:
                    data["__version__"] = data["version"]
                return data["__version__"], data["__build__"], data[
                    "__branch__"], reponame

        else:
            self.log.warning(
                "Failed to check for updates - are you connected to the"
                " internet? (Status Code %d)", r.status_code)
            return False

    def performupdate(self, version, build):
        """
        Perform update; returns True if update succeeds.  User must
        still restart wrapper manually.

        :param version: first argument from get_wrapper_update_info()
        :param build: second argument from get_wrapper_update_info()
        :return: True if update succeeds
        """

        wraphash = requests.get("%s/build/Wrapper.py.md5" % self.update_url)
        wrapperfile = requests.get("%s/Wrapper.py" % self.update_url)

        if wraphash.status_code == 200 and wrapperfile.status_code == 200:
            self.log.info("Verifying Wrapper.py...")
            if hashlib.md5(wrapperfile.content).hexdigest() == wraphash.text:
                self.log.info(
                    "Update file successfully verified. Installing...")
                with open(sys.argv[0], "wb") as f:
                    f.write(wrapperfile.content)
                self.log.info(
                    "Wrapper.py %s (#%d) installed. Please reboot Wrapper.py.",
                    ".".join([str(_) for _ in version]), build)
                self.updated = True
                return True
            else:
                return False
        else:
            self.log.error(
                "Failed to update due to an internal error (%d, %d)",
                wraphash.status_code,
                wrapperfile.status_code,
                exc_info=True)
            return False

    def event_timer_second(self):
        while not self.halt.halt:
            time.sleep(1)
            self.events.callevent("timer.second", None)
            """ eventdoc
                <group> wrapper <group>

                <description> a timer that is called each second.
                <description>

                <abortable> No <abortable>

            """

    def event_timer_tick(self):
        while not self.halt.halt:
            self.events.callevent("timer.tick", None)
            time.sleep(0.05)
            """ eventdoc
                <group> wrapper <group>

                <description> a timer that is called each 1/20th
                <sp> of a second, like a minecraft tick.
                <description>

                <abortable> No <abortable>

                <comments>
                Use of this timer is not suggested and is turned off
                <sp> by default in the wrapper.config.json file
                <comments>

            """

    def _pause_console(self, pause_time):
        if not self.javaserver:
            readout("ERROR - ",
                    "There is no running server instance to mute.",
                    separator="",
                    pad=10,
                    usereadline=self.use_readline)
            return
        self.javaserver.server_muted = True
        readout("Server is now muted for %d seconds." % pause_time,
                "",
                separator="",
                command_text_fg="yellow",
                usereadline=self.use_readline)
        time.sleep(pause_time)
        readout("Server now unmuted.",
                "",
                separator="",
                usereadline=self.use_readline)
        self.javaserver.server_muted = False
        for lines in self.javaserver.queued_lines:
            readout("Q\\", "", lines, pad=3, usereadline=self.use_readline)
            time.sleep(.1)
        self.javaserver.queued_lines = []

    def _mute_console(self, all_args):
        pausetime = 30
        if len(all_args) > 0:
            pausetime = get_int(all_args[0])
        # spur off a pause thread
        cm = threading.Thread(target=self._pause_console, args=(pausetime, ))
        cm.daemon = True
        cm.start()

    def _freeze(self):
        try:
            self.javaserver.freeze()
        except OSError as ex:
            self.log.error(ex)
        except EnvironmentError as e:
            self.log.warning(e)
        except Exception as exc:
            self.log.exception(
                "Something went wrong when trying to freeze the"
                " server! (%s)", exc)

    def _memory(self):
        try:
            get_bytes = self.javaserver.getmemoryusage()
        except OSError as e:
            self.log.error(e)
        except Exception as ex:
            self.log.exception(
                "Something went wrong when trying to fetch"
                " memory usage! (%s)", ex)
        else:
            amount, units = format_bytes(get_bytes)
            self.log.info("Server Memory Usage: %s %s (%s bytes)" %
                          (amount, units, get_bytes))

    def _raw(self, console_input):
        try:
            if len(getargsafter(console_input[1:].split(" "), 1)) > 0:
                self.javaserver.console(
                    getargsafter(console_input[1:].split(" "), 1))
            else:
                self.log.info("Usage: /raw [command]")
        except EnvironmentError as e:
            self.log.warning(e)

    def _unfreeze(self):
        try:
            self.javaserver.unfreeze()
        except OSError as ex:
            self.log.error(ex)
        except EnvironmentError as e:
            self.log.warning(e)
        except Exception as exc:
            self.log.exception(
                "Something went wrong when trying to unfreeze"
                " the server! (%s)", exc)

    def _show_help_console(self):
        # This is the console help command display.
        readout("",
                "Get Minecraft help.",
                separator="help (no slash) - ",
                pad=0,
                usereadline=self.use_readline)
        readout("/reload",
                "Reload Wrapper.py plugins.",
                usereadline=self.use_readline)
        readout("/plugins",
                "Lists Wrapper.py plugins.",
                usereadline=self.use_readline)
        readout("/update-wrapper",
                "Checks for new Wrapper.py updates, and will install\n"
                "them automatically if one is available.",
                usereadline=self.use_readline)
        readout("/stop", "Stop the minecraft server without"
                " auto-restarting and without\n"
                "                  shuttingdown Wrapper.py.",
                usereadline=self.use_readline)
        readout("/start",
                "Start the minecraft server.",
                usereadline=self.use_readline)
        readout("/restart",
                "Restarts the minecraft server.",
                usereadline=self.use_readline)
        readout("/halt",
                "Shutdown Wrapper.py completely.",
                usereadline=self.use_readline)
        readout("/cm [seconds]", "Mute server output (Wrapper console"
                " logging still happens)",
                usereadline=self.use_readline)
        readout("/kill",
                "Force kill the server without saving.",
                usereadline=self.use_readline)
        readout("/freeze", "Temporarily locks the server up"
                " until /unfreeze is executed\n"
                "                  (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/unfreeze", "Unlocks a frozen state server"
                " (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/mem", "Get memory usage of the server"
                " (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/raw [command]", "Send command to the Minecraft"
                " Server. Useful for Forge\n"
                "                  commands like '/fml confirm'.",
                usereadline=self.use_readline)
        readout("/password",
                "run `/password help` for more...)",
                usereadline=self.use_readline)
        readout("/perms", "/perms for more...)", usereadline=self.use_readline)
        readout("/config", "Change wrapper.properties (type"
                " /config help for more..)",
                usereadline=self.use_readline)
        readout("/version",
                self.getbuildstring(),
                usereadline=self.use_readline)
        readout("/entity",
                "Work with entities (run /entity for more...)",
                usereadline=self.use_readline)
        readout("/bans",
                "Display the ban help page.",
                usereadline=self.use_readline)

    def _show_help_bans(self):
        # ban commands help.
        if not self.proxymode:
            readout("ERROR - ", "Wrapper proxy-mode bans are not enabled "
                    "(proxy mode is not on).",
                    separator="",
                    pad=10,
                    usereadline=self.use_readline)
            return

        readout("",
                "Bans - To use the server's versions, do not type a slash.",
                separator="",
                pad=5,
                usereadline=self.use_readline)
        readout("",
                "",
                separator="-----1.7.6 and later ban commands-----",
                pad=10,
                usereadline=self.use_readline)
        readout("/ban", " - Ban a player. Specifying h:<hours> or d:<days>"
                " creates a temp ban.",
                separator="<name> [reason..] [d:<days>/h:<hours>] ",
                pad=12,
                usereadline=self.use_readline)
        readout("/ban-ip",
                " - Ban an IP address. Reason and days (d:) are optional.",
                separator="<ip> [<reason..> <d:<number of days>] ",
                pad=12,
                usereadline=self.use_readline)
        readout("/pardon",
                " - pardon a player. Default is byuuidonly.  To unban a"
                "specific name (without checking uuid), use"
                " `pardon <player> False`",
                separator="<player> [byuuidonly(true/false)]",
                pad=12,
                usereadline=self.use_readline)
        readout("/pardon-ip",
                " - Pardon an IP address.",
                separator="<address> ",
                pad=12,
                usereadline=self.use_readline)
        readout("/banlist", " - search and display the banlist (warning -"
                " displays on single page!)",
                separator="[players|ips] [searchtext] ",
                pad=12,
                usereadline=self.use_readline)
Esempio n. 14
0
    def start(self):
        """wrapper execution starts here"""

        self.signals()

        self.backups = Backups(self)

        self.api = API(self, "Wrapper.py")
        self._registerwrappershelp()

        # This is not the actual server... the MCServer
        # class is a console wherein the server is started
        self.javaserver = MCServer(self, self.servervitals)
        self.javaserver.init()

        # load plugins
        self.plugins.loadplugins()

        if self.config["IRC"]["irc-enabled"]:  # this should be a plugin
            self.irc = IRC(self.javaserver, self.log, self)
            t = threading.Thread(target=self.irc.init, args=())
            t.daemon = True
            t.start()

        if self.config["Web"]["web-enabled"]:  # this should be a plugin
            if manageweb.pkg_resources and manageweb.requests:
                self.log.warning(
                    "Our apologies!  Web mode is currently broken.  Wrapper"
                    " will start web mode anyway, but it will not likely "
                    "function well (or at all).  For now, you should turn "
                    "off web mode in wrapper.properties.json.")
                self.web = manageweb.Web(self)
                t = threading.Thread(target=self.web.wrap, args=())
                t.daemon = True
                t.start()
            else:
                self.log.error(
                    "Web remote could not be started because you do not have"
                    " the required modules installed: pkg_resources\n"
                    "Hint: http://stackoverflow.com/questions/7446187")

        # Console Daemon runs while not wrapper.halt.halt
        consoledaemon = threading.Thread(target=self.parseconsoleinput,
                                         args=())
        consoledaemon.daemon = True
        consoledaemon.start()

        # Timer runs while not wrapper.halt.halt
        ts = threading.Thread(target=self.event_timer_second, args=())
        ts.daemon = True
        ts.start()

        if self.use_timer_tick_event:
            # Timer runs while not wrapper.halt.halt
            tt = threading.Thread(target=self.event_timer_tick, args=())
            tt.daemon = True
            tt.start()

        if self.config["General"]["shell-scripts"]:
            if os.name in ("posix", "mac"):
                self.scripts = Scripts(self)
            else:
                self.log.error(
                    "Sorry, but shell scripts only work on *NIX-based systems!"
                    " If you are using a  *NIX-based system, please file a "
                    "bug report.")

        if self.proxymode:
            t = threading.Thread(target=self._startproxy, args=())
            t.daemon = True
            t.start()

        if self.auto_update_wrapper:
            t = threading.Thread(target=self._auto_update_process, args=())
            t.daemon = True
            t.start()

        self.javaserver.handle_server()
        # handle_server always runs, even if the actual server is not started

        self.plugins.disableplugins()
        self.log.info("Plugins disabled")
        self.wrapper_storage.close()
        self.wrapper_permissions.close()
        self.wrapper_usercache.close()
        self.log.info("Wrapper Storages closed and saved.")

        # wrapper execution ends here.  handle_server ends when
        # wrapper.halt.halt is True.
        if self.sig_int:
            self.log.info("Ending threads, please wait...")
            time.sleep(5)
            os.system("reset")
Esempio n. 15
0
class IRC(object):

    def __init__(self, mcserver, log, wrapper):
        self.socket = False
        self.state = False
        self.javaserver = mcserver
        self.config = wrapper.config
        self.configmgr = wrapper.configManager
        self.wrapper = wrapper
        self.pass_handler = self.wrapper.cipher
        self.address = self.config["IRC"]["server"]
        self.port = self.config["IRC"]["port"]
        self.nickname = self.config["IRC"]["nick"]
        self.originalNickname = self.nickname[0:]
        self.nickAttempts = 0
        self.channels = self.config["IRC"]["channels"]
        self.encoding = self.config["General"]["encoding"]
        self.log = log
        self.timeout = False
        self.ready = False
        self.msgQueue = []
        self.authorized = {}

        self.api = API(self.wrapper, "IRC", internal=True)

        self.api.registerEvent("irc.message", self.onchannelmessage)
        self.api.registerEvent("irc.action", self.onchannelaction)
        self.api.registerEvent("irc.join", self.onchanneljoin)
        self.api.registerEvent("irc.part", self.onchannelpart)
        self.api.registerEvent("irc.quit", self.onchannelquit)

        self.api.registerEvent("server.starting", self.onServerStarting)
        self.api.registerEvent("server.started", self.onServerStarted)
        self.api.registerEvent("server.stopping", self.onServerStopping)
        self.api.registerEvent("server.stopped", self.onServerStopped)
        self.api.registerEvent("player.login", self.onPlayerLogin)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.action", self.onPlayerAction)
        self.api.registerEvent("player.logout", self.onPlayerLogout)
        self.api.registerEvent("player.achievement", self.onPlayerAchievement)
        self.api.registerEvent("player.death", self.onPlayerDeath)
        self.api.registerEvent("wrapper.backupBegin", self.onBackupBegin)
        self.api.registerEvent("wrapper.backupEnd", self.onBackupEnd)
        self.api.registerEvent("wrapper.backupFailure", self.onBackupFailure)
        self.api.registerEvent("server.say", self.onPlayerSay)

    def init(self):
        while not self.wrapper.haltsig.halt:
            try:
                self.log.info("Connecting to IRC...")
                self.connect()
                t = threading.Thread(target=self.queue, args=())
                t.daemon = True
                t.start()
                self.handle()
            except Exception as e:
                self.log.exception(e)
                self.disconnect("Error in Wrapper.py - restarting")
            self.log.info("Disconnected from IRC")
            time.sleep(5)

    def connect(self):
        self.nickname = self.originalNickname[0:]
        self.socket = socket.socket()
        self.socket.connect((self.address, self.port))
        self.socket.setblocking(120)

        self.auth()

    def auth(self):
        if self.config["IRC"]["password"]:
            plain_password = self.pass_handler.decrypt(self.config["IRC"]["password"])
            if plain_password:
                self.send("PASS %s" % plain_password)
            else:
                # fall back if password did not decrypt successfully
                self.send("PASS %s" % self.config["IRC"]["password"])
        self.send("NICK %s" % self.nickname)
        self.send("USER %s 0 * :%s" % (self.nickname, self.nickname))

    def disconnect(self, message):
        try:
            self.send("QUIT :%s" % message)
            self.socket.close()
            self.socket = False
        except Exception as e:
            self.log.debug("Exception in IRC disconnect: \n%s", e)

    def send(self, payload):
        pay = py_bytes("%s\n" % payload, self.encoding)
        if self.socket:
            self.socket.send(pay)
        else:
            return False

    # Event Handlers

    def messagefromchannel(self, channel, message):
        if self.config["IRC"]["show-channel-server"]:
            self.javaserver.broadcast("&6[%s] %s" % (channel, message))
        else:
            self.javaserver.broadcast(message)

    def onchanneljoin(self, payload):
        channel, nick = payload["channel"], payload["nick"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rjoined the channel" % nick)

    def onchannelpart(self, payload):
        channel, nick = payload["channel"], payload["nick"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rparted the channel" % nick)

    def onchannelmessage(self, payload):
        channel, nick, message = payload["channel"], payload["nick"], payload["message"]
        final = ""
        for i, chunk in enumerate(message.split(" ")):
            if not i == 0:
                final += " "
            try:
                if chunk[0:7] in ("http://", "https://"):
                    final += "&b&n&@%s&@&r" % chunk
                else:
                    final += chunk
            except Exception as e:
                self.log.debug("Exception in IRC onchannelmessage: \n%s", e)
                final += chunk
        self.messagefromchannel(channel, "&a<%s> &r%s" % (nick, final))

    def onchannelaction(self, payload):
        channel, nick, action = payload["channel"], payload["nick"], payload["action"]
        self.messagefromchannel(channel, "&a* %s &r%s" % (nick, action))

    def onchannelquit(self, payload):
        channel, nick, message = payload["channel"], payload["nick"], payload["message"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rquit: %s" % (nick, message))

    def onPlayerLogin(self, payload):
        player = self.filterName(payload["player"])
        self.msgQueue.append("[%s connected]" % player)

    def onPlayerLogout(self, payload):
        player = payload["player"]
        self.msgQueue.append("[%s disconnected]" % player)

    def onPlayerMessage(self, payload):
        player = self.filterName(payload["player"])
        message = payload["message"]
        self.msgQueue.append("<%s> %s" % (player, message))

    def onPlayerAction(self, payload):
        player = self.filterName(payload["player"])
        action = payload["action"]
        self.msgQueue.append("* %s %s" % (player, action))

    def onPlayerSay(self, payload):
        player = self.filterName(payload["player"])
        message = payload["message"]
        self.msgQueue.append("[%s] %s" % (player, message))

    def onPlayerAchievement(self, payload):
        player = self.filterName(payload["player"])
        achievement = payload["achievement"]
        self.msgQueue.append("%s has just earned the achievement %s" % (player, achievement))

    def onPlayerDeath(self, payload):
        player = self.filterName(payload["player"])
        death = payload["death"]
        self.msgQueue.append("%s %s" % (player, death))

    def onBackupBegin(self, payload):
        self.msgQueue.append("Backing up... lag may occur!")

    def onBackupEnd(self, payload):
        time.sleep(1)
        self.msgQueue.append("Backup complete!")

    def onBackupFailure(self, payload):
        if "reasonText" in payload:
            self.msgQueue.append("ERROR: %s" % payload["reasonText"])
        else:
            self.msgQueue.append("An unknown error occurred while trying to backup.")

    def onServerStarting(self, payload):
        self.msgQueue.append("Server starting...")

    def onServerStarted(self, payload):
        self.msgQueue.append("Server started!")

    def onServerStopping(self, payload):
        self.msgQueue.append("Server stopping...")

    def onServerStopped(self, payload):
        self.msgQueue.append("Server stopped!")

    def handle(self):
        while self.socket:
            try:
                irc_buffer = self.socket.recv(1024)
                if irc_buffer == b"":
                    self.log.error("Disconnected from IRC")
                    self.socket = False
                    self.ready = False
                    break
            except socket.timeout:
                if self.timeout:
                    self.socket = False
                    break
                else:
                    self.send("PING :%s" % str(random.randint()))
                    self.timeout = True
                irc_buffer = ""
            except Exception as e:
                self.log.debug("Exception in IRC handle: \n%s", e)
                irc_buffer = ""
            for line in irc_buffer.split(b"\n"):
                self.parse(line)

    def queue(self):
        while self.socket:
            if not self.ready:
                time.sleep(0.1)
                continue
            for i, message in enumerate(self.msgQueue):
                for channel in self.channels:
                    if len(message) > 400:
                        for l in xrange(int(math.ceil(len(message) / 400.0))):
                            chunk = message[l * 400:(l + 1) * 400]
                            self.send("PRIVMSG %s :%s" % (channel, chunk))
                    else:
                        self.send("PRIVMSG %s :%s" % (channel, message))
                del self.msgQueue[i]
            self.msgQueue = []
            time.sleep(0.1)

    def filterName(self, name):
        if self.config["IRC"]["obstruct-nicknames"]:
            return "_" + str(name)[1:]
        else:
            return name

    def rawConsole(self, payload):
        self.javaserver.console(payload)

    def console(self, channel, payload):
        if self.config["IRC"]["show-channel-server"]:
            self.rawConsole({"text": "[%s] " % channel, "color": "gold", "extra": payload})
        else:
            self.rawConsole({"extra": payload})

    def parse(self, dataline):
        _line = py_str(dataline, self.encoding)
        if getargs(_line.split(" "), 1) == "001":
            for command in self.config["IRC"]["autorun-irc-commands"]:
                self.send(command)
            for channel in self.channels:
                self.send("JOIN %s" % channel)
            self.ready = True
            self.log.info("Connected to IRC!")
            self.state = True
            self.nickAttempts = 0
        if getargs(_line.split(" "), 1) == "433":
            self.log.info("Nickname '%s' already in use.", self.nickname)
            self.nickAttempts += 1
            if self.nickAttempts > 2:
                name = bytearray(self.nickname)
                for i in xrange(3):
                    name[len(self.nickname) / 3 * i] = chr(random.randrange(97, 122))
                self.nickname = str(name)
            else:
                self.nickname += "_"
            self.auth()
            self.log.info("Attemping to use nickname '%s'.", self.nickname)
        if getargs(_line.split(" "), 1) == "JOIN":
            nick = getargs(_line.split(" "), 0)[1:getargs(_line.split(" "), 0).find("!")]
            channel = getargs(_line.split(" "), 2)[1:][:-1]
            self.log.info("%s joined %s", nick, channel)
            self.wrapper.events.callevent("irc.join", {"nick": nick, "channel": channel}, abortable=False)
        if getargs(_line.split(" "), 1) == "PART":
            nick = getargs(_line.split(" "), 0)[1:getargs(_line.split(" "), 0).find("!")]
            channel = getargs(_line.split(" "), 2)
            self.log.info("%s parted %s", nick, channel)
            self.wrapper.events.callevent("irc.part", {"nick": nick, "channel": channel}, abortable=False)
        if getargs(_line.split(" "), 1) == "MODE":
            try:
                nick = getargs(_line.split(" "), 0)[1:getargs(_line.split(" "), 0).find('!')]
                channel = getargs(_line.split(" "), 2)
                modes = getargs(_line.split(" "), 3)
                user = getargs(_line.split(" "), 4)[:-1]
                self.console(channel, [{
                    "text": user, 
                    "color": "green"
                }, {
                    "text": " received modes %s from %s" % (modes, nick), 
                    "color": "white"
                }])
            except Exception as e:
                self.log.debug("Exception in IRC in parse (MODE): \n%s", e)
                pass
        if getargs(_line.split(" "), 0) == "PING":
            self.send("PONG %s" % getargs(_line.split(" "), 1))
        if getargs(_line.split(" "), 1) == "QUIT":
            nick = getargs(_line.split(" "), 0)[1:getargs(_line.split(" "), 0).find("!")]
            message = getargsafter(_line.split(" "), 2)[1:].strip("\n").strip("\r")

            self.wrapper.events.callevent("irc.quit", {"nick": nick, "message": message, "channel": None}, abortable=False)
        if getargs(_line.split(" "), 1) == "PRIVMSG":
            channel = getargs(_line.split(" "), 2)
            nick = getargs(_line.split(" "), 0)[1:getargs(_line.split(" "), 0).find("!")]
            message = getargsafter(_line.split(" "), 3)[1:].strip("\n").strip("\r")
            if channel[0] == "#":
                if message.strip() == ".players":
                    users = ""
                    for user in self.javaserver.players:
                        users += "%s " % user
                    self.send("PRIVMSG %s :There are currently %s users on the server: %s" %
                              (channel, len(self.javaserver.players), users))
                elif message.strip() == ".about":
                    self.send("PRIVMSG %s :Wrapper.py Version %s" % (channel, self.wrapper.getbuildstring()))
                else:
                    if not PY3:
                        message = message.decode(self.encoding, "ignore")
                        # TODO - not sure if this part is going to work in PY3
                        # now that message is a properly encoded string, not a b"" sequence
                    if getargs(message.split(" "), 0) == "\x01ACTION":
                        self.wrapper.events.callevent("irc.action", {"nick": nick,
                                                                     "channel": channel,
                                                                     "action":
                                                                         getargsafter(message.split(" "), 1)[:-1]},
                                                      abortable = False
                        )
                        self.log.info("[%s] * %s %s", channel, nick, getargsafter(message.split(" "), 1)[:-1])
                    else:
                        self.wrapper.events.callevent("irc.message", {"nick": nick,
                                                                      "channel": channel,
                                                                      "message": message},
                                                      abortable=False
                                                      )
                        self.log.info("[%s] <%s> %s", channel, nick, message)
            elif self.config["IRC"]["control-from-irc"]:
                self.log.info("[PRIVATE] (%s) %s", nick, message)

                def msg(string):
                    self.log.info("[PRIVATE] (%s) %s", self.nickname, string)
                    self.send("PRIVMSG %s :%s" % (nick, string))
                if self.config["IRC"]["control-irc-pass"] == "password":
                    msg("A new password is required in wrapper.properties. Please change it.")
                if "password" in self.config["IRC"]["control-irc-pass"]:
                    msg("The password is not secure.  You must use the console to enter a password.")
                    return
                if nick in self.authorized:
                    if int(time.time()) - self.authorized[nick] < 900:
                        if getargs(message.split(" "), 0) == 'hi':
                            msg('Hey there!')
                        elif getargs(message.split(" "), 0) == 'help':
                            # eventually I need to make help only one or two
                            # lines, to prevent getting kicked/banned for spam
                            msg("run [command] - run command on server")
                            msg("togglebackups - temporarily turn backups on or off. this setting is not permanent "
                                "and will be lost on restart")
                            msg("halt - shutdown server and Wrapper.py, will not auto-restart")
                            msg("kill - force server restart without clean shutdown - only use when server "
                                "is unresponsive")
                            msg("start/restart/stop - start the server/automatically stop and start server/stop "
                                "the server without shutting down Wrapper")
                            msg("status - show status of the server")
                            msg("check-update - check for new Wrapper.py updates, but don't install them")
                            msg("update-wrapper - check and install new Wrapper.py updates")
                            msg("Wrapper.py Version %s by benbaptist" %
                                self.wrapper.getbuildstring())
                            # msg('console - toggle console output to this private message')
                        elif getargs(message.split(" "), 0) == 'togglebackups':
                            self.config["Backups"]["enabled"] = not self.config["Backups"]["enabled"]
                            if self.config["Backups"]["enabled"]:
                                msg('Backups are now on.')
                            else:
                                msg('Backups are now off.')
                            self.configmgr.save()  # 'config' is just the json dictionary of items, not the Config class
                        elif getargs(message.split(" "), 0) == 'run':
                            if getargs(message.split(" "), 1) == '':
                                msg('Usage: run [command]')
                            else:
                                command = " ".join(message.split(' ')[1:])
                                self.javaserver.console(command)
                        elif getargs(message.split(" "), 0) == 'halt':
                            msg("Halting wrapper... Bye.")
                            self.wrapper.shutdown()
                        elif getargs(message.split(" "), 0) == 'restart':
                            msg("restarting from IRC remote")
                            self.log.info("Restarting server from IRC remote")
                            self.javaserver.restart()
                        elif getargs(message.split(" "), 0) == 'stop':
                            msg("Stopping from IRC remote")
                            self.log.info("Stopped from IRC remote")
                            self.javaserver.stop_server_command()
                        elif getargs(message.split(" "), 0) == 'start':
                            self.javaserver.start()
                            msg("Server starting")
                        elif getargs(message.split(" "), 0) == 'kill':
                            self.javaserver.kill("Killing server from IRC remote")
                            msg("Server terminated.")
                        elif getargs(message.split(" "), 0) == 'status':
                            if self.javaserver.state == 2:
                                msg("Server is running.")
                            elif self.javaserver.state == 1:
                                msg("Server is currently starting/frozen.")
                            elif self.javaserver.state == 0:
                                msg("Server is stopped. Type 'start' to fire it back up.")
                            elif self.javaserver.state == 3:
                                msg("Server is in the process of shutting down/restarting.")
                            else:
                                msg("Server is in unknown state. This is probably a Wrapper.py bug - report it! "
                                    "(state #%d)" % self.javaserver.state)
                            if self.wrapper.javaserver.getmemoryusage():
                                msg("Server Memory Usage: %d bytes" % self.wrapper.javaserver.getmemoryusage())
                        elif getargs(message.split(" "), 0) in ('check-update', 'update-wrapper'):
                            msg("Checking for new updates...")
                            update = self.wrapper.get_wrapper_update_info()
                            repotype = None
                            version = None
                            if update:
                                version, repotype = update
                                build = version[4]
                                newversion = version_handler.get_version(version)
                                yourversion = version_handler.get_version(version_info.__version__)

                                msg(
                                    "New Wrapper.py Version %s (%s) is available! (you have %s)" %
                                    (newversion, repotype, yourversion)
                                )
                                msg("To perform the update, type update-wrapper.")
                            else:
                                msg("No new %s Wrapper.py versions available." % version_info.__branch__)
                            if getargs(message.split(" "), 0) == 'update-wrapper' and update:
                                msg("Performing update..")
                                if self.wrapper.performupdate(version, repotype):
                                    msg(
                                        "Update completed! Version %s (%s) is now installed. Please reboot "
                                        "Wrapper.py to apply changes." % (version, repotype)
                                    )
                                else:
                                    msg("An error occured while performing update.")
                                    msg("Please check the Wrapper.py console as soon as possible for an explanation "
                                        "and traceback.")
                                    msg("If you are unsure of the cause, please file a bug report.")

                        elif getargs(message.split(" "), 0) == "about":
                            msg("Wrapper.py by benbaptist - Version %s (%d)" % (
                                version_info.__version__, version_info.__branch__))
                        else:
                            msg('Unknown command. Type help for more commands')
                    else:
                        msg("Session expired, re-authorize.")
                        del self.authorized[nick]
                else:
                    if getargs(message.split(" "), 0) == 'auth':
                        if self.pass_handler.check_pw(getargs(message.split(" "), 1), self.config["IRC"]["control-irc-pass"]):
                            msg("Authorization success! You'll remain logged in for 15 minutes.")
                            self.authorized[nick] = int(time.time())
                        else:
                            msg("Invalid password.")
                    else:
                        msg('Not authorized. Type "auth [password]" to login.')
Esempio n. 16
0
class Web(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = API(wrapper, "Web", internal=True)
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.socket = False
        self.data = Storage("web")
        self.pass_handler = self.wrapper.cipher

        if "keys" not in self.data.Data:
            self.data.Data["keys"] = []

        self.api.registerEvent("server.consoleMessage", self.onServerConsole)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.login", self.onPlayerJoin)
        self.api.registerEvent("player.logout", self.onPlayerLeave)
        self.api.registerEvent("irc.message", self.onChannelMessage)
        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0

        # t = threading.Thread(target=self.updateGraph, args=())
        # t.daemon = True
        # t.start()

    def __del__(self):
            self.data.close()

    def onServerConsole(self, payload):
        while len(self.consoleScrollback) > 1000:
            try:
                del self.consoleScrollback[0]
            except Exception as e:
                break
        self.consoleScrollback.append((time.time(), payload["message"]))

    def onPlayerMessage(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "player", 
            "payload": {
                "player": payload["player"].username, 
                "message": payload["message"]
            }
        }))

    def onPlayerJoin(self, payload):
        # print(payload)
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "playerJoin", 
            "payload": {
                "player": payload["player"].username
            }
        }))

    def onPlayerLeave(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "playerLeave", 
            "payload": {
                "player": payload["player"]
            }
        }))

    def onChannelMessage(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {"type": "irc", "payload": payload}))

    def updateGraph(self):
        while not self.wrapper.halt.halt:
            while len(self.memoryGraph) > 200:
                del self.memoryGraph[0]
            if self.wrapper.javaserver.getmemoryusage():
                self.memoryGraph.append([time.time(), self.wrapper.javaserver.getmemoryusage()])
            time.sleep(1)

    def checkLogin(self, password):
        if time.time() - self.disableLogins < 60:
            return False  # Threshold for logins
        if self.pass_handler.check_pw(password, self.config["Web"]["web-password"]):
            return True
        self.loginAttempts += 1
        if self.loginAttempts > 10 and time.time() - self.lastAttempt < 60:
            self.disableLogins = time.time()
            self.log.warning("Disabled login attempts for one minute")
        self.lastAttempt = time.time()

    def makeKey(self, rememberme):
        a = ""
        z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_"
        for i in range(64):  # not enough performance issue to justify xrange
            a += z[random.randrange(0, len(z))]
            # a += chr(random.randrange(97, 122))
        if rememberme:
            print("Will remember!")
        self.data.Data["keys"].append([a, time.time(), rememberme])
        return a

    def validateKey(self, key):
        for i in self.data.Data["keys"]:
            expiretime = 2592000
            if len(i) > 2:
                if not i[2]:
                    expiretime = 21600
            # Validate key and ensure it's under a week old
            if i[0] == key and time.time() - i[1] < expiretime:
                self.loginAttempts = 0
                return True
        return False

    def removeKey(self, key):
        # we dont want to do things like this.  Never delete or insert while iterating over a dictionary
        #  because dictionaries change order as the hashtables are changed during insert and delete operations...
        for i, v in enumerate(self.data.Data["keys"]):
            if v[0] == key:
                del self.data.Data["keys"][i]

    def wrap(self):
        while not self.wrapper.halt.halt:
            try:
                if self.bind():
                    self.listen()
                else:
                    self.log.error("Could not bind web to %s:%d - retrying in 5 seconds",
                                   self.config["Web"]["web-bind"], self.config["Web"]["web-port"])
            except Exception as e:
                self.log.exception(e)
            time.sleep(5)

    def bind(self):
        if self.socket is not False:
            self.socket.close()
        try:
            self.socket = socket.socket()
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.config["Web"]["web-bind"], self.config["Web"]["web-port"]))
            self.socket.listen(5)
            return True
        except Exception as e:
            return False

    def listen(self):
        self.log.info("Web Interface bound to %s:%d",
                      self.config["Web"]["web-bind"], self.config["Web"]["web-port"])
        while not self.wrapper.halt.halt:
            # noinspection PyUnresolvedReferences
            sock, addr = self.socket.accept()
            # self.log.debug("(WEB) Connection %s started", str(addr))
            client = WebClient(sock, addr, self)
            t = threading.Thread(target=client.wrap, args=())
            t.daemon = True
            t.start()
Esempio n. 17
0
class MCServer(object):

    def __init__(self, wrapper):
        self.log = wrapper.log
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.serverpath = self.config["General"]["server-directory"]

        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config[
            "General"]["timed-reboot-minutes"]
        self.reboot_warning_minutes = self.config[
            "General"]["timed-reboot-warning-minutes"]

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        self.state = OFF
        self.bootTime = time.time()
        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.rebootWarnings = 0
        self.lastsizepoll = 0
        self.console_output_data = []
        self.spammy_stuff = ["found nothing", "vehicle of", "Wrong location!",
                             "Tried to add entity"]
        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.players = {}
        self.player_eids = {}
        self.worldname = None
        self.worldSize = 0
        self.maxPlayers = 20
        # -1 until proxy mode checks the server's MOTD on boot
        self.protocolVersion = -1
        # this is string name of the version, collected by console output
        self.version = None
        # a comparable number = x0y0z, where x, y, z = release,
        #  major, minor, of version.
        self.version_compute = 0
        # this port should be hidden from outside traffic.
        self.server_port = "25564"

        self.world = None
        self.entity_control = None
        self.motd = None
        # -1 until a player logs on and server sends a time update
        self.timeofday = -1
        self.onlineMode = True
        self.serverIcon = None

        # get OPs
        self.ownernames = {}
        self.operator_list = []
        self.refresh_ops()

        self.properties = {}

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.
        if self.config["General"]["timed-reboot"] or self.config[
                "Web"]["web-enabled"]:
            self.api.registerEvent("timer.second", self.eachsecond)

    def init(self):
        """ Start up the listen threads for reading server console
        output.
        """
        capturethread = threading.Thread(target=self.__stdout__, args=())
        capturethread.daemon = True
        capturethread.start()

        capturethread = threading.Thread(target=self.__stderr__, args=())
        capturethread.daemon = True
        capturethread.start()

    def __del__(self):
        self.state = 0

    def accepteula(self):

        if os.path.isfile("%s/eula.txt" % self.serverpath):
            self.log.debug("Checking EULA agreement...")
            with open("%s/eula.txt" % self.serverpath) as f:
                eula = f.read()

            # if forced, should be at info level since acceptance
            # is a legal matter.
            if "eula=false" in eula:
                self.log.warning(
                    "EULA agreement was not accepted, accepting on"
                    " your behalf...")
                set_item("eula", "true", "eula.txt", self.serverpath)

            self.log.debug("EULA agreement has been accepted.")
            return True
        else:
            return False

    def handle_server(self):
        """ Function that handles booting the server, parsing console
        output, and such.
        """
        trystart = 0
        while not self.wrapper.halt:
            trystart += 1
            self.proc = None

            # endless loop for not booting the server (while still
            # allowing handle to run).
            if not self.boot_server:
                time.sleep(0.2)
                trystart = 0
                continue

            self.changestate(STARTING)
            self.log.info("Starting server...")
            self.reloadproperties()

            # stuff I was trying to get colorized output to come through
            # for non-vanilla servers.
            command = '2>&1'
            self.args.append(command)
            command2 = self.args
            # print("args:\n%s\n" % command2)

            self.proc = subprocess.Popen(
                command2, cwd=self.serverpath, stdout=subprocess.PIPE,
                stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                universal_newlines=True)
            self.players = {}
            self.accepteula()  # Auto accept eula

            if self.proc.poll() is None and trystart > 3:
                self.log.error(
                    "Could not start server.  check your server.properties,"
                    " wrapper.properties and this your startup 'command'"
                    " from wrapper.properties:\n'%s'", " ".join(self.args))
                self.changestate(OFF)
                # halt wrapper
                self.wrapper.halt = True
                # exit server_handle
                break

            # The server loop
            while True:
                # Loop runs continously as long as server console is running
                time.sleep(0.1)
                if self.proc.poll() is not None:
                    self.changestate(OFF)
                    trystart = 0
                    self.boot_server = self.server_autorestart
                    # break back out to `while not self.wrapper.halt:` loop
                    # to (possibly) connect to server again.
                    break

                # is is only reading server console output
                for line in self.console_output_data:
                    try:
                        self.readconsole(line.replace("\r", ""))
                    except Exception as e:
                        self.log.exception(e)
                self.console_output_data = []

        # code ends here on wrapper.halt and execution returns to
        # the end of wrapper.start()

    def _toggle_server_started(self, server_started=True):
        self.wrapper.storage["ServerStarted"] = server_started
        self.wrapper.wrapper_storage.save()

    def start(self):
        """
        Start the Minecraft server
        """
        self.server_autorestart = self.config["General"]["auto-restart"]
        if self.state in (STARTED, STARTING):
            self.log.warning("The server is already running!")
            return
        if not self.boot_server:
            self.boot_server = True
        else:
            self.handle_server()

        self._toggle_server_started()

    def restart(self, reason=""):
        """Restart the Minecraft server, and kick people with the
        specified reason
        """
        if reason == "":
            reason = self.restart_message
        if self.state in (STOPPING, OFF):
            self.log.warning(
                "The server is not already running... Just use '/start'.")
            return
        self.stop(reason)

    def stop(self, reason="", restart_the_server=True):
        """Stop the Minecraft server from an automatic process.  Allow
        it to restart by default.
        """
        self.log.info("Stopping Minecraft server with reason: %s", reason)
        self.changestate(STOPPING, reason)
        for player in self.players:
            self.console("kick %s %s" % (player, reason))
        self.console("stop")

        # False will allow this loop to run with no server (and
        # reboot if permitted).
        self.boot_server = restart_the_server

    def stop_server_command(self, reason="", restart_the_server=False):
        """
        Stop the Minecraft server (as a command).  By default, do not restart.
        """
        if reason == "":
            reason = self.stop_message
        if self.state == OFF:
            self.log.warning("The server is not running... :?")
            return
        if self.state == FROZEN:
            self.log.warning("The server is currently frozen.\n"
                             "To stop it, you must /unfreeze it first")
            return
        self.server_autorestart = False
        self.stop(reason, restart_the_server)
        self._toggle_server_started(restart_the_server)

    def kill(self, reason="Killing Server"):
        """Forcefully kill the server. It will auto-restart if set
        in the configuration file.
        """
        if self.state in (STOPPING, OFF):
            self.log.warning("The server is already dead, my friend...")
            return
        self.log.info("Killing Minecraft server with reason: %s", reason)
        self.changestate(OFF, reason)
        self.proc.kill()

    def freeze(self, reason="Server is now frozen. You may disconnect."):
        """Freeze the server with `kill -STOP`. Can be used to
        stop the server in an emergency without shutting it down,
        so it doesn't write corrupted data - e.g. if the disk is
        full, you can freeze the server, free up some disk space,
        and then unfreeze 'reason' argument is printed in the
        chat for all currently-connected players, unless you
        specify None.  This command currently only works for
        *NIX based systems.
        """
        if self.state != OFF:
            if os.name == "posix":
                self.log.info("Freezing server with reason: %s", reason)
                self.broadcast("&c%s" % reason)
                time.sleep(0.5)
                self.changestate(FROZEN)
                os.system("kill -STOP %d" % self.proc.pid)
            else:
                raise UnsupportedOSException(
                    "Your current OS (%s) does not support this"
                    " command at this time." % os.name)
        else:
            raise InvalidServerStartedError(
                "Server is not started. You may run '/start' to boot it up.")

    def unfreeze(self):
        """Unfreeze the server with `kill -CONT`. Counterpart
        to .freeze(reason) This command currently only works
        for *NIX based systems.
        """
        if self.state != OFF:
            if os.name == "posix":
                self.log.info("Unfreezing server (ignore any"
                              " messages to type /start)...")
                self.broadcast("&aServer unfrozen.")
                self.changestate(STARTED)
                os.system("kill -CONT %d" % self.proc.pid)
            else:
                raise UnsupportedOSException(
                    "Your current OS (%s) does not support this command"
                    " at this time." % os.name)
        else:
            raise InvalidServerStartedError(
                "Server is not started. Please run '/start' to boot it up.")

    def broadcast(self, message, who="@a"):
        """Broadcasts the specified message to all clients
        connected. message can be a JSON chat object, or a
        string with formatting codes using the § as a prefix.
        """
        if isinstance(message, dict):
            if self.version_compute < 10700:
                self.console("say %s %s" % (who, chattocolorcodes(message)))
            else:
                encoding = self.wrapper.encoding
                self.console("tellraw %s %s" % (
                    who, json.dumps(message, ensure_ascii=False)))
        else:
            if self.version_compute < 10700:
                temp = processcolorcodes(message)
                self.console("say %s %s" % (
                    who, chattocolorcodes(json.loads(temp))))
            else:
                self.console("tellraw %s %s" % (
                    who, processcolorcodes(message)))

    def login(self, username, eid, location):
        """Called when a player logs in."""

        # place to store EID if proxy is not fully connected yet.
        self.player_eids[username] = [eid, location]
        if username not in self.players:
            self.players[username] = Player(username, self.wrapper)
        if self.wrapper.proxy:
            playerclient = self.getplayer(username).getClient()
            if playerclient:
                playerclient.server_connection.eid = eid
                playerclient.position = location
        self.players[username].loginposition = self.player_eids[username][1]
        self.wrapper.events.callevent(
            "player.login",
            {"player": self.getplayer(username)})

    def logout(self, players_name):
        """Called when a player logs out."""

        # self.wrapper.callEvent(
        #    "player.logout", {"player": self.getPlayer(username)})
        self.wrapper.events.callevent(
            "player.logout", self.getplayer(players_name))
        if self.wrapper.proxy:
            self.wrapper.proxy.removestaleclients()

        # remove a hub player or not??
        if players_name in self.players:
            self.players[players_name].abort = True
            del self.players[players_name]

    def getplayer(self, username):
        """Returns a player object with the specified name, or
        False if the user is not logged in/doesn't exist.
        """
        if username in self.players:
            return self.players[username]
        return False

    def reloadproperties(self):
        # Load server icon
        if os.path.exists("%s/server-icon.png" % self.serverpath):
            with open("%s/server-icon.png" % self.serverpath, "rb") as f:
                theicon = f.read()
                iconencoded = base64.standard_b64encode(theicon)
                self.serverIcon = b"data:image/png;base64," + iconencoded

        # Read server.properties and extract some information out of it
        # the PY3.5 ConfigParser seems broken.  This way was much more
        # straightforward and works in both PY2 and PY3
        self.properties = config_to_dict_read(
            "server.properties", self.serverpath)

        if self.properties == {}:
            self.log.warning("File 'server.properties' not found.")
            return False

        if "level-name" in self.properties:
            self.worldname = self.properties["level-name"]
        else:
            self.log.warning("No 'level-name=(worldname)' was"
                             " found in the server.properties.")
            return False
        self.motd = self.properties["motd"]
        if "max-players" in self.properties:
            self.maxPlayers = self.properties["max-players"]
        else:
            self.log.warning(
                "No 'max-players=(count)' was found in the"
                " server.properties. The default of '20' will be used.")
            self.maxPlayers = 20
        self.onlineMode = self.properties["online-mode"]

    def console(self, command):
        """Execute a console command on the server."""
        if self.state in (STARTING, STARTED, STOPPING) and self.proc:
            self.proc.stdin.write("%s\n" % command)
            self.proc.stdin.flush()
        else:
            self.log.debug("Attempted to run console command"
                           " '%s' but the Server is not started.", command)

    def changestate(self, state, reason=None):
        """Change the boot state indicator of the server, with a
        reason message.
        """
        self.state = state
        if self.state == OFF:
            self.wrapper.events.callevent(
                "server.stopped", {"reason": reason})
        elif self.state == STARTING:
            self.wrapper.events.callevent(
                "server.starting", {"reason": reason})
        elif self.state == STARTED:
            self.wrapper.events.callevent(
                "server.started", {"reason": reason})
        elif self.state == STOPPING:
            self.wrapper.events.callevent(
                "server.stopping", {"reason": reason})
        self.wrapper.events.callevent(
            "server.state", {"state": state, "reason": reason})

    def getservertype(self):
        if "spigot" in self.config["General"]["command"].lower():
            return "spigot"
        elif "bukkit" in self.config["General"]["command"].lower():
            return "bukkit"
        else:
            return "vanilla"

    def server_reload(self):
        """This is not used yet.. intended to restart a server
        without kicking players restarts the server quickly.
        Wrapper "auto-restart" must be set to True. If wrapper
        is in proxy mode, it will reconnect all clients to the
        serverconnection.
        """
        if self.state in (STOPPING, OFF):
            self.log.warning(
                "The server is not already running... Just use '/start'.")
            return
        if self.wrapper.proxymode:
            # discover who all is playing and store that knowledge

            # tell the serverconnection to stop processing play packets
            self.server_stalled = True

        # stop the server.

        # Call events to "do stuff" while server is down (write
        # whilelists, OP files, server properties, etc)

        # restart the server.

        if self.wrapper.proxymode:
            pass
            # once server is back up,  Reconnect stalled/idle
            # clients back to the serverconnection process.

            # Do I need to create a new serverconnection,
            # or can the old one be tricked into continuing??

        self.stop_server_command()

    def __stdout__(self):
        """handles server output, not lines typed in console."""
        while not self.wrapper.halt:
            # noinspection PyBroadException,PyUnusedLocal

            # this reads the line and puts the line in the
            # 'self.data' buffer for processing by
            # readconsole() (inside handle_server)
            try:
                data = self.proc.stdout.readline()
                for line in data.split("\n"):
                    if len(line) < 1:
                        continue
                    self.console_output_data.append(line)
            except Exception as e:
                time.sleep(0.1)
                continue

    def __stderr__(self):
        """like __stdout__, handles server output (not lines
        typed in console)."""
        while not self.wrapper.halt:
            try:
                data = self.proc.stderr.readline()
                if len(data) > 0:
                    for line in data.split("\n"):
                        self.console_output_data.append(line.replace("\r", ""))
            except Exception as e:
                time.sleep(0.1)
                continue

    def read_ops_file(self, read_super_ops=True):
        """Keep a list of ops in the server instance to stop
        reading the disk for it.
        :rtype: Dictionary
        """
        ops = False
        # (4 = PROTOCOL_1_7 ) - 1.7.6 or greater use ops.json
        if self.protocolVersion > 4:
            ops = getjsonfile("ops", self.serverpath, encodedas=self.encoding)
        if not ops:
            # try for an old "ops.txt" file instead.
            ops = []
            opstext = getfileaslines("ops.txt", self.serverpath)
            if not opstext:
                return False
            for op in opstext:
                # create a 'fake' ops list from the old pre-1.8
                # text line name list notice that the level (an
                # option not the old list) is set to 1 This will
                # pass as true, but if the plugin is also
                # checking op-levels, it may not pass truth.
                indivop = {"uuid": op,
                           "name": op,
                           "level": 1}
                ops.append(indivop)

        # Grant "owner" an op level above 4. required for some wrapper commands
        if read_super_ops:
            for eachop in ops:
                if eachop["name"] in self.ownernames:
                    eachop["level"] = self.ownernames[eachop["name"]]
        return ops

    def refresh_ops(self, read_super_ops=True):
        self.ownernames = config_to_dict_read("superops.txt", ".")
        if self.ownernames == {}:
            sample = "<op_player_1>=10\n<op_player_2>=9"
            with open("superops.txt", "w") as f:
                f.write(sample)
        self.operator_list = self.read_ops_file(read_super_ops)

    def getmemoryusage(self):
        """Returns allocated memory in bytes. This command
        currently only works for *NIX based systems.
        """
        if not resource or not os.name == "posix" or self.proc is None:
            raise UnsupportedOSException(
                "Your current OS (%s) does not support"
                " this command at this time." % os.name)
        try:
            with open("/proc/%d/statm" % self.proc.pid) as f:
                getbytes = int(f.read().split(" ")[1]) * resource.getpagesize()
            return getbytes
        except Exception as e:
            raise e

    @staticmethod
    def getstorageavailable(folder):
        """Returns the disk space for the working directory
        in bytes.
        """
        if platform.system() == "Windows":
            free_bytes = ctypes.c_ulonglong(0)
            ctypes.windll.kernel32.GetDiskFreeSpaceExW(
                ctypes.c_wchar_p(folder), None, None,
                ctypes.pointer(free_bytes))
            return free_bytes.value
        else:
            st = os.statvfs(folder)
            return st.f_bavail * st.f_frsize

    @staticmethod
    def stripspecial(text):
        a = ""
        it = iter(range(len(text)))
        for i in it:
            char = text[i]
            if char == "\xc2":
                try:
                    next(it)
                    next(it)
                except Exception as e:
                    pass
            else:
                a += char
        return a

    def readconsole(self, buff):
        """Internally-used function that parses a particular
        console line.
        """
        if not self.wrapper.events.callevent(
                "server.consoleMessage", {"message": buff}):
            return False

        if len(buff) < 1:
            return
        # Standardize the line to only include the text (removing
        # time and log pre-pends)
        line_words = buff.split(' ')[self.prepends_offset:]

        # find the actual offset is where server output line
        # starts (minus date/time and info stamps).
        # .. and load the proper ops file
        if "Starting minecraft server version" in buff and \
                self.prepends_offset == 0:

            for place in range(len(line_words)-1):
                self.prepends_offset = place
                if line_words[place] == "Starting":
                    break

            line_words = buff.split(' ')[self.prepends_offset:]
            self.version = getargs(line_words, 4)
            semantics = self.version.split(".")
            release = get_int(getargs(semantics, 0))
            major = get_int(getargs(semantics, 1))
            minor = get_int(getargs(semantics, 2))
            self.version_compute = minor + (major * 100) + (release * 10000)

            # 1.7.6 (protocol 5) is the cutoff where ops.txt became ops.json
            if self.version_compute > 10705 and self.protocolVersion < 0:
                self.protocolVersion = 5
            self.refresh_ops()

        if len(line_words) < 1:
            return

        # the server attempted to print a blank line
        if len(line_words[0]) < 1:
            print('')
            return

        # parse or modify the server output section
        #
        #

        # Over-ride OP help console display
        if "/op <player>" in buff:
            new_usage = "player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]"
            message = buff.replace("player>", new_usage)
            buff = message

        if "While this makes the game possible to play" in buff:
            prefix = " ".join(buff.split(' ')[:self.prepends_offset])

            if not self.wrapper.wrapper_onlinemode:
                message = (
                    "%s Since you are running Wrapper in OFFLINE mode, THIS "
                    "COULD BE SERIOUS!\n%s Wrapper is not handling any"
                    " authenication.\n%s This is only ok if this wrapper "
                    "is not accessible from either port %s or port %s"
                    " (I.e., this wrapper is a multiworld for a hub server, or"
                    " you are doing your own authorization via a plugin)." % (
                        prefix, prefix, prefix,
                        self.server_port, self.wrapper.proxy.proxy_port))
            else:
                message = (
                    "%s Since you are running Wrapper in proxy mode, this"
                    " should be ok because Wrapper is handling the"
                    " authenication, PROVIDED no one can access port"
                    " %s from outside your network." % (
                        prefix, self.server_port))

            if self.wrapper.proxymode:
                buff = message

        # check for server console spam before printing to wrapper console
        server_spaming = False
        for things in self.spammy_stuff:
            if things in buff:
                server_spaming = True

        # server_spaming setting does not stop it from being parsed below.
        if not server_spaming:
            if not self.server_muted:
                self.wrapper.write_stdout(buff, "server")
            else:
                self.queued_lines.append(buff)

        # region server console parsing section

        # read port of server
        if "Starting Minecraft server" in buff:
            self.server_port = get_int(buff.split('on *:')[1])

        # confirm server start
        elif "Done (" in buff:
            self._toggle_server_started()
            self.changestate(STARTED)
            self.log.info("Server started")
            self.bootTime = time.time()

        # Getting world name
        elif "Preparing level" in buff:
            self.worldname = getargs(line_words, 2).replace('"', "")
            self.world = World(self.worldname, self)
            if self.wrapper.proxymode:
                self.entity_control = EntityControl(self)
        # Player Message
        if getargs(line_words, 0)[0] == "<":
            name = self.stripspecial(getargs(line_words, 0)[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            self.wrapper.events.callevent("player.message", {
                "player": self.getplayer(name),
                "message": message,
                "original": original
            })

        # Player Login
        elif getargs(line_words, 1) == "logged":
            name = self.stripspecial(
                getargs(line_words, 0)[0:getargs(line_words, 0).find("[")])
            eid = get_int(getargs(line_words, 6))
            locationtext = getargs(buff.split(" ("), 1)[:-1].split(", ")
            locationtext[0] = locationtext[0].replace("[world]", "")
            location = get_int(
                float(locationtext[0])), get_int(
                float(locationtext[1])), get_int(
                float(locationtext[2]))
            self.login(name, eid, location)

        # Player Logout
        elif "lost connection" in buff:
            name = getargs(line_words, 0)
            self.logout(name)

        # player action
        elif getargs(line_words, 0) == "*":
            name = self.stripspecial(getargs(line_words, 1))
            message = self.stripspecial(getargsafter(line_words, 2))
            self.wrapper.events.callevent("player.action", {
                "player": self.getplayer(name),
                "action": message
            })

        # Player Achievement
        elif "has just earned the achievement" in buff:
            name = self.stripspecial(getargs(line_words, 0))
            achievement = getargsafter(line_words, 6)
            self.wrapper.events.callevent("player.achievement", {
                "player": name,
                "achievement": achievement
            })

        # /say command
        elif getargs(
                line_words, 0)[0] == "[" and getargs(line_words, 0)[-1] == "]":
            if self.getservertype != "vanilla":
                # Unfortunately, Spigot and Bukkit output things
                # that conflict with this.
                return
            name = self.stripspecial(getargs(line_words, 0)[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            self.wrapper.events.callevent("server.say", {
                "player": name,
                "message": message,
                "original": original
            })

        # Player Death
        elif getargs(line_words, 1) in self.deathprefixes:
            name = self.stripspecial(getargs(line_words, 0))
            self.wrapper.events.callevent("player.death", {
                "player": self.getplayer(name),
                "death": getargsafter(line_words, 1)
            })

        # server lagged
        elif "Can't keep up!" in buff:
            skipping_ticks = getargs(line_words, 17)
            self.wrapper.events.callevent("server.lagged", {
                "ticks": get_int(skipping_ticks)
            })

    # mcserver.py onsecond Event Handler
    def eachsecond(self, payload):
        if self.config["General"]["timed-reboot"]:
            if time.time() - self.bootTime > (self.reboot_minutes * 60):
                if self.config["General"]["timed-reboot-warning-minutes"] > 0:
                    if self.rebootWarnings <= self.reboot_warning_minutes:
                        l = (time.time()
                             - self.bootTime
                             - self.reboot_minutes * 60)
                        if l > self.rebootWarnings:
                            self.rebootWarnings += 1
                            if int(self.reboot_warning_minutes - l + 1) > 0:
                                self.broadcast(
                                    "&cServer will reboot in %d minute(s)!" %
                                    int(self.reboot_warning_minutes - l + 1))
                        return
                self.restart(self.reboot_message)
                self.bootTime = time.time()
                self.rebootWarnings = 0

        # only used by web management module
        if self.config["Web"]["web-enabled"]:
            if time.time() - self.lastsizepoll > 120:
                if self.worldname is None:
                    return True
                self.lastsizepoll = time.time()
                size = 0
                # os.scandir not in standard library on early py2.7.x systems
                for i in os.walk("%s/%s" % (self.serverpath, self.worldname)):
                    for f in os.listdir(i[0]):
                        size += os.path.getsize(os.path.join(i[0], f))
                self.worldSize = size
Esempio n. 18
0
    def __init__(self, wrapper):
        self.log = wrapper.log
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.state = OFF
        self.properties = {}
        self.worldname = None
        self.worldsize = 0
        # owner/op info
        self.ownernames = {}
        self.operator_list = []
        self.spammy_stuff = ["found nothing", "vehicle of", "Wrong location!",
                             "Tried to add entity", ]
        # this is string name of the version, collected by console output
        self.version = ""
        self.version_compute = 0
        self.servericon = None
        self.motd = None
        self.timeofday = -1
        self.protocolVersion = -1
        self.server_port = "25564"
        
        self.encoding = self.config["General"]["encoding"]
        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config["General"]["timed-reboot-minutes"]
        self.reboot_warn_minutes = self.config["General"]["timed-reboot-warning-minutes"]  # noqa

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.lastsizepoll = 0
        self.console_output_data = []

        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot",
                              "slain"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.world = None

        # get OPs
        self.refresh_ops()

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.

        if self.config["General"]["timed-reboot"]:
            rb = threading.Thread(target=self.reboot_timer, args=())
            rb.daemon = True
            rb.start()

        if self.config["Web"]["web-enabled"]:
            wb = threading.Thread(target=self.eachsecond_web, args=())
            wb.daemon = True
            wb.start()

        # This event is used to allow proxy to make console commands via
        # callevent() without referencing mcserver.py code (the eventhandler
        # is passed as an argument to the proxy).
        self.api.registerEvent("proxy.console", self._console_event)
Esempio n. 19
0
class IRC(object):

    def __init__(self, mcserver, log, wrapper):
        self.socket = False
        self.state = False
        self.javaserver = mcserver
        self.config = wrapper.config
        self.configmgr = wrapper.configManager
        self.wrapper = wrapper
        self.address = self.config["IRC"]["server"]
        self.port = self.config["IRC"]["port"]
        self.nickname = self.config["IRC"]["nick"]
        self.originalNickname = self.nickname[0:]
        self.nickAttempts = 0
        self.channels = self.config["IRC"]["channels"]
        self.log = log
        self.timeout = False
        self.ready = False
        self.msgQueue = []
        self.authorized = {}
        self.line = ""

        self.api = API(self.wrapper, "IRC", internal=True)

        self.api.registerEvent("irc.message", self.onchannelmessage)
        self.api.registerEvent("irc.action", self.onchannelaction)
        self.api.registerEvent("irc.join", self.onchanneljoin)
        self.api.registerEvent("irc.part", self.onchannelpart)
        self.api.registerEvent("irc.quit", self.onchannelquit)

        self.api.registerEvent("server.starting", self.onServerStarting)
        self.api.registerEvent("server.started", self.onServerStarted)
        self.api.registerEvent("server.stopping", self.onServerStopping)
        self.api.registerEvent("server.stopped", self.onServerStopped)
        self.api.registerEvent("player.login", self.onPlayerLogin)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.action", self.onPlayerAction)
        self.api.registerEvent("player.logout", self.onPlayerLogout)
        self.api.registerEvent("player.achievement", self.onPlayerAchievement)
        self.api.registerEvent("player.death", self.onPlayerDeath)
        self.api.registerEvent("wrapper.backupBegin", self.onBackupBegin)
        self.api.registerEvent("wrapper.backupEnd", self.onBackupEnd)
        self.api.registerEvent("wrapper.backupFailure", self.onBackupFailure)
        self.api.registerEvent("server.say", self.onPlayerSay)

    def init(self):
        while not self.wrapper.halt:
            try:
                self.log.info("Connecting to IRC...")
                self.connect()
                t = threading.Thread(target=self.queue, args=())
                t.daemon = True
                t.start()
                self.handle()
            except Exception as e:
                self.log.exception(e)
                self.disconnect("Error in Wrapper.py - restarting")
            self.log.info("Disconnected from IRC")
            time.sleep(5)

    def connect(self):
        self.nickname = self.originalNickname[0:]
        self.socket = socket.socket()
        self.socket.connect((self.address, self.port))
        self.socket.setblocking(120)

        self.auth()

    def auth(self):
        if self.config["IRC"]["password"]:
            self.send("PASS %s" % self.config["IRC"]["password"])
        self.send("NICK %s" % self.nickname)
        self.send("USER %s 0 * :%s" % (self.nickname, self.nickname))

    def disconnect(self, message):
        try:
            self.send("QUIT :%s" % message)
            self.socket.close()
            self.socket = False
        except Exception as e:
            self.log.debug("Exception in IRC disconnect: \n%s", e)

    def send(self, payload):
        if self.socket:
            self.socket.send("%s\n" % payload)
        else:
            return False

    # Event Handlers

    def messagefromchannel(self, channel, message):
        if self.config["IRC"]["show-channel-server"]:
            self.javaserver.broadcast("&6[%s] %s" % (channel, message))
        else:
            self.javaserver.broadcast(message)

    def onchanneljoin(self, payload):
        channel, nick = payload["channel"], payload["nick"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rjoined the channel" % nick)

    def onchannelpart(self, payload):
        channel, nick = payload["channel"], payload["nick"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rparted the channel" % nick)

    def onchannelmessage(self, payload):
        channel, nick, message = payload["channel"], payload["nick"], payload["message"]
        final = ""
        for i, chunk in enumerate(message.split(" ")):
            if not i == 0:
                final += " "
            try:
                if chunk[0:7] in ("http://", "https://"):
                    final += "&b&n&@%s&@&r" % chunk
                else:
                    final += chunk
            except Exception as e:
                self.log.debug("Exception in IRC onchannelmessage: \n%s", e)
                final += chunk
        self.messagefromchannel(channel, "&a<%s> &r%s" % (nick, final))

    def onchannelaction(self, payload):
        channel, nick, action = payload["channel"], payload["nick"], payload["action"]
        self.messagefromchannel(channel, "&a* %s &r%s" % (nick, action))

    def onchannelquit(self, payload):
        channel, nick, message = payload["channel"], payload["nick"], payload["message"]
        if not self.config["IRC"]["show-irc-join-part"]:
            return
        self.messagefromchannel(channel, "&a%s &rquit: %s" % (nick, message))

    def onPlayerLogin(self, payload):
        player = self.filterName(payload["player"])
        self.msgQueue.append("[%s connected]" % player)

    def onPlayerLogout(self, payload):
        player = payload["player"]
        self.msgQueue.append("[%s disconnected]" % player)

    def onPlayerMessage(self, payload):
        player = self.filterName(payload["player"])
        message = payload["message"]
        self.msgQueue.append("<%s> %s" % (player, message))

    def onPlayerAction(self, payload):
        player = self.filterName(payload["player"])
        action = payload["action"]
        self.msgQueue.append("* %s %s" % (player, action))

    def onPlayerSay(self, payload):
        player = self.filterName(payload["player"])
        message = payload["message"]
        self.msgQueue.append("[%s] %s" % (player, message))

    def onPlayerAchievement(self, payload):
        player = self.filterName(payload["player"])
        achievement = payload["achievement"]
        self.msgQueue.append("%s has just earned the achievement %s" % (player, achievement))

    def onPlayerDeath(self, payload):
        player = self.filterName(payload["player"])
        death = payload["death"]
        self.msgQueue.append("%s %s" % (player, death))

    def onBackupBegin(self, payload):
        self.msgQueue.append("Backing up... lag may occur!")

    def onBackupEnd(self, payload):
        time.sleep(1)
        self.msgQueue.append("Backup complete!")

    def onBackupFailure(self, payload):
        if "reasonText" in payload:
            self.msgQueue.append("ERROR: %s" % payload["reasonText"])
        else:
            self.msgQueue.append("An unknown error occurred while trying to backup.")

    def onServerStarting(self, payload):
        self.msgQueue.append("Server starting...")

    def onServerStarted(self, payload):
        self.msgQueue.append("Server started!")

    def onServerStopping(self, payload):
        self.msgQueue.append("Server stopping...")

    def onServerStopped(self, payload):
        self.msgQueue.append("Server stopped!")

    def handle(self):
        while self.socket:
            try:
                irc_buffer = self.socket.recv(1024)  # more duck typing
                if irc_buffer == "":
                    self.log.error("Disconnected from IRC")
                    self.socket = False
                    self.ready = False
                    break
            except socket.timeout:
                if self.timeout:
                    self.socket = False
                    break
                else:
                    self.send("PING :%s" % str(random.randint()))
                    self.timeout = True
                irc_buffer = ""
            except Exception as e:
                self.log.debug("Exception in IRC handle: \n%s", e)
                irc_buffer = ""
            for line in irc_buffer.split("\n"):
                self.line = line
                self.parse()

    def queue(self):
        while self.socket:
            if not self.ready:
                time.sleep(0.1)
                continue
            for i, message in enumerate(self.msgQueue):
                for channel in self.channels:
                    if len(message) > 400:
                        for l in xrange(int(math.ceil(len(message) / 400.0))):
                            chunk = message[l * 400:(l + 1) * 400]
                            self.send("PRIVMSG %s :%s" % (channel, chunk))
                    else:
                        self.send("PRIVMSG %s :%s" % (channel, message))
                del self.msgQueue[i]
            self.msgQueue = []
            time.sleep(0.1)

    def filterName(self, name):
        if self.config["IRC"]["obstruct-nicknames"]:
            return "_" + str(name)[1:]
        else:
            return name

    def rawConsole(self, payload):
        self.javaserver.console(payload)

    def console(self, channel, payload):
        if self.config["IRC"]["show-channel-server"]:
            self.rawConsole({"text": "[%s] " % channel, "color": "gold", "extra": payload})
        else:
            self.rawConsole({"extra": payload})

    def parse(self):
        if getargs(self.line.split(" "), 1) == "001":
            for command in self.config["IRC"]["autorun-irc-commands"]:
                self.send(command)
            for channel in self.channels:
                self.send("JOIN %s" % channel)
            self.ready = True
            self.log.info("Connected to IRC!")
            self.state = True
            self.nickAttempts = 0
        if getargs(self.line.split(" "), 1) == "433":
            self.log.info("Nickname '%s' already in use.", self.nickname)
            self.nickAttempts += 1
            if self.nickAttempts > 2:
                name = bytearray(self.nickname)
                for i in xrange(3):
                    name[len(self.nickname) / 3 * i] = chr(random.randrange(97, 122))
                self.nickname = str(name)
            else:
                self.nickname += "_"
            self.auth()
            self.log.info("Attemping to use nickname '%s'.", self.nickname)
        if getargs(self.line.split(" "), 1) == "JOIN":
            nick = getargs(self.line.split(" "), 0)[1:getargs(self.line.split(" "), 0).find("!")]
            channel = getargs(self.line.split(" "), 2)[1:][:-1]
            self.log.info("%s joined %s", nick, channel)
            self.wrapper.events.callevent("irc.join", {"nick": nick, "channel": channel})
        if getargs(self.line.split(" "), 1) == "PART":
            nick = getargs(self.line.split(" "), 0)[1:getargs(self.line.split(" "), 0).find("!")]
            channel = getargs(self.line.split(" "), 2)
            self.log.info("%s parted %s", nick, channel)
            self.wrapper.events.callevent("irc.part", {"nick": nick, "channel": channel})
        if getargs(self.line.split(" "), 1) == "MODE":
            try:
                nick = getargs(self.line.split(" "), 0)[1:getargs(self.line.split(" "), 0).find('!')]
                channel = getargs(self.line.split(" "), 2)
                modes = getargs(self.line.split(" "), 3)
                user = getargs(self.line.split(" "), 4)[:-1]
                self.console(channel, [{
                    "text": user, 
                    "color": "green"
                }, {
                    "text": " received modes %s from %s" % (modes, nick), 
                    "color": "white"
                }])
            except Exception as e:
                self.log.debug("Exception in IRC in parse (MODE): \n%s", e)
                pass
        if getargs(self.line.split(" "), 0) == "PING":
            self.send("PONG %s" % getargs(self.line.split(" "), 1))
        if getargs(self.line.split(" "), 1) == "QUIT":
            nick = getargs(self.line.split(" "), 0)[1:getargs(self.line.split(" "), 0).find("!")]
            message = getargsafter(self.line.split(" "), 2)[1:].strip("\n").strip("\r")

            self.wrapper.events.callevent("irc.quit", {"nick": nick, "message": message, "channel": None})
        if getargs(self.line.split(" "), 1) == "PRIVMSG":
            channel = getargs(self.line.split(" "), 2)
            nick = getargs(self.line.split(" "), 0)[1:getargs(self.line.split(" "), 0).find("!")]
            message = getargsafter(self.line.split(" "), 3)[1:].strip("\n").strip("\r")

            if channel[0] == "#":
                if message.strip() == ".players":
                    users = ""
                    for user in self.javaserver.players:
                        users += "%s " % user
                    self.send("PRIVMSG %s :There are currently %s users on the server: %s" %
                              (channel, len(self.javaserver.players), users))
                elif message.strip() == ".about":
                    self.send("PRIVMSG %s :Wrapper.py Version %s" % (channel, self.wrapper.getbuildstring()))
                else:
                    message = message.decode("utf-8", "ignore")
                    if getargs(message.split(" "), 0) == "\x01ACTION":
                        self.wrapper.events.callevent("irc.action", {"nick": nick,
                                                                     "channel": channel,
                                                                     "action":
                                                                         getargsafter(message.split(" "), 1)[:-1]})
                        self.log.info("[%s] * %s %s", channel, nick, getargsafter(message.split(" "), 1)[:-1])
                    else:
                        self.wrapper.events.callevent("irc.message", {"nick": nick,
                                                                      "channel": channel,
                                                                      "message": message})
                        self.log.info("[%s] <%s> %s", channel, nick, message)
            elif self.config["IRC"]["control-from-irc"]:
                self.log.info("[PRIVATE] (%s) %s", nick, message)

                def msg(string):
                    self.log.info("[PRIVATE] (%s) %s", self.nickname, string)
                    self.send("PRIVMSG %s :%s" % (nick, string))
                if self.config["IRC"]["control-irc-pass"] == "password":
                    msg("A new password is required in wrapper.properties. Please change it.")
                    return
                if "password" in self.config["IRC"]["control-irc-pass"]:
                    msg("Please choose a password that doesn't contain the term 'password'.")
                    return
                if nick in self.authorized:
                    if int(time.time()) - self.authorized[nick] < 900:
                        if getargs(message.split(" "), 0) == 'hi':
                            msg('Hey there!')
                        elif getargs(message.split(" "), 0) == 'help':
                            # eventually I need to make help only one or two
                            # lines, to prevent getting kicked/banned for spam
                            msg("run [command] - run command on server")
                            msg("togglebackups - temporarily turn backups on or off. this setting is not permanent "
                                "and will be lost on restart")
                            msg("halt - shutdown server and Wrapper.py, will not auto-restart")
                            msg("kill - force server restart without clean shutdown - only use when server "
                                "is unresponsive")
                            msg("start/restart/stop - start the server/automatically stop and start server/stop "
                                "the server without shutting down Wrapper")
                            msg("status - show status of the server")
                            msg("check-update - check for new Wrapper.py updates, but don't install them")
                            msg("update-wrapper - check and install new Wrapper.py updates")
                            msg("Wrapper.py Version %s by benbaptist" %
                                self.wrapper.getbuildstring())
                            # msg('console - toggle console output to this private message')
                        elif getargs(message.split(" "), 0) == 'togglebackups':
                            self.config["Backups"]["enabled"] = not self.config["Backups"]["enabled"]
                            if self.config["Backups"]["enabled"]:
                                msg('Backups are now on.')
                            else:
                                msg('Backups are now off.')
                            self.configmgr.save()  # 'config' is just the json dictionary of items, not the Config class
                        elif getargs(message.split(" "), 0) == 'run':
                            if getargs(message.split(" "), 1) == '':
                                msg('Usage: run [command]')
                            else:
                                command = " ".join(message.split(' ')[1:])
                                self.javaserver.console(command)
                        elif getargs(message.split(" "), 0) == 'halt':
                            self.wrapper.halt = True
                            self.javaserver.console("stop")
                            self.javaserver.changestate(3)
                        elif getargs(message.split(" "), 0) == 'restart':
                            self.javaserver.restart("Restarting server from IRC remote")
                            self.javaserver.changestate(3)
                        elif getargs(message.split(" "), 0) == 'stop':
                            self.javaserver.console('stop')
                            self.javaserver.stop_server_command("Stopped from IRC remote")
                            msg("Server stopping")
                        elif getargs(message.split(" "), 0) == 'start':
                            self.javaserver.start()
                            msg("Server starting")
                        elif getargs(message.split(" "), 0) == 'kill':
                            self.javaserver.kill("Killing server from IRC remote")
                            msg("Server terminated.")
                        elif getargs(message.split(" "), 0) == 'status':
                            if self.javaserver.state == 2:
                                msg("Server is running.")
                            elif self.javaserver.state == 1:
                                msg("Server is currently starting/frozen.")
                            elif self.javaserver.state == 0:
                                msg("Server is stopped. Type 'start' to fire it back up.")
                            elif self.javaserver.state == 3:
                                msg("Server is in the process of shutting down/restarting.")
                            else:
                                msg("Server is in unknown state. This is probably a Wrapper.py bug - report it! "
                                    "(state #%d)" % self.javaserver.state)
                            if self.wrapper.javaserver.getmemoryusage():
                                msg("Server Memory Usage: %d bytes" % self.wrapper.javaserver.getmemoryusage())
                        elif getargs(message.split(" "), 0) == 'check-update':
                            msg("Checking for new updates...")
                            update = self.wrapper.get_wrapper_update_info()
                            if update:
                                version, build, repotype = update
                                if repotype == "stable":
                                    msg("New Wrapper.py Version %s available! (you have %s)" %
                                        (".".join([str(_) for _ in version]), self.wrapper.getbuildstring()))
                                elif repotype == "dev":
                                    msg("New Wrapper.py development build %s #%d available! (you have %s #%d)" % 
                                        (".".join([str(_) for _ in version]), build, version_info.__version__,
                                         version_info.__build__))
                                else:
                                    msg("Unknown new version: %s | %d | %s" % (version, build, repotype))
                                msg("To perform the update, type update-wrapper.")
                            else:
                                if version_info.__branch__ == "stable":
                                    msg("No new stable Wrapper.py versions available.")
                                elif version_info.__branch__ == "dev":
                                    msg("No new development Wrapper.py versions available.")
                        elif getargs(message.split(" "), 0) == 'update-wrapper':
                            msg("Checking for new updates...")
                            update = self.wrapper.get_wrapper_update_info()
                            if update:
                                version, build, repotype = update
                                if repotype == "stable":
                                    msg("New Wrapper.py Version %s available! (you have %s)" %
                                        (".".join([str(_) for _ in version]), self.wrapper.getbuildstring()))
                                elif repotype == "dev":
                                    msg("New Wrapper.py development build %s #%d available! (you have %s #%d)" %
                                        (".".join(version), build, version_info.__version__, version_info.__build__))
                                else:
                                    msg("Unknown new version: %s | %d | %s" % (version, build, repotype))
                                msg("Performing update..")
                                if self.wrapper.performupdate(version, build, repotype):
                                    msg("Update completed! Version %s #%d (%s) is now installed. Please reboot "
                                        "Wrapper.py to apply changes." % (version, build, repotype))
                                else:
                                    msg("An error occured while performing update.")
                                    msg("Please check the Wrapper.py console as soon as possible for an explanation "
                                        "and traceback.")
                                    msg("If you are unsure of the cause, please file a bug report on http://github.com"
                                        "/benbaptist/minecraft-wrapper.")
                            else:
                                if version_info.__branch__ == "stable":
                                    msg("No new stable Wrapper.py versions available.")
                                elif version_info.__branch__ == "dev":
                                    msg("No new development Wrapper.py versions available.")
                        elif getargs(message.split(" "), 0) == "about":
                            msg("Wrapper.py by benbaptist - Version %s (build #%d)" % (version_info.__version__,
                                                                                       version_info.__branch__))
                        else:
                            msg('Unknown command. Type help for more commands')
                    else:
                        msg("Session expired, re-authorize.")
                        del self.authorized[nick]
                else:
                    if getargs(message.split(" "), 0) == 'auth':
                        if getargs(message.split(" "), 1) == self.config["IRC"]["control-irc-pass"]:
                            msg("Authorization success! You'll remain logged in for 15 minutes.")
                            self.authorized[nick] = int(time.time())
                        else:
                            msg("Invalid password.")
                    else:
                        msg('Not authorized. Type "auth [password]" to login.')
Esempio n. 20
0
class Backups(object):

    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        self.interval = 0
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.time = time.time()
        self.backups = []
        self.enabled = self.config["Backups"]["enabled"]  # allow plugins to shutdown backups via api
        self.timerstarted = False
        if self.enabled and self.dotarchecks():  # only register event if used and tar installed!
            self.api.registerEvent("timer.second", self.eachsecond)
            self.timerstarted = True
            self.log.debug("Backups Enabled..")

    # noinspection PyUnusedLocal
    def eachsecond(self, payload):
        self.interval += 1
        if time.time() - self.time > self.backup_interval and self.enabled:
            self.dobackup()

    def pruneoldbackups(self, filename="IndependentPurge"):
        if len(self.backups) > self.config["Backups"]["backups-keep"]:
            self.log.info("Deleting old backups...")
            while len(self.backups) > self.config["Backups"]["backups-keep"]:
                backup = self.backups[0]
                if not self.wrapper.events.callevent("wrapper.backupDelete", {"file": filename}):
                    """ eventdoc
                                    <group> Backups <group>

                                    <description> Called upon deletion of a backup file.
                                    <description>

                                    <abortable> Yes, return False to abort. <abortable>

                                    <comments>
                                    
                                    <comments>
                                    <payload>
                                    "file": filename
                                    <payload>

                                """
                    break
                try:
                    os.remove('%s/%s' % (self.config["Backups"]["backup-location"], backup[1]))
                except Exception as e:
                    self.log.error("Failed to delete backup (%s)", e)
                self.log.info("Deleting old backup: %s",
                              datetime.datetime.fromtimestamp(int(backup[0])).strftime('%Y-%m-%d_%H:%M:%S'))
                # hink = self.backups[0][1][:]  # not used...
                del self.backups[0]
        putjsonfile(self.backups, "backups", self.config["Backups"]["backup-location"])

    def dotarchecks(self):
        # Check if tar is installed
        which = "where" if platform.system() == "Windows" else "which"
        if not subprocess.call([which, "tar"]) == 0:
            self.wrapper.events.callevent("wrapper.backupFailure",
                                          {"reasonCode": 1, "reasonText": "Tar is not installed. Please install "
                                                                          "tar before trying to make backups."})
            """ eventdoc
                <group> Backups <group>

                <description> Indicates failure of backup.
                <description>

                <abortable> No - informatinal only <abortable>

                <comments>
                Reasoncode and text provide more detail about specific problem.
                1 - Tar not installed.
                2 - Backup file does not exist after the tar operation.
                3 - Specified file does not exist.
                4 - backups.json is corrupted
                <comments>
                <payload>
                "reasonCode": an integer 1-4
                "reasonText": a string description of the failure.
                <payload>

            """
            self.log.error("Backups will not work, because tar does not appear to be installed!")
            self.log.error("If you are on a Linux-based system, please install it through your preferred package "
                           "manager.")
            self.log.error("If you are on Windows, you can find GNU/Tar from this link: http://goo.gl/SpJSVM")
            return False
        else:
            return True

    def dobackup(self):
        self.log.debug("Backup starting.")
        self._settime()
        self._checkforbackupfolder()
        self._getbackups()  # populate self.backups
        self._performbackup()
        self.log.debug("Backup cycle complete.")

    def _checkforbackupfolder(self):
        if not os.path.exists(self.config["Backups"]["backup-location"]):
            self.log.warning("Backup location %s does not exist -- creating target location...",
                             self.config["Backups"]["backup-location"])
            mkdir_p(self.config["Backups"]["backup-location"])

    def _doserversaving(self, desiredstate=True):
        """
        :param desiredstate: True = turn serversaving on
                             False = turn serversaving off
        :return:

        Future expansion to allow config of server saving state glabally in config.  Plan to include a glabal
        config option for periodic or continuous server disk saving of the minecraft server.
        """
        if desiredstate:
            self.api.minecraft.console("save-all flush")  # flush argument is required
            self.api.minecraft.console("save-on")
        else:
            self.api.minecraft.console("save-all flush")  # flush argument is required
            self.api.minecraft.console("save-off")
            time.sleep(0.5)

    def _performbackup(self):
        timestamp = int(time.time())

        # Turn off server saves...
        self._doserversaving(False)

        # Create tar arguments
        filename = "backup-%s.tar" % datetime.datetime.fromtimestamp(int(timestamp)).strftime("%Y-%m-%d_%H.%M.%S")
        if self.config["Backups"]["backup-compression"]:
            filename += ".gz"
            arguments = ["tar", "czf", "%s/%s" % (self.config["Backups"]["backup-location"].replace(" ", "\\ "),
                                                  filename)]
        else:
            arguments = ["tar", "cfpv", "%s/%s" % (self.config["Backups"]["backup-location"], filename)]

        # Process begin Events
        if not self.wrapper.events.callevent("wrapper.backupBegin", {"file": filename}):
            self.log.warning("A backup was scheduled, but was cancelled by a plugin!")
            """ eventdoc
                <group> Backups <group>

                <description> Indicates a backup is being initiated.
                <description>

                <abortable> Yes, return False to abort. <abortable>

                <comments>
                A console warning will be issued if a plugin cancels the backup.
                <comments>
                <payload>
                "file": Name of backup file.
                <payload>

            """
            return
        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&cBacking up... lag may occur!", irc=False)

        # Do backups
        serverpath = self.config["General"]["server-directory"]
        for backupfile in self.config["Backups"]["backup-folders"]:
            backup_file_and_path = "%s/%s" % (serverpath, backupfile)
            if os.path.exists(backup_file_and_path):
                arguments.append(backup_file_and_path)
            else:
                self.log.warning("Backup file '%s' does not exist - canceling backup", backup_file_and_path)
                self.wrapper.events.callevent("wrapper.backupFailure", {"reasonCode": 3,
                                                                        "reasonText": "Backup file '%s' does not exist."
                                                                        % backup_file_and_path})
                """ eventdoc
                                <description> internalfunction <description>

                            """
                return
        statuscode = os.system(" ".join(arguments))

        # TODO add a wrapper properties config item to set save mode of server
        # restart saves, call finish Events
        self._doserversaving()
        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&aBackup complete!", irc=False)
        self.wrapper.events.callevent("wrapper.backupEnd", {"file": filename, "status": statuscode})
        """ eventdoc
            <group> Backups <group>

            <description> Indicates a backup is complete.
            <description>

            <abortable> No - informational only <abortable>

            <comments>
            <comments>
            <payload>
            "file": Name of backup file.
            <payload>

        """
        self.backups.append((timestamp, filename))

        # Prune backups
        self.pruneoldbackups(filename)

        # Check for success
        if not os.path.exists(self.config["Backups"]["backup-location"] + "/" + filename):
            self.wrapper.events.callevent("wrapper.backupFailure",
                                          {"reasonCode": 2, "reasonText": "Backup file didn't exist after the tar "
                                                                          "command executed - assuming failure."})
            """ eventdoc
                <description> internalfunction <description>

            """

    def _getbackups(self):
        if len(self.backups) == 0 and os.path.exists(self.config["Backups"]["backup-location"] + "/backups.json"):
            loadcode = getjsonfile("backups", self.config["Backups"]["backup-location"],
                                   encodedas=self.encoding)
            if not loadcode:
                self.log.error("NOTE - backups.json was unreadable. It might be corrupted. Backups will no "
                               "longer be automatically pruned.")
                self.wrapper.events.callevent("wrapper.backupFailure", {
                    "reasonCode": 4,
                    "reasonText": "backups.json is corrupted. Please contact an administer instantly, as this "
                                  "may be critical."
                })
                """ eventdoc
                    <description> internalfunction <description>

                """
                self.backups = []
            else:
                self.backups = loadcode
        else:
            if len(os.listdir(self.config["Backups"]["backup-location"])) > 0:
                # import old backups from previous versions of Wrapper.py
                backuptimestamps = []
                for backupNames in os.listdir(self.config["Backups"]["backup-location"]):
                    # noinspection PyBroadException,PyUnusedLocal
                    try:
                        backuptimestamps.append(int(backupNames[backupNames.find('-') + 1:backupNames.find('.')]))
                    except Exception as e:
                        pass
                backuptimestamps.sort()
                for backupI in backuptimestamps:
                    self.backups.append((int(backupI), "backup-%s.tar" % str(backupI)))

    def _settime(self):
        self.time = time.time()
Esempio n. 21
0
class Wrapper(object):
    def __init__(self, secret_passphrase):
        # setup log and config
        # needs a false setting on first in case config does not
        # load (like after changes).
        self.storage = False
        self.log = logging.getLogger('Wrapper.py')
        self.configManager = Config()
        self.configManager.loadconfig()
        self.config = self.configManager.config

        # Read Config items
        # hard coded cursor for non-readline mode
        self.cursor = ">"
        # This was to allow alternate encodings
        self.encoding = self.config["General"]["encoding"]
        self.serverpath = self.config["General"]["server-directory"]
        self.proxymode = self.config["Proxy"]["proxy-enabled"]
        self.wrapper_onlinemode = self.config["Proxy"]["online-mode"]
        self.halt_message = self.config["Misc"]["halt-message"]

        # encryption items (for passwords and sensitive user data)
        # salt is generated and stored in wrapper.properties.json
        config_changes = False
        salt = self.config["General"]["salt"]
        if not salt:
            salt = gensalt(self.encoding)
            self.config["General"]["salt"] = salt
            config_changes = True
        # passphrase is provided at startup by the wrapper operator or script (not stored)
        passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding, salt)
        self.cipher = Crypt(passphrase, self.encoding)

        # Update passwords (hash any plaintext passwords)
        for groups in self.config:
            for cfg_items in self.config[groups]:
                if cfg_items[-10:] == "-plaintext":
                    # i.e., cfg_items ===> like ["web-password-plaintext"]
                    hash_item = cfg_items[:-10]
                    # hash_item ===> i.e., ["web-password"]
                    if hash_item in self.config[groups] and self.config[groups][cfg_items]:
                        # encrypt contents of (i.e.) ["web-password-plaintext"]
                        hashed_item = self.cipher.encrypt(
                            self.config[groups][cfg_items])
                        # store in "" ["Web"]["web-password"]
                        self.config[groups][hash_item] = hashed_item
                        # set plaintext item to false (successful digest)
                        self.config[groups][cfg_items] = False
                        config_changes = True

        # Patch any old update paths "..wrapper/development/build/version.json"
        # new paths are: "..wrapper/development"
        for entries in self.config["Updates"]:
            if "/build/version.json" in str(self.config["Updates"][entries]):
                oldentry = copy.copy(self.config["Updates"][entries])
                self.config["Updates"][entries] = oldentry.split("/build/version.json")[0]
                config_changes = True

        # save changes made to config file
        if config_changes:
            self.configManager.save()

        # reload branch update info.
        self.auto_update_wrapper = self.config["Updates"]["auto-update-wrapper"]
        self.auto_update_branch = self.config["Updates"]["auto-update-branch"]
        if not self.auto_update_branch:
            self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development"
        else:
            self.update_url = self.config["Updates"][self.auto_update_branch]

        self.use_timer_tick_event = self.config[
            "Gameplay"]["use-timer-tick-event"]
        self.use_readline = not(self.config["Misc"]["use-betterconsole"])

        # Storages
        self.wrapper_storage = Storage(
            "wrapper", encoding=self.encoding)
        self.wrapper_permissions = Storage(
            "permissions", encoding=self.encoding, pickle=False)
        self.wrapper_usercache = Storage(
            "usercache", encoding=self.encoding, pickle=False)

        # storage Data objects
        self.storage = self.wrapper_storage.Data
        self.usercache = self.wrapper_usercache.Data
        # self.wrapper_permissions accessed only by permissions module

        # core functions and datasets
        self.perms = Permissions(self)
        self.uuids = UUIDS(self.log, self.usercache)
        self.plugins = Plugins(self)
        self.commands = Commands(self)
        self.events = Events(self)
        self.players = {}
        self.registered_permissions = {}
        self.help = {}
        self.input_buff = ""
        self.sig_int = False
        self.command_hist = ['/help', 'help']
        self.command_index = 1

        # init items that are set up later (or opted out of/ not set up.)
        self.javaserver = None
        self.api = None
        self.irc = None
        self.scripts = None
        self.web = None
        self.proxy = None
        self.backups = None

        #  HaltSig - Why? ... because if self.halt was just `False`, passing
        #  a self.halt would simply be passing `False` (immutable).  Changing
        # the value of self.halt would not necessarily change the value of the
        # passed parameter (unless it was specifically referenced back as
        # `wrapper.halt`). Since the halt signal needs to be passed, possibly
        # several layers deep, and into modules that it may be desireable to
        # not have direct access to wrapper, using a HaltSig object is
        # more desireable and reliable in behavior.
        self.halt = HaltSig()

        self.updated = False
        # future plan to expose this to api
        self.xplayer = ConsolePlayer(self)

        # Error messages for non-standard import failures.
        if not readline and self.use_readline:
            self.log.warning(
                "'readline' not imported.  This is needed for proper"
                " console functioning. Press <Enter> to acknowledge...")
            sys.stdin.readline()

        # requests is just being used in too many places to try
        # and track its usages piece-meal.
        if not requests:
            self.log.error(
                "You must have the requests module installed to use wrapper!"
                " console functioning. Press <Enter> to Exit...")
            sys.stdin.readline()
            self._halt()

        # create server/proxy vitals and config objects
        self.servervitals = ServerVitals(self.players)

        # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS:
        # The ServerVitals class gets passed the player object list now, but
        # player objects are now housed in wrapper.  This is how we are
        # passing information between proxy and wrapper.

        self.servervitals.serverpath = self.config[
            "General"]["server-directory"]
        self.servervitals.state = OFF
        self.servervitals.command_prefix = self.config[
            "Misc"]["command-prefix"]

        self.proxyconfig = ProxyConfig()
        self.proxyconfig.proxy = self.config["Proxy"]
        self.proxyconfig.entity = self.config["Entities"]

    def __del__(self):
        """prevent error message on very first wrapper starts when
        wrapper exits after creating new wrapper.properties file.
        """
        if self.storage:
            self.wrapper_storage.close()
            self.wrapper_permissions.close()
            self.wrapper_usercache.close()

    def start(self):
        """wrapper execution starts here"""

        self.signals()

        self.backups = Backups(self)

        self.api = API(self, "Wrapper.py")
        self._registerwrappershelp()

        # This is not the actual server... the MCServer
        # class is a console wherein the server is started
        self.javaserver = MCServer(self, self.servervitals)
        self.javaserver.init()

        # load plugins
        self.plugins.loadplugins()

        if self.config["IRC"]["irc-enabled"]:  # this should be a plugin
            self.irc = IRC(self.javaserver, self.log, self)
            t = threading.Thread(target=self.irc.init, args=())
            t.daemon = True
            t.start()

        if self.config["Web"]["web-enabled"]:  # this should be a plugin
            if manageweb.pkg_resources and manageweb.requests:
                self.log.warning(
                    "Our apologies!  Web mode is currently broken.  Wrapper"
                    " will start web mode anyway, but it will not likely "
                    "function well (or at all).  For now, you should turn "
                    "off web mode in wrapper.properties.json.")
                self.web = manageweb.Web(self)
                t = threading.Thread(target=self.web.wrap, args=())
                t.daemon = True
                t.start()
            else:
                self.log.error(
                    "Web remote could not be started because you do not have"
                    " the required modules installed: pkg_resources\n"
                    "Hint: http://stackoverflow.com/questions/7446187")

        # Console Daemon runs while not wrapper.halt.halt
        consoledaemon = threading.Thread(
            target=self.parseconsoleinput, args=())
        consoledaemon.daemon = True
        consoledaemon.start()

        # Timer runs while not wrapper.halt.halt
        ts = threading.Thread(target=self.event_timer_second, args=())
        ts.daemon = True
        ts.start()

        if self.use_timer_tick_event:
            # Timer runs while not wrapper.halt.halt
            tt = threading.Thread(target=self.event_timer_tick, args=())
            tt.daemon = True
            tt.start()

        if self.config["General"]["shell-scripts"]:
            if os.name in ("posix", "mac"):
                self.scripts = Scripts(self)
            else:
                self.log.error(
                    "Sorry, but shell scripts only work on *NIX-based systems!"
                    " If you are using a  *NIX-based system, please file a "
                    "bug report.")

        if self.proxymode:
            t = threading.Thread(target=self._startproxy, args=())
            t.daemon = True
            t.start()

        if self.auto_update_wrapper:
            t = threading.Thread(target=self._auto_update_process, args=())
            t.daemon = True
            t.start()

        self.javaserver.handle_server()
        # handle_server always runs, even if the actual server is not started

        self.plugins.disableplugins()
        self.log.info("Plugins disabled")
        self.wrapper_storage.close()
        self.wrapper_permissions.close()
        self.wrapper_usercache.close()
        self.log.info("Wrapper Storages closed and saved.")

        # wrapper execution ends here.  handle_server ends when
        # wrapper.halt.halt is True.
        if self.sig_int:
            self.log.info("Ending threads, please wait...")
            time.sleep(5)
            os.system("reset")

    def signals(self):
        signal.signal(signal.SIGINT, self.sigint)
        signal.signal(signal.SIGTERM, self.sigterm)
        # noinspection PyBroadException
        try:
            # lacking in Windows
            signal.signal(signal.SIGTSTP, self.sigtstp)
        except:
            pass

    def sigint(*args):
        self = args[0]  # We are only interested in the self component
        self.log.info("Wrapper.py received SIGINT; halting...\n")
        self.sig_int = True
        self._halt()

    def sigterm(*args):
        self = args[0]  # We are only interested in the self component
        self.log.info("Wrapper.py received SIGTERM; halting...\n")
        self._halt()

    def sigtstp(*args):
        # We are only interested in the 'self' component
        self = args[0]
        self.log.info("Wrapper.py received SIGTSTP; NO sleep support!"
                      " Wrapper halting...\n")
        os.system("kill -CONT %d" % self.javaserver.proc.pid)
        self._halt()

    def _halt(self):
        self.javaserver.stop(self.halt_message, restart_the_server=False)
        self.halt.halt = True

    def shutdown(self):
        self._halt()

    def write_stdout(self, message="", source="print"):
        """
        :param message: desired output line.  Default is wrapper.
        :param source: "server", "wrapper", "print" or "log".  Default is
         print.

        """
        cursor = self.cursor

        if self.use_readline:
            print(message)
            return

        def _wrapper(msg):
            """_wrapper is normally displaying a live typing buffer.
            Therefore, there is no cr/lf at end because it is 
            constantly being re-printed in the same spot as the
            user types."""
            if msg != "":
                # re-print what the console user was typing right below that.
                # /wrapper commands receive special magenta coloring
                if msg[0:1] == '/':
                    print("{0}{1}{2}{3}{4}{5}".format(
                        UP_LINE, cursor, FG_YELLOW,
                        msg, RESET, CLEAR_EOL))
                else:
                    print("{0}{1}{2}{3}".format(
                        BACKSPACE, cursor,
                        msg, CLEAR_EOL))

        def _server(msg):
            # print server lines
            print("{0}{1}{2}\r\n".format(UP_LINE, CLEAR_LINE, msg, CLEAR_EOL))

        def _print(msg):
            _server(msg)

        parse = {
            "server": _server,
            "wrapper": _wrapper,
            "print": _print,
        }

        # if this fails due to key error, we WANT that raised, as it is
        #  a program code error, not a run-time error.
        parse[source](message)

    def getconsoleinput(self):
        """If wrapper is NOT using readline (self.use_readline == False),
        then getconsoleinput manually implements our own character 
        reading, parsing, arrow keys, command history, etc.  This 
        is desireable because it allows the user to continue to see
        their input and modify it, even if the server is producing
        console line messages that would normally "carry away" the 
        user's typing.

        Implemented in response to issue 326:
        'Command being typed gets carried off by console every time
         server generates output #326' by @Darkness3840:
        https://github.com/benbaptist/minecraft-wrapper/issues/326
        """
        if self.use_readline:
            # Obtain a line of console input
            try:
                consoleinput = sys.stdin.readline().strip()
            except Exception as e:
                self.log.error(
                    "[continue] variable 'consoleinput' in 'console()' did"
                    " not evaluate \n%s" % e)
                consoleinput = ""

        else:
            arrow_index = 0
            # working buffer allows arrow use to restore what they
            # were typing but did not enter as a command yet
            working_buff = ''
            while not self.halt.halt:
                keypress = readkey.getcharacter()
                keycode = readkey.convertchar(keypress)
                length = len(self.input_buff)

                if keycode == "right":
                    arrow_index += 1
                    if arrow_index > length:
                        arrow_index = length

                if keycode == "left":
                    arrow_index -= 1
                    if arrow_index < 1:
                        arrow_index = 0

                if keycode == "up":
                    # goes 'back' in command history time
                    self.command_index -= 1
                    if self.command_index < 1:
                        self.command_index = 0
                    self.input_buff = self.command_hist[self.command_index]
                    arrow_index = len(self.input_buff)

                if keycode == "down":
                    # goes forward in command history time
                    self.command_index += 1

                    if self.command_index + 1 > len(self.command_hist):
                        # These actions happen when at most recent typing
                        self.command_index = len(self.command_hist)
                        self.input_buff = '%s' % working_buff
                        self.write_stdout(
                            "%s " % self.input_buff, source="wrapper")
                        arrow_index = len(self.input_buff)
                        continue

                    self.input_buff = self.command_hist[self.command_index]
                    arrow_index = len(self.input_buff)

                buff_left = "%s" % self.input_buff[:arrow_index]
                buff_right = "%s" % self.input_buff[arrow_index:]

                if keycode == "backspace":
                    if len(buff_left) > 0:
                        buff_left = buff_left[:-1]
                        self.input_buff = "%s%s" % (buff_left, buff_right)
                        working_buff = "%s" % self.input_buff
                        arrow_index -= 1

                if keycode == "delete":
                    if len(buff_right) > 0:
                        buff_right = buff_right[1:]
                    self.input_buff = "%s%s" % (buff_left, buff_right)
                    working_buff = "%s" % self.input_buff

                if keycode in ("enter", "cr", "lf"):
                    # scroll up (because cr is not added to buffer)
                    # print("")
                    break

                if keycode in ("ctrl-c", "ctrl-x"):
                    self.sigterm()
                    break

                # hide special key codes like PAGE_UP, etc if not used
                if not keycode:
                    buff_left = "%s%s" % (buff_left, keypress)
                    self.input_buff = "%s%s" % (buff_left, buff_right)
                    working_buff = "%s" % self.input_buff
                    arrow_index += 1

                # with open('readout.txt', "w") as f:
                #     f.write("left: '%s'\nright: '%s'\nbuff: '%s'" % (
                #         buff_left, buff_right, self.input_buff))

                if len(buff_right) > 0:
                    self.write_stdout("{0}{1}{2}{3}".format(
                        REVERSED, buff_left, RESET, buff_right),
                        "wrapper")
                else:
                    self.write_stdout(
                        "%s " % self.input_buff, source="wrapper")

            consoleinput = "%s" % self.input_buff
            self.input_buff = ""

            if consoleinput in self.command_hist:
                # if the command is already in the history somewhere,
                # remove it and re-append to the end (most recent)
                self.command_hist.remove(consoleinput)
                self.command_hist.append(consoleinput)
            else:
                # or just add it.
                self.command_hist.append(consoleinput)
            self.command_index = len(self.command_hist)

            # print the finished command to console
            self.write_stdout(
                "%s\r\n" % self.input_buff, source="wrapper")

        return consoleinput

    def parseconsoleinput(self):
        while not self.halt.halt:
            consoleinput = self.getconsoleinput()
            # No command (perhaps just a line feed or spaces?)
            if len(consoleinput) < 1:
                continue

            # for use with runwrapperconsolecommand() command
            wholecommandline = consoleinput[0:].split(" ")
            command = str(getargs(wholecommandline, 0)).lower()

            # this can be passed to runwrapperconsolecommand() command for args
            allargs = wholecommandline[1:]

            # Console only commands (not accessible in-game)
            if command in ("/halt", "halt"):
                self._halt()
            elif command in ("/stop", "stop"):
                self.javaserver.stop_server_command()
            # "kill" (with no slash) is a server command.
            elif command == "/kill":
                self.javaserver.kill("Server killed at Console...")
            elif command in ("/start", "start"):
                self.javaserver.start()
            elif command in ("/restart", "restart"):
                self.javaserver.restart()
            elif command in ("/update-wrapper", "update-wrapper"):
                self._checkforupdate(True)
            # "plugins" command (with no slash) reserved for server commands
            elif command == "/plugins":
                self.listplugins()
            elif command in ("/mem", "/memory", "mem", "memory"):
                self._memory()
            elif command in ("/raw", "raw"):
                self._raw(consoleinput)
            elif command in ("/freeze", "freeze"):
                self._freeze()
            elif command in ("/unfreeze", "unfreeze"):
                self._unfreeze()
            elif command == "/version":
                readout("/version", self.getbuildstring(),
                        usereadline=self.use_readline)
            elif command in ("/mute", "/pause", "/cm", "/m", "/p"):
                self._mute_console(allargs)

            # Commands that share the commands.py in-game interface

            # "reload" (with no slash) may be used by bukkit servers
            elif command == "/reload":
                self.runwrapperconsolecommand("reload", [])

            # proxy mode ban system
            elif self.proxymode and command == "/ban":
                self.runwrapperconsolecommand("ban", allargs)

            elif self.proxymode and command == "/ban-ip":
                self.runwrapperconsolecommand("ban-ip", allargs)

            elif self.proxymode and command == "/pardon-ip":
                self.runwrapperconsolecommand("pardon-ip", allargs)

            elif self.proxymode and command == "/pardon":
                self.runwrapperconsolecommand("pardon", allargs)

            elif command in ("/perm", "/perms", "/super", "/permissions",
                             "perm", "perms", "super", "permissions"):
                self.runwrapperconsolecommand("perms", allargs)

            elif command in ("/playerstats", "/stats", "playerstats", "stats"):
                self.runwrapperconsolecommand("playerstats", allargs)

            elif command in ("/ent", "/entity", "/entities", "ent",
                             "entity", "entities"):
                self.runwrapperconsolecommand("ent", allargs)

            elif command in ("/config", "/con", "/prop",
                             "/property", "/properties"):
                self.runwrapperconsolecommand("config", allargs)

            elif command in ("op", "/op"):
                self.runwrapperconsolecommand("op", allargs)

            elif command in ("deop", "/deop"):
                self.runwrapperconsolecommand("deop", allargs)

            elif command in ("pass", "/pass", "pw", "/pw", "password", "/password"):
                self.runwrapperconsolecommand("password", allargs)

            # TODO Add more commands below here, below the original items:
            # TODO __________________

            # more commands here...

            # TODO __________________
            # TODO add more commands above here, above the help-related items:

            # minecraft help command
            elif command == "help":
                readout("/help", "Get wrapper.py help.",
                        separator=" (with a slash) - ",
                        usereadline=self.use_readline)
                self.javaserver.console(consoleinput)

            # wrapper's help (console version)
            elif command == "/help":
                self._show_help_console()

            # wrapper ban help
            elif command == "/bans":
                self._show_help_bans()

            # Commmand not recognized by wrapper
            else:
                try:
                    self.javaserver.console(consoleinput)
                except Exception as e:
                    self.log.error("[BREAK] Console input exception"
                                   " (nothing passed to server) \n%s" % e)
                    break
                continue

    def _registerwrappershelp(self):
        # All commands listed herein are accessible in-game
        # Also require player.isOp()
        new_usage = "<player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]"
        self.api.registerHelp(
            "Wrapper", "Internal Wrapper.py commands ",
            [
                ("/wrapper [update/memory/halt]",
                 "If no subcommand is provided, it will"
                 " show the Wrapper version.", None),
                ("/playerstats [all]",
                 "Show the most active players. If no subcommand"
                 " is provided, it'll show the top 10 players.",
                 None),
                ("/plugins",
                 "Show a list of the installed plugins", None),
                ("/reload", "Reload all plugins.", None),
                ("/op %s" % new_usage, "This and deop are Wrapper commands.",
                 None),
                ("/permissions <groups/users/RESET>",
                 "Command used to manage permission groups and"
                 " users, add permission nodes, etc.",
                 None),
                ("/entity <count/kill> [eid] [count]",
                 "/entity help/? for more help.. ", None),
                ("/config", "Change wrapper.properties (type"
                            " /config help for more..)", None),
                ("/password", "Sample usage: /pw IRC control-irc-pass <new"
                              "password>", None),

                # Minimum server version for commands to appear is
                # 1.7.6 (registers perm later in serverconnection.py)
                # These won't appear if proxy mode is not on (since
                # serverconnection is part of proxy).
                ("/ban <name> [reason..] [d:<days>/h:<hours>]",
                 "Ban a player. Specifying h:<hours> or d:<days>"
                 " creates a temp ban.", "mc1.7.6"),
                ("/ban-ip <ip> [<reason..> <d:<number of days>]",
                 "- Ban an IP address. Reason and days"
                 " (d:) are optional.", "mc1.7.6"),
                ("/pardon <player> [False]",
                 " - pardon a player. Default is byuuidonly."
                 "  To unban a specific "
                 "name (without checking uuid), use `pardon"
                 " <player> False`", "mc1.7.6"),
                ("/pardon-ip <address>", "Pardon an IP address.", "mc1.7.6"),
                ("/banlist [players|ips] [searchtext]",
                 "search and display the banlist (warning -"
                 " displays on single page!)", "mc1.7.6")
            ])

    def runwrapperconsolecommand(self, wrappercommand, argslist):
        xpayload = {
            'player': self.xplayer,
            'command': wrappercommand,
            'args': argslist
        }
        self.commands.playercommand(xpayload)

    def isonlinemode(self):
        """
        :returns: Whether the server OR (for proxy mode) wrapper
        is in online mode.  This should normally 'always' render
        True. Under rare circumstances it could be false, such
        as when this wrapper and its server are the target for
        a wrapper lobby with player.connect().
        """
        if self.proxymode:
            # if wrapper is using proxy mode (which should be set to online)
            return self.config["Proxy"]["online-mode"]
        if self.javaserver is not None:
            if self.servervitals.onlineMode:
                # if local server is online-mode
                return True
        return False

    def listplugins(self):
        readout("",
                "List of Wrapper.py plugins installed:", separator="", pad=4,
                usereadline=self.use_readline)
        for plid in self.plugins:
            plugin = self.plugins[plid]
            if plugin["good"]:
                name = plugin["name"]
                summary = plugin["summary"]
                if summary is None:
                    summary = "No description available for this plugin"

                version = plugin["version"]
                readout(name, summary,
                        separator=(
                            " - v%s - " % ".".join([str(_) for _ in version])),
                        usereadline=self.use_readline)
            else:
                readout("failed to load plugin", plugin, pad=25,
                        usereadline=self.use_readline)

    def _start_emailer(self):
        alerts = self.config["Alerts"]["enabled"]
        if alerts:
            self.config["Alerts"] = "alerts true"

    def _startproxy(self):

        # error will raise if requests or cryptography is missing.
        self.proxy = Proxy(self.halt, self.proxyconfig,
                           self.servervitals, self.log,
                           self.usercache, self.events)

        # wait for server to start
        timer = 0
        while self.servervitals.state < STARTED:
            timer += 1
            time.sleep(.1)
            if timer > 1200:
                self.log.warning(
                    "Proxy mode did not detect a started server within 2"
                    " minutes.  Disabling proxy mode because something is"
                    " wrong.")
                self.disable_proxymode()
                return

        if self.proxy.proxy_port == self.servervitals.server_port:
            self.log.warning("Proxy mode cannot start because the wrapper"
                             " port is identical to the server port.")
            self.disable_proxymode()
            return

        proxythread = threading.Thread(target=self.proxy.host, args=())
        proxythread.daemon = True
        proxythread.start()

    def disable_proxymode(self):
        self.proxymode = False
        self.config["Proxy"]["proxy-enabled"] = False
        self.configManager.save()
        self.log.warning(
            "\nProxy mode is now turned off in wrapper.properties.json.\n")

    @staticmethod
    def getbuildstring():
        if core_buildinfo_version.__branch__ == "dev":
            return "%s (development build #%d)" % (
                core_buildinfo_version.__version__,
                core_buildinfo_version.__build__)

        elif core_buildinfo_version.__branch__ == "stable":
            return "%s (stable)" % core_buildinfo_version.__version__
        else:
            return "Version: %s (%s build #%d)" % (
                core_buildinfo_version.__version__,
                core_buildinfo_version.__branch__,
                core_buildinfo_version.__build__)

    def _auto_update_process(self):
        while not self.halt.halt:
            time.sleep(3600)
            if self.updated:
                self.log.info(
                    "An update for wrapper has been loaded,"
                    " Please restart wrapper.")
            else:
                self._checkforupdate()

    def _checkforupdate(self, update_now=False):
        """ checks for update """
        self.log.info("Checking for new builds...")
        update = self.get_wrapper_update_info()
        if update:
            version, build, repotype, reponame = update
            self.log.info(
                "New Wrapper.py %s build #%d is available!"
                " (current build is #%d)",
                repotype, build, core_buildinfo_version.__build__)

            if self.auto_update_wrapper or update_now:
                self.log.info("Updating...")
                self.performupdate(version, build)
            else:
                self.log.info(
                    "Because you have 'auto-update-wrapper' set to False,"
                    " you must manually update Wrapper.py. To update"
                    " Wrapper.py manually, please type /update-wrapper.")
        else:
            self.log.info("No new versions available.")

    def get_wrapper_update_info(self, repotype=None):
        """get the applicable branch wrapper update"""
        # read the installed branch info
        if repotype is None:
            repotype = core_buildinfo_version.__branch__
        if self.auto_update_branch:
            branch_key = self.auto_update_branch
        else:
            branch_key = "%s-branch" % repotype
        r = requests.get("%s/build/version.json" % self.config["Updates"][branch_key])
        if r.status_code == 200:
            data = r.json()
            if data["__build__"] > core_buildinfo_version.__build__:
                if repotype == "dev":
                    reponame = "development"
                elif repotype == "stable":
                    reponame = "master"
                else:
                    reponame = data["__branch__"]
                if "__version__" not in data:
                    data["__version__"] = data["version"]
                return data["__version__"], data["__build__"], data["__branch__"], reponame

        else:
            self.log.warning(
                "Failed to check for updates - are you connected to the"
                " internet? (Status Code %d)", r.status_code)
            return False

    def performupdate(self, version, build):
        """
        Perform update; returns True if update succeeds.  User must
        still restart wrapper manually.

        :param version: first argument from get_wrapper_update_info()
        :param build: second argument from get_wrapper_update_info()
        :return: True if update succeeds
        """

        wraphash = requests.get("%s/build/Wrapper.py.md5" % self.update_url)
        wrapperfile = requests.get("%s/Wrapper.py" % self.update_url)

        if wraphash.status_code == 200 and wrapperfile.status_code == 200:
            self.log.info("Verifying Wrapper.py...")
            if hashlib.md5(wrapperfile.content).hexdigest() == wraphash.text:
                self.log.info(
                    "Update file successfully verified. Installing...")
                with open(sys.argv[0], "wb") as f:
                    f.write(wrapperfile.content)
                self.log.info(
                    "Wrapper.py %s (#%d) installed. Please reboot Wrapper.py.",
                    ".".join([str(_) for _ in version]), build)
                self.updated = True
                return True
            else:
                return False
        else:
            self.log.error(
                "Failed to update due to an internal error (%d, %d)",
                wraphash.status_code,
                wrapperfile.status_code, exc_info=True)
            return False

    def event_timer_second(self):
        while not self.halt.halt:
            time.sleep(1)
            self.events.callevent("timer.second", None)
            """ eventdoc
                <group> wrapper <group>

                <description> a timer that is called each second.
                <description>

                <abortable> No <abortable>

            """

    def event_timer_tick(self):
        while not self.halt.halt:
            self.events.callevent("timer.tick", None)
            time.sleep(0.05)
            """ eventdoc
                <group> wrapper <group>

                <description> a timer that is called each 1/20th
                <sp> of a second, like a minecraft tick.
                <description>

                <abortable> No <abortable>

                <comments>
                Use of this timer is not suggested and is turned off
                <sp> by default in the wrapper.config.json file
                <comments>

            """

    def _pause_console(self, pause_time):
        if not self.javaserver:
            readout("ERROR - ",
                    "There is no running server instance to mute.",
                    separator="", pad=10, usereadline=self.use_readline)
            return
        self.javaserver.server_muted = True
        readout("Server is now muted for %d seconds." % pause_time, "",
                separator="", command_text_fg="yellow",
                usereadline=self.use_readline)
        time.sleep(pause_time)
        readout("Server now unmuted.", "", separator="",
                usereadline=self.use_readline)
        self.javaserver.server_muted = False
        for lines in self.javaserver.queued_lines:
            readout("Q\\", "", lines, pad=3, usereadline=self.use_readline)
            time.sleep(.1)
        self.javaserver.queued_lines = []

    def _mute_console(self, all_args):
        pausetime = 30
        if len(all_args) > 0:
            pausetime = get_int(all_args[0])
        # spur off a pause thread
        cm = threading.Thread(target=self._pause_console, args=(pausetime,))
        cm.daemon = True
        cm.start()

    def _freeze(self):
        try:
            self.javaserver.freeze()
        except OSError as ex:
            self.log.error(ex)
        except EnvironmentError as e:
            self.log.warning(e)
        except Exception as exc:
            self.log.exception(
                "Something went wrong when trying to freeze the"
                " server! (%s)", exc)

    def _memory(self):
        try:
            get_bytes = self.javaserver.getmemoryusage()
        except OSError as e:
            self.log.error(e)
        except Exception as ex:
            self.log.exception(
                "Something went wrong when trying to fetch"
                " memory usage! (%s)", ex)
        else:
            amount, units = format_bytes(get_bytes)
            self.log.info(
                "Server Memory Usage: %s %s (%s bytes)" % (
                    amount, units, get_bytes))

    def _raw(self, console_input):
        try:
            if len(getargsafter(console_input[1:].split(" "), 1)) > 0:
                self.javaserver.console(
                    getargsafter(console_input[1:].split(" "), 1))
            else:
                self.log.info("Usage: /raw [command]")
        except EnvironmentError as e:
            self.log.warning(e)

    def _unfreeze(self):
        try:
            self.javaserver.unfreeze()
        except OSError as ex:
            self.log.error(ex)
        except EnvironmentError as e:
            self.log.warning(e)
        except Exception as exc:
            self.log.exception(
                "Something went wrong when trying to unfreeze"
                " the server! (%s)", exc)

    def _show_help_console(self):
        # This is the console help command display.
        readout("", "Get Minecraft help.",
                separator="help (no slash) - ", pad=0,
                usereadline=self.use_readline)
        readout("/reload", "Reload Wrapper.py plugins.",
                usereadline=self.use_readline)
        readout("/plugins", "Lists Wrapper.py plugins.",
                usereadline=self.use_readline)
        readout("/update-wrapper",
                "Checks for new Wrapper.py updates, and will install\n"
                "them automatically if one is available.",
                usereadline=self.use_readline)
        readout("/stop",
                "Stop the minecraft server without"
                " auto-restarting and without\n"
                "                  shuttingdown Wrapper.py.",
                usereadline=self.use_readline)
        readout("/start", "Start the minecraft server.",
                usereadline=self.use_readline)
        readout("/restart", "Restarts the minecraft server.",
                usereadline=self.use_readline)
        readout("/halt", "Shutdown Wrapper.py completely.",
                usereadline=self.use_readline)
        readout("/cm [seconds]",
                "Mute server output (Wrapper console"
                " logging still happens)",
                usereadline=self.use_readline)
        readout("/kill", "Force kill the server without saving.",
                usereadline=self.use_readline)
        readout("/freeze",
                "Temporarily locks the server up"
                " until /unfreeze is executed\n"
                "                  (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/unfreeze", "Unlocks a frozen state server"
                             " (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/mem", "Get memory usage of the server"
                        " (Only works on *NIX servers).",
                usereadline=self.use_readline)
        readout("/raw [command]",
                "Send command to the Minecraft"
                " Server. Useful for Forge\n"
                "                  commands like '/fml confirm'.",
                usereadline=self.use_readline)
        readout("/password",
                "run `/password help` for more...)",
                usereadline=self.use_readline)
        readout("/perms", "/perms for more...)",
                usereadline=self.use_readline)
        readout("/config", "Change wrapper.properties (type"
                           " /config help for more..)",
                usereadline=self.use_readline)
        readout("/version", self.getbuildstring(),
                usereadline=self.use_readline)
        readout("/entity",
                "Work with entities (run /entity for more...)",
                usereadline=self.use_readline)
        readout("/bans", "Display the ban help page.",
                usereadline=self.use_readline)

    def _show_help_bans(self):
        # ban commands help.
        if not self.proxymode:
            readout(
                "ERROR - ",
                "Wrapper proxy-mode bans are not enabled "
                "(proxy mode is not on).", separator="",
                pad=10,
                usereadline=self.use_readline)
            return

        readout(
            "",
            "Bans - To use the server's versions, do not type a slash.",
            separator="", pad=5,
            usereadline=self.use_readline)
        readout(
            "", "", separator="-----1.7.6 and later ban commands-----",
            pad=10,
            usereadline=self.use_readline)
        readout(
            "/ban",
            " - Ban a player. Specifying h:<hours> or d:<days>"
            " creates a temp ban.",
            separator="<name> [reason..] [d:<days>/h:<hours>] ",
            pad=12,
            usereadline=self.use_readline)
        readout(
            "/ban-ip",
            " - Ban an IP address. Reason and days (d:) are optional.",
            separator="<ip> [<reason..> <d:<number of days>] ", pad=12,
            usereadline=self.use_readline)
        readout(
            "/pardon",
            " - pardon a player. Default is byuuidonly.  To unban a"
            "specific name (without checking uuid), use"
            " `pardon <player> False`",
            separator="<player> [byuuidonly(true/false)]", pad=12,
            usereadline=self.use_readline)
        readout(
            "/pardon-ip", " - Pardon an IP address.",
            separator="<address> ", pad=12,
            usereadline=self.use_readline)
        readout(
            "/banlist",
            " - search and display the banlist (warning -"
            " displays on single page!)",
            separator="[players|ips] [searchtext] ", pad=12,
            usereadline=self.use_readline)
Esempio n. 22
0
    def start(self):
        """wrapper execution starts here"""

        self.signals()

        self.backups = Backups(self)

        self.api = API(self, "Wrapper.py")
        self._registerwrappershelp()

        # This is not the actual server... the MCServer
        # class is a console wherein the server is started
        self.javaserver = MCServer(self, self.servervitals)
        self.javaserver.init()

        # load plugins
        self.plugins.loadplugins()

        if self.config["IRC"]["irc-enabled"]:  # this should be a plugin
            self.irc = IRC(self.javaserver, self.log, self)
            t = threading.Thread(target=self.irc.init, args=())
            t.daemon = True
            t.start()

        if self.config["Web"]["web-enabled"]:  # this should be a plugin
            if manageweb.pkg_resources and manageweb.requests:
                self.log.warning(
                    "Our apologies!  Web mode is currently broken.  Wrapper"
                    " will start web mode anyway, but it will not likely "
                    "function well (or at all).  For now, you should turn "
                    "off web mode in wrapper.properties.json.")
                self.web = manageweb.Web(self)
                t = threading.Thread(target=self.web.wrap, args=())
                t.daemon = True
                t.start()
            else:
                self.log.error(
                    "Web remote could not be started because you do not have"
                    " the required modules installed: pkg_resources\n"
                    "Hint: http://stackoverflow.com/questions/7446187")

        # Console Daemon runs while not wrapper.halt.halt
        consoledaemon = threading.Thread(
            target=self.parseconsoleinput, args=())
        consoledaemon.daemon = True
        consoledaemon.start()

        # Timer runs while not wrapper.halt.halt
        ts = threading.Thread(target=self.event_timer_second, args=())
        ts.daemon = True
        ts.start()

        if self.use_timer_tick_event:
            # Timer runs while not wrapper.halt.halt
            tt = threading.Thread(target=self.event_timer_tick, args=())
            tt.daemon = True
            tt.start()

        if self.config["General"]["shell-scripts"]:
            if os.name in ("posix", "mac"):
                self.scripts = Scripts(self)
            else:
                self.log.error(
                    "Sorry, but shell scripts only work on *NIX-based systems!"
                    " If you are using a  *NIX-based system, please file a "
                    "bug report.")

        if self.proxymode:
            t = threading.Thread(target=self._startproxy, args=())
            t.daemon = True
            t.start()

        if self.auto_update_wrapper:
            t = threading.Thread(target=self._auto_update_process, args=())
            t.daemon = True
            t.start()

        self.javaserver.handle_server()
        # handle_server always runs, even if the actual server is not started

        self.plugins.disableplugins()
        self.log.info("Plugins disabled")
        self.wrapper_storage.close()
        self.wrapper_permissions.close()
        self.wrapper_usercache.close()
        self.log.info("Wrapper Storages closed and saved.")

        # wrapper execution ends here.  handle_server ends when
        # wrapper.halt.halt is True.
        if self.sig_int:
            self.log.info("Ending threads, please wait...")
            time.sleep(5)
            os.system("reset")
Esempio n. 23
0
class Web(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.api = API(wrapper, "Web", internal=True)
        self.log = logging.getLogger('Web')
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.socket = False
        self.data = Storage("web")
        self.pass_handler = self.wrapper.cipher

        if "keys" not in self.data.Data:
            self.data.Data["keys"] = []

        self.api.registerEvent("server.consoleMessage", self.onServerConsole)
        self.api.registerEvent("player.message", self.onPlayerMessage)
        self.api.registerEvent("player.login", self.onPlayerJoin)
        self.api.registerEvent("player.logout", self.onPlayerLeave)
        self.api.registerEvent("irc.message", self.onChannelMessage)
        self.consoleScrollback = []
        self.chatScrollback = []
        self.memoryGraph = []
        self.loginAttempts = 0
        self.lastAttempt = 0
        self.disableLogins = 0

        # t = threading.Thread(target=self.updateGraph, args=())
        # t.daemon = True
        # t.start()

    def __del__(self):
        self.data.close()

    def onServerConsole(self, payload):
        while len(self.consoleScrollback) > 1000:
            try:
                del self.consoleScrollback[0]
            except Exception as e:
                break
        self.consoleScrollback.append((time.time(), payload["message"]))

    def onPlayerMessage(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "player",
            "payload": {
                "player": payload["player"].username,
                "message": payload["message"]
            }
        }))

    def onPlayerJoin(self, payload):
        # print(payload)
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "playerJoin",
            "payload": {
                "player": payload["player"].username
            }
        }))

    def onPlayerLeave(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "playerLeave",
            "payload": {
                "player": payload["player"]
            }
        }))

    def onChannelMessage(self, payload):
        while len(self.chatScrollback) > 200:
            try:
                del self.chatScrollback[0]
            except Exception as e:
                break
        self.chatScrollback.append((time.time(), {
            "type": "irc",
            "payload": payload
        }))

    def updateGraph(self):
        while not self.wrapper.halt.halt:
            while len(self.memoryGraph) > 200:
                del self.memoryGraph[0]
            if self.wrapper.javaserver.getmemoryusage():
                self.memoryGraph.append(
                    [time.time(),
                     self.wrapper.javaserver.getmemoryusage()])
            time.sleep(1)

    def checkLogin(self, password):
        if time.time() - self.disableLogins < 60:
            return False  # Threshold for logins
        if self.pass_handler.check_pw(password,
                                      self.config["Web"]["web-password"]):
            return True
        self.loginAttempts += 1
        if self.loginAttempts > 10 and time.time() - self.lastAttempt < 60:
            self.disableLogins = time.time()
            self.log.warning("Disabled login attempts for one minute")
        self.lastAttempt = time.time()

    def makeKey(self, rememberme):
        a = ""
        z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_"
        for i in range(64):  # not enough performance issue to justify xrange
            a += z[random.randrange(0, len(z))]
            # a += chr(random.randrange(97, 122))
        if rememberme:
            print("Will remember!")
        self.data.Data["keys"].append([a, time.time(), rememberme])
        return a

    def validateKey(self, key):
        for i in self.data.Data["keys"]:
            expiretime = 2592000
            if len(i) > 2:
                if not i[2]:
                    expiretime = 21600
            # Validate key and ensure it's under a week old
            if i[0] == key and time.time() - i[1] < expiretime:
                self.loginAttempts = 0
                return True
        return False

    def removeKey(self, key):
        # we dont want to do things like this.  Never delete or insert while iterating over a dictionary
        #  because dictionaries change order as the hashtables are changed during insert and delete operations...
        for i, v in enumerate(self.data.Data["keys"]):
            if v[0] == key:
                del self.data.Data["keys"][i]

    def wrap(self):
        while not self.wrapper.halt.halt:
            try:
                if self.bind():
                    self.listen()
                else:
                    self.log.error(
                        "Could not bind web to %s:%d - retrying in 5 seconds",
                        self.config["Web"]["web-bind"],
                        self.config["Web"]["web-port"])
            except Exception as e:
                self.log.exception(e)
            time.sleep(5)

    def bind(self):
        if self.socket is not False:
            self.socket.close()
        try:
            self.socket = socket.socket()
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.config["Web"]["web-bind"],
                              self.config["Web"]["web-port"]))
            self.socket.listen(5)
            return True
        except Exception as e:
            return False

    def listen(self):
        self.log.info("Web Interface bound to %s:%d",
                      self.config["Web"]["web-bind"],
                      self.config["Web"]["web-port"])
        while not self.wrapper.halt.halt:
            # noinspection PyUnresolvedReferences
            sock, addr = self.socket.accept()
            # self.log.debug("(WEB) Connection %s started", str(addr))
            client = WebClient(sock, addr, self)
            t = threading.Thread(target=client.wrap, args=())
            t.daemon = True
            t.start()
Esempio n. 24
0
    def __init__(self, wrapper, servervitals):
        self.log = wrapper.log
        self.config = wrapper.config
        self.vitals = servervitals

        self.encoding = self.config["General"]["encoding"]
        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config["General"]["timed-reboot-minutes"]
        self.reboot_warn_minutes = self.config["General"]["timed-reboot-warning-minutes"]  # noqa

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.vitals.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.lastsizepoll = 0
        self.console_output_data = []

        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot",
                              "slain"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.world = None

        # get OPs
        self.refresh_ops()

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.

        if self.config["General"]["timed-reboot"]:
            rb = threading.Thread(target=self.reboot_timer, args=())
            rb.daemon = True
            rb.start()

        if self.config["Web"]["web-enabled"]:
            wb = threading.Thread(target=self.eachsecond_web, args=())
            wb.daemon = True
            wb.start()

        # This event is used to allow proxy to make console commands via
        # callevent() without referencing mcserver.py code (the eventhandler
        # is passed as an argument to the proxy).
        self.api.registerEvent("proxy.console", self._console_event)
Esempio n. 25
0
class Backups(object):

    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        self.interval = 0
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.time = time.time()
        self.backups = []
        self.enabled = self.config["Backups"]["enabled"]  # allow plugins to shutdown backups via api
        self.timerstarted = False
        if self.enabled and self.dotarchecks():  # only register event if used and tar installed!
            self.api.registerEvent("timer.second", self.eachsecond)
            self.timerstarted = True
            self.log.debug("Backups Enabled..")

    # noinspection PyUnusedLocal
    def eachsecond(self, payload):
        self.interval += 1
        if time.time() - self.time > self.backup_interval and self.enabled:
            self.dobackup()

    def pruneoldbackups(self, filename="IndependentPurge"):
        if len(self.backups) > self.config["Backups"]["backups-keep"]:
            self.log.info("Deleting old backups...")
            while len(self.backups) > self.config["Backups"]["backups-keep"]:
                backup = self.backups[0]
                if not self.wrapper.events.callevent("wrapper.backupDelete", {"file": filename}):
                    break
                try:
                    os.remove('%s/%s' % (self.config["Backups"]["backup-location"], backup[1]))
                except Exception as e:
                    self.log.error("Failed to delete backup (%s)", e)
                self.log.info("Deleting old backup: %s",
                              datetime.datetime.fromtimestamp(int(backup[0])).strftime('%Y-%m-%d_%H:%M:%S'))
                # hink = self.backups[0][1][:]  # not used...
                del self.backups[0]
        putjsonfile(self.backups, "backups", self.config["Backups"]["backup-location"])

    def dotarchecks(self):
        # Check if tar is installed
        which = "where" if platform.system() == "Windows" else "which"
        if not subprocess.call([which, "tar"]) == 0:
            self.wrapper.events.callevent("wrapper.backupFailure",
                                          {"reasonCode": 1, "reasonText": "Tar is not installed. Please install "
                                                                          "tar before trying to make backups."})
            self.log.error("Backups will not work, because tar does not appear to be installed!")
            self.log.error("If you are on a Linux-based system, please install it through your preferred package "
                           "manager.")
            self.log.error("If you are on Windows, you can find GNU/Tar from this link: http://goo.gl/SpJSVM")
            return False
        else:
            return True

    def dobackup(self):
        self.log.debug("Backup starting.")
        self._settime()
        self._checkforbackupfolder()
        self._getbackups()  # populate self.backups
        self._performbackup()
        self.log.debug("Backup cycle complete.")

    def _checkforbackupfolder(self):
        if not os.path.exists(self.config["Backups"]["backup-location"]):
            self.log.warning("Backup location %s does not exist -- creating target location...",
                             self.config["Backups"]["backup-location"])
            mkdir_p(self.config["Backups"]["backup-location"])

    def _doserversaving(self, desiredstate=True):
        """
        :param desiredstate: True = turn serversaving on
                             False = turn serversaving off
        :return:

        Future expansion to allow config of server saving state glabally in config.  Plan to include a glabal
        config option for periodic or continuous server disk saving of the minecraft server.
        """
        if desiredstate:
            self.api.minecraft.console("save-all flush")  # flush argument is required
            self.api.minecraft.console("save-on")
        else:
            self.api.minecraft.console("save-all flush")  # flush argument is required
            self.api.minecraft.console("save-off")
            time.sleep(0.5)

    def _performbackup(self):
        timestamp = int(time.time())

        # Turn off server saves...
        self._doserversaving(False)

        # Create tar arguments
        filename = "backup-%s.tar" % datetime.datetime.fromtimestamp(int(timestamp)).strftime("%Y-%m-%d_%H.%M.%S")
        if self.config["Backups"]["backup-compression"]:
            filename += ".gz"
            arguments = ["tar", "czf", "%s/%s" % (self.config["Backups"]["backup-location"].replace(" ", "\\ "),
                                                  filename)]
        else:
            arguments = ["tar", "cfpv", "%s/%s" % (self.config["Backups"]["backup-location"], filename)]

        # Process begin Events
        if not self.wrapper.events.callevent("wrapper.backupBegin", {"file": filename}):
            self.log.warning("A backup was scheduled, but was cancelled by a plugin!")
            return
        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&cBacking up... lag may occur!", irc=False)

        # Do backups
        serverpath = self.config["General"]["server-directory"]
        for backupfile in self.config["Backups"]["backup-folders"]:
            backup_file_and_path = "%s/%s" % (serverpath, backupfile)
            if os.path.exists(backup_file_and_path):
                arguments.append(backup_file_and_path)
            else:
                self.log.warning("Backup file '%s' does not exist - canceling backup", backup_file_and_path)
                self.wrapper.events.callevent("wrapper.backupFailure", {"reasonCode": 3,
                                                                        "reasonText": "Backup file '%s' does not exist."
                                                                        % backup_file_and_path})
                return
        statuscode = os.system(" ".join(arguments))

        # TODO add a wrapper properties config item to set save mode of server
        # restart saves, call finish Events
        self._doserversaving()
        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&aBackup complete!", irc=False)
        self.wrapper.events.callevent("wrapper.backupEnd", {"file": filename, "status": statuscode})
        self.backups.append((timestamp, filename))

        # Prune backups
        self.pruneoldbackups(filename)

        # Check for success
        if not os.path.exists(self.config["Backups"]["backup-location"] + "/" + filename):
            self.wrapper.events.callevent("wrapper.backupFailure",
                                          {"reasonCode": 2, "reasonText": "Backup file didn't exist after the tar "
                                                                          "command executed - assuming failure."})

    def _getbackups(self):
        if len(self.backups) == 0 and os.path.exists(self.config["Backups"]["backup-location"] + "/backups.json"):
            loadcode = getjsonfile("backups", self.config["Backups"]["backup-location"],
                                   encodedas=self.encoding)
            if not loadcode:
                self.log.error("NOTE - backups.json was unreadable. It might be corrupted. Backups will no "
                               "longer be automatically pruned.")
                self.wrapper.events.callevent("wrapper.backupFailure", {
                    "reasonCode": 4,
                    "reasonText": "backups.json is corrupted. Please contact an administer instantly, as this "
                                  "may be critical."
                })
                self.backups = []
            else:
                self.backups = loadcode
        else:
            if len(os.listdir(self.config["Backups"]["backup-location"])) > 0:
                # import old backups from previous versions of Wrapper.py
                backuptimestamps = []
                for backupNames in os.listdir(self.config["Backups"]["backup-location"]):
                    # noinspection PyBroadException,PyUnusedLocal
                    try:
                        backuptimestamps.append(int(backupNames[backupNames.find('-') + 1:backupNames.find('.')]))
                    except Exception as e:
                        pass
                backuptimestamps.sort()
                for backupI in backuptimestamps:
                    self.backups.append((int(backupI), "backup-%s.tar" % str(backupI)))

    def _settime(self):
        self.time = time.time()
Esempio n. 26
0
class MCServer(object):

    def __init__(self, wrapper):
        self.log = wrapper.log
        self.config = wrapper.config
        self.serverpath = self.config["General"]["server-directory"]
        self.state = OFF
        self.properties = {}
        self.worldname = None
        self.worldsize = 0
        # owner/op info
        self.ownernames = {}
        self.operator_list = []
        self.spammy_stuff = ["found nothing", "vehicle of", "Wrong location!",
                             "Tried to add entity", ]
        # this is string name of the version, collected by console output
        self.version = ""
        self.version_compute = 0
        self.servericon = None
        self.motd = None
        self.timeofday = -1
        self.protocolVersion = -1
        self.server_port = "25564"
        
        self.encoding = self.config["General"]["encoding"]
        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config["General"]["timed-reboot-minutes"]
        self.reboot_warn_minutes = self.config["General"]["timed-reboot-warning-minutes"]  # noqa

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.lastsizepoll = 0
        self.console_output_data = []

        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot",
                              "slain"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.world = None

        # get OPs
        self.refresh_ops()

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.

        if self.config["General"]["timed-reboot"]:
            rb = threading.Thread(target=self.reboot_timer, args=())
            rb.daemon = True
            rb.start()

        if self.config["Web"]["web-enabled"]:
            wb = threading.Thread(target=self.eachsecond_web, args=())
            wb.daemon = True
            wb.start()

        # This event is used to allow proxy to make console commands via
        # callevent() without referencing mcserver.py code (the eventhandler
        # is passed as an argument to the proxy).
        self.api.registerEvent("proxy.console", self._console_event)

    def init(self):
        """ Start up the listen threads for reading server console
        output.
        """
        capturethread = threading.Thread(target=self.__stdout__, args=())
        capturethread.daemon = True
        capturethread.start()

        capturethread = threading.Thread(target=self.__stderr__, args=())
        capturethread.daemon = True
        capturethread.start()

    def __del__(self):
        self.state = 0

    def accepteula(self):

        if os.path.isfile("%s/eula.txt" % self.serverpath):
            self.log.debug("Checking EULA agreement...")
            with open("%s/eula.txt" % self.serverpath) as f:
                eula = f.read()

            # if forced, should be at info level since acceptance
            # is a legal matter.
            if "eula=false" in eula:
                self.log.warning(
                    "EULA agreement was not accepted, accepting on"
                    " your behalf...")
                set_item("eula", "true", "eula.txt", self.serverpath)

            self.log.debug("EULA agreement has been accepted.")
            return True
        else:
            return False

    def handle_server(self):
        """ Function that handles booting the server, parsing console
        output, and such.
        """
        trystart = 0
        while not self.wrapper.haltsig.halt:
            trystart += 1
            self.proc = None

            # endless loop for not booting the server (while still
            # allowing handle to run).
            if not self.boot_server:
                time.sleep(0.2)
                trystart = 0
                continue

            self.changestate(STARTING)
            self.log.info("Starting server...")
            self.reloadproperties()

            command = self.args
            self.proc = subprocess.Popen(
                command, cwd=self.serverpath, stdout=subprocess.PIPE,
                stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                universal_newlines=True)
            self.wrapper.players = {}
            self.accepteula()  # Auto accept eula

            if self.proc.poll() is None and trystart > 3:
                self.log.error(
                    "Could not start server.  check your server.properties,"
                    " wrapper.properties and this your startup 'command'"
                    " from wrapper.properties:\n'%s'", " ".join(self.args))
                self.changestate(OFF)
                # halt wrapper
                self.wrapper.haltsig.halt = True
                # exit server_handle
                break

            # The server loop
            while True:
                # Loop runs continously as long as server console is running
                time.sleep(0.1)
                if self.proc.poll() is not None:
                    self.changestate(OFF)
                    trystart = 0
                    self.boot_server = self.server_autorestart
                    # break out to `while not self.wrapper.haltsig.halt:` loop
                    # to (possibly) connect to server again.
                    break

                # is is only reading server console output
                for line in self.console_output_data:
                    try:
                        self.readconsole(line.replace("\r", ""))
                    except Exception as e:
                        self.log.exception(e)
                self.console_output_data = []

        # code ends here on wrapper.haltsig.halt and execution returns to
        # the end of wrapper.start()

    def _toggle_server_started(self, server_started=True):
        self.wrapper.storage["ServerStarted"] = server_started
        self.wrapper.wrapper_storage.save()

    def start(self):
        """
        Start the Minecraft server
        """
        self.server_autorestart = self.config["General"]["auto-restart"]
        if self.state in (STARTED, STARTING):
            self.log.warning("The server is already running!")
            return
        if not self.boot_server:
            self.boot_server = True
        else:
            self.handle_server()

        self._toggle_server_started()

    def restart(self, reason=""):
        """Restart the Minecraft server, and kick people with the
        specified reason.  If server was already stopped, restart it.
        """
        if reason == "":
            reason = self.restart_message

        if self.state in (STOPPING, OFF):
            self.start()
            return
        self.doserversaving()
        self.stop(reason)

    def kick_players(self, reasontext):
        playerlist = copy.copy(self.wrapper.players)
        for player in playerlist:
            self.kick_player(player, reasontext)

    def kick_player(self, player, reasontext):
        if self.wrapper.proxymode:
            try:
                playerclient = self.wrapper.players[player].client
                playerclient.notify_disconnect(reasontext)
            except AttributeError:
                self.log.warning(
                    "Proxy kick failed - Gould not get client %s.\n"
                    "I'll try using the console..", player)
                self.console("kick %s %s" % (player, reasontext))
            except KeyError:
                self.log.warning(
                    "Kick failed - No player called %s", player)
            except Exception as e:
                self.log.warning(
                    "Kick failed - something else went wrong:"
                    " %s\n%s", player, e,)
        else:
            self.console("kick %s %s" % (player, reasontext))
            # this sleep is here for Spigot McBans reasons/compatibility.
            time.sleep(2)

    def stop(self, reason="", restart_the_server=True):
        """Stop the Minecraft server from an automatic process.  Allow
        it to restart by default.
        """
        self.doserversaving()
        self.log.info("Stopping Minecraft server with reason: %s", reason)

        self.kick_players(reason)

        self.changestate(STOPPING, reason)
        self.console("stop")

        # False will allow this loop to run with no server (and
        # reboot if permitted).
        self.boot_server = restart_the_server

    def stop_server_command(self, reason="", restart_the_server=False):
        """
        Stop the Minecraft server (as a command).  By default, do not restart.
        """
        if reason == "":
            reason = self.stop_message
        if self.state == OFF:
            self.log.warning("The server is not running... :?")
            return
        if self.state == FROZEN:
            self.log.warning("The server is currently frozen.\n"
                             "To stop it, you must /unfreeze it first")
            return
        self.server_autorestart = False
        self.stop(reason, restart_the_server)
        self._toggle_server_started(restart_the_server)

    def kill(self, reason="Killing Server"):
        """Forcefully kill the server. It will auto-restart if set
        in the configuration file.
        """
        if self.state in (STOPPING, OFF):
            self.log.warning("The server is already dead, my friend...")
            return
        self.log.info("Killing Minecraft server with reason: %s", reason)
        self.changestate(OFF, reason)
        self.proc.kill()

    def freeze(self, reason="Server is now frozen. You may disconnect."):
        """Freeze the server with `kill -STOP`. Can be used to
        stop the server in an emergency without shutting it down,
        so it doesn't write corrupted data - e.g. if the disk is
        full, you can freeze the server, free up some disk space,
        and then unfreeze 'reason' argument is printed in the
        chat for all currently-connected players, unless you
        specify None.  This command currently only works for
        *NIX based systems.
        """
        if self.state != OFF:
            if os.name == "posix":
                self.log.info("Freezing server with reason: %s", reason)
                self.broadcast("&c%s" % reason)
                time.sleep(0.5)
                self.changestate(FROZEN)
                os.system("kill -STOP %d" % self.proc.pid)
            else:
                raise OSError(
                    "Your current OS (%s) does not support this"
                    " command at this time." % os.name)
        else:
            raise EnvironmentError(
                "Server is not started. You may run '/start' to boot it up.")

    def unfreeze(self):
        """Unfreeze the server with `kill -CONT`. Counterpart
        to .freeze(reason) This command currently only works
        for *NIX based systems.
        """
        if self.state != OFF:
            if os.name == "posix":
                self.log.info("Unfreezing server (ignore any"
                              " messages to type /start)...")
                self.broadcast("&aServer unfrozen.")
                self.changestate(STARTED)
                os.system("kill -CONT %d" % self.proc.pid)
            else:
                raise OSError(
                    "Your current OS (%s) does not support this command"
                    " at this time." % os.name)
        else:
            raise EnvironmentError(
                "Server is not started. Please run '/start' to boot it up.")

    def broadcast(self, message, who="@a"):
        """Broadcasts the specified message to all clients
        connected. message can be a JSON chat object, or a
        string with formatting codes using the § as a prefix.
        """
        if isinstance(message, dict):
            if self.version_compute < 10700:
                self.console("say %s %s" % (who, chattocolorcodes(message)))
            else:
                encoding = self.wrapper.encoding
                self.console("tellraw %s %s" % (
                    who, json.dumps(message, ensure_ascii=False)))
        else:
            temp = processcolorcodes(message)
            if self.version_compute < 10700:
                temp = processcolorcodes(message)
                self.console("say %s %s" % (
                    who, chattocolorcodes(temp)))
            else:
                self.console("tellraw %s %s" % (
                    who, json.dumps(processcolorcodes(message))))

    def login(self, username, servereid, position, ipaddr):
        """Called when a player logs in."""

        if username not in self.wrapper.players:
            self.wrapper.players[username] = Player(username, self.wrapper)
        # store EID if proxy is not fully connected yet (or is not enabled).
        self.wrapper.players[username].playereid = servereid
        self.wrapper.players[username].loginposition = position
        if self.wrapper.players[username].ipaddress == "127.0.0.0":
            self.wrapper.players[username].ipaddress = ipaddr

        if self.wrapper.proxy and self.wrapper.players[username].client:
            self.wrapper.players[username].client.server_eid = servereid
            self.wrapper.players[username].client.position = position

        # activate backup status
        self.wrapper.backups.idle = False
        player = self.getplayer(username)
        # proxy will handle the login event if enabled
        if player and player.client:
            return
        self.wrapper.events.callevent(
            "player.login",
            {"player": player,
             "playername": username},
            abortable=False
        )

        """ eventdoc
            <group> core/mcserver.py <group>

            <description> When player logs into the java MC server.
            <description>

            <abortable> No <abortable>

            <comments> All events in the core/mcserver.py group are collected
            from the console output, do not require proxy mode, and 
            therefore, also, cannot be aborted.
            <comments>

            <payload>
            "player": player object (if object available -could be False if not)
            "playername": user name of player (string)
            <payload>

        """

    def logout(self, players_name):
        """Called when a player logs out."""
        if players_name in self.wrapper.players:
            player = self.wrapper.players[players_name]
            self.wrapper.events.callevent(
                "player.logout", {"player": player,
                                  "playername": players_name},
                abortable=True
            )
            """ eventdoc
                <group> core/mcserver.py <group>

                <description> When player logs out of the java MC server.
                <description>

                <abortable> No - but This will pause long enough for you to deal with the playerobject. <abortable>

                <comments> All events in the core/mcserver.py group are collected
                from the console output, do not require proxy mode, and 
                therefore, also, cannot be aborted.
                <comments>

                <payload>
                "player": player object (if object available -could be False if not)
                "playername": user name of player (string)
                <payload>

            """  # noqa

            if player.client is None:
                player.abort = True
                del self.wrapper.players[players_name]
            elif player.client.state != LOBBY and player.client.local:
                player.abort = True
                del self.wrapper.players[players_name]
            if self.wrapper.proxy:
                self.wrapper.proxy.removestaleclients()

        if len(self.wrapper.players) == 0:
            self.wrapper.backups.idle = True

    def getplayer(self, username):
        """Returns a player object with the specified name, or
        False if the user is not logged in/doesn't exist.

        this getplayer only deals with local players on this server.
        api.minecraft.getPlayer will deal in all players, including
        those in proxy and/or other hub servers.
        """
        if username in self.wrapper.players:
            player = self.wrapper.players[username]
            if player.client and player.client.state != LOBBY and player.client.local:  # noqa
                return player
            elif not self.wrapper.proxymode:
                return player
        return False

    def reloadproperties(self):
        # Read server.properties and extract some information out of it
        # the PY3.5 ConfigParser seems broken.  This way was much more
        # straightforward and works in both PY2 and PY3

        # Load server icon
        if os.path.exists("%s/server-icon.png" % self.serverpath):
            with open("%s/server-icon.png" % self.serverpath, "rb") as f:
                theicon = f.read()
                iconencoded = base64.standard_b64encode(theicon)
                self.servericon = b"data:image/png;base64," + iconencoded

        self.properties = config_to_dict_read(
            "server.properties", self.serverpath)

        if self.properties == {}:
            self.log.warning("File 'server.properties' not found.")
            return False

        if "level-name" in self.properties:
            self.worldname = self.properties["level-name"]
        else:
            self.log.warning("No 'level-name=(worldname)' was"
                             " found in the server.properties.")
            return False
        self.motd = self.properties["motd"]

    def console(self, command):
        """Execute a console command on the server."""
        if self.state in (STARTING, STARTED, STOPPING) and self.proc:
            self.proc.stdin.write("%s\n" % command)
            self.proc.stdin.flush()
        else:
            self.log.debug("Attempted to run console command"
                           " '%s' but the Server is not started.", command)

    def changestate(self, state, reason=None):
        """Change the boot state indicator of the server, with a
        reason message.
        """
        self.state = state
        if self.state == OFF:
            self.wrapper.events.callevent(
                "server.stopped", {"reason": reason}, abortable=False)
        elif self.state == STARTING:
            self.wrapper.events.callevent(
                "server.starting", {"reason": reason}, abortable=False)
        elif self.state == STARTED:
            self.wrapper.events.callevent(
                "server.started", {"reason": reason}, abortable=False)
        elif self.state == STOPPING:
            self.wrapper.events.callevent(
                "server.stopping", {"reason": reason}, abortable=False)
        self.wrapper.events.callevent(
            "server.state", {"state": state, "reason": reason}, abortable=False)

    def doserversaving(self, desiredstate=True):
        """
        :param desiredstate: True = turn serversaving on
                             False = turn serversaving off
        :return:

        Future expansion to allow config of server saving state glabally in
        config.  Plan to include a global config option for periodic or
        continuous server disk saving of the minecraft server.

        """
        if desiredstate:
            self.console("save-all flush")  # flush argument is required
            self.console("save-on")
        else:
            self.console("save-all flush")  # flush argument is required
            self.console("save-off")
        time.sleep(1)

    def getservertype(self):
        if "spigot" in self.config["General"]["command"].lower():
            return "spigot"
        elif "bukkit" in self.config["General"]["command"].lower():
            return "bukkit"
        else:
            return "vanilla"

    def server_reload(self):
        """This is not used yet.. intended to restart a server
        without kicking players restarts the server quickly.
        Wrapper "auto-restart" must be set to True. If wrapper
        is in proxy mode, it will reconnect all clients to the
        serverconnection.
        """
        if self.state in (STOPPING, OFF):
            self.log.warning(
                "The server is not already running... Just use '/start'.")
            return
        if self.wrapper.proxymode:
            # discover who all is playing and store that knowledge

            # tell the serverconnection to stop processing play packets
            self.server_stalled = True

        # stop the server.

        # Call events to "do stuff" while server is down (write
        # whilelists, OP files, server properties, etc)

        # restart the server.

        if self.wrapper.proxymode:
            pass
            # once server is back up,  Reconnect stalled/idle
            # clients back to the serverconnection process.

            # Do I need to create a new serverconnection,
            # or can the old one be tricked into continuing??

        self.stop_server_command()

    def __stdout__(self):
        """handles server output, not lines typed in console."""
        while not self.wrapper.haltsig.halt:
            # noinspection PyBroadException,PyUnusedLocal

            # this reads the line and puts the line in the
            # 'self.data' buffer for processing by
            # readconsole() (inside handle_server)
            try:
                data = self.proc.stdout.readline()
                for line in data.split("\n"):
                    if len(line) < 1:
                        continue
                    self.console_output_data.append(line)
            except Exception as e:
                time.sleep(0.1)
                continue

    def __stderr__(self):
        """like __stdout__, handles server output (not lines
        typed in console)."""
        while not self.wrapper.haltsig.halt:
            try:
                data = self.proc.stderr.readline()
                if len(data) > 0:
                    for line in data.split("\n"):
                        self.console_output_data.append(line.replace("\r", ""))
            except Exception as e:
                time.sleep(0.1)
                continue

    def read_ops_file(self, read_super_ops=True):
        """Keep a list of ops in the server instance to stop
        reading the disk for it.
        :rtype: Dictionary
        """
        ops = False
        # (4 = PROTOCOL_1_7 ) - 1.7.6 or greater use ops.json
        if self.version_compute > 10700:
            ops = getjsonfile(
                "ops", self.serverpath, encodedas=self.encoding
            )
        if not ops:
            # try for an old "ops.txt" file instead.
            ops = []
            opstext = getfileaslines("ops.txt", self.serverpath)
            if not opstext:
                return False
            for op in opstext:
                # create a 'fake' ops list from the old pre-1.8
                # text line name list notice that the level (an
                # option not the old list) is set to 1 This will
                # pass as true, but if the plugin is also
                # checking op-levels, it may not pass truth.
                indivop = {"uuid": op,
                           "name": op,
                           "level": 1}
                ops.append(indivop)

        # Grant "owner" an op level above 4. required for some wrapper commands
        if read_super_ops:
            for eachop in ops:
                if eachop["name"] in self.ownernames:
                    eachop["level"] = self.ownernames[eachop["name"]]
        return ops

    def refresh_ops(self, read_super_ops=True):
        self.ownernames = config_to_dict_read("superops.txt", ".")
        if self.ownernames == {}:
            sample = "<op_player_1>=10\n<op_player_2>=9"
            with open("superops.txt", "w") as f:
                f.write(sample)
        self.operator_list = self.read_ops_file(read_super_ops)

    def getmemoryusage(self):
        """Returns allocated memory in bytes. This command
        currently only works for *NIX based systems.
        """
        if not resource or not os.name == "posix":
            raise OSError(
                "Your current OS (%s) does not support"
                " this command at this time." % os.name)
        if self.proc is None:
            self.log.debug("There is no running server to getmemoryusage().")
            return 0
        try:
            with open("/proc/%d/statm" % self.proc.pid) as f:
                getbytes = int(f.read().split(" ")[1]) * resource.getpagesize()
            return getbytes
        except Exception as e:
            raise e

    @staticmethod
    def getstorageavailable(folder):
        """Returns the disk space for the working directory
        in bytes.
        """
        if platform.system() == "Windows":
            free_bytes = ctypes.c_ulonglong(0)
            ctypes.windll.kernel32.GetDiskFreeSpaceExW(
                ctypes.c_wchar_p(folder), None, None,
                ctypes.pointer(free_bytes))
            return free_bytes.value
        else:
            st = os.statvfs(folder)
            return st.f_bavail * st.f_frsize

    @staticmethod
    def stripspecial(text):
        # not sure what this is actually removing...
        # this must be legacy code of some kind
        pass
        a = ""
        it = iter(range(len(text)))
        for i in it:
            char = text[i]
            if char == "\xc2":
                try:
                    next(it)
                    next(it)
                except Exception as e:
                    pass
            else:
                a += char
        return a

    def readconsole(self, buff):
        """Internally-used function that parses a particular
        console line.
        """

        if len(buff) < 1:
            return
        # Standardize the line to only include the text (removing
        # time and log pre-pends)
        line_words = buff.split(' ')[self.prepends_offset:]

        # find the actual offset is where server output line
        # starts (minus date/time and info stamps).
        # .. and load the proper ops file
        if "Starting minecraft server version" in buff and \
                self.prepends_offset == 0:
            for place in range(len(line_words)-1):
                self.prepends_offset = place
                if line_words[place] == "Starting":
                    break

            line_words = buff.split(' ')[self.prepends_offset:]
            self.version = getargs(line_words, 4)
            semantics = self.version.split(".")
            release = get_int(getargs(semantics, 0))
            major = get_int(getargs(semantics, 1))
            minor = get_int(getargs(semantics, 2))
            self.version_compute = minor + (major * 100) + (release * 10000)  # noqa

            if len(self.version.split("w")) > 1:
                # It is a snap shot
                self.version_compute = 10800
            # 1.7.6 (protocol 5) is the cutoff where ops.txt became ops.json
            if self.version_compute > 10705 and self.protocolVersion < 0:  # noqa
                self.protocolVersion = 5
                self.wrapper.api.registerPermission("mc1.7.6", value=True)
            if self.version_compute < 10702 and self.wrapper.proxymode:
                self.log.warning("\nProxy mode cannot run because the "
                                 "server is a pre-Netty version:\n\n"
                                 "http://wiki.vg/Protocol_version_numbers"
                                 "#Versions_before_the_Netty_rewrite\n\n"
                                 "Server will continue in non-proxy mode.")
                self.wrapper.disable_proxymode()
                return

            self.refresh_ops()

        if len(line_words) < 1:
            return

        # the server attempted to print a blank line
        if len(line_words[0]) < 1:
            print('')
            return

        # parse or modify the server output section
        #
        #

        # Over-ride OP help console display
        if "/op <player>" in buff:
            new_usage = "player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]"
            message = buff.replace("player>", new_usage)
            buff = message
        if "/whitelist <on|off" in buff:
            new_usage = "/whitelist <on|off|list|add|remvove|reload|offline|online>"  # noqa
            message = new_usage
            buff = message

        if "While this makes the game possible to play" in buff:
            prefix = " ".join(buff.split(' ')[:self.prepends_offset])

            if not self.wrapper.wrapper_onlinemode:
                try:
                    pport = "either port %s or " % self.wrapper.proxy.proxy_port
                except AttributeError:
                    pport = ""
                message = (
                    "%s Since you are running Wrapper in OFFLINE mode, THIS "
                    "COULD BE SERIOUS!\n%s Wrapper is not handling any"
                    " authentication.\n%s This is only ok if this wrapper "
                    "is not accessible from %sport %s"
                    " (I.e., this wrapper is a multiworld for a hub server, or"
                    " you are doing your own authorization via a plugin)." % (
                        prefix, prefix, prefix, pport, self.server_port))
            else:
                message = (
                    "%s Since you are running Wrapper in proxy mode, this"
                    " should be ok because Wrapper is handling the"
                    " authentication, PROVIDED no one can access port"
                    " %s from outside your network." % (
                        prefix, self.server_port))

            if self.wrapper.proxymode:
                buff = message

        # read port of server and display proxy port, if applicable
        if "Starting Minecraft server on" in buff:
            self.server_port = get_int(buff.split(':')[-1:][0])

        # check for server console spam before printing to wrapper console
        server_spaming = False
        for things in self.spammy_stuff:
            if things in buff:
                server_spaming = True

        # server_spaming setting does not stop it from being parsed below.
        if not server_spaming:
            if not self.server_muted:
                self.wrapper.write_stdout(buff, "server")
            else:
                self.queued_lines.append(buff)

        first_word = getargs(line_words, 0)
        second_word = getargs(line_words, 1)
        # be careful about how these elif's are handled!
        # confirm server start
        if "Done (" in buff:
            self._toggle_server_started()
            self.changestate(STARTED)
            self.log.info("Server started")
            if self.wrapper.proxymode:
                self.log.info("Proxy listening on *:%s", self.wrapper.proxy.proxy_port)  # noqa

        # Getting world name
        elif "Preparing level" in buff:
            self.worldname = getargs(line_words, 2).replace('"', "")
            self.world = World(self.worldname, self)

        # Player Message
        elif first_word[0] == "<":
            # get a name out of <name>
            name = self.stripspecial(first_word[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            playerobj = self.getplayer(name)
            if playerobj:
                self.wrapper.events.callevent("player.message", {
                    "player": self.getplayer(name),
                    "message": message,
                    "original": original
                }, abortable=False)
                """ eventdoc
                    <group> core/mcserver.py <group>
    
                    <description> Player chat scrubbed from the console.
                    <description>
    
                    <abortable> No
                    <abortable>
    
                    <comments>
                    This event is triggered by console chat which has already been sent. 
                    This event returns the player object. if used in a string context, 
                    ("%s") it's repr (self.__str__) is self.username (no need to do 
                    str(player) or player.username in plugin code).
                    <comments>
    
                    <payload>
                    "player": playerobject (self.__str__ represents as player.username)
                    "message": <str> type - what the player said in chat. ('hello everyone')
                    "original": The original line of text from the console ('<mcplayer> hello everyone`)
                    <payload>
    
                """  # noqa
            else:
                self.log.debug("Console has chat from '%s', but wrapper has no "
                               "known logged-in player object by that name.", name)  # noqa
        # Player Login
        elif second_word == "logged":
            user_desc = first_word.split("[/")
            name = user_desc[0]
            ip_addr = user_desc[1].split(":")[0]
            eid = get_int(getargs(line_words, 6))
            locationtext = getargs(buff.split(" ("), 1)[:-1].split(", ")
            # spigot versus vanilla
            # SPIGOT - [12:13:19 INFO]: *******[/] logged in with entity id 123 at ([world]316.86789318152546, 67.12426603789697, -191.9069627257038)  # noqa
            # VANILLA - [23:24:34] [Server thread/INFO]: *******[/127.0.0.1:47434] logged in with entity id 149 at (46.29907483845001, 63.0, -270.1293488726086)  # noqa
            if len(locationtext[0].split("]")) > 1:
                x_c = get_int(float(locationtext[0].split("]")[1]))
            else:
                x_c = get_int(float(locationtext[0]))
            y_c = get_int(float(locationtext[1]))
            z_c = get_int(float(locationtext[2]))
            location = x_c, y_c, z_c

            self.login(name, eid, location, ip_addr)

        # Player Logout
        elif "lost connection" in buff:
            name = first_word
            self.logout(name)

        # player action
        elif first_word == "*":
            name = self.stripspecial(second_word)
            message = self.stripspecial(getargsafter(line_words, 2))
            self.wrapper.events.callevent("player.action", {
                "player": self.getplayer(name),
                "action": message
            }, abortable=False)

        # Player Achievement
        elif "has just earned the achievement" in buff:
            name = self.stripspecial(first_word)
            achievement = getargsafter(line_words, 6)
            self.wrapper.events.callevent("player.achievement", {
                "player": name,
                "achievement": achievement
            }, abortable=False)

        # /say command
        elif getargs(
                line_words, 0)[0] == "[" and first_word[-1] == "]":
            if self.getservertype != "vanilla":
                # Unfortunately, Spigot and Bukkit output things
                # that conflict with this.
                return
            name = self.stripspecial(first_word[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            self.wrapper.events.callevent("server.say", {
                "player": name,
                "message": message,
                "original": original
            }, abortable=False)

        # Player Death
        elif second_word in self.deathprefixes:
            name = self.stripspecial(first_word)
            self.wrapper.events.callevent("player.death", {
                "player": self.getplayer(name),
                "death": getargsafter(line_words, 1)
            }, abortable=False)

        # server lagged
        elif "Can't keep up!" in buff:
            skipping_ticks = getargs(line_words, 17)
            self.wrapper.events.callevent("server.lagged", {
                "ticks": get_int(skipping_ticks)
            }, abortable=False)

        # player teleport
        elif second_word == "Teleported" and getargs(line_words, 3) == "to":
            playername = getargs(line_words, 2)
            # [SurestTexas00: Teleported SapperLeader to 48.49417131908783, 77.67081086259394, -279.88880690937475]  # noqa
            if playername in self.wrapper.players:
                playerobj = self.getplayer(playername)
                try:
                    playerobj._position = [
                        get_int(float(getargs(line_words, 4).split(",")[0])),
                        get_int(float(getargs(line_words, 5).split(",")[0])),
                        get_int(float(getargs(line_words, 6).split("]")[0])), 0, 0
                    ]
                except ValueError:
                    pass
                self.wrapper.events.callevent(
                    "player.teleport",
                    {"player": playerobj}, abortable=False)

                """ eventdoc
                    <group> core/mcserver.py <group>

                    <description> When player teleports.
                    <description>

                    <abortable> No <abortable>

                    <comments> driven from console message "Teleported ___ to ....".
                    <comments>

                    <payload>
                    "player": player object
                    <payload>

                """  # noqa
        elif first_word == "Teleported" and getargs(line_words, 2) == "to":
            playername = second_word
            # Teleported SurestTexas00 to 48.49417131908783, 77.67081086259394, -279.88880690937475  # noqa
            if playername in self.wrapper.players:
                playerobj = self.getplayer(playername)
                try:
                    playerobj._position = [
                        get_int(float(getargs(line_words, 3).split(",")[0])),
                        get_int(float(getargs(line_words, 4).split(",")[0])),
                        get_int(float(getargs(line_words, 5))), 0, 0
                    ]
                except ValueError:
                    pass
                self.wrapper.events.callevent(
                    "player.teleport",
                    {"player": playerobj}, abortable=False)

                """ eventdoc
                    <group> core/mcserver.py <group>
        
                    <description> When player teleports.
                    <description>
        
                    <abortable> No <abortable>
        
                    <comments> driven from console message "Teleported ___ to ....".
                    <comments>
        
                    <payload>
                    "player": player object
                    <payload>
        
                """  # noqa
    # mcserver.py onsecond Event Handlers
    def reboot_timer(self):
        rb_mins = self.reboot_minutes
        rb_mins_warn = self.config["General"]["timed-reboot-warning-minutes"]
        while not self.wrapper.haltsig.halt:
            time.sleep(1)
            timer = rb_mins - rb_mins_warn
            while self.state in (STARTED, STARTING):
                timer -= 1
                time.sleep(60)
                if timer > 0:
                    continue
                if timer + rb_mins_warn > 0:
                    if rb_mins_warn + timer > 1:
                        self.broadcast("&cServer will reboot in %d "
                                       "minutes!" % (rb_mins_warn + timer))
                    else:
                        self.broadcast("&cServer will reboot in %d "
                                       "minute!" % (rb_mins_warn + timer))
                        countdown = 59
                        timer -= 1
                        while countdown > 0:
                            time.sleep(1)
                            countdown -= 1
                            if countdown == 0:
                                if self.wrapper.backups_idle():
                                    self.restart(self.reboot_message)
                                else:
                                    self.broadcast(
                                        "&cBackup in progress. Server reboot "
                                        "delayed for one minute..")
                                    countdown = 59
                            if countdown % 15 == 0:
                                self.broadcast("&cServer will reboot in %d "
                                               "seconds" % countdown)
                            if countdown < 6:
                                self.broadcast("&cServer will reboot in %d "
                                               "seconds" % countdown)
                    continue
                if self.wrapper.backups_idle():
                    self.restart(self.reboot_message)
                else:
                    self.broadcast(
                        "&cBackup in progress. Server reboot "
                        "delayed..")
                    timer = rb_mins + rb_mins_warn + 1

    def eachsecond_web(self):
        if time.time() - self.lastsizepoll > 120:
            if self.worldname is None:
                return True
            self.lastsizepoll = time.time()
            size = 0
            # os.scandir not in standard library on early py2.7.x systems
            for i in os.walk(
                    "%s/%s" % (self.serverpath, self.worldname)
            ):
                for f in os.listdir(i[0]):
                    size += os.path.getsize(os.path.join(i[0], f))
            self.worldsize = size

    def _console_event(self, payload):
        """This function is used in conjunction with event handlers to
        permit a proxy object to make a command call to this server."""

        # make commands pass through the command interface.
        comm_pay = payload["command"].split(" ")
        if len(comm_pay) > 1:
            args = comm_pay[1:]
        else:
            args = [""]
        new_payload = {"player": self.wrapper.xplayer,
                       "command": comm_pay[0],
                       "args": args
                       }
        self.wrapper.commands.playercommand(new_payload)
Esempio n. 27
0
class Backups(object):
    def __init__(self, wrapper):
        self.wrapper = wrapper
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.log = wrapper.log
        self.api = API(wrapper, "Backups", internal=True)

        # self.wrapper.backups.idle
        self.idle = True
        self.inprogress = False
        self.backup_interval = self.config["Backups"]["backup-interval"]
        self.time = time.time()
        self.backups = []

        # allow plugins to shutdown backups via api
        self.enabled = self.config["Backups"]["enabled"]

        # only register event if used and tar installed.
        if self.enabled and self.dotarchecks():
            self.api.registerEvent("timer.second", self.eachsecond)
            self.log.debug("Backups Enabled..")

    # noinspection PyUnusedLocal
    def eachsecond(self, payload):
        # only run backups in server running/starting states
        if self.wrapper.javaserver.vitals.state in (1, 2) and not self.idle:
            if time.time() - self.time > self.backup_interval and self.enabled:
                self.dobackup()

    def pruneoldbackups(self, filename="IndependentPurge"):
        if len(self.backups) > self.config["Backups"]["backups-keep"]:
            self.log.info("Deleting old backups...")
            while len(self.backups) > self.config["Backups"]["backups-keep"]:
                backup = self.backups[0]
                if not self.wrapper.events.callevent(
                        "wrapper.backupDelete", {"file": filename}):  # noqa
                    """ eventdoc
                                    <group> Backups <group>

                                    <description> Called upon deletion of a backup file.
                                    <description>

                                    <abortable> Yes, return False to abort. <abortable>

                                    <comments>
                                    
                                    <comments>
                                    <payload>
                                    "file": filename
                                    <payload>

                                """  # noqa
                    break
                try:
                    os.remove(
                        '%s/%s' %
                        (self.config["Backups"]["backup-location"], backup[1]))
                except Exception as e:
                    self.log.error("Failed to delete backup (%s)", e)
                self.log.info(
                    "Deleting old backup: %s",
                    datetime.datetime.fromtimestamp(int(
                        backup[0])).strftime('%Y-%m-%d_%H:%M:%S'))  # noqa
                # hink = self.backups[0][1][:]  # not used...
                del self.backups[0]
        putjsonfile(self.backups, "backups",
                    self.config["Backups"]["backup-location"])

    def dotarchecks(self):
        # Check if tar is installed
        which = "where" if platform.system() == "Windows" else "which"
        if not subprocess.call([which, "tar"]) == 0:
            self.wrapper.events.callevent("wrapper.backupFailure", {
                "reasonCode":
                1,
                "reasonText":
                "Tar is not installed. Please install "
                "tar before trying to make backups."
            },
                                          abortable=False)
            """ eventdoc
                <group> Backups <group>

                <description> Indicates failure of backup.
                <description>

                <abortable> No - informatinal only <abortable>

                <comments>
                Reasoncode and text provide more detail about specific problem.
                1 - Tar not installed.
                2 - Backup file does not exist after the tar operation.
                3 - Specified file does not exist.
                4 - backups.json is corrupted
                5 - unable to create backup directory
                <comments>
                <payload>
                "reasonCode": an integer 1-4
                "reasonText": a string description of the failure.
                <payload>

            """
            self.log.error(
                "Backups will not work, because tar does not appear "
                "to be installed!")
            self.log.error(
                "If you are on a Linux-based system, please install it through"
                " your preferred package manager.")
            self.log.error(
                "If you are on Windows, you can find GNU/Tar from this link:"
                " http://goo.gl/SpJSVM")
            return False
        else:
            return True

    def dobackup(self):
        self.inprogress = True
        self.log.debug("Backup starting.")
        self._settime()
        if not self._checkforbackupfolder():
            self.inprogress = False
            self.wrapper.events.callevent(
                "wrapper.backupFailure", {
                    "reasonCode": 5,
                    "reasonText": "Backup location could not be found/created!"
                },
                abortable=False)
            self.log.warning("")
        self._getbackups()  # populate self.backups
        self._performbackup()
        self.log.debug("dobackup() cycle complete.")
        self.inprogress = False

    def _checkforbackupfolder(self):
        if not os.path.exists(self.config["Backups"]["backup-location"]):
            self.log.warning(
                "Backup location %s does not exist -- creating target "
                "location...", self.config["Backups"]["backup-location"])
            mkdir_p(self.config["Backups"]["backup-location"])
        if not os.path.exists(self.config["Backups"]["backup-location"]):
            self.log.error("Backup location %s could not be created!",
                           self.config["Backups"]["backup-location"])
            return False
        return True

    def _performbackup(self):
        timestamp = int(time.time())

        # Turn off server saves...
        self.wrapper.javaserver.doserversaving(False)
        # give server time to save
        time.sleep(1)

        # Create tar arguments
        filename = "backup-%s.tar" % datetime.datetime.fromtimestamp(
            int(timestamp)).strftime("%Y-%m-%d_%H.%M.%S")
        if self.config["Backups"]["backup-compression"]:
            filename += ".gz"
            arguments = [
                "tar", "czf",
                "%s/%s" % (self.config["Backups"]["backup-location"].replace(
                    " ", "\\ "), filename)
            ]
        else:
            arguments = [
                "tar", "cfpv",
                "%s/%s" % (self.config["Backups"]["backup-location"], filename)
            ]

        # Process begin Events
        if not self.wrapper.events.callevent("wrapper.backupBegin",
                                             {"file": filename}):  # noqa
            self.log.warning(
                "A backup was scheduled, but was cancelled by a plugin!")
            """ eventdoc
                <group> Backups <group>

                <description> Indicates a backup is being initiated.
                <description>

                <abortable> Yes, return False to abort. <abortable>

                <comments>
                A console warning will be issued if a plugin cancels the backup.
                <comments>
                <payload>
                "file": Name of backup file.
                <payload>

            """
            self.wrapper.javaserver.doserversaving(True)
            # give server time to save
            time.sleep(1)
            return

        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&cBacking up... lag may occur!",
                                         irc=False)

        # Do backups
        serverpath = self.config["General"]["server-directory"]
        for backupfile in self.config["Backups"]["backup-folders"]:
            backup_file_and_path = "%s/%s" % (serverpath, backupfile)
            if os.path.exists(backup_file_and_path):
                arguments.append(backup_file_and_path)
            else:
                self.log.warning(
                    "Backup file '%s' does not exist - canceling backup",
                    backup_file_and_path)
                self.wrapper.events.callevent("wrapper.backupFailure", {
                    "reasonCode":
                    3,
                    "reasonText":
                    "Backup file '%s' does not "
                    "exist." % backup_file_and_path
                },
                                              abortable=False)
                """ eventdoc
                                <description> internalfunction <description>

                            """
                return
        # perform TAR backup
        statuscode = os.system(" ".join(arguments))

        # TODO add a wrapper properties config item to set save mode of server
        # restart saves, call finish Events
        self.wrapper.javaserver.doserversaving(True)
        self.backups.append((timestamp, filename))

        # Prune backups
        self.pruneoldbackups(filename)

        # Check for success
        finalbackup = "%s/%s" % (self.config["Backups"]["backup-location"],
                                 filename)

        if not os.path.exists(finalbackup):
            self.wrapper.events.callevent("wrapper.backupFailure", {
                "reasonCode":
                2,
                "reasonText":
                "Backup file didn't exist after the tar "
                "command executed - assuming failure."
            },
                                          abortable=False)
            """ eventdoc
                <description> internalfunction <description>

            """
            summary = "backup failed"
        else:
            # find size of completed backup file
            backupsize = os.path.getsize(finalbackup)
            size_of, units = format_bytes(backupsize)
            timetook = _secondstohuman(int(time.time()) - timestamp)
            desc = "were backed up.  The operation took"
            summary = "%s %s %s %s" % (size_of, units, desc, timetook)

        self.wrapper.events.callevent("wrapper.backupEnd", {
            "file": filename,
            "status": statuscode,
            "summary": summary
        },
                                      abortable=False)
        """ eventdoc
            <group> Backups <group>

            <description> Indicates a backup is complete.
            <description>

            <abortable> No - informational only <abortable>

            <comments>
            <comments>
            <payload>
            "file": Name of backup file.
            "status": Status code from TAR
            "summary": string summary of operation 
            <payload>

        """
        if self.config["Backups"]["backup-notification"]:
            self.api.minecraft.broadcast("&aBackup cycle complete!", irc=False)
            self.api.minecraft.broadcast("&a%s" % summary, irc=False)

    def _getbackups(self):
        if len(self.backups) == 0 and os.path.exists(
                self.config["Backups"]["backup-location"] +
                "/backups.json"):  # noqa - long if statement
            loadcode = getjsonfile("backups",
                                   self.config["Backups"]["backup-location"],
                                   encodedas=self.encoding)
            if not loadcode:
                self.log.error(
                    "NOTE - backups.json was unreadable. It might be corrupted."
                    " Backups will no longer be automatically pruned.")
                self.wrapper.events.callevent("wrapper.backupFailure", {
                    "reasonCode":
                    4,
                    "reasonText":
                    "backups.json is corrupted. Please contact"
                    " an administer instantly, as this may be "
                    "critical."
                },
                                              abortable=False)
                """ eventdoc
                    <description> internalfunction <description>

                """
                self.backups = []
            else:
                self.backups = loadcode
        else:
            if len(os.listdir(self.config["Backups"]["backup-location"])) > 0:
                # import old backups from previous versions of Wrapper.py
                backuptimestamps = []
                for backupNames in os.listdir(
                        self.config["Backups"]["backup-location"]):
                    # noinspection PyBroadException,PyUnusedLocal
                    try:
                        backuptimestamps.append(
                            int(backupNames[backupNames.find('-') +
                                            1:backupNames.find('.')])
                        )  # noqa - large one-liner
                    except Exception as e:
                        pass
                backuptimestamps.sort()
                for backupI in backuptimestamps:
                    self.backups.append(
                        (int(backupI), "backup-%s.tar" % str(backupI)))

    def _settime(self):
        self.time = time.time()
Esempio n. 28
0
    def loadplugin(self, name, available_files):
        if name in self.plugins_loaded:
            # Don't try to load a previously errored ( attempted to load..) plugin
            return False
        self.log.debug("Reading plugin file %s.py ...", name)
        # hack to remove previously loaded modules during reloads
        if name in sys.modules:
            del sys.modules[name]

        plugin = importlib.import_module(name)
        pid = getattr(
            plugin, 'ID', name
        )  # from the plugin head.. e.g., 'ID = "com.benbaptist.plugins.essentials"'
        disabled = getattr(plugin, 'DISABLED', False)
        dependencies = getattr(plugin, 'DEPENDENCIES', [])

        if pid in self.wrapper.storage["disabled_plugins"] or disabled:
            self.log.debug("Plugin '%s' disabled - not loading", name)
            return True

        if pid in self.plugins:  # Once successfully loaded, further attempts to load the plugin are ignored
            self.log.debug(
                "Plugin '%s' is already loaded (probably as a dependency) - not reloading",
                name)
            return True

        # check for unloaded dependencies and develop a list of required dependencies.
        good_deps = True
        dep_loads = []
        if dependencies:
            for dep in dependencies:
                # allow a user to specify name or full filename for dependencies
                dep_name = dep
                if dep[-3:] == '.py':
                    dep_name = dep[:-3]
                if dep_name in available_files:
                    # if the plugin was already loaded, the dependency is satisfied...
                    if dep_name not in self.plugins_loaded:
                        dep_loads.append(dep_name)
                else:
                    good_deps = False
                    self.log.warn(
                        "Plugin '%s'.py is missing a dependency: '%s.py'",
                        name, dep_name)
        if not good_deps:
            self.log.warn(
                "Plugin '%s'.py failed to load because of missing dependencies.",
                name)
            return False

        # load the required dependencies first.
        for dependency in dep_loads:
            if self.loadplugin(dependency, available_files):
                self.log.debug("Dependency '%s' loaded.", dependency)
                self.plugins_loaded.append(name)
            else:
                self.log.warn("Dependency '%s' could not be loaded.",
                              dependency)
                self.log.warn(
                    "Plugin '%s'.py failed to load because of missing dependency '%s'.",
                    name, dependency)
                self.plugins_loaded.append(name)
                return False

        # Finally, initialize this plugin
        self.log.debug("Loading plugin %s...", name)
        if not getattr(plugin, 'Main', False):
            self.log.warn(
                "Plugin '%s' is malformed and missing a class 'Main'", name)
            self.plugins_loaded.append(name)
            return False
        has_onenable = getattr(getattr(plugin, 'Main', False), 'onEnable',
                               False)
        if not has_onenable:
            self.log.warn("Plugin '%s' is missing an 'onEnable' method.", name)
            self.plugins_loaded.append(name)
            return False

        main = plugin.Main(API(self.wrapper, name, pid),
                           logging.getLogger(name))
        self.plugins[pid] = {
            "main": main,
            "good": True,
            "module": plugin
        }  # "events": {}, "commands": {}}
        self.plugins[pid]["name"] = getattr(plugin, "NAME", name)
        self.plugins[pid]["version"] = getattr(plugin, 'VERSION', (0, 1))
        self.plugins[pid]["summary"] = getattr(plugin, 'SUMMARY', None)
        self.plugins[pid]["description"] = getattr(plugin, 'DESCRIPTION', None)
        self.plugins[pid]["author"] = getattr(plugin, 'AUTHOR', None)
        self.plugins[pid]["website"] = getattr(plugin, 'WEBSITE', None)
        self.plugins[pid]["filename"] = "%s.py" % name
        self.wrapper.commands[pid] = {}
        self.wrapper.events[pid] = {}
        self.wrapper.registered_permissions[pid] = {}
        self.wrapper.help[pid] = {}
        main.onEnable()
        self.log.info("Plugin %s loaded...", name)
        self.plugins_loaded.append(name)
        return True
Esempio n. 29
0
class MCServer(object):

    def __init__(self, wrapper, servervitals):
        self.log = wrapper.log
        self.config = wrapper.config
        self.vitals = servervitals

        self.encoding = self.config["General"]["encoding"]
        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config["General"]["timed-reboot-minutes"]
        self.reboot_warn_minutes = self.config["General"]["timed-reboot-warning-minutes"]  # noqa

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.vitals.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.lastsizepoll = 0
        self.console_output_data = []

        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot",
                              "slain"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.world = None

        # get OPs
        self.refresh_ops()

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.

        if self.config["General"]["timed-reboot"]:
            rb = threading.Thread(target=self.reboot_timer, args=())
            rb.daemon = True
            rb.start()

        if self.config["Web"]["web-enabled"]:
            wb = threading.Thread(target=self.eachsecond_web, args=())
            wb.daemon = True
            wb.start()

        # This event is used to allow proxy to make console commands via
        # callevent() without referencing mcserver.py code (the eventhandler
        # is passed as an argument to the proxy).
        self.api.registerEvent("proxy.console", self._console_event)

    def init(self):
        """ Start up the listen threads for reading server console
        output.
        """
        capturethread = threading.Thread(target=self.__stdout__, args=())
        capturethread.daemon = True
        capturethread.start()

        capturethread = threading.Thread(target=self.__stderr__, args=())
        capturethread.daemon = True
        capturethread.start()

    def __del__(self):
        self.vitals.state = 0

    def accepteula(self):

        if os.path.isfile("%s/eula.txt" % self.vitals.serverpath):
            self.log.debug("Checking EULA agreement...")
            with open("%s/eula.txt" % self.vitals.serverpath) as f:
                eula = f.read()

            # if forced, should be at info level since acceptance
            # is a legal matter.
            if "eula=false" in eula:
                self.log.warning(
                    "EULA agreement was not accepted, accepting on"
                    " your behalf...")
                set_item("eula", "true", "eula.txt", self.vitals.serverpath)

            self.log.debug("EULA agreement has been accepted.")
            return True
        else:
            return False

    def handle_server(self):
        """ Function that handles booting the server, parsing console
        output, and such.
        """
        trystart = 0
        while not self.wrapper.halt.halt:
            trystart += 1
            self.proc = None

            # endless loop for not booting the server (while still
            # allowing handle to run).
            if not self.boot_server:
                time.sleep(0.2)
                trystart = 0
                continue

            self.changestate(STARTING)
            self.log.info("Starting server...")
            self.reloadproperties()

            command = self.args
            self.proc = subprocess.Popen(
                command, cwd=self.vitals.serverpath, stdout=subprocess.PIPE,
                stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                universal_newlines=True)
            self.wrapper.players = {}
            self.accepteula()  # Auto accept eula

            if self.proc.poll() is None and trystart > 3:
                self.log.error(
                    "Could not start server.  check your server.properties,"
                    " wrapper.properties and this your startup 'command'"
                    " from wrapper.properties:\n'%s'", " ".join(self.args))
                self.changestate(OFF)
                # halt wrapper
                self.wrapper.halt.halt = True
                # exit server_handle
                break

            # The server loop
            while True:
                # Loop runs continously as long as server console is running
                time.sleep(0.1)
                if self.proc.poll() is not None:
                    self.changestate(OFF)
                    trystart = 0
                    self.boot_server = self.server_autorestart
                    # break out to `while not self.wrapper.halt.halt:` loop
                    # to (possibly) connect to server again.
                    break

                # is is only reading server console output
                for line in self.console_output_data:
                    try:
                        self.readconsole(line.replace("\r", ""))
                    except Exception as e:
                        self.log.exception(e)
                self.console_output_data = []

        # code ends here on wrapper.halt.halt and execution returns to
        # the end of wrapper.start()

    def _toggle_server_started(self, server_started=True):
        self.wrapper.storage["ServerStarted"] = server_started
        self.wrapper.wrapper_storage.save()

    def start(self):
        """
        Start the Minecraft server
        """
        self.server_autorestart = self.config["General"]["auto-restart"]
        if self.vitals.state in (STARTED, STARTING):
            self.log.warning("The server is already running!")
            return
        if not self.boot_server:
            self.boot_server = True
        else:
            self.handle_server()

        self._toggle_server_started()

    def restart(self, reason=""):
        """Restart the Minecraft server, and kick people with the
        specified reason.  If server was already stopped, restart it.
        """
        if reason == "":
            reason = self.restart_message

        if self.vitals.state in (STOPPING, OFF):
            self.start()
            return
        self.doserversaving()
        self.stop(reason)

    def kick_players(self, reasontext):
        playerlist = copy.copy(self.vitals.players)
        for player in playerlist:
            self.kick_player(player, reasontext)

    def kick_player(self, player, reasontext):
        if self.wrapper.proxymode:
            try:
                playerclient = self.vitals.players[player].client
                playerclient.notify_disconnect(reasontext)
            except AttributeError:
                self.log.warning(
                    "Proxy kick failed - Gould not get client %s.\n"
                    "I'll try using the console..", player)
                self.console("kick %s %s" % (player, reasontext))
            except KeyError:
                self.log.warning(
                    "Kick failed - No player called %s", player)
            except Exception as e:
                self.log.warning(
                    "Kick failed - something else went wrong:"
                    " %s\n%s", player, e,)
        else:
            self.console("kick %s %s" % (player, reasontext))
            # this sleep is here for Spigot McBans reasons/compatibility.
            time.sleep(2)

    def stop(self, reason="", restart_the_server=True):
        """Stop the Minecraft server from an automatic process.  Allow
        it to restart by default.
        """
        self.doserversaving()
        self.log.info("Stopping Minecraft server with reason: %s", reason)

        self.kick_players(reason)

        self.changestate(STOPPING, reason)
        self.console("stop")

        # False will allow this loop to run with no server (and
        # reboot if permitted).
        self.boot_server = restart_the_server

    def stop_server_command(self, reason="", restart_the_server=False):
        """
        Stop the Minecraft server (as a command).  By default, do not restart.
        """
        if reason == "":
            reason = self.stop_message
        if self.vitals.state == OFF:
            self.log.warning("The server is not running... :?")
            return
        if self.vitals.state == FROZEN:
            self.log.warning("The server is currently frozen.\n"
                             "To stop it, you must /unfreeze it first")
            return
        self.server_autorestart = False
        self.stop(reason, restart_the_server)
        self._toggle_server_started(restart_the_server)

    def kill(self, reason="Killing Server"):
        """Forcefully kill the server. It will auto-restart if set
        in the configuration file.
        """
        if self.vitals.state in (STOPPING, OFF):
            self.log.warning("The server is already dead, my friend...")
            return
        self.log.info("Killing Minecraft server with reason: %s", reason)
        self.changestate(OFF, reason)
        self.proc.kill()

    def freeze(self, reason="Server is now frozen. You may disconnect."):
        """Freeze the server with `kill -STOP`. Can be used to
        stop the server in an emergency without shutting it down,
        so it doesn't write corrupted data - e.g. if the disk is
        full, you can freeze the server, free up some disk space,
        and then unfreeze 'reason' argument is printed in the
        chat for all currently-connected players, unless you
        specify None.  This command currently only works for
        *NIX based systems.
        """
        if self.vitals.state != OFF:
            if os.name == "posix":
                self.log.info("Freezing server with reason: %s", reason)
                self.broadcast("&c%s" % reason)
                time.sleep(0.5)
                self.changestate(FROZEN)
                os.system("kill -STOP %d" % self.proc.pid)
            else:
                raise OSError(
                    "Your current OS (%s) does not support this"
                    " command at this time." % os.name)
        else:
            raise EnvironmentError(
                "Server is not started. You may run '/start' to boot it up.")

    def unfreeze(self):
        """Unfreeze the server with `kill -CONT`. Counterpart
        to .freeze(reason) This command currently only works
        for *NIX based systems.
        """
        if self.vitals.state != OFF:
            if os.name == "posix":
                self.log.info("Unfreezing server (ignore any"
                              " messages to type /start)...")
                self.broadcast("&aServer unfrozen.")
                self.changestate(STARTED)
                os.system("kill -CONT %d" % self.proc.pid)
            else:
                raise OSError(
                    "Your current OS (%s) does not support this command"
                    " at this time." % os.name)
        else:
            raise EnvironmentError(
                "Server is not started. Please run '/start' to boot it up.")

    def broadcast(self, message, who="@a"):
        """Broadcasts the specified message to all clients
        connected. message can be a JSON chat object, or a
        string with formatting codes using the § as a prefix.
        """
        if isinstance(message, dict):
            if self.vitals.version_compute < 10700:
                self.console("say %s %s" % (who, chattocolorcodes(message)))
            else:
                encoding = self.wrapper.encoding
                self.console("tellraw %s %s" % (
                    who, json.dumps(message, ensure_ascii=False)))
        else:
            temp = processcolorcodes(message)
            if self.vitals.version_compute < 10700:
                temp = processcolorcodes(message)
                self.console("say %s %s" % (
                    who, chattocolorcodes(temp)))
            else:
                self.console("tellraw %s %s" % (
                    who, json.dumps(processcolorcodes(message))))

    def login(self, username, servereid, position, ipaddr):
        """Called when a player logs in."""

        if username not in self.vitals.players:
            self.vitals.players[username] = Player(username, self.wrapper)
        # store EID if proxy is not fully connected yet (or is not enabled).
        self.vitals.players[username].playereid = servereid
        self.vitals.players[username].loginposition = position
        if self.vitals.players[username].ipaddress == "127.0.0.0":
            self.vitals.players[username].ipaddress = ipaddr

        if self.wrapper.proxy and self.vitals.players[username].client:
            self.vitals.players[username].client.server_eid = servereid
            self.vitals.players[username].client.position = position

        # activate backup status
        self.wrapper.backups.idle = False
        player = self.getplayer(username)
        # proxy will handle the login event if enabled
        if player and player.client:
            return
        self.wrapper.events.callevent(
            "player.login",
            {"player": player,
             "playername": username},
            abortable=False
        )

        """ eventdoc
            <group> core/mcserver.py <group>

            <description> When player logs into the java MC server.
            <description>

            <abortable> No <abortable>

            <comments> All events in the core/mcserver.py group are collected
            from the console output, do not require proxy mode, and 
            therefore, also, cannot be aborted.
            <comments>

            <payload>
            "player": player object (if object available -could be False if not)
            "playername": user name of player (string)
            <payload>

        """

    def logout(self, players_name):
        """Called when a player logs out."""
        if players_name in self.vitals.players:
            player = self.vitals.players[players_name]
            self.wrapper.events.callevent(
                "player.logout", {"player": player,
                                  "playername": players_name},
                abortable=True
            )
            """ eventdoc
                <group> core/mcserver.py <group>

                <description> When player logs out of the java MC server.
                <description>

                <abortable> No - but This will pause long enough for you to deal with the playerobject. <abortable>

                <comments> All events in the core/mcserver.py group are collected
                from the console output, do not require proxy mode, and 
                therefore, also, cannot be aborted.
                <comments>

                <payload>
                "player": player object (if object available -could be False if not)
                "playername": user name of player (string)
                <payload>

            """  # noqa

            if player.client is None:
                player.abort = True
                del self.vitals.players[players_name]
            elif player.client.state != LOBBY and player.client.local:
                player.abort = True
                del self.vitals.players[players_name]

            self.wrapper.proxy.removestaleclients()

        if len(self.vitals.players) == 0:
            self.wrapper.backups.idle = True

    def getplayer(self, username):
        """Returns a player object with the specified name, or
        False if the user is not logged in/doesn't exist.

        this getplayer only deals with local players on this server.
        api.minecraft.getPlayer will deal in all players, including
        those in proxy and/or other hub servers.
        """
        if username in self.vitals.players:
            player = self.vitals.players[username]
            if player.client and player.client.state != LOBBY and player.client.local:  # noqa
                return player
        return False

    def reloadproperties(self):
        # Read server.properties and extract some information out of it
        # the PY3.5 ConfigParser seems broken.  This way was much more
        # straightforward and works in both PY2 and PY3

        # Load server icon
        if os.path.exists("%s/server-icon.png" % self.vitals.serverpath):
            with open("%s/server-icon.png" % self.vitals.serverpath, "rb") as f:
                theicon = f.read()
                iconencoded = base64.standard_b64encode(theicon)
                self.vitals.serverIcon = b"data:image/png;base64," + iconencoded

        self.vitals.properties = config_to_dict_read(
            "server.properties", self.vitals.serverpath)

        if self.vitals.properties == {}:
            self.log.warning("File 'server.properties' not found.")
            return False

        if "level-name" in self.vitals.properties:
            self.vitals.worldname = self.vitals.properties["level-name"]
        else:
            self.log.warning("No 'level-name=(worldname)' was"
                             " found in the server.properties.")
            return False
        self.vitals.motd = self.vitals.properties["motd"]
        if "max-players" in self.vitals.properties:
            self.vitals.maxPlayers = self.vitals.properties["max-players"]
        else:
            self.log.warning(
                "No 'max-players=(count)' was found in the"
                " server.properties. The default of '20' will be used.")
            self.vitals.maxPlayers = 20
        self.vitals.onlineMode = self.vitals.properties["online-mode"]

    def console(self, command):
        """Execute a console command on the server."""
        if self.vitals.state in (STARTING, STARTED, STOPPING) and self.proc:
            self.proc.stdin.write("%s\n" % command)
            self.proc.stdin.flush()
        else:
            self.log.debug("Attempted to run console command"
                           " '%s' but the Server is not started.", command)

    def changestate(self, state, reason=None):
        """Change the boot state indicator of the server, with a
        reason message.
        """
        self.vitals.state = state
        if self.vitals.state == OFF:
            self.wrapper.events.callevent(
                "server.stopped", {"reason": reason}, abortable=False)
        elif self.vitals.state == STARTING:
            self.wrapper.events.callevent(
                "server.starting", {"reason": reason}, abortable=False)
        elif self.vitals.state == STARTED:
            self.wrapper.events.callevent(
                "server.started", {"reason": reason}, abortable=False)
        elif self.vitals.state == STOPPING:
            self.wrapper.events.callevent(
                "server.stopping", {"reason": reason}, abortable=False)
        self.wrapper.events.callevent(
            "server.state", {"state": state, "reason": reason}, abortable=False)

    def doserversaving(self, desiredstate=True):
        """
        :param desiredstate: True = turn serversaving on
                             False = turn serversaving off
        :return:

        Future expansion to allow config of server saving state glabally in
        config.  Plan to include a global config option for periodic or
        continuous server disk saving of the minecraft server.

        """
        if desiredstate:
            self.console("save-all flush")  # flush argument is required
            self.console("save-on")
        else:
            self.console("save-all flush")  # flush argument is required
            self.console("save-off")
        time.sleep(1)

    def getservertype(self):
        if "spigot" in self.config["General"]["command"].lower():
            return "spigot"
        elif "bukkit" in self.config["General"]["command"].lower():
            return "bukkit"
        else:
            return "vanilla"

    def server_reload(self):
        """This is not used yet.. intended to restart a server
        without kicking players restarts the server quickly.
        Wrapper "auto-restart" must be set to True. If wrapper
        is in proxy mode, it will reconnect all clients to the
        serverconnection.
        """
        if self.vitals.state in (STOPPING, OFF):
            self.log.warning(
                "The server is not already running... Just use '/start'.")
            return
        if self.wrapper.proxymode:
            # discover who all is playing and store that knowledge

            # tell the serverconnection to stop processing play packets
            self.server_stalled = True

        # stop the server.

        # Call events to "do stuff" while server is down (write
        # whilelists, OP files, server properties, etc)

        # restart the server.

        if self.wrapper.proxymode:
            pass
            # once server is back up,  Reconnect stalled/idle
            # clients back to the serverconnection process.

            # Do I need to create a new serverconnection,
            # or can the old one be tricked into continuing??

        self.stop_server_command()

    def __stdout__(self):
        """handles server output, not lines typed in console."""
        while not self.wrapper.halt.halt:
            # noinspection PyBroadException,PyUnusedLocal

            # this reads the line and puts the line in the
            # 'self.data' buffer for processing by
            # readconsole() (inside handle_server)
            try:
                data = self.proc.stdout.readline()
                for line in data.split("\n"):
                    if len(line) < 1:
                        continue
                    self.console_output_data.append(line)
            except Exception as e:
                time.sleep(0.1)
                continue

    def __stderr__(self):
        """like __stdout__, handles server output (not lines
        typed in console)."""
        while not self.wrapper.halt.halt:
            try:
                data = self.proc.stderr.readline()
                if len(data) > 0:
                    for line in data.split("\n"):
                        self.console_output_data.append(line.replace("\r", ""))
            except Exception as e:
                time.sleep(0.1)
                continue

    def read_ops_file(self, read_super_ops=True):
        """Keep a list of ops in the server instance to stop
        reading the disk for it.
        :rtype: Dictionary
        """
        ops = False
        # (4 = PROTOCOL_1_7 ) - 1.7.6 or greater use ops.json
        if self.vitals.protocolVersion > 4:
            ops = getjsonfile(
                "ops", self.vitals.serverpath, encodedas=self.encoding
            )
        if not ops:
            # try for an old "ops.txt" file instead.
            ops = []
            opstext = getfileaslines("ops.txt", self.vitals.serverpath)
            if not opstext:
                return False
            for op in opstext:
                # create a 'fake' ops list from the old pre-1.8
                # text line name list notice that the level (an
                # option not the old list) is set to 1 This will
                # pass as true, but if the plugin is also
                # checking op-levels, it may not pass truth.
                indivop = {"uuid": op,
                           "name": op,
                           "level": 1}
                ops.append(indivop)

        # Grant "owner" an op level above 4. required for some wrapper commands
        if read_super_ops:
            for eachop in ops:
                if eachop["name"] in self.vitals.ownernames:
                    eachop["level"] = self.vitals.ownernames[eachop["name"]]
        return ops

    def refresh_ops(self, read_super_ops=True):
        self.vitals.ownernames = config_to_dict_read("superops.txt", ".")
        if self.vitals.ownernames == {}:
            sample = "<op_player_1>=10\n<op_player_2>=9"
            with open("superops.txt", "w") as f:
                f.write(sample)
        self.vitals.operator_list = self.read_ops_file(read_super_ops)

    def getmemoryusage(self):
        """Returns allocated memory in bytes. This command
        currently only works for *NIX based systems.
        """
        if not resource or not os.name == "posix":
            raise OSError(
                "Your current OS (%s) does not support"
                " this command at this time." % os.name)
        if self.proc is None:
            self.log.debug("There is no running server to getmemoryusage().")
            return 0
        try:
            with open("/proc/%d/statm" % self.proc.pid) as f:
                getbytes = int(f.read().split(" ")[1]) * resource.getpagesize()
            return getbytes
        except Exception as e:
            raise e

    @staticmethod
    def getstorageavailable(folder):
        """Returns the disk space for the working directory
        in bytes.
        """
        if platform.system() == "Windows":
            free_bytes = ctypes.c_ulonglong(0)
            ctypes.windll.kernel32.GetDiskFreeSpaceExW(
                ctypes.c_wchar_p(folder), None, None,
                ctypes.pointer(free_bytes))
            return free_bytes.value
        else:
            st = os.statvfs(folder)
            return st.f_bavail * st.f_frsize

    @staticmethod
    def stripspecial(text):
        # not sure what this is actually removing...
        # this must be legacy code of some kind
        pass
        a = ""
        it = iter(range(len(text)))
        for i in it:
            char = text[i]
            if char == "\xc2":
                try:
                    next(it)
                    next(it)
                except Exception as e:
                    pass
            else:
                a += char
        return a

    def readconsole(self, buff):
        """Internally-used function that parses a particular
        console line.
        """

        if len(buff) < 1:
            return
        # Standardize the line to only include the text (removing
        # time and log pre-pends)
        line_words = buff.split(' ')[self.prepends_offset:]

        # find the actual offset is where server output line
        # starts (minus date/time and info stamps).
        # .. and load the proper ops file
        if "Starting minecraft server version" in buff and \
                self.prepends_offset == 0:

            for place in range(len(line_words)-1):
                self.prepends_offset = place
                if line_words[place] == "Starting":
                    break

            line_words = buff.split(' ')[self.prepends_offset:]
            self.vitals.version = getargs(line_words, 4)
            semantics = self.vitals.version.split(".")
            release = get_int(getargs(semantics, 0))
            major = get_int(getargs(semantics, 1))
            minor = get_int(getargs(semantics, 2))
            self.vitals.version_compute = minor + (major * 100) + (release * 10000)  # noqa

            # 1.7.6 (protocol 5) is the cutoff where ops.txt became ops.json
            if self.vitals.version_compute > 10705 and self.vitals.protocolVersion < 0:  # noqa
                self.vitals.protocolVersion = 5
                self.wrapper.api.registerPermission("mc1.7.6", value=True)
            if self.vitals.version_compute < 10702 and self.wrapper.proxymode:
                self.log.warning("\nProxy mode cannot run because the "
                                 "server is a pre-Netty version:\n\n"
                                 "http://wiki.vg/Protocol_version_numbers"
                                 "#Versions_before_the_Netty_rewrite\n\n"
                                 "Server will continue in non-proxy mode.")
                self.wrapper.disable_proxymode()
                return

            self.refresh_ops()

        if len(line_words) < 1:
            return

        # the server attempted to print a blank line
        if len(line_words[0]) < 1:
            print('')
            return

        # parse or modify the server output section
        #
        #

        # Over-ride OP help console display
        if "/op <player>" in buff:
            new_usage = "player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]"
            message = buff.replace("player>", new_usage)
            buff = message
        if "/whitelist <on|off" in buff:
            new_usage = "/whitelist <on|off|list|add|remvove|reload|offline|online>"  # noqa
            message = new_usage
            buff = message

        if "While this makes the game possible to play" in buff:
            prefix = " ".join(buff.split(' ')[:self.prepends_offset])

            if not self.wrapper.wrapper_onlinemode:
                message = (
                    "%s Since you are running Wrapper in OFFLINE mode, THIS "
                    "COULD BE SERIOUS!\n%s Wrapper is not handling any"
                    " authentication.\n%s This is only ok if this wrapper "
                    "is not accessible from either port %s or port %s"
                    " (I.e., this wrapper is a multiworld for a hub server, or"
                    " you are doing your own authorization via a plugin)." % (
                        prefix, prefix, prefix,
                        self.vitals.server_port, self.wrapper.proxy.proxy_port))
            else:
                message = (
                    "%s Since you are running Wrapper in proxy mode, this"
                    " should be ok because Wrapper is handling the"
                    " authentication, PROVIDED no one can access port"
                    " %s from outside your network." % (
                        prefix, self.vitals.server_port))

            if self.wrapper.proxymode:
                buff = message

        # read port of server and display proxy port, if applicable
        if "Starting Minecraft server on" in buff:
            self.vitals.server_port = get_int(buff.split(':')[-1:][0])

        # check for server console spam before printing to wrapper console
        server_spaming = False
        for things in self.vitals.spammy_stuff:
            if things in buff:
                server_spaming = True

        # server_spaming setting does not stop it from being parsed below.
        if not server_spaming:
            if not self.server_muted:
                self.wrapper.write_stdout(buff, "server")
            else:
                self.queued_lines.append(buff)

        first_word = getargs(line_words, 0)
        second_word = getargs(line_words, 1)
        # be careful about how these elif's are handled!
        # confirm server start
        if "Done (" in buff:
            self._toggle_server_started()
            self.changestate(STARTED)
            self.log.info("Server started")
            if self.wrapper.proxymode:
                self.log.info("Proxy listening on *:%s", self.wrapper.proxy.proxy_port)  # noqa

        # Getting world name
        elif "Preparing level" in buff:
            self.vitals.worldname = getargs(line_words, 2).replace('"', "")
            self.world = World(self.vitals.worldname, self)

        # Player Message
        elif first_word[0] == "<":
            # get a name out of <name>
            name = self.stripspecial(first_word[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            playerobj = self.getplayer(name)
            if playerobj:
                self.wrapper.events.callevent("player.message", {
                    "player": self.getplayer(name),
                    "message": message,
                    "original": original
                }, abortable=False)
                """ eventdoc
                    <group> core/mcserver.py <group>
    
                    <description> Player chat scrubbed from the console.
                    <description>
    
                    <abortable> No
                    <abortable>
    
                    <comments>
                    This event is triggered by console chat which has already been sent. 
                    This event returns the player object. if used in a string context, 
                    ("%s") it's repr (self.__str__) is self.username (no need to do 
                    str(player) or player.username in plugin code).
                    <comments>
    
                    <payload>
                    "player": playerobject (self.__str__ represents as player.username)
                    "message": <str> type - what the player said in chat. ('hello everyone')
                    "original": The original line of text from the console ('<mcplayer> hello everyone`)
                    <payload>
    
                """  # noqa
            else:
                self.log.debug("Console has chat from '%s', but wrapper has no "
                               "known logged-in player object by that name.", name)  # noqa
        # Player Login
        elif second_word == "logged":
            user_desc = first_word.split("[/")
            name = user_desc[0]
            ip_addr = user_desc[1].split(":")[0]
            eid = get_int(getargs(line_words, 6))
            locationtext = getargs(buff.split(" ("), 1)[:-1].split(", ")
            # spigot versus vanilla
            # SPIGOT - [12:13:19 INFO]: *******[/] logged in with entity id 123 at ([world]316.86789318152546, 67.12426603789697, -191.9069627257038)  # noqa
            # VANILLA - [23:24:34] [Server thread/INFO]: *******[/127.0.0.1:47434] logged in with entity id 149 at (46.29907483845001, 63.0, -270.1293488726086)  # noqa
            if len(locationtext[0].split("]")) > 1:
                x_c = get_int(float(locationtext[0].split("]")[1]))
            else:
                x_c = get_int(float(locationtext[0]))
            y_c = get_int(float(locationtext[1]))
            z_c = get_int(float(locationtext[2]))
            location = x_c, y_c, z_c

            self.login(name, eid, location, ip_addr)

        # Player Logout
        elif "lost connection" in buff:
            name = first_word
            self.logout(name)

        # player action
        elif first_word == "*":
            name = self.stripspecial(second_word)
            message = self.stripspecial(getargsafter(line_words, 2))
            self.wrapper.events.callevent("player.action", {
                "player": self.getplayer(name),
                "action": message
            }, abortable=False)

        # Player Achievement
        elif "has just earned the achievement" in buff:
            name = self.stripspecial(first_word)
            achievement = getargsafter(line_words, 6)
            self.wrapper.events.callevent("player.achievement", {
                "player": name,
                "achievement": achievement
            }, abortable=False)

        # /say command
        elif getargs(
                line_words, 0)[0] == "[" and first_word[-1] == "]":
            if self.getservertype != "vanilla":
                # Unfortunately, Spigot and Bukkit output things
                # that conflict with this.
                return
            name = self.stripspecial(first_word[1:-1])
            message = self.stripspecial(getargsafter(line_words, 1))
            original = getargsafter(line_words, 0)
            self.wrapper.events.callevent("server.say", {
                "player": name,
                "message": message,
                "original": original
            }, abortable=False)

        # Player Death
        elif second_word in self.deathprefixes:
            name = self.stripspecial(first_word)
            self.wrapper.events.callevent("player.death", {
                "player": self.getplayer(name),
                "death": getargsafter(line_words, 1)
            }, abortable=False)

        # server lagged
        elif "Can't keep up!" in buff:
            skipping_ticks = getargs(line_words, 17)
            self.wrapper.events.callevent("server.lagged", {
                "ticks": get_int(skipping_ticks)
            }, abortable=False)

        # player teleport
        elif second_word == "Teleported" and getargs(line_words, 3) == "to":
            playername = getargs(line_words, 2)
            # [SurestTexas00: Teleported SapperLeader to 48.49417131908783, 77.67081086259394, -279.88880690937475]  # noqa
            if playername in self.wrapper.servervitals.players:
                playerobj = self.getplayer(playername)
                playerobj._position = [
                    get_int(float(getargs(line_words, 4).split(",")[0])),
                    get_int(float(getargs(line_words, 5).split(",")[0])),
                    get_int(float(getargs(line_words, 6).split("]")[0])), 0, 0
                ]
                self.wrapper.events.callevent(
                    "player.teleport",
                    {"player": playerobj}, abortable=False)

                """ eventdoc
                    <group> core/mcserver.py <group>

                    <description> When player teleports.
                    <description>

                    <abortable> No <abortable>

                    <comments> driven from console message "Teleported ___ to ....".
                    <comments>

                    <payload>
                    "player": player object
                    <payload>

                """  # noqa
        elif first_word == "Teleported" and getargs(line_words, 2) == "to":
            playername = second_word
            # Teleported SurestTexas00 to 48.49417131908783, 77.67081086259394, -279.88880690937475  # noqa
            if playername in self.wrapper.servervitals.players:
                playerobj = self.getplayer(playername)
                playerobj._position = [
                    get_int(float(getargs(line_words, 3).split(",")[0])),
                    get_int(float(getargs(line_words, 4).split(",")[0])),
                    get_int(float(getargs(line_words, 5))), 0, 0
                ]
                self.wrapper.events.callevent(
                    "player.teleport",
                    {"player": playerobj}, abortable=False)

                """ eventdoc
                    <group> core/mcserver.py <group>
        
                    <description> When player teleports.
                    <description>
        
                    <abortable> No <abortable>
        
                    <comments> driven from console message "Teleported ___ to ....".
                    <comments>
        
                    <payload>
                    "player": player object
                    <payload>
        
                """  # noqa
    # mcserver.py onsecond Event Handlers
    def reboot_timer(self):
        rb_mins = self.reboot_minutes
        rb_mins_warn = self.config["General"]["timed-reboot-warning-minutes"]
        while not self.wrapper.halt.halt:
            time.sleep(1)
            timer = rb_mins - rb_mins_warn
            while self.vitals.state in (STARTED, STARTING):
                timer -= 1
                time.sleep(60)
                if timer > 0:
                    continue
                if timer + rb_mins_warn > 0:
                    if rb_mins_warn + timer > 1:
                        self.broadcast("&cServer will reboot in %d "
                                       "minutes!" % (rb_mins_warn + timer))
                    else:
                        self.broadcast("&cServer will reboot in %d "
                                       "minute!" % (rb_mins_warn + timer))
                        countdown = 59
                        timer -= 1
                        while countdown > 0:
                            time.sleep(1)
                            countdown -= 1
                            if countdown == 0:
                                if self.wrapper.backups_idle():
                                    self.restart(self.reboot_message)
                                else:
                                    self.broadcast(
                                        "&cBackup in progress. Server reboot "
                                        "delayed for one minute..")
                                    countdown = 59
                            if countdown % 15 == 0:
                                self.broadcast("&cServer will reboot in %d "
                                               "seconds" % countdown)
                            if countdown < 6:
                                self.broadcast("&cServer will reboot in %d "
                                               "seconds" % countdown)
                    continue
                if self.wrapper.backups_idle():
                    self.restart(self.reboot_message)
                else:
                    self.broadcast(
                        "&cBackup in progress. Server reboot "
                        "delayed..")
                    timer = rb_mins + rb_mins_warn + 1

    def eachsecond_web(self):
        if time.time() - self.lastsizepoll > 120:
            if self.vitals.worldname is None:
                return True
            self.lastsizepoll = time.time()
            size = 0
            # os.scandir not in standard library on early py2.7.x systems
            for i in os.walk(
                    "%s/%s" % (self.vitals.serverpath, self.vitals.worldname)
            ):
                for f in os.listdir(i[0]):
                    size += os.path.getsize(os.path.join(i[0], f))
            self.vitals.worldsize = size

    def _console_event(self, payload):
        """This function is used in conjunction with event handlers to
        permit a proxy object to make a command call to this server."""

        # make commands pass through the command interface.
        comm_pay = payload["command"].split(" ")
        if len(comm_pay) > 1:
            args = comm_pay[1:]
        else:
            args = [""]
        new_payload = {"player": self.wrapper.xplayer,
                       "command": comm_pay[0],
                       "args": args
                       }
        self.wrapper.commands.playercommand(new_payload)
Esempio n. 30
0
    def __init__(self, wrapper):
        self.log = wrapper.log
        self.config = wrapper.config
        self.encoding = self.config["General"]["encoding"]
        self.serverpath = self.config["General"]["server-directory"]

        self.stop_message = self.config["Misc"]["stop-message"]
        self.reboot_message = self.config["Misc"]["reboot-message"]
        self.restart_message = self.config["Misc"]["default-restart-message"]

        self.reboot_minutes = self.config[
            "General"]["timed-reboot-minutes"]
        self.reboot_warning_minutes = self.config[
            "General"]["timed-reboot-warning-minutes"]

        # These will be used to auto-detect the number of prepend
        # items in the server output.
        self.prepends_offset = 0

        self.wrapper = wrapper
        commargs = self.config["General"]["command"].split(" ")
        self.args = []

        for part in commargs:
            if part[-4:] == ".jar":
                self.args.append("%s/%s" % (self.serverpath, part))
            else:
                self.args.append(part)

        self.api = API(wrapper, "Server", internal=True)

        if "ServerStarted" not in self.wrapper.storage:
            self._toggle_server_started(False)

        self.state = OFF
        self.bootTime = time.time()
        # False/True - whether server will attempt boot
        self.boot_server = self.wrapper.storage["ServerStarted"]
        # whether a stopped server tries rebooting
        self.server_autorestart = self.config["General"]["auto-restart"]
        self.proc = None
        self.rebootWarnings = 0
        self.lastsizepoll = 0
        self.console_output_data = []
        self.spammy_stuff = ["found nothing", "vehicle of", "Wrong location!",
                             "Tried to add entity"]
        self.server_muted = False
        self.queued_lines = []
        self.server_stalled = False
        self.deathprefixes = ["fell", "was", "drowned", "blew", "walked",
                              "went", "burned", "hit", "tried", "died", "got",
                              "starved", "suffocated", "withered", "shot"]

        if not self.wrapper.storage["ServerStarted"]:
            self.log.warning(
                "NOTE: Server was in 'STOP' state last time  Wrapper.py was"
                " running. To start the server, run /start.")

        # Server Information
        self.players = {}
        self.player_eids = {}
        self.worldname = None
        self.worldSize = 0
        self.maxPlayers = 20
        # -1 until proxy mode checks the server's MOTD on boot
        self.protocolVersion = -1
        # this is string name of the version, collected by console output
        self.version = None
        # a comparable number = x0y0z, where x, y, z = release,
        #  major, minor, of version.
        self.version_compute = 0
        # this port should be hidden from outside traffic.
        self.server_port = "25564"

        self.world = None
        self.entity_control = None
        self.motd = None
        # -1 until a player logs on and server sends a time update
        self.timeofday = -1
        self.onlineMode = True
        self.serverIcon = None

        # get OPs
        self.ownernames = {}
        self.operator_list = []
        self.refresh_ops()

        self.properties = {}

        # This will be redone on server start. However, it
        # has to be done immediately to get worldname; otherwise a
        # "None" folder gets created in the server folder.
        self.reloadproperties()

        # don't reg. an unused event.  The timer still is running, we
        #  just have not cluttered the events holder with another
        #  registration item.
        if self.config["General"]["timed-reboot"] or self.config[
                "Web"]["web-enabled"]:
            self.api.registerEvent("timer.second", self.eachsecond)
Esempio n. 31
0
import api

from api.base import API

#from api.base import API
api = API(wrapper, "Web", internal=True)
world = api.minecraft.getWorld()