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:
Greg Taylor 2009-04-11 05:55:26 +00:00
parent 65d8ac8bdc
commit a7e89c1e54
6 changed files with 272 additions and 1 deletions

24
src/commands/imc2.py Normal file
View 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)

View file

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

97
src/imc2/connection.py Normal file
View 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
View 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)

View file

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