Added SSH support, based on patch by hagna (issue 166).

This commit is contained in:
Griatch 2011-05-27 17:47:35 +00:00
parent d2400a8a6b
commit 7c56c69cea
7 changed files with 445 additions and 102 deletions

View file

@ -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"}

View file

@ -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.
""" """

View file

@ -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

View file

@ -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
View 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

View file

@ -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'.

View file

@ -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-->