From 8a2e362b7cfca4fe420e6653a695de44bb09af00 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 20 Aug 2017 23:11:33 +0200 Subject: [PATCH] Make IDLE_TIMEOUT avoidable with account-lock. Resolves #701. Check the Account-lock 'no_idle_disconnect before kicking an idle session. This also means superusers will never be kicked. Move the idle task to the Server to avoid lock imports in Portal. Make the 'lock' command also able to target Accounts. Also some other fixes. --- evennia/accounts/accounts.py | 9 ++++--- evennia/commands/default/account.py | 6 +++-- evennia/commands/default/building.py | 26 +++++++++++------- evennia/server/portal/portal.py | 27 ------------------- evennia/server/portal/portalsessionhandler.py | 1 - evennia/server/portal/ssh.py | 6 +++++ evennia/server/portal/telnet.py | 6 +++++ evennia/server/portal/webclient_ajax.py | 6 +++++ evennia/server/server.py | 16 +++++++++-- evennia/server/serversession.py | 5 ++-- evennia/server/sessionhandler.py | 7 ++--- 11 files changed, 63 insertions(+), 52 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 7e896e1c1d..a12cb61128 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -190,7 +190,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): # session-related methods - def disconnect_session_from_account(self, session): + def disconnect_session_from_account(self, session, reason=None): """ Access method for disconnecting a given session from the account (connection happens automatically in the @@ -198,12 +198,13 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): Args: session (Session): Session to disconnect. + reason (str, optional): Eventual reason for the disconnect. """ global _SESSIONS if not _SESSIONS: from evennia.server.sessionhandler import SESSIONS as _SESSIONS - _SESSIONS.disconnect(session) + _SESSIONS.disconnect(session, reason) # puppeting operations @@ -789,8 +790,8 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): """ - reason = reason and "(%s)" % reason or "" - self._send_to_connect_channel("|R%s disconnected %s|n" % (self.key, reason)) + reason = " (%s)" % reason if reason else "" + self._send_to_connect_channel("|R%s disconnected%s|n" % (self.key, reason)) def at_post_disconnect(self, **kwargs): """ diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index ced83c6e2f..912a2a0a2b 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -657,10 +657,12 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): if 'all' in self.switches: account.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session) + reason = "quit/all" for session in account.sessions.all(): - account.disconnect_session_from_account(session) + account.disconnect_session_from_account(session, reason) else: nsess = len(account.sessions.all()) + reason = "quit" if nsess == 2: account.msg("|RQuitting|n. One session is still connected.", session=self.session) elif nsess > 2: @@ -668,7 +670,7 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): else: # we are quitting the last available session account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session) - account.disconnect_session_from_account(self.session) + account.disconnect_session_from_account(self.session, reason) class CmdColorTest(COMMAND_DEFAULT_CLASS): diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 582ab1f90f..06da867ced 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1735,9 +1735,9 @@ class CmdLock(ObjManipCommand): assign a lock definition to an object Usage: - @lock [ = ] + @lock [ = ] or - @lock[/switch] / + @lock[/switch] / Switch: del - delete given access type @@ -1779,16 +1779,19 @@ class CmdLock(ObjManipCommand): if '/' in self.lhs: # call on the form @lock obj/access_type objname, access_type = [p.strip() for p in self.lhs.split('/', 1)] - obj = caller.search(objname) - if not obj: + if objname.startswith("*"): + obj = caller.search_account(objname.lstrip('*')) + if not obj: + obj = caller.search(objname) + if not obj: + return + if not (obj.access(caller, 'control') or obj.access(caller, "edit")): + caller.msg("You are not allowed to do that.") return lockdef = obj.locks.get(access_type) string = "" if lockdef: if 'del' in self.switches: - if not (obj.access(caller, 'control') or obj.access(caller, "edit")): - caller.msg("You are not allowed to do that.") - return obj.locks.delete(access_type) string = "deleted lock %s" % lockdef else: @@ -1808,9 +1811,12 @@ class CmdLock(ObjManipCommand): return objname, lockdef = self.lhs, self.rhs - obj = caller.search(objname) - if not obj: - return + if objname.startswith("*"): + obj = caller.search_account(objname.lstrip('*')) + if not obj: + obj = caller.search(objname) + if not obj: + return if not (obj.access(caller, 'control') or obj.access(caller, "edit")): caller.msg("You are not allowed to do that.") return diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index f8db12b9e2..ccfb7b02eb 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -73,33 +73,6 @@ AMP_INTERFACE = settings.AMP_INTERFACE AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE -# Maintenance function - this is called repeatedly by the portal. - -_IDLE_TIMEOUT = settings.IDLE_TIMEOUT - - -def _portal_maintenance(): - """ - The maintenance function handles repeated checks and updates that - the server needs to do. It is called every minute. - - """ - # check for idle sessions - now = time.time() - - reason = "Idle timeout exceeded, disconnecting." - for session in [sess for sess in PORTAL_SESSIONS.values() - if (now - sess.cmd_last) > _IDLE_TIMEOUT]: - session.disconnect(reason=reason) - PORTAL_SESSIONS.disconnect(session) - - -if _IDLE_TIMEOUT > 0: - # only start the maintenance task if we care about idling. - _maintenance_task = LoopingCall(_portal_maintenance) - _maintenance_task.start(60) # called every minute - - # ------------------------------------------------------------- # Portal Service object # ------------------------------------------------------------- diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 7da1dd1ec1..171216f8d8 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -268,7 +268,6 @@ class PortalSessionHandler(SessionHandler): data (dict): The session sync data. """ - print("server_logged_in: %s: %s" % (session, data)) session.load_sync_data(data) session.at_login() diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 7f82faf129..e993e74bc3 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -213,6 +213,12 @@ class SshProtocol(Manhole, session.Session): # session-general method hooks + def at_login(self): + """ + Called when this session gets authenticated by the server. + """ + pass + def disconnect(self, reason="Connection closed. Goodbye for now."): """ Disconnect from server. diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 39c236e9e9..4112d85e2a 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -116,6 +116,12 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # do the sync self.sessionhandler.sync(self) + def at_login(self): + """ + Called when this session gets authenticated by the server. + """ + pass + def enableRemote(self, option): """ This sets up the remote-activated options we allow for this protocol. diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index 993e0a011f..4ec78e3eb7 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -107,6 +107,12 @@ class WebClient(resource.Resource): self.keep_alive.stop() self.keep_alive = None + def at_login(self): + """ + Called when this session gets authenticated by the server. + """ + pass + def lineSend(self, csessid, data): """ This adds the data to the buffer and/or sends it to the client diff --git a/evennia/server/server.py b/evennia/server/server.py index 65afc4d873..18b1220c43 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -16,7 +16,7 @@ import os from twisted.web import static from twisted.application import internet, service from twisted.internet import reactor, defer -from twisted.internet.task import LoopingCall, deferLater +from twisted.internet.task import LoopingCall import django django.setup() @@ -36,6 +36,8 @@ from evennia.utils.utils import get_evennia_version, mod_import, make_iter from evennia.comms import channelhandler from evennia.server.sessionhandler import SESSIONS +from django.utils.translation import ugettext as _ + _SA = object.__setattr__ SERVER_PIDFILE = "" @@ -89,11 +91,13 @@ _FLUSH_CACHE = None _IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE _GAMETIME_MODULE = None +_IDLE_TIMEOUT = settings.IDLE_TIMEOUT + def _server_maintenance(): """ This maintenance function handles repeated checks and updates that - the server needs to do. It is called every 5 minutes. + the server needs to do. It is called every minute. """ global EVENNIA, _MAINTENANCE_COUNT, _FLUSH_CACHE, _GAMETIME_MODULE if not _FLUSH_CACHE: @@ -124,6 +128,14 @@ def _server_maintenance(): # validate channels off-sync with scripts evennia.CHANNEL_HANDLER.update() + # handle idle timeouts + reason = _("idle timeout exceeded") + for session in (sess for sess in SESSIONS.values() + if (now - sess.cmd_last) > _IDLE_TIMEOUT): + if not session.account or not \ + session.account.access(session.account, "no_idle_disconnect", default=False): + SESSIONS.disconnect(session, reason=reason) + # Commenting this out, it is probably not needed # with CONN_MAX_AGE set. Keeping it as a reminder # if database-gone-away errors appears again /Griatch diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index e84f5cf7bd..47591383cd 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -9,7 +9,6 @@ are stored on the Portal side) from builtins import object import weakref -import importlib import time from django.utils import timezone from django.conf import settings @@ -232,7 +231,7 @@ class ServerSession(Session): # add the session-level cmdset self.cmdset = CmdSetHandler(self, True) - def at_disconnect(self): + def at_disconnect(self, reason=None): """ Hook called by sessionhandler when disconnecting this session. @@ -245,7 +244,7 @@ class ServerSession(Session): uaccount.last_login = timezone.now() uaccount.save() # calling account hook - account.at_disconnect() + account.at_disconnect(reason) self.logged_in = False if not self.sessionhandler.sessions_from_account(account): # no more sessions connected to this account diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 33b04d1386..825cf6da30 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -500,11 +500,12 @@ class ServerSessionHandler(SessionHandler): if hasattr(session, "account") and session.account: # only log accounts logging off nsess = len(self.sessions_from_account(session.account)) - 1 - string = "Logged out: {account} {address} ({nsessions} sessions(s) remaining)" - string = string.format(account=session.account, address=session.address, nsessions=nsess) + sreason = " ({})".format(reason) if reason else "" + string = "Logged out: {account} {address} ({nsessions} sessions(s) remaining){reason}" + string = string.format(reason=sreason, account=session.account, address=session.address, nsessions=nsess) session.log(string) - session.at_disconnect() + session.at_disconnect(reason) sessid = session.sessid if sessid in self and not hasattr(self, "_disconnect_all"): del self[sessid]