Added SSH support, based on patch by hagna (issue 166).
This commit is contained in:
parent
d2400a8a6b
commit
7c56c69cea
7 changed files with 445 additions and 102 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
This defines the cmdset for the red_button. Here we have defined
|
This defines the cmdset for the red_button. Here we have defined
|
||||||
the commands and the cmdset in the same module, but if you
|
the commands and the cmdset in the same module, but if you
|
||||||
have many different commands to merge it if often better
|
have many different commands to merge it is often better
|
||||||
to define the cmdset separately, picking and choosing from
|
to define the cmdset separately, picking and choosing from
|
||||||
among the available commands as to what should be included in the
|
among the available commands as to what should be included in the
|
||||||
cmdset - this way you can often re-use the commands too.
|
cmdset - this way you can often re-use the commands too.
|
||||||
|
|
@ -10,11 +10,13 @@ cmdset - this way you can often re-use the commands too.
|
||||||
import random
|
import random
|
||||||
from src.commands.cmdset import CmdSet
|
from src.commands.cmdset import CmdSet
|
||||||
from game.gamesrc.commands.basecommand import Command
|
from game.gamesrc.commands.basecommand import Command
|
||||||
|
from game.gamesrc.scripts.examples import red_button_scripts as scriptexamples
|
||||||
|
|
||||||
|
|
||||||
# Some simple commands for the red button
|
# Some simple commands for the red button
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
# Commands defined for the red button
|
# Commands defined on the red button
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
class CmdNudge(Command):
|
class CmdNudge(Command):
|
||||||
|
|
@ -33,23 +35,16 @@ class CmdNudge(Command):
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
nudge the lid.
|
nudge the lid. Random chance of success to open it.
|
||||||
"""
|
"""
|
||||||
rand = random.random()
|
rand = random.random()
|
||||||
open_ok = False
|
|
||||||
|
|
||||||
if rand < 0.5:
|
if rand < 0.5:
|
||||||
string = "You nudge at the lid. It seems stuck."
|
self.caller.msg("You nudge at the lid. It seems stuck.")
|
||||||
elif 0.5 <= rand < 0.7:
|
elif 0.5 <= rand < 0.7:
|
||||||
string = "You move the lid back and forth. It won't budge."
|
self.caller.msg("You move the lid back and forth. It won't budge.")
|
||||||
else:
|
else:
|
||||||
string = "You manage to get a nail under the lid. It pops open."
|
self.caller.msg("You manage to get a nail under the lid.")
|
||||||
open_ok = True
|
self.caller.execute_cmd("open lid")
|
||||||
self.caller.msg(string)
|
|
||||||
|
|
||||||
if open_ok:
|
|
||||||
"""open_lid() does its own emits, so defer it until we speak"""
|
|
||||||
self.obj.open_lid()
|
|
||||||
|
|
||||||
class CmdPush(Command):
|
class CmdPush(Command):
|
||||||
"""
|
"""
|
||||||
|
|
@ -66,7 +61,7 @@ class CmdPush(Command):
|
||||||
"""
|
"""
|
||||||
Note that we choose to implement this with checking for
|
Note that we choose to implement this with checking for
|
||||||
if the lid is open/closed. This is because this command
|
if the lid is open/closed. This is because this command
|
||||||
is likely to be tries regardless of the state of the lid.
|
is likely to be tried regardless of the state of the lid.
|
||||||
|
|
||||||
An alternative would be to make two versions of this command
|
An alternative would be to make two versions of this command
|
||||||
and tuck them into the cmdset linked to the Open and Closed
|
and tuck them into the cmdset linked to the Open and Closed
|
||||||
|
|
@ -75,11 +70,17 @@ class CmdPush(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.obj.db.lid_open:
|
if self.obj.db.lid_open:
|
||||||
|
|
||||||
|
# assign the blind state script to the caller.
|
||||||
|
# this will assign the restricted BlindCmdset to
|
||||||
|
# the caller at startup, and remove it again
|
||||||
|
# once the time has run out
|
||||||
|
self.caller.scripts.add(scriptexamples.BlindedState)
|
||||||
|
|
||||||
string = "You reach out to press the big red button ..."
|
string = "You reach out to press the big red button ..."
|
||||||
string += "\n\nA BOOM! A bright light blinds you!"
|
string += "\n\nA BOOM! A bright light blinds you!"
|
||||||
string += "\nThe world goes dark ..."
|
string += "\nThe world goes dark ..."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
self.obj.press_button(self.caller)
|
|
||||||
self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" %
|
self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" %
|
||||||
(self.caller.name, self.caller.name), exclude=self.caller)
|
(self.caller.name, self.caller.name), exclude=self.caller)
|
||||||
else:
|
else:
|
||||||
|
|
@ -111,10 +112,8 @@ class CmdSmashGlass(Command):
|
||||||
string = "You smash your hand against the glass"
|
string = "You smash your hand against the glass"
|
||||||
string += " with all your might. The lid won't budge"
|
string += " with all your might. The lid won't budge"
|
||||||
string += " but you cause quite the tremor through the button's mount."
|
string += " but you cause quite the tremor through the button's mount."
|
||||||
self.caller.msg(string) # have to be called before breakage since that
|
string += "\nIt looks like the button's lamp stopped working for the time being."
|
||||||
# also gives a return feedback to the room.
|
self.obj.lamp_works = False
|
||||||
self.obj.break_lamp()
|
|
||||||
return
|
|
||||||
elif rand < 0.6:
|
elif rand < 0.6:
|
||||||
string = "You hit the lid hard. It doesn't move an inch."
|
string = "You hit the lid hard. It doesn't move an inch."
|
||||||
else:
|
else:
|
||||||
|
|
@ -143,6 +142,19 @@ class CmdOpenLid(Command):
|
||||||
if self.obj.db.lid_locked:
|
if self.obj.db.lid_locked:
|
||||||
self.caller.msg("This lid seems locked in place for the moment.")
|
self.caller.msg("This lid seems locked in place for the moment.")
|
||||||
return
|
return
|
||||||
|
string += "\nA ticking sound is heard, like a winding mechanism. Seems "
|
||||||
|
string += "the lid will soon close again."
|
||||||
|
self.db.lid_open = False
|
||||||
|
# add the relevant cmdsets to button
|
||||||
|
self.obj.cmdset.add(LidClosedCmdsSet)
|
||||||
|
# add the lid-close ticker script
|
||||||
|
self.obj.scripts.add(scriptexamples.LidCloseTimer)
|
||||||
|
|
||||||
|
# add more info to the button description
|
||||||
|
desc = self.obj.db.closed_desc
|
||||||
|
self.obj.db.temp_desc = desc
|
||||||
|
self.obj.db.desc = "%s\n%s" % (desc, "Its glass cover is open and the button exposed.")
|
||||||
|
self.caller.msg(string)
|
||||||
|
|
||||||
self.caller.location.msg_contents("%s opens the lid of the button." %
|
self.caller.location.msg_contents("%s opens the lid of the button." %
|
||||||
(self.caller.name), exclude=self.caller)
|
(self.caller.name), exclude=self.caller)
|
||||||
|
|
@ -163,7 +175,14 @@ class CmdCloseLid(Command):
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"Close the lid"
|
"Close the lid"
|
||||||
self.obj.close_lid()
|
|
||||||
|
if self.db.closed_desc:
|
||||||
|
self.obj.desc = self.db.closed_desc
|
||||||
|
self.obj.db.lid_open = False
|
||||||
|
|
||||||
|
# this will clean out scripts dependent on lid being open.
|
||||||
|
self.obj.scripts.validate()
|
||||||
|
self.caller.msg("You close the button's lid. It clicks back into place.")
|
||||||
self.caller.location.msg_contents("%s closes the button's lid." %
|
self.caller.location.msg_contents("%s closes the button's lid." %
|
||||||
(self.caller.name), exclude=self.caller)
|
(self.caller.name), exclude=self.caller)
|
||||||
|
|
||||||
|
|
@ -253,7 +272,7 @@ class LidClosedCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
key = "LidClosedCmdSet"
|
key = "LidClosedCmdSet"
|
||||||
# default Union is used *except* if we are adding to a
|
# default Union is used *except* if we are adding to a
|
||||||
# cmdset named RedButtonOpen - this one we replace
|
# cmdset named LidOpenCmdSet - this one we replace
|
||||||
# completely.
|
# completely.
|
||||||
key_mergetype = {"LidOpenCmdSet": "Replace"}
|
key_mergetype = {"LidOpenCmdSet": "Replace"}
|
||||||
|
|
||||||
|
|
@ -269,7 +288,7 @@ class LidOpenCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
key = "LidOpenCmdSet"
|
key = "LidOpenCmdSet"
|
||||||
# default Union is used *except* if we are adding to a
|
# default Union is used *except* if we are adding to a
|
||||||
# cmdset named RedButtonClose - this one we replace
|
# cmdset named LidClosedCmdSet - this one we replace
|
||||||
# completely.
|
# completely.
|
||||||
key_mergetype = {"LidClosedCmdSet": "Replace"}
|
key_mergetype = {"LidClosedCmdSet": "Replace"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,14 @@
|
||||||
"""
|
"""
|
||||||
An example script parent for a nice red button object. It has
|
|
||||||
custom commands defined on itself that are only useful in relation to this
|
|
||||||
particular object. See example.py in gamesrc/commands for more info
|
|
||||||
on the pluggable command system.
|
|
||||||
|
|
||||||
Assuming this script remains in gamesrc/parents/examples, create an object
|
This is a more advanced example object. It combines functions from
|
||||||
of this type using @create button:examples.red_button
|
script.examples as well as commands.examples to make an interactive
|
||||||
|
button typeclass.
|
||||||
|
|
||||||
This file also shows the use of the Event system to make the button
|
Create this button with
|
||||||
send a message to the players at regular intervals. To show the use of
|
|
||||||
Events, we are tying two types of events to the red button, one which cause ALL
|
|
||||||
red buttons in the game to blink in sync (gamesrc/events/example.py) and one
|
|
||||||
event which cause the protective glass lid over the button to close
|
|
||||||
again some time after it was opened.
|
|
||||||
|
|
||||||
Note that if you create a test button you must drop it before you can
|
@create/drop examples.red_button.RedButton
|
||||||
see its messages!
|
|
||||||
|
Note that if you must drop the button before you can see its messages!
|
||||||
"""
|
"""
|
||||||
import random
|
import random
|
||||||
from game.gamesrc.objects.baseobjects import Object
|
from game.gamesrc.objects.baseobjects import Object
|
||||||
|
|
@ -28,19 +21,17 @@ from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
|
||||||
|
|
||||||
class RedButton(Object):
|
class RedButton(Object):
|
||||||
"""
|
"""
|
||||||
This class describes an evil red button.
|
This class describes an evil red button. It will use the script
|
||||||
It will use the script definition in
|
definition in game/gamesrc/events/example.py to blink at regular
|
||||||
game/gamesrc/events/example.py to blink
|
intervals. It also uses a series of script and commands to handle
|
||||||
at regular intervals until the lightbulb
|
pushing the button and causing effects when doing so.
|
||||||
breaks. It also use the EventCloselid script to
|
|
||||||
close the lid and do nasty stuff when pressed.
|
|
||||||
"""
|
"""
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
"""
|
"""
|
||||||
This function is called when object is created. Use this
|
This function is called when object is created. Use this
|
||||||
instead of e.g. __init__.
|
instead of e.g. __init__.
|
||||||
"""
|
"""
|
||||||
# store desc
|
# store desc (default, you can change this at creation time)
|
||||||
desc = "This is a large red button, inviting yet evil-looking. "
|
desc = "This is a large red button, inviting yet evil-looking. "
|
||||||
desc += "A closed glass lid protects it."
|
desc += "A closed glass lid protects it."
|
||||||
self.db.desc = desc
|
self.db.desc = desc
|
||||||
|
|
@ -52,9 +43,9 @@ class RedButton(Object):
|
||||||
self.db.lamp_works = True
|
self.db.lamp_works = True
|
||||||
self.db.lid_locked = False
|
self.db.lid_locked = False
|
||||||
|
|
||||||
# set the default cmdset to the object, permanent=True means a
|
# set the default cmdset to the object. This will by default surivive
|
||||||
# script will automatically be created to always add this.
|
# a server reboot (otherwise we could have used permanent=False).
|
||||||
self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True)
|
self.cmdset.add_default(cmdsetexamples.DefaultCmdSet)
|
||||||
|
|
||||||
# since the other cmdsets relevant to the button are added 'on the fly',
|
# since the other cmdsets relevant to the button are added 'on the fly',
|
||||||
# we need to setup custom scripts to do this for us (also, these scripts
|
# we need to setup custom scripts to do this for us (also, these scripts
|
||||||
|
|
@ -68,7 +59,7 @@ class RedButton(Object):
|
||||||
|
|
||||||
def open_lid(self, feedback=True):
|
def open_lid(self, feedback=True):
|
||||||
"""
|
"""
|
||||||
Open the glass lid and start the timer so it will soon close
|
Opens the glass lid and start the timer so it will soon close
|
||||||
again.
|
again.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,99 +5,80 @@ with Evennia's permissions system.
|
||||||
To call these locks, make sure this module is included in the
|
To call these locks, make sure this module is included in the
|
||||||
settings tuple PERMISSION_FUNC_MODULES then define a lock on the form
|
settings tuple PERMISSION_FUNC_MODULES then define a lock on the form
|
||||||
'<access_type>:func(args)' and add it to the object's lockhandler.
|
'<access_type>:func(args)' and add it to the object's lockhandler.
|
||||||
Run the check method of the handler to execute the lock check.
|
Run the access() method of the handler to execute the lock check.
|
||||||
|
|
||||||
Note that accessing_obj and accessed_obj can be any object type
|
Note that accessing_obj and accessed_obj can be any object type
|
||||||
with a lock variable/field, so be careful to not expect
|
with a lock variable/field, so be careful to not expect
|
||||||
a certain object type.
|
a certain object type.
|
||||||
|
|
||||||
|
|
||||||
|
Appendix: MUX locks
|
||||||
|
|
||||||
|
Below is a list nicked from the MUX help file on the locks available
|
||||||
|
in standard MUX. Most of these are not relevant to core Evennia since
|
||||||
|
locks in Evennia are considerably more flexible and can be implemented
|
||||||
|
on an individual command/typeclass basis rather than as globally
|
||||||
|
available like the MUX ones. So many of these are not available in
|
||||||
|
basic Evennia, but could all be implemented easily if needed for the
|
||||||
|
individual game.
|
||||||
|
|
||||||
|
MUX Name: Affects: Effect:
|
||||||
MUX locks
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
Below is a list nicked from the MUX docs on the locks available
|
|
||||||
in MUX. These are not all necessarily relevant to an Evennia game
|
|
||||||
but to show they are all possible with Evennia, each entry is a
|
|
||||||
suggestion on how one could implement similar functionality in Evennia.
|
|
||||||
|
|
||||||
Name: Affects: Effect:
|
|
||||||
-------------------------------------------------------------------------
|
|
||||||
DefaultLock: Exits: controls who may traverse the exit to
|
DefaultLock: Exits: controls who may traverse the exit to
|
||||||
its destination.
|
its destination.
|
||||||
Evennia: specialized permission key
|
Evennia: "traverse:<lockfunc()>"
|
||||||
'traverse' checked in move method
|
|
||||||
Rooms: controls whether the player sees the SUCC
|
Rooms: controls whether the player sees the SUCC
|
||||||
or FAIL message for the room following the
|
or FAIL message for the room following the
|
||||||
room description when looking at the room.
|
room description when looking at the room.
|
||||||
Evennia: This is better done by implementing
|
Evennia: Custom typeclass
|
||||||
a clever room class ...
|
|
||||||
Players/Things: controls who may GET the object.
|
Players/Things: controls who may GET the object.
|
||||||
Evennia: specialized permission key 'get'
|
Evennia: "get:<lockfunc()"
|
||||||
defined on object, checked by get command
|
|
||||||
EnterLock: Players/Things: controls who may ENTER the object
|
EnterLock: Players/Things: controls who may ENTER the object
|
||||||
Evennia: specialized permission key 'enter'
|
Evennia:
|
||||||
defined on object, checked by move command
|
|
||||||
GetFromLock: All but Exits: controls who may gets things from a given
|
GetFromLock: All but Exits: controls who may gets things from a given
|
||||||
location.
|
location.
|
||||||
Evennia: Probably done best with a lock function
|
Evennia:
|
||||||
that searches the database for permitted users
|
|
||||||
GiveLock: Players/Things: controls who may give the object.
|
GiveLock: Players/Things: controls who may give the object.
|
||||||
Evennia: specialized permission key 'give'
|
Evennia:
|
||||||
checked by the give command
|
|
||||||
LeaveLock: Players/Things: controls who may LEAVE the object.
|
LeaveLock: Players/Things: controls who may LEAVE the object.
|
||||||
Evennia: specialized permission key 'leave'
|
Evennia:
|
||||||
checked by move command
|
|
||||||
LinkLock: All but Exits: controls who may link to the location if the
|
LinkLock: All but Exits: controls who may link to the location if the
|
||||||
location is LINK_OK (for linking exits or
|
location is LINK_OK (for linking exits or
|
||||||
setting drop-tos) or ABODE (for setting
|
setting drop-tos) or ABODE (for setting
|
||||||
homes)
|
homes)
|
||||||
Evennia: specialized permission key 'link'
|
Evennia:
|
||||||
set on obj and checked by link command
|
|
||||||
MailLock: Players: controls who may @mail the player.
|
MailLock: Players: controls who may @mail the player.
|
||||||
Evennia: Lock function that pulls the
|
Evennia:
|
||||||
config from the player to see if the
|
|
||||||
calling player is on the blacklist/whitelist
|
|
||||||
OpenLock: All but Exits: controls who may open an exit.
|
OpenLock: All but Exits: controls who may open an exit.
|
||||||
Evennia: specialized permission key 'open'
|
Evennia:
|
||||||
set on exit, checked by open command
|
|
||||||
PageLock: Players: controls who may page the player.
|
PageLock: Players: controls who may page the player.
|
||||||
Evennia: see Maillock
|
Evennia: "send:<lockfunc()>"
|
||||||
ParentLock: All: controls who may make @parent links to the
|
ParentLock: All: controls who may make @parent links to the
|
||||||
object.
|
object.
|
||||||
Evennia: This is handled with typeclasses
|
Evennia: Typeclasses and "puppet:<lockstring()>"
|
||||||
and typeclass switching instead.
|
|
||||||
ReceiveLock: Players/Things: controls who may give things to the object.
|
ReceiveLock: Players/Things: controls who may give things to the object.
|
||||||
Evennia: See GiveLock
|
Evennia:
|
||||||
SpeechLock: All but Exits: controls who may speak in that location
|
SpeechLock: All but Exits: controls who may speak in that location
|
||||||
Evennia: Lock function checking if there
|
Evennia:
|
||||||
is some special restrictions on the room
|
|
||||||
(game dependent)
|
|
||||||
TeloutLock: All but Exits: controls who may teleport out of the
|
TeloutLock: All but Exits: controls who may teleport out of the
|
||||||
location.
|
location.
|
||||||
Evennia: See LeaveLock
|
Evennia:
|
||||||
TportLock: Rooms/Things: controls who may teleport there
|
TportLock: Rooms/Things: controls who may teleport there
|
||||||
Evennia: See EnterLock
|
Evennia:
|
||||||
UseLock: All but Exits: controls who may USE the object, GIVE the
|
UseLock: All but Exits: controls who may USE the object, GIVE the
|
||||||
object money and have the PAY attributes
|
object money and have the PAY attributes
|
||||||
run, have their messages heard and possibly
|
run, have their messages heard and possibly
|
||||||
acted on by LISTEN and AxHEAR, and invoke
|
acted on by LISTEN and AxHEAR, and invoke
|
||||||
$-commands stored on the object.
|
$-commands stored on the object.
|
||||||
Evennia: Implemented per game
|
Evennia: Commands and Cmdsets.
|
||||||
DropLock: All but rooms: controls who may drop that object.
|
DropLock: All but rooms: controls who may drop that object.
|
||||||
Evennia: specialized permission key 'drop'
|
Evennia:
|
||||||
set on room, checked by drop command.
|
|
||||||
VisibleLock: All: Controls object visibility when the object
|
VisibleLock: All: Controls object visibility when the object
|
||||||
is not dark and the looker passes the lock.
|
is not dark and the looker passes the lock.
|
||||||
In DARK locations, the object must also be
|
In DARK locations, the object must also be
|
||||||
set LIGHT and the viewer must pass the
|
set LIGHT and the viewer must pass the
|
||||||
VisibleLock.
|
VisibleLock.
|
||||||
Evennia: Better done with Scripts implementing
|
Evennia: Room typeclass with Dark/light script
|
||||||
a dark state/cmdset. For a single object,
|
|
||||||
use a specialized permission key 'visible'
|
|
||||||
set on object and checked by look command.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,11 @@ SERVERNAME = settings.SERVERNAME
|
||||||
VERSION = get_evennia_version()
|
VERSION = get_evennia_version()
|
||||||
|
|
||||||
TELNET_PORTS = settings.TELNET_PORTS
|
TELNET_PORTS = settings.TELNET_PORTS
|
||||||
|
SSH_PORTS = settings.SSH_PORTS
|
||||||
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
||||||
|
|
||||||
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS
|
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS
|
||||||
|
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS
|
||||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS
|
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS
|
||||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||||
IMC2_ENABLED = settings.IMC2_ENABLED
|
IMC2_ENABLED = settings.IMC2_ENABLED
|
||||||
|
|
@ -149,6 +151,8 @@ class Evennia(object):
|
||||||
print ' %s (%s) started on port(s):' % (SERVERNAME, VERSION)
|
print ' %s (%s) started on port(s):' % (SERVERNAME, VERSION)
|
||||||
if TELNET_ENABLED:
|
if TELNET_ENABLED:
|
||||||
print " telnet: " + ", ".join([str(port) for port in TELNET_PORTS])
|
print " telnet: " + ", ".join([str(port) for port in TELNET_PORTS])
|
||||||
|
if SSH_ENABLED:
|
||||||
|
print " ssh: " + ", ".join([str(port) for port in SSH_PORTS])
|
||||||
if WEBSERVER_ENABLED:
|
if WEBSERVER_ENABLED:
|
||||||
clientstring = ""
|
clientstring = ""
|
||||||
if WEBCLIENT_ENABLED:
|
if WEBCLIENT_ENABLED:
|
||||||
|
|
@ -203,6 +207,18 @@ if TELNET_ENABLED:
|
||||||
telnet_service.setName('EvenniaTelnet%s' % port)
|
telnet_service.setName('EvenniaTelnet%s' % port)
|
||||||
EVENNIA.services.addService(telnet_service)
|
EVENNIA.services.addService(telnet_service)
|
||||||
|
|
||||||
|
if SSH_ENABLED:
|
||||||
|
|
||||||
|
from src.server import ssh
|
||||||
|
|
||||||
|
for port in SSH_PORTS:
|
||||||
|
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
|
||||||
|
'protocolArgs':()})
|
||||||
|
|
||||||
|
ssh_service = internet.TCPServer(port, factory)
|
||||||
|
ssh_service.setName('EvenniaSSH%s' % port)
|
||||||
|
EVENNIA.services.addService(ssh_service)
|
||||||
|
|
||||||
if WEBSERVER_ENABLED:
|
if WEBSERVER_ENABLED:
|
||||||
|
|
||||||
# a django-compatible webserver.
|
# a django-compatible webserver.
|
||||||
|
|
|
||||||
332
src/server/ssh.py
Normal file
332
src/server/ssh.py
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
"""
|
||||||
|
This module implements the ssh (Secure SHell) protocol for encrypted
|
||||||
|
connections.
|
||||||
|
|
||||||
|
This depends on a generic session module that implements
|
||||||
|
the actual login procedure of the game, tracks
|
||||||
|
sessions etc.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
from twisted.cred.checkers import credentials
|
||||||
|
from twisted.cred.portal import Portal
|
||||||
|
from twisted.conch.ssh.keys import Key
|
||||||
|
from twisted.conch.interfaces import IConchUser
|
||||||
|
from twisted.conch.ssh.userauth import SSHUserAuthServer
|
||||||
|
from twisted.conch.ssh import common
|
||||||
|
from twisted.conch.insults import insults
|
||||||
|
from twisted.conch.manhole_ssh import TerminalRealm, _Glue, ConchFactory
|
||||||
|
from twisted.conch.manhole import Manhole, recvline
|
||||||
|
from twisted.internet import defer
|
||||||
|
from django.conf import settings
|
||||||
|
from src.server import session
|
||||||
|
from src.utils import ansi, utils, logger
|
||||||
|
|
||||||
|
|
||||||
|
ENCODINGS = settings.ENCODINGS
|
||||||
|
|
||||||
|
CTRL_C = '\x03'
|
||||||
|
CTRL_D = '\x04'
|
||||||
|
CTRL_BACKSLASH = '\x1c'
|
||||||
|
CTRL_L = '\x0c'
|
||||||
|
|
||||||
|
class SshProtocol(Manhole, session.Session):
|
||||||
|
"""
|
||||||
|
Each player connecting over ssh gets this protocol assigned to
|
||||||
|
them. All communication between game and player goes through
|
||||||
|
here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def terminalSize(self, width, height):
|
||||||
|
"""
|
||||||
|
Initialize the terminal and connect to the new session.
|
||||||
|
"""
|
||||||
|
# Clear the previous input line, redraw it at the new
|
||||||
|
# cursor position
|
||||||
|
self.terminal.eraseDisplay()
|
||||||
|
self.terminal.cursorHome()
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
# initialize the session
|
||||||
|
self.session_connect(self.getClientAddress())
|
||||||
|
|
||||||
|
|
||||||
|
def connectionMade(self):
|
||||||
|
"""
|
||||||
|
This is called when the connection is first
|
||||||
|
established.
|
||||||
|
"""
|
||||||
|
recvline.HistoricRecvLine.connectionMade(self)
|
||||||
|
self.keyHandlers[CTRL_C] = self.handle_INT
|
||||||
|
self.keyHandlers[CTRL_D] = self.handle_EOF
|
||||||
|
self.keyHandlers[CTRL_L] = self.handle_FF
|
||||||
|
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
|
||||||
|
|
||||||
|
|
||||||
|
def handle_INT(self):
|
||||||
|
"""
|
||||||
|
Handle ^C as an interrupt keystroke by resetting the current input
|
||||||
|
variables to their initial state.
|
||||||
|
"""
|
||||||
|
self.lineBuffer = []
|
||||||
|
self.lineBufferIndex = 0
|
||||||
|
|
||||||
|
self.terminal.nextLine()
|
||||||
|
self.terminal.write("KeyboardInterrupt")
|
||||||
|
self.terminal.nextLine()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_EOF(self):
|
||||||
|
"""
|
||||||
|
Handles EOF generally used to exit.
|
||||||
|
"""
|
||||||
|
if self.lineBuffer:
|
||||||
|
self.terminal.write('\a')
|
||||||
|
else:
|
||||||
|
self.handle_QUIT()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_FF(self):
|
||||||
|
"""
|
||||||
|
Handle a 'form feed' byte - generally used to request a screen
|
||||||
|
refresh/redraw.
|
||||||
|
"""
|
||||||
|
self.terminal.eraseDisplay()
|
||||||
|
self.terminal.cursorHome()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_QUIT(self):
|
||||||
|
"""
|
||||||
|
Quit, end, and lose the connection.
|
||||||
|
"""
|
||||||
|
self.terminal.loseConnection()
|
||||||
|
|
||||||
|
|
||||||
|
def connectionLost(self, reason=None, step=1):
|
||||||
|
"""
|
||||||
|
This is executed when the connection is lost for
|
||||||
|
whatever reason.
|
||||||
|
|
||||||
|
Closing the connection takes two steps
|
||||||
|
|
||||||
|
step 1 - is the default and is used when this method is
|
||||||
|
called automatically. The method should then call self.session_disconnect().
|
||||||
|
Step 2 - means this method is called from at_disconnect(). At this point
|
||||||
|
the sessions are assumed to have been handled, and so the transport can close
|
||||||
|
without further ado.
|
||||||
|
"""
|
||||||
|
insults.TerminalProtocol.connectionLost(self, reason)
|
||||||
|
if step == 1:
|
||||||
|
self.session_disconnect()
|
||||||
|
else:
|
||||||
|
self.terminal.loseConnection()
|
||||||
|
|
||||||
|
|
||||||
|
def getClientAddress(self):
|
||||||
|
"""
|
||||||
|
Returns the client's address and port in a tuple. For example
|
||||||
|
('127.0.0.1', 41917)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.terminal.transport.getPeer()
|
||||||
|
|
||||||
|
|
||||||
|
def lineReceived(self, string):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Communication Player -> Evennia. Any line return indicates a
|
||||||
|
command for the purpose of the MUD. So we take the user input
|
||||||
|
and pass it on to the game engine.
|
||||||
|
"""
|
||||||
|
self.at_data_in(string)
|
||||||
|
|
||||||
|
def lineSend(self, string):
|
||||||
|
"""
|
||||||
|
Communication Evennia -> Player
|
||||||
|
Any string sent should already have been
|
||||||
|
properly formatted and processed
|
||||||
|
before reaching this point.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for line in string.split('\n'):
|
||||||
|
self.terminal.write(line) #this is the telnet-specific method for sending
|
||||||
|
self.terminal.nextLine()
|
||||||
|
|
||||||
|
# session-general method hooks
|
||||||
|
|
||||||
|
def at_connect(self):
|
||||||
|
"""
|
||||||
|
Show the banner screen.
|
||||||
|
"""
|
||||||
|
self.telnet_markup = True
|
||||||
|
# show connection screen
|
||||||
|
self.execute_cmd('look')
|
||||||
|
|
||||||
|
def at_login(self, player):
|
||||||
|
"""
|
||||||
|
Called after authentication. self.logged_in=True at this point.
|
||||||
|
"""
|
||||||
|
if player.has_attribute('telnet_markup'):
|
||||||
|
self.telnet_markup = player.get_attribute("telnet_markup")
|
||||||
|
else:
|
||||||
|
self.telnet_markup = True
|
||||||
|
|
||||||
|
def at_disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||||
|
"""
|
||||||
|
Disconnect from server
|
||||||
|
"""
|
||||||
|
char = self.get_character()
|
||||||
|
if char:
|
||||||
|
char.at_disconnect()
|
||||||
|
self.at_data_out(reason)
|
||||||
|
self.connectionLost(step=2)
|
||||||
|
|
||||||
|
def at_data_out(self, string, data=None):
|
||||||
|
"""
|
||||||
|
Data Evennia -> Player access hook. 'data' argument is ignored.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
string = utils.to_str(string, encoding=self.encoding)
|
||||||
|
except Exception, e:
|
||||||
|
self.lineSend(str(e))
|
||||||
|
return
|
||||||
|
nomarkup = not self.telnet_markup
|
||||||
|
raw = False
|
||||||
|
if type(data) == dict:
|
||||||
|
# check if we want escape codes to go through unparsed.
|
||||||
|
raw = data.get("raw", self.telnet_markup)
|
||||||
|
# check if we want to remove all markup
|
||||||
|
nomarkup = data.get("nomarkup", not self.telnet_markup)
|
||||||
|
if raw:
|
||||||
|
self.lineSend(string)
|
||||||
|
else:
|
||||||
|
self.lineSend(ansi.parse_ansi(string, strip_ansi=nomarkup))
|
||||||
|
|
||||||
|
def at_data_in(self, string, data=None):
|
||||||
|
"""
|
||||||
|
Line from Player -> Evennia. 'data' argument is not used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
string = utils.to_unicode(string, encoding=self.encoding)
|
||||||
|
self.execute_cmd(string)
|
||||||
|
return
|
||||||
|
except Exception, e:
|
||||||
|
logger.log_errmsg(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraInfoAuthServer(SSHUserAuthServer):
|
||||||
|
def auth_password(self, packet):
|
||||||
|
"""
|
||||||
|
Password authentication.
|
||||||
|
|
||||||
|
Used mostly for setting up the transport so we can query
|
||||||
|
username and password later.
|
||||||
|
"""
|
||||||
|
password = common.getNS(packet[1:])[0]
|
||||||
|
c = credentials.UsernamePassword(self.user, password)
|
||||||
|
c.transport = self.transport
|
||||||
|
return self.portal.login(c, None, IConchUser).addErrback(
|
||||||
|
self._ebPassword)
|
||||||
|
|
||||||
|
|
||||||
|
class AnyAuth(object):
|
||||||
|
"""
|
||||||
|
Special auth method that accepts any credentials.
|
||||||
|
"""
|
||||||
|
credentialInterfaces = (credentials.IUsernamePassword,)
|
||||||
|
|
||||||
|
def requestAvatarId(self, c):
|
||||||
|
"Generic credentials"
|
||||||
|
up = credentials.IUsernamePassword(c, None)
|
||||||
|
username = up.username
|
||||||
|
password = up.password
|
||||||
|
src_ip = str(up.transport.transport.getPeer().host)
|
||||||
|
return defer.succeed(username)
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalSessionTransport_getPeer:
|
||||||
|
"""
|
||||||
|
Taken from twisted's TerminalSessionTransport which doesn't
|
||||||
|
provide getPeer to the transport. This one does.
|
||||||
|
"""
|
||||||
|
def __init__(self, proto, chainedProtocol, avatar, width, height):
|
||||||
|
self.proto = proto
|
||||||
|
self.avatar = avatar
|
||||||
|
self.chainedProtocol = chainedProtocol
|
||||||
|
|
||||||
|
session = self.proto.session
|
||||||
|
|
||||||
|
self.proto.makeConnection(
|
||||||
|
_Glue(write=self.chainedProtocol.dataReceived,
|
||||||
|
loseConnection=lambda: avatar.conn.sendClose(session),
|
||||||
|
name="SSH Proto Transport"))
|
||||||
|
|
||||||
|
def loseConnection():
|
||||||
|
self.proto.loseConnection()
|
||||||
|
|
||||||
|
def getPeer():
|
||||||
|
session.conn.transport.transport.getPeer()
|
||||||
|
|
||||||
|
self.chainedProtocol.makeConnection(
|
||||||
|
_Glue(getPeer=getPeer, write=self.proto.write,
|
||||||
|
loseConnection=loseConnection,
|
||||||
|
name="Chained Proto Transport"))
|
||||||
|
|
||||||
|
self.chainedProtocol.terminalProtocol.terminalSize(width, height)
|
||||||
|
|
||||||
|
def getKeyPair():
|
||||||
|
"""
|
||||||
|
This function looks for RSA keypair files in the current directory. If they
|
||||||
|
do not exist, the keypair is created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not (os.path.exists('ssh-public.key') and os.path.exists('ssh-private.key')):
|
||||||
|
# No keypair exists. Generate a new RSA keypair
|
||||||
|
print " Generating SSH RSA keypair (only done once) ...",
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
KEY_LENGTH = 1024
|
||||||
|
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
||||||
|
publicKeyString = rsaKey.public().toString(type="OPENSSH")
|
||||||
|
privateKeyString = rsaKey.toString(type="OPENSSH")
|
||||||
|
|
||||||
|
# save keys for the future.
|
||||||
|
file('ssh-public.key', 'w+b').write(publicKeyString)
|
||||||
|
file('ssh-private.key', 'w+b').write(privateKeyString)
|
||||||
|
print " done."
|
||||||
|
else:
|
||||||
|
publicKeyString = file('ssh-public.key').read()
|
||||||
|
privateKeyString = file('ssh-private.key').read()
|
||||||
|
|
||||||
|
return Key.fromString(publicKeyString), Key.fromString(privateKeyString)
|
||||||
|
|
||||||
|
def makeFactory(configdict):
|
||||||
|
"""
|
||||||
|
Creates the ssh server factory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def chainProtocolFactory():
|
||||||
|
return insults.ServerProtocol(
|
||||||
|
configdict['protocolFactory'],
|
||||||
|
*configdict.get('protocolConfigdict', ()),
|
||||||
|
**configdict.get('protocolKwArgs', {}))
|
||||||
|
|
||||||
|
rlm = TerminalRealm()
|
||||||
|
rlm.transportFactory = TerminalSessionTransport_getPeer
|
||||||
|
rlm.chainedProtocolFactory = chainProtocolFactory
|
||||||
|
factory = ConchFactory(Portal(rlm))
|
||||||
|
|
||||||
|
# create/get RSA keypair
|
||||||
|
publicKey, privateKey = getKeyPair()
|
||||||
|
|
||||||
|
factory.publicKeys = {'ssh-rsa': publicKey}
|
||||||
|
factory.privateKeys = {'ssh-rsa': privateKey}
|
||||||
|
|
||||||
|
factory.services = factory.services.copy()
|
||||||
|
factory.services['ssh-userauth'] = ExtraInfoAuthServer
|
||||||
|
|
||||||
|
factory.portal.registerChecker(AnyAuth())
|
||||||
|
|
||||||
|
return factory
|
||||||
|
|
@ -36,6 +36,10 @@ WEBSERVER_PORTS = [8000]
|
||||||
# Start the evennia ajax client on /webclient
|
# Start the evennia ajax client on /webclient
|
||||||
# (the webserver must also be running)
|
# (the webserver must also be running)
|
||||||
WEBCLIENT_ENABLED = True
|
WEBCLIENT_ENABLED = True
|
||||||
|
# Activate SSH protocol
|
||||||
|
SSH_ENABLED = False
|
||||||
|
# Ports to use for SSH
|
||||||
|
SSH_PORTS = [8022]
|
||||||
# Activate full persistence if you want everything in-game to be
|
# Activate full persistence if you want everything in-game to be
|
||||||
# stored to the database. With it set, you can do typeclass.attr=value
|
# stored to the database. With it set, you can do typeclass.attr=value
|
||||||
# and value will be saved to the database under the name 'attr'.
|
# and value will be saved to the database under the name 'attr'.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<link rel='stylesheet' type="text/css" media="screen" href="/media/css/webclient.css">
|
<link rel='stylesheet' type="text/css" media="screen" href="/media/css/webclient.css">
|
||||||
|
|
||||||
<!-- Importing the online jQuery javascript library -->
|
<!-- Importing the online jQuery javascript library -->
|
||||||
<script src="http://code.jquery.com/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script>
|
<script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
|
||||||
<!--for offline testing, download the jquery library from jquery.com-->
|
<!--for offline testing, download the jquery library from jquery.com-->
|
||||||
<!--script src="/media/javascript/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script-->
|
<!--script src="/media/javascript/jquery-1.4.4.js" type="text/javascript" charset="utf-8"></script-->
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue