print "Couldn't find a HTTPClient. If you're using Twisted 2.0 make sure you've installed twisted.web"
raise
import msnp11chl
-from msnp2p import random_guid
-import msnp2p
# Twisted imports
from twisted.internet import reactor, task
from tlib import xmlw
# System imports
-import types, operator, os, sys, base64, random
+import types, operator, os, sys, base64, random, struct, random, sha, base64, StringIO
from urllib import quote, unquote
+
MSN_PROTOCOL_VERSION = "MSNP11 CVR0" # protocol version
MSN_PORT = 1863 # default dispatch server port
MSN_MAX_MESSAGE = 1664 # max message length
LINEDEBUG = True
MESSAGEDEBUG = True
+MSNP2PDEBUG = True
def getVal(inp):
return inp.split('=')[1]
return userHandle, screenName, userGuid, lists, groups
+def b64enc(s):
+ return base64.encodestring(s).replace("\n", "")
+
+def random_guid():
+ format = "{%4X%4X-%4X-%4X-%4X-%4X%4X%4X}"
+ data = []
+ for x in xrange(8):
+ data.append(random.random() * 0xAAFF + 0x1111)
+ data = tuple(data)
+
+ return format % data
+
def checkParamLen(num, expected, cmd, error=None):
if error == None: error = "Invalid Number of Parameters for %s" % cmd
if num != expected: raise MSNProtocolError, error
""" set the message text """
self.message = message
+
+class MSNObject:
+ """
+ Used to represent a MSNObject. This can be currently only be an avatar.
+
+ @ivar creator: The userHandle of the creator of this picture.
+ @ivar imageData: The PNG image data (only for our own avatar)
+ @ivar type: Always set to 3, for avatar.
+ @ivar size: The size of the image.
+ @ivar location: The filename of the image.
+ @ivar friendly: Unknown.
+ @ivar text: The textual representation of this MSNObject.
+ """
+ def __init__(self, s=""):
+ """ Pass a XML MSNObject string to parse it, or pass no arguments for a null MSNObject to be created. """
+ if s:
+ self.parse(s)
+ else:
+ self.setNull()
+
+ def setData(self, creator, imageData):
+ """ Set the creator and imageData for this object """
+ self.creator = creator
+ self.imageData = imageData
+ self.size = len(imageData)
+ self.type = 3
+ self.location = "TMP" + str(random.randint(1000,9999))
+ self.friendly = "AAA="
+ self.sha1d = b64enc(sha.sha(imageData).digest())
+ self.makeText()
+
+ def setNull(self):
+ self.creator = ""
+ self.imageData = ""
+ self.size = 0
+ self.type = 0
+ self.location = ""
+ self.friendly = ""
+ self.sha1d = ""
+ self.text = ""
+
+ def makeText(self):
+ """ Makes a textual representation of this MSNObject. Stores it in self.text """
+ h = []
+ h.append("Creator")
+ h.append(self.creator)
+ h.append("Size")
+ h.append(str(self.size))
+ h.append("Type")
+ h.append(str(self.type))
+ h.append("Location")
+ h.append(self.location)
+ h.append("Friendly")
+ h.append(self.friendly)
+ h.append("SHA1D")
+ h.append(self.sha1d)
+ sha1c = b64enc(sha.sha("".join(h)).digest())
+ self.text = '<msnobj Creator="%s" Size="%s" Type="%s" Location="%s" Friendly="%s" SHA1D="%s" SHA1C="%s"/>' % (self.creator, str(self.size), str(self.type), self.location, self.friendly, self.sha1d, sha1c)
+
+ def parse(self, s):
+ e = xmlw.parseText(s, True)
+ self.creator = e.getAttribute("Creator")
+ self.size = int(e.getAttribute("Size"))
+ self.type = int(e.getAttribute("Type"))
+ self.location = e.getAttribute("Location")
+ self.friendly = e.getAttribute("Friendly")
+ self.sha1d = e.getAttribute("SHA1D")
+ self.text = s
+
+
class MSNContact:
"""
@ivar lists: An integer representing the sum of all lists
that this contact belongs to.
@ivar caps: int, The capabilities of this client
- @ivar msnobj: msnp2p.MSNOBJ, The MSNObject representing the contact's avatar
+ @ivar msnobj: The MSNObject representing the contact's avatar
@ivar status: The contact's status code.
@type status: str if contact's status is known, None otherwise.
@ivar personal: The contact's personal message .
self._state = ['DISCONNECTED', {}]
self.pingCounter = 0
self.pingCheckTask = None
- self.msnobj = msnp2p.MSNOBJ()
+ self.msnobj = MSNObject()
def _setState(self, state):
self._state[0] = state
self.contactAvatarChanged(msnContact.userHandle, "")
def handleAvatarHelper(self, msnContact, msnobjStr):
- msnobj = msnp2p.MSNOBJ(unquote(msnobjStr))
+ msnobj = MSNObject(unquote(msnobjStr))
if not msnContact.msnobj or msnobj.sha1d != msnContact.msnobj.sha1d:
- if msnp2p.MSNP2P_DEBUG: log.msg("Updated MSNOBJ received!" + msnobjStr)
+ if MSNP2PDEBUG: log.msg("Updated MSNObject received!" + msnobjStr)
msnContact.msnobj = msnobj
msnContact.msnobjGot = False
self.contactAvatarChanged(msnContact.userHandle, msnContact.msnobj.sha1d)
to a switchboard invitation
@ivar reply: set this to 1 in connectionMade or before to signifiy
that you are replying to a switchboard invitation.
+ @ivar msnobj: the MSNObject for the user. So that the switchboard can distribute it.
"""
key = 0
userHandle = ""
sessionID = ""
reply = 0
+ msnobj = None
_iCookie = 0
- def __init__(self, msnobj=None):
+ def __init__(self):
MSNEventBase.__init__(self)
self.pendingUsers = {}
self.cookies = {'iCookies' : {}} # will maybe be moved to a factory in the future
self.slpLinks = {}
- self.msnobj = msnobj
def connectionMade(self):
MSNEventBase.connectionMade(self)
self.gotSendRequest(msnft.MSNFTP_Receive(filename, filesize, message.userHandle, cookie, connectivity, self))
return 1
- def _checkP2PMessage(self, message, cTypes):
+ def _handleP2PMessage(self, message):
""" helper method for msnslp messages (file transfer & avatars) """
packet = message.message
- binaryFields = msnp2p.BinaryFields(packet=packet)
+ binaryFields = BinaryFields(packet=packet)
if binaryFields[0] != 0:
slpLink = self.slpLinks[binaryFields[0]]
if slpLink.remoteUser == message.userHandle:
elif binaryFields[5] == BinaryFields.ACK or binaryFields[5] == BinaryFields.BYEGOT:
pass # Ignore the ACKs
else:
- slpMessage = msnp2p.MSNSLPMessage(packet)
+ slpMessage = MSNSLPMessage(packet)
slpLink = None
if slpMessage.method == "INVITE":
if slpMessage.euf_guid == MSN_MSNFTP_GUID:
- slpLink = msnp2p.SLPLink_Receive(slpMessage.fro, slpMessage.sessionID, slpMessage.sessionGuid)
- context = msnp2p.FileContext(slpMessage.context)
- fileReceive = msnft.MSNP2P_Receive(context.filename, context.filesize, slpMessage.fro, self)
+ slpLink = SLPLink_Receive(slpMessage.fro, slpMessage.sessionID, slpMessage.sessionGuid)
+ context = FileContext(slpMessage.context)
+ fileReceive = msnft.MSNP2P_Receive(context.filename, context.filesize, slpMessage.fro)
elif slpMessage.euf_guid == MSN_AVATAR_GUID:
- slpLink = msnp2p.SLPLink_Send(slpMessage.fro, slpMessage.sessionID, slpMessage.sessionGuid)
- self._sendMSNSLPResponse(slpLink, "200 OK")
+ # Check that we have an avatar to send
+ if self.msnobj:
+ slpLink = SLPLink_Send(slpMessage.fro, self, self.msnobj.size, slpMessage.sessionID, slpMessage.sessionGuid)
+ self._sendMSNSLPResponse(slpLink, "200 OK")
+ slpLink.write(self.msnobj.imageData)
+ slpLink.finish()
+ else:
+ pass # FIXME, should send an error response
if slpLink:
self.slpLinks[slpMessage.sessionID] = slpLink
else:
slpLink.transferReady()
if slpLink:
# Always need to ACK these packets if we can
- self._sendP2PACK(self, slpLink, binaryHeaders)
-
- return 1
+ self._sendP2PACK(slpLink, binaryFields)
+
def _sendP2PACK(self, slpLink, ackHeaders):
- binaryFields = msnp2p.BinaryFields()
- binaryFields[1] = slpLink.nextBaseID()
+ binaryFields = BinaryFields()
+ binaryFields[1] = slpLink.seqID.next()
binaryFields[3] = ackHeaders[3]
binaryFields[5] = BinaryFields.ACK
binaryFields[6] = ackHeaders[1]
binaryFields[7] = ackHeaders[6]
binaryFields[8] = ackHeaders[3]
- self._sendP2PMessage(binaryFields, "")
+ self._sendP2PMessage(binaryFields, "", slpLink.remoteUser)
- def _sendMSNSLPInvite(self, slpLink, guid, context):
- msg = msnp2p.MSNSLP_Message()
- msg.create(method="INVITE", to=slpLink.remoteUser, fro=self.userHandle, cseq=0, sessionGuid=slpLink.sessionGuid)
- msg.setData(sessionID=slpLink.sessionID, appID="1", guid=guid, context=msnp2p.b64enc(context))
+ def _sendMSNSLPCommand(self, command, slpLink, guid, context):
+ msg = MSNSLPMessage()
+ msg.create(method=command, to=slpLink.remoteUser, fro=self.userHandle, cseq=0, sessionGuid=slpLink.sessionGuid)
+ msg.setData(sessionID=slpLink.sessionID, appID="1", guid=guid, context=b64enc(context + chr(0)))
self._sendMSNSLPMessage(slpLink, msg)
def _sendMSNSLPResponse(self, slpLink, response):
- msg = msnp2p.MSNSLPMessage()
+ msg = MSNSLPMessage()
msg.create(status=response, to=slpLink.remoteUser, fro=self.userHandle, cseq=1, sessionGuid=slpLink.sessionGuid)
msg.setData(sessionID=slpLink.sessionID)
self._sendMSNSLPMessage(slpLink, msg)
- def _sendMSNSLPMessage(self, slpLink, msnSlpMessage):
- msgStr = str(msg)
- binaryFields = msnp2p.BinaryFields()
- binaryFields[1] = slpLink.nextBaseID()
+ def _sendMSNSLPMessage(self, slpLink, msnSLPMessage):
+ msgStr = str(msnSLPMessage)
+ binaryFields = BinaryFields()
+ binaryFields[1] = slpLink.seqID.next()
binaryFields[3] = len(msgStr)
binaryFields[4] = binaryFields[3]
binaryFields[6] = random.randint(0, sys.maxint)
- self._sendP2PMessage(binaryFields, msgStr)
+ self._sendP2PMessage(binaryFields, msgStr, msnSLPMessage.to)
- def _sendP2PMessage(self, binaryFields, msgStr):
+ def _sendP2PMessage(self, binaryFields, msgStr, to):
packet = binaryFields.packHeaders() + msgStr + binaryFields.packFooter()
message = MSNMessage(message=packet)
message.setHeader("Content-Type", "application/x-msnmsgrp2p")
- message.setHeader("P2P-Dest", handler.to)
+ message.setHeader("P2P-Dest", to)
message.ack = MSNMessage.MESSAGE_ACK_FAT
self.sendMessage(message)
+
+
def checkMessage(self, message):
"""
hook for detecting any notification type messages
info[key] = val.lstrip()
except ValueError: continue
if self._checkFileInvitation(message, info): return 0
- if 'application/x-msnmsgrp2p' in cTypes:
- if self._checkP2PMessage(message, cTypes): return 0
+ elif 'application/x-msnmsgrp2p' in cTypes:
+ self._handleP2PMessage(message)
+ return 0
return 1
# negotiation
"""
pass
- def gotAvatarImage(self, userHandle, image):
- """
- called when we receive an avatar from a contact
-
- @param userHandle: the person who's avatar we have got
- @param image: the avatar image
- """
- pass
-
def gotSendRequest(self, fileReceive):
"""
called when we receive a file send request from a contact
if MESSAGEDEBUG: log.msg(message.message)
return d
- def sendAvatarRequest(self, userHandle, msnobj):
- pass
+ def sendAvatarRequest(self, msnContact):
+ """
+ used to request an avatar from a user in this switchboard
+ session.
+
+ @param msnContact: the msnContact object to request an avatar for
+
+ @return: A Deferred, the callback for which will be called
+ when the avatar transfer succeeds.
+ The callback argument will be a tuple with one element,
+ the PNG avatar data.
+ """
+ if not msnContact.msnobj: return
+ d = Deferred()
+ def bufferClosed(data):
+ d.callback((data,))
+ buffer = StringBuffer(bufferClosed)
+ slpLink = SLPLink_Receive(msnContact.userHandle, self, buffer)
+ slpLink.avatarDataBuffer = buffer
+ self.slpLinks[slpLink.sessionID] = slpLink
+ self._sendMSNSLPCommand("INVITE", slpLink, MSN_AVATAR_GUID, msnContact.msnobj.text)
+ return d
def sendTypingNotification(self):
"""
- used to send a typing notification. Upon receiving this
+ Used to send a typing notification. Upon receiving this
message the official client will display a 'user is typing'
message to all other users in the chat session for 10 seconds.
- The official client sends one of these every 5 seconds (I think)
- as long as you continue to type.
+ You should send one of these every 5 seconds as long as the
+ user is typing.
"""
m = MSNMessage()
m.ack = m.MESSAGE_ACK_NONE
self.sendMessage(m)
+class BinaryFields:
+ """ Utility class for the binary header & footer in p2p messages """
+ ACK = 0x02
+ WAIT = 0x04
+ ERR = 0x08
+ DATA = 0x20
+ BYEGOT = 0x40
+ BYESENT = 0x80
+ DATAFT = 0x1000030
+
+ def __init__(self, fields=None, packet=None):
+ if fields:
+ self.fields = fields
+ else:
+ self.fields = [0] * 10
+ if packet:
+ self.unpackFields(packet)
+
+ def __getitem__(self, key):
+ return self.fields[key]
+
+ def __setitem__(self, key, value):
+ self.fields[key] = value
+
+ def unpackFields(self, packet):
+ self.fields = struct.unpack("<LLQQLLLLQ", packet[0:48])
+ self.fields += struct.unpack(">L", packet[len(packet)-4:])
+ if MSNP2PDEBUG:
+ print "Unpacked fields:",
+ for i in self.fields:
+ print hex(i),
+ print
+
+ def packHeaders(self):
+ f = tuple(self.fields)
+ if MSNP2PDEBUG:
+ print "Packed fields:",
+ for i in self.fields:
+ print hex(i),
+ print
+ return struct.pack("<LLQQLLLLQ", f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8])
+
+ def packFooter(self):
+ return struct.pack(">L", self.fields[9])
+
+
+class MSNSLPMessage:
+ """ Representation of a single MSNSLP message """
+ def __init__(self, packet=None):
+ self.method = ""
+ self.status = ""
+ self.to = ""
+ self.fro = ""
+ self.cseq = 0
+ self.sessionGuid = ""
+ self.sessionID = None
+ self.euf_guid = ""
+ self.data = "\r\n" + chr(0)
+ if packet:
+ self.parse(packet)
+
+ def create(self, method=None, status=None, to=None, fro=None, cseq=0, sessionGuid=None, data=None):
+ self.method = method
+ self.status = status
+ self.to = to
+ self.fro = fro
+ self.cseq = cseq
+ self.sessionGuid = sessionGuid
+ if data: self.data = data
+
+ def setData(self, sessionID=None, appID=None, guid=None, context=None):
+ s = []
+ if guid: s.append("EUF-GUID: %s\r\n" % guid)
+ if sessionID: s.append("SessionID: %s\r\n" % sessionID)
+ if appID: s.append("AppID: %s\r\n" % appID)
+ if context: s.append("Context: %s\r\n\r\n" % context)
+ s.append(chr(0))
+
+ self.data = "".join(s)
+
+ def parse(self, s):
+ s = s[48:len(s)-4:]
+ if s.find("MSNSLP/1.0") < 0: return
+
+ lines = s.split("\r\n")
+
+ # Get the MSNSLP method or status
+ msnslp = lines[0].split(" ")
+ if MSNP2PDEBUG: print "Parsing MSNSLPMessage", s, len(s)
+ if msnslp[0] in ("INVITE", "BYE"):
+ self.method = msnslp[0].strip()
+ else:
+ self.status = msnslp[1].strip()
+
+ lines.remove(lines[0])
+
+ for line in lines:
+ line = line.split(":")
+ if len(line) > 1:
+ if len(line) > 2 and line[0] == "To":
+ self.to = line[2][:line[2].find('>')]
+ elif len(line) > 2 and line[0] == "From":
+ self.fro = line[2][:line[2].find('>')]
+ elif line[0] == "Call-ID":
+ self.sessionGuid = line[1].strip()
+ elif line[0] == "CSeq":
+ self.cseq = int(line[1].strip())
+ elif line[0] == "SessionID":
+ self.sessionID = int(line[1].strip())
+ elif line[0] == "EUF-GUID":
+ self.euf_guid = line[1].strip()
+
+ def __str__(self):
+ s = []
+ if self.method:
+ s.append("%s MSNMSGR:%s MSNSLP/1.0\r\n" % (self.method, self.to))
+ else:
+ s.append("MSNSLP/1.0 %s\r\n" % self.status)
+ s.append("To: <msnmsgr:%s>\r\n" % self.to)
+ s.append("From: <msnmsgr:%s>\r\n" % self.fro)
+ s.append("Via: MSNSLP/1.0/TLP ;branch=%s\r\n" % random_guid())
+ s.append("CSeq: %s \r\n" % str(self.cseq))
+ s.append("Call-ID: %s\r\n" % self.sessionGuid)
+ s.append("Max-Forwards: 0\r\n")
+ if self.method == "BYE":
+ s.append("Content-Type: application/x-msnmsgr-sessionclosebody\r\n")
+ else:
+ s.append("Content-Type: application/x-msnmsgr-sessionreqbody\r\n")
+ s.append("Content-Length: %s\r\n\r\n" % len(self.data))
+ s.append(self.data)
+ return "".join(s)
+
+
+class SeqID:
+ """ Utility for handling the weird sequence IDs in p2p messages """
+ def __init__(self, baseID=None):
+ if baseID:
+ self.seqID = baseID
+ else:
+ self.seqID = random.randint(4, 2**30)
+ self.pos = -1
+
+ def get(self):
+ if self.pos == 0:
+ return self.seqID
+ else:
+ return self.seqID + self.pos - 3
+
+ def next(self):
+ self.pos += 1
+ if self.pos == 3:
+ self.pos += 1
+ return self.get()
+
+class StringBuffer(StringIO.StringIO):
+ def __init__(self, notifyFunc=None):
+ self.notifyFunc = notifyFunc
+ StringIO.StringIO.__init__(self)
+
+ def finish(self):
+ if self.notifyFunc:
+ self.notifyFunc(self.getvalue())
+ self.notifyFunc = None
+ StringIO.StringIO.close(self)
+
+
+class SLPLink:
+ def __init__(self, remoteUser, switchboard, sessionID, sessionGuid):
+ if not sessionID:
+ sessionID = random.randint(1000, sys.maxint)
+ if not sessionGuid:
+ sessionGuid = random_guid()
+ self.remoteUser = remoteUser
+ self.switchboard = switchboard
+ self.sessionID = sessionID
+ self.sessionGuid = sessionGuid
+ self.seqID = SeqID()
+
+ def transferReady(self):
+ pass
+
+ def setError(self, text):
+ if MSNP2PDEBUG:
+ print "ERROR in avatar transfer: ", self, text, "in state:", self.state
+ self.state = self.ERROR
+
+ def warn(self, text):
+ if MSNP2PDEBUG:
+ print "Warning in avatar transfer: ", self, text, "in state:", self.state
+
+
+class SLPLink_Send(SLPLink):
+ def __init__(self, remoteUser, switchboard, length, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.handlePacket = None
+ self.offset = 0
+ self.length = length
+ self.ready = False # Have we received the go ahead to transmit yet?
+ self.data = ""
+ # Send dataprep
+ self.send_dataprep()
+
+ def send_dataprep(self):
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ binaryFields[1] = self.seqID.next()
+ binaryFields[3] = 4
+ binaryFields[4] = 4
+ binaryFields[6] = random.randint(0, sys.maxint)
+ binaryFields[9] = 1
+
+ self.switchboard._sendP2PMessage(binaryFields, chr(0) * 4, self.remoteUser)
+
+ def transferReady(self):
+ self.ready = True
+ if self.data:
+ data = self.data
+ self.data = ""
+ self.write(data)
+
+ def write(self, data):
+ if not self.ready:
+ self.data += data
+ return
+
+ i = 0
+ length = len(data)
+ while i < length:
+ if i + 1202 < length:
+ self._writeChunk(data[i:i+1202])
+ i += 1202
+ else:
+ self.data += data[i:]
+ if len(self.data) >= 1202:
+ data = self.data
+ self.data = ""
+ self.write(data)
+ return
+
+ def _writeChunk(self, chunk):
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ binaryFields[1] = self.seqID.next()
+ binaryFields[2] = self.offset
+ binaryFields[3] = self.length
+ binaryFields[4] = len(chunk)
+ binaryFields[5] = BinaryFields.DATA
+ binaryFields[6] = random.randint(0, 2**30)
+ binaryFields[9] = 1
+
+ self.offset += len(chunk)
+ self.switchboard._sendP2PMessage(binaryFields, chunk, self.remoteUser)
+
+ def finish(self):
+ if self.data:
+ self._writeChunk(self.data)
+ self.switchboard = None
+ # FIXME Now tell the switchboard
+
+ def error(self):
+ pass
+ # Same as above?
+
+
+class SLPLink_Receive(SLPLink):
+ def __init__(self, remoteUser, switchboard, consumer, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.consumer = consumer
+ self.pos = 0
+
+ self.handlePacket = self.wait_dataprep
+
+ def wait_dataprep(self, packet):
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+
+ if binaryFields[0] != self.sessionID:
+ self.warn("field0," + str(binaryFields[0]) + "," + str(self.sessionID))
+ return
+ if binaryFields[3] != 4:
+ self.setError("field3," + str(binaryFields[3]))
+ return
+ if binaryFields[4] != 4:
+ self.setError("field4," + str(binaryFields[4]))
+ return
+ if binaryFields[9] != 1:
+ self.warn("field9," + str(binaryFields[9]))
+ # return
+
+ self.switchboard._sendP2PACK(self, binaryFields)
+ self.handlePacket = self.wait_data
+
+ def wait_data(self, packet):
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+ if binaryFields[0] != self.sessionID:
+ self.warn("field0," + str(binaryFields[0]) + "," + str(self.sessionID))
+ return
+ if binaryFields[5] != BinaryFields.DATA:
+ self.setError("field5," + str(binaryFields[5]))
+ return
+ if binaryFields[9] != 1:
+ self.warn("field9," + str(binaryFields[9]))
+ # return
+ offset = binaryFields[2]
+ total = binaryFields[3]
+ length = binaryFields[4]
+
+ data = packet[48:-4]
+ if offset != self.pos:
+ self.setError("Received packet out of order")
+ self.consumer.error()
+ return
+ if len(data) != length:
+ self.setError("Received bad length of slp")
+ self.consumer.error()
+ return
+
+ self.pos += length
+
+ self.consumer.write(data)
+
+ if self.pos == total:
+ self.switchboard._sendP2PACK(self, binaryFields)
+ self.consumer.finish()
+ self.handlePacket = None
+ del self.switchboard.slpLinks[self.sessionID]
+ self.switchboard = None
+
+
+
+
+
+
# mapping of error codes to error messages
errorCodes = {
import random
import sha
import base64
+import sys
from tlib import xmlw
return base64.encodestring(s).replace("\n", "")
def random_guid():
- format = "{%4X%4X-%4X-%4X-%4X-%4X%4X%4X}"
- data = []
- for x in xrange(8):
- data.append(random.random() * 0xAAFF + 0x1111)
- data = tuple(data)
+ format = "{%4X%4X-%4X-%4X-%4X-%4X%4X%4X}"
+ data = []
+ for x in xrange(8):
+ data.append(random.random() * 0xAAFF + 0x1111)
+ data = tuple(data)
- return format % data
+ return format % data
class MSNOBJ:
def setData(self, sessionID=None, appID=None, guid=None, context=None):
s = []
- if guid: s.append("EUF-GUID: %s\r\n" % guid)
+ if guid: s.append("EUF-GUID: %s\r\n" % guid)
if sessionID: s.append("SessionID: %s\r\n" % sessionID)
- if appID: s.append("AppID: %s\r\n" % appID)
+ if appID: s.append("AppID: %s\r\n" % appID)
if context: s.append("Context: %s\r\n\r\n" % context)
s.append(chr(0))
self.pos += 1
return self.get()
-class FileData:
- def __init__(self, size):
- self.data = chr(0) * size
- self.totalSize = size
- self.gotSize = 0
-
- def put(self, offset, data):
- self.data = self.data[0:offset] + data + self.data[offset+len(data):]
- self.gotSize += len(data)
-
- def finished(self):
- return self.gotSize >= self.totalSize
-
-
-#class SLPLink:
-
-
-
-#class SLPLink_Receive:
-
-
class MSNP2P_Avatar:
TRANSFER_COUNT = 0
ERROR_COUNT = 0
+class SLPLink:
+ def __init__(self, remoteUser, switchboard, sessionID, sessionGuid):
+ if not sessionID:
+ sessionID = random.randint(1000, sys.maxint)
+ if not sessionGuid:
+ sessionGuid = random_guid()
+ self.remoteUser = remoteUser
+ self.switchboard = switchboard
+ self.sessionID = sessionID
+ self.sessionGuid = sessionGuid
+
+ def setError(self, text):
+ if MSNP2P_DEBUG:
+ print "ERROR in avatar transfer: ", self, text, "in state:", self.state
+ self.state = self.ERROR
+
+ def warn(self, text):
+ if MSNP2P_DEBUG:
+ print "Warning in avatar transfer: ", self, text, "in state:", self.state
+
+
+class SLPLink_Send(SLPLink):
+ def __init__(self, remoteUser, switchboard, length, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.handlePacket = None
+ self.offset = 0
+ self.length = length
+ self.data = ""
+ # Send dataprep
+ self.send_dataprep()
+
+ def send_dataprep(self):
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ binaryFields[1] = self.baseID.next()
+ binaryFields[3] = 4
+ binaryFields[4] = 4
+ binaryFields[6] = random.randint(0, sys.maxint)
+ binaryFields[9] = 1
+
+ self.switchboard._sendP2PMessage(binaryFields, chr(0) * 4)
+
+ def write(self, data):
+ i = 0
+ length = len(data)
+ while i < length:
+ if i + 1202 < length:
+ self._writeChunk(data[i:i+1202])
+ i += 1202
+ else:
+ self.data += data
+ if len(self.data) >= 1202:
+ data = self.data
+ self.data = ""
+ self.write(data)
+ return
+
+ def _writeChunk(self, chunk):
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ binaryFields[1] = self.baseID.get()
+ binaryFields[2] = self.fileOffset
+ binaryFields[3] = len(chunk)
+ binaryFields[4] = self.length
+ binaryFields[5] = BinaryFields.DATA
+ binaryFields[6] = random.randint(0, 2**30)
+ binaryFields[9] = 1
+
+ self.offset += len(chunk)
+ self.switchboard._sendP2PMessage(binaryFields, chunk)
+
+ def finish(self):
+ self._writeChunk(self.data)
+ # FIXME Now tell the switchboard
+
+ def error(self):
+ pass
+ # Same as above?
+
+
+class SLPLink_Receive(SLPLink):
+ def __init__(self, remoteUser, switchboard, consumer, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.consumer = consumer
+ self.pos = 0
+
+ self.handlePacket = wait_dataprep
+
+ def wait_dataprep(self, packet):
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+
+ if binaryFields[0] != self.sessionID:
+ self.warn("field0," + str(binaryFields[0]) + "," + str(self.sessionID))
+ return
+ if binaryFields[3] != 4:
+ self.setError("field3," + str(binaryFields[3]))
+ return
+ if binaryFields[4] != 4:
+ self.setError("field4," + str(binaryFields[4]))
+ return
+ if binaryFields[9] != 1:
+ self.warn("field9," + str(binaryFields[9]))
+ # return
+
+ self.switchboard._sendP2PACK(self, binaryFields)
+ self.handlePacket = self.wait_data
+
+ def wait_data(self, packet):
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+ if binaryFields[0] != self.sessionID:
+ self.warn("field0," + str(binaryFields[0]) + "," + str(self.sessionID))
+ return
+ if binaryFields[5] != BinaryFields.DATA:
+ self.setError("field5," + str(binaryFields[5]))
+ return
+ if binaryFields[9] != 1):
+ self.warn("field9," + str(binaryFields[9]))
+ # return
+ offset = binaryFields[2]
+ total = binaryFields[3]
+ length = binaryFields[4]
+
+ data = packet[48:-4]
+ if offset != self.pos:
+ self.setError("Received packet out of order")
+ self.consumer.error()
+ return
+ if len(data) != length:
+ self.setError("Received bad length of slp")
+ self.consumer.error()
+ return
+
+ self.pos += length
+
+ self.consumer.write(data)
+
+ if self.pos == total:
+ self.switchboard._sendP2PACK(self, binaryFields)
+ self.consumer.finish()
+ self.switchboard.gotAvatarImage(self.remoteUser, self.fileData.data)
+ self.handlePacket = None
+ del self.switchboard.slpLinks[self.sessionID]
+
+
+
+
+class SwitchboardP2PMixin:
+ def handleP2PMessage(self, message, cTypes):
+ """ helper method for msnslp messages (file transfer & avatars) """
+ packet = message.message
+ binaryFields = msnp2p.BinaryFields(packet=packet)
+ if binaryFields[0] != 0:
+ slpLink = self.slpLinks[binaryFields[0]]
+ if slpLink.remoteUser == message.userHandle:
+ slpLink.handlePacket(packet)
+ elif binaryFields[5] == BinaryFields.ACK or binaryFields[5] == BinaryFields.BYEGOT:
+ pass # Ignore the ACKs
+ else:
+ slpMessage = msnp2p.MSNSLPMessage(packet)
+ slpLink = None
+ if slpMessage.method == "INVITE":
+ if slpMessage.euf_guid == MSN_MSNFTP_GUID:
+ slpLink = msnp2p.SLPLink_Receive(slpMessage.fro, slpMessage.sessionID, slpMessage.sessionGuid)
+ context = msnp2p.FileContext(slpMessage.context)
+ fileReceive = msnft.MSNP2P_Receive(context.filename, context.filesize, slpMessage.fro)
+ elif slpMessage.euf_guid == MSN_AVATAR_GUID:
+ slpLink = msnp2p.SLPLink_Send(slpMessage.fro, slpMessage.sessionID, slpMessage.sessionGuid)
+ self._sendMSNSLPResponse(slpLink, "200 OK")
+ if slpLink:
+ self.slpLinks[slpMessage.sessionID] = slpLink
+ else:
+ if slpMessage.status != "200":
+ for slpLink in self.slpLinks:
+ if slpLink.sessionGuid == slpMessage.sessionGuid:
+ del self.slpLinks[slpLink.sessionID]
+ if slpMessage.method != "BYE":
+ # Must be an error. If its a file transfer we need to signal that it failed
+ slpLink.transferError()
+ else:
+ slpLink = self.slpLinks[slpMessage.sessionID]
+ slpLink.transferReady()
+ if slpLink:
+ # Always need to ACK these packets if we can
+ self._sendP2PACK(self, slpLink, binaryHeaders)
+
+
+ def _sendP2PACK(self, slpLink, ackHeaders):
+ binaryFields = msnp2p.BinaryFields()
+ binaryFields[1] = slpLink.nextBaseID()
+ binaryFields[3] = ackHeaders[3]
+ binaryFields[5] = BinaryFields.ACK
+ binaryFields[6] = ackHeaders[1]
+ binaryFields[7] = ackHeaders[6]
+ binaryFields[8] = ackHeaders[3]
+ self._sendP2PMessage(binaryFields, "")
+
+ def _sendMSNSLPCommand(self, command, slpLink, guid, context):
+ msg = msnp2p.MSNSLP_Message()
+ msg.create(method=command, to=slpLink.remoteUser, fro=self.userHandle, cseq=0, sessionGuid=slpLink.sessionGuid)
+ msg.setData(sessionID=slpLink.sessionID, appID="1", guid=guid, context=msnp2p.b64enc(context + chr(0)))
+ self._sendMSNSLPMessage(slpLink, msg)
+
+ def _sendMSNSLPResponse(self, slpLink, response):
+ msg = msnp2p.MSNSLPMessage()
+ msg.create(status=response, to=slpLink.remoteUser, fro=self.userHandle, cseq=1, sessionGuid=slpLink.sessionGuid)
+ msg.setData(sessionID=slpLink.sessionID)
+ self._sendMSNSLPMessage(slpLink, msg)
+
+ def _sendMSNSLPMessage(self, slpLink, msnSlpMessage):
+ msgStr = str(msg)
+ binaryFields = msnp2p.BinaryFields()
+ binaryFields[1] = slpLink.nextBaseID()
+ binaryFields[3] = len(msgStr)
+ binaryFields[4] = binaryFields[3]
+ binaryFields[6] = random.randint(0, sys.maxint)
+ self._sendP2PMessage(binaryFields, msgStr)
+
+ def _sendP2PMessage(self, binaryFields, msgStr):
+ packet = binaryFields.packHeaders() + msgStr + binaryFields.packFooter()
+
+ message = MSNMessage(message=packet)
+ message.setHeader("Content-Type", "application/x-msnmsgrp2p")
+ message.setHeader("P2P-Dest", handler.to)
+ message.ack = MSNMessage.MESSAGE_ACK_FAT
+ self.sendMessage(message)
+
+