Adding the beginnings of IMC2 support. We are now able to connect and authenticate with an IMC2 network, theoretically without blocking our server thread. We can't actually do anything useful just yet, but stay tuned.
This commit is contained in:
parent
65d8ac8bdc
commit
a7e89c1e54
6 changed files with 272 additions and 1 deletions
24
src/commands/imc2.py
Normal file
24
src/commands/imc2.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
IMC2 user and administrative commands.
|
||||
"""
|
||||
import time
|
||||
from django.conf import settings
|
||||
from src.config.models import ConfigValue
|
||||
from src.objects.models import Object
|
||||
from src import defines_global
|
||||
from src import ansi
|
||||
from src.util import functions_general
|
||||
from src.cmdtable import GLOBAL_CMD_TABLE
|
||||
from src.imc2 import connection as imc2_conn
|
||||
from src.imc2.packets import *
|
||||
|
||||
def cmd_imctest(command):
|
||||
"""
|
||||
Shows a player's inventory.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
source_object.emit_to("Sending")
|
||||
packet = IMC2PacketWhois(source_object, 'Cratylus')
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
|
||||
source_object.emit_to("Sent")
|
||||
GLOBAL_CMD_TABLE.add_command("imctest", cmd_imctest)
|
||||
|
|
@ -66,6 +66,35 @@ DATABASE_HOST = ''
|
|||
# Empty string defaults to localhost. Not used with sqlite3.
|
||||
DATABASE_PORT = ''
|
||||
|
||||
"""
|
||||
IMC Configuration
|
||||
|
||||
This is static and important enough to put in the server-side settings file.
|
||||
Copy and paste this section to your game/settings.py file and change the
|
||||
values to fit your needs.
|
||||
|
||||
Evennia's IMC2 client was developed against MudByte's network. You may
|
||||
register and join it by going to:
|
||||
http://www.mudbytes.net/imc2-intermud-join-network
|
||||
|
||||
Choose "Other unsupported IMC2 version" and enter your information there.
|
||||
You'll want to change the values below to reflect what you entered.
|
||||
"""
|
||||
# Make sure this is True in your settings.py.
|
||||
IMC2_ENABLED = False
|
||||
# The hostname/ip address of your IMC2 server of choice.
|
||||
IMC2_SERVER_ADDRESS = None
|
||||
# The port to connect to on your IMC2 server.
|
||||
IMC2_SERVER_PORT = None
|
||||
# This is your game's IMC2 name.
|
||||
IMC2_MUDNAME = None
|
||||
# Your IMC2 client-side password. Used to authenticate with your network.
|
||||
IMC2_CLIENT_PW = None
|
||||
# Your IMC2 server-side password. Used to verify your network's identity.
|
||||
IMC2_SERVER_PW = None
|
||||
# This isn't something you should generally change.
|
||||
IMC2_PROTOCOL_VERSION = '2'
|
||||
|
||||
# Local time zone for this installation. All choices can be found here:
|
||||
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
|
@ -220,6 +249,7 @@ COMMAND_MODULES = (
|
|||
'src.commands.parents',
|
||||
'src.commands.privileged',
|
||||
'src.commands.search',
|
||||
'src.commands.imc2',
|
||||
)
|
||||
|
||||
"""
|
||||
|
|
|
|||
0
src/imc2/__init__.py
Normal file
0
src/imc2/__init__.py
Normal file
97
src/imc2/connection.py
Normal file
97
src/imc2/connection.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
IMC2 client module. Handles connecting to and communicating with an IMC2 server.
|
||||
"""
|
||||
import telnetlib
|
||||
import time
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
from twisted.internet import reactor, task
|
||||
from twisted.conch.telnet import StatefulTelnetProtocol
|
||||
from django.conf import settings
|
||||
from src.imc2.packets import *
|
||||
|
||||
# The active instance of IMC2Protocol. Set at server startup.
|
||||
IMC2_PROTOCOL_INSTANCE = None
|
||||
|
||||
class IMC2Protocol(StatefulTelnetProtocol):
|
||||
"""
|
||||
Provides the abstraction for the IMC2 protocol. Handles connection,
|
||||
authentication, and all necessary packets.
|
||||
"""
|
||||
def __init__(self):
|
||||
print "IMC2: Client connecting to %s:%s..." % (settings.IMC2_SERVER_ADDRESS,
|
||||
settings.IMC2_SERVER_PORT)
|
||||
global IMC2_PROTOCOL_INSTANCE
|
||||
IMC2_PROTOCOL_INSTANCE = self
|
||||
self.is_authenticated = False
|
||||
self.auth_type = None
|
||||
self.network_name = None
|
||||
self.sequence = None
|
||||
|
||||
def connectionMade(self):
|
||||
"""
|
||||
Triggered after connecting to the IMC2 network.
|
||||
"""
|
||||
print "IMC2: Connected to network server."
|
||||
self.auth_type = "plaintext"
|
||||
print "IMC2: Sending authentication packet."
|
||||
self.send_packet(IMC2PacketAuthPlaintext())
|
||||
|
||||
def send_packet(self, packet):
|
||||
"""
|
||||
Given a sub-class of IMC2Packet, assemble the packet and send it
|
||||
on its way.
|
||||
"""
|
||||
if self.sequence:
|
||||
# This gets incremented with every command.
|
||||
self.sequence += 1
|
||||
|
||||
packet.imc2_protocol = self
|
||||
packet_str = packet.assemble()
|
||||
print "IMC2: SENT> %s" % packet_str
|
||||
self.sendLine(packet_str)
|
||||
|
||||
def _parse_auth_response(self, line):
|
||||
"""
|
||||
Parses the IMC2 network authentication packet.
|
||||
"""
|
||||
if self.auth_type == "plaintext":
|
||||
"""
|
||||
SERVER Sends: PW <servername> <serverpw> version=<version#> <networkname>
|
||||
"""
|
||||
if line[:2] == "PW":
|
||||
line_split = line.split(' ')
|
||||
self.network_name = line_split[4]
|
||||
self.is_authenticated = True
|
||||
self.sequence = time.time()
|
||||
print "IMC2: Successfully authenticated to the '%s' network." % self.network_name
|
||||
|
||||
def lineReceived(self, line):
|
||||
"""
|
||||
Triggered when text is received from the IMC2 network. Figures out
|
||||
what to do with the packet.
|
||||
"""
|
||||
if not self.is_authenticated:
|
||||
self._parse_auth_response(line)
|
||||
else:
|
||||
split_line = line.split(' ')
|
||||
packet_type = split_line[3]
|
||||
if packet_type == "is-alive":
|
||||
pass
|
||||
elif packet_type == "user-cache":
|
||||
pass
|
||||
else:
|
||||
print "receive:", line
|
||||
|
||||
class IMC2ClientFactory(ClientFactory):
|
||||
"""
|
||||
Creates instances of the IMC2Protocol. Should really only ever create one
|
||||
in our particular instance. Tied in via src/server.py.
|
||||
"""
|
||||
protocol = IMC2Protocol
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
print 'connection failed:', reason.getErrorMessage()
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
print 'connection lost:', reason.getErrorMessage()
|
||||
109
src/imc2/packets.py
Normal file
109
src/imc2/packets.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
IMC2 packets. These are pretty well documented at:
|
||||
http://www.mudbytes.net/index.php?a=articles&s=imc2_protocol
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
class IMC2Packet(object):
|
||||
"""
|
||||
Base IMC2 packet class. This should never be used directly. Sub-class
|
||||
and profit.
|
||||
"""
|
||||
# The following fields are all according to the basic packet format of:
|
||||
# <sender>@<origin> <sequence> <route> <packet-type> <target>@<destination> <data...>
|
||||
sender = None
|
||||
origin = settings.IMC2_MUDNAME
|
||||
sequence = None
|
||||
route = settings.IMC2_MUDNAME
|
||||
packet_type = None
|
||||
target = None
|
||||
destination = None
|
||||
# Optional data.
|
||||
optional_data = {}
|
||||
# Reference to the IMC2Protocol object doing the sending.
|
||||
imc2_protocol = None
|
||||
|
||||
def _get_optional_data_string(self):
|
||||
"""
|
||||
Generates the optional data string to tack on to the end of the packet.
|
||||
"""
|
||||
if self.optional_data:
|
||||
data_string = ''
|
||||
for key, value in self.optional_data.items():
|
||||
self.data_string += '%s=%s ' % (key, value)
|
||||
return data_string.strip()
|
||||
else:
|
||||
return ''
|
||||
|
||||
def _get_sender_name(self):
|
||||
"""
|
||||
Calculates the sender name to be sent with the packet.
|
||||
"""
|
||||
if self.sender:
|
||||
name = self.sender.get_name(fullname=False, show_dbref=False,
|
||||
show_flags=False,
|
||||
no_ansi=True)
|
||||
# IMC2 does not allow for spaces.
|
||||
return name.strip().replace(' ', '_')
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
def assemble(self):
|
||||
"""
|
||||
Assembles the packet and returns the ready-to-send string.
|
||||
"""
|
||||
self.sequence = self.imc2_protocol.sequence
|
||||
packet = "%s@%s %s %s %s %s@%s %s\n" % (
|
||||
self._get_sender_name(),
|
||||
self.origin,
|
||||
self.sequence,
|
||||
self.route,
|
||||
self.packet_type,
|
||||
self.target,
|
||||
self.destination,
|
||||
self._get_optional_data_string())
|
||||
return packet.strip()
|
||||
|
||||
class IMC2PacketWhois(IMC2Packet):
|
||||
"""
|
||||
Description:
|
||||
Sends a request to the network for the location of the specified player.
|
||||
|
||||
Data:
|
||||
level=<int> The permission level of the person making the request.
|
||||
|
||||
Example:
|
||||
You@YourMUD 1234567890 YourMUD whois dude@* level=5
|
||||
"""
|
||||
def __init__(self, pobject, whois_target):
|
||||
self.sender = pobject
|
||||
self.packet_type = 'whois'
|
||||
self.target = whois_target
|
||||
self.destination = '*'
|
||||
self.data = {'level': '5'}
|
||||
|
||||
class IMC2PacketAuthPlaintext(object):
|
||||
"""
|
||||
IMC2 plain-text authentication packet. Auth packets are strangely
|
||||
formatted, so this does not sub-class IMC2Packet. The SHA and plain text
|
||||
auth packets are the two only non-conformers.
|
||||
|
||||
CLIENT Sends:
|
||||
PW <mudname> <clientpw> version=<version#> autosetup <serverpw> (SHA256)
|
||||
|
||||
Optional Arguments( required if using the specified authentication method:
|
||||
(SHA256) The literal string: SHA256. This is sent to notify the server
|
||||
that the MUD is SHA256-Enabled. All future logins from this
|
||||
client will be expected in SHA256-AUTH format if the server
|
||||
supports it.
|
||||
"""
|
||||
def assemble(self):
|
||||
"""
|
||||
This is one of two strange packets, just assemble the packet manually
|
||||
and go.
|
||||
"""
|
||||
return 'PW %s %s version=%s autosetup %s\n' %(
|
||||
settings.IMC2_MUDNAME,
|
||||
settings.IMC2_CLIENT_PW,
|
||||
settings.IMC2_PROTOCOL_VERSION,
|
||||
settings.IMC2_SERVER_PW)
|
||||
|
|
@ -7,6 +7,7 @@ from django.db import connection
|
|||
from django.conf import settings
|
||||
from src.config.models import ConfigValue
|
||||
from src.session import SessionProtocol
|
||||
from src.imc2.connection import IMC2ClientFactory
|
||||
from src import events
|
||||
from src import logger
|
||||
from src import session_mgr
|
||||
|
|
@ -140,4 +141,14 @@ mud_service = EvenniaService()
|
|||
# Sheet sheet, fire ze missiles!
|
||||
serviceCollection = service.IServiceCollection(application)
|
||||
for port in settings.GAMEPORTS:
|
||||
internet.TCPServer(port, mud_service.getEvenniaServiceFactory()).setServiceParent(serviceCollection)
|
||||
internet.TCPServer(port,
|
||||
mud_service.getEvenniaServiceFactory()).setServiceParent(serviceCollection)
|
||||
|
||||
|
||||
if settings.IMC2_ENABLED:
|
||||
imc2_factory = IMC2ClientFactory()
|
||||
svc = internet.TCPClient(settings.IMC2_SERVER_ADDRESS,
|
||||
settings.IMC2_SERVER_PORT,
|
||||
imc2_factory)
|
||||
svc.setName('IMC2')
|
||||
svc.setServiceParent(serviceCollection)
|
||||
Loading…
Add table
Add a link
Reference in a new issue