+class FileReceive:
+ def __init__(self, filename, filesize, userHandle):
+ self.consumer = None
+ self.finished = False
+ self.error = False
+ self.buffer = []
+ self.filename, self.filesize, self.userHandle = filename, filesize, userHandle
+
+ def reject(self):
+ raise NotImplementedError
+
+ def accept(self, consumer):
+ if self.consumer: raise "AlreadyAccepted"
+ self.consumer = consumer
+ for data in self.buffer:
+ self.consumer.write(data)
+ self.buffer = None
+ if self.finished:
+ self.consumer.close()
+ if self.error:
+ self.consumer.error()
+
+ def write(self, data):
+ if self.error or self.finished:
+ raise IOError, "Attempt to write in an invalid state"
+ if self.consumer:
+ self.consumer.write(data)
+ else:
+ self.buffer.append(data)
+
+ def close(self):
+ self.finished = True
+ if self.consumer:
+ self.consumer.close()
+
+class FileContext:
+ """ Represents the Context field for P2P file transfers """
+ def __init__(self, data=""):
+ if data:
+ self.parse(data)
+ else:
+ self.filename = ""
+ self.filesize = 0
+
+ def pack(self):
+ if MSNP2PDEBUG: log.msg("FileContext packing:", self.filename, self.filesize)
+ data = struct.pack("<LLQL", 638, 0x03, self.filesize, 0x01)
+ data = data[:-1] # Uck, weird, but it works
+ data += utf16net(self.filename)
+ data = ljust(data, 570, '\0')
+ data += struct.pack("<L", 0xFFFFFFFFL)
+ data = ljust(data, 638, '\0')
+ return data
+
+ def parse(self, packet):
+ self.filesize = struct.unpack("<Q", packet[8:16])[0]
+ chunk = packet[19:540]
+ chunk = chunk[:chunk.find('\x00\x00')]
+ self.filename = unicode((codecs.BOM_UTF16_BE + chunk).decode("utf-16"))
+ if MSNP2PDEBUG: log.msg("FileContext parsed:", self.filesize, self.filename)
+
+
+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:
+ out = "Unpacked fields: "
+ for i in self.fields:
+ out += hex(i) + ' '
+ log.msg(out)
+
+ def packHeaders(self):
+ f = tuple(self.fields)
+ if MSNP2PDEBUG:
+ out = "Packed fields: "
+ for i in self.fields:
+ out += hex(i) + ' '
+ log.msg(out)
+ 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.branch = ""
+ 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, branch=None, cseq=0, sessionGuid=None, data=None):
+ self.method = method
+ self.status = status
+ self.to = to
+ self.fro = fro
+ self.branch = branch
+ self.cseq = cseq
+ self.sessionGuid = sessionGuid
+ if data: self.data = data
+
+ def setData(self, ctype, data):
+ self.ctype = ctype
+ s = []
+ order = ["EUF-GUID", "SessionID", "AppID", "Context", "Bridge", "Listening","Bridges", "NetID", "Conn-Type", "UPnPNat", "ICF", "Hashed-Nonce"]
+ for key in order:
+ if key == "Context" and data.has_key(key):
+ s.append("Context: %s\r\n" % b64enc(data[key]))
+ elif data.has_key(key):
+ s.append("%s: %s\r\n" % (key, str(data[key])))
+ s.append("\r\n"+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: log.msg("Parsing MSNSLPMessage %s %s" % (len(s), 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: continue
+ try:
+ 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()
+ elif line[0] == "Content-Type":
+ self.ctype = line[1].strip()
+ elif line[0] == "Context":
+ self.context = b64dec(line[1])
+ elif line[0] == "Via":
+ self.branch = line[1].split(";")[1].split("=")[1].strip()
+ except:
+ if MSNP2PDEBUG:
+ log.msg("Error parsing MSNSLP message.")
+ raise
+
+ def __str__(self):
+ s = []
+ if self.method:
+ s.append("%s MSNMSGR:%s MSNSLP/1.0\r\n" % (self.method, self.to))
+ else:
+ if self.status == "200": status = "200 OK"
+ elif self.status == "603": status = "603 Decline"
+ s.append("MSNSLP/1.0 %s\r\n" % 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" % self.branch)
+ 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")
+ s.append("Content-Type: %s\r\n" % self.ctype)
+ 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.baseID = baseID
+ else:
+ self.baseID = random.randint(1000, sys.maxint)
+ self.pos = -1
+
+ def get(self):
+ return p2pseq(self.pos) + self.baseID
+
+ def next(self):
+ self.pos += 1
+ return self.get()
+
+
+class StringBuffer(StringIO.StringIO):
+ def __init__(self, notifyFunc=None):
+ self.notifyFunc = notifyFunc
+ StringIO.StringIO.__init__(self)
+
+ def close(self):
+ if self.notifyFunc:
+ self.notifyFunc(self.getvalue())
+ self.notifyFunc = None
+ StringIO.StringIO.close(self)
+
+
+class SLPLink:
+ def __init__(self, remoteUser, switchboard, sessionID, sessionGuid):
+ self.dataFlag = 0
+ 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 killLink(self):
+ if MSNP2PDEBUG: log.msg("killLink")
+ def kill():
+ if MSNP2PDEBUG: log.msg("killLink - kill()")
+ if not self.switchboard: return
+ del self.switchboard.slpLinks[self.sessionID]
+ self.switchboard = None
+ # This is so that handleP2PMessage can still use the SLPLink
+ # one last time, for ACKing BYEs and 601s.
+ reactor.callLater(0, kill)
+
+ def warn(self, text):
+ log.msg("Warning in transfer: %s %s" % (self, text))
+
+ def sendP2PACK(self, ackHeaders):
+ binaryFields = BinaryFields()
+ binaryFields[0] = ackHeaders[0]
+ binaryFields[1] = self.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, "")
+
+ def sendSLPMessage(self, cmd, ctype, data, branch=None):
+ msg = MSNSLPMessage()
+ if cmd.isdigit():
+ msg.create(status=cmd, to=self.remoteUser, fro=self.switchboard.userHandle, branch=branch, cseq=1, sessionGuid=self.sessionGuid)
+ else:
+ msg.create(method=cmd, to=self.remoteUser, fro=self.switchboard.userHandle, branch=random_guid(), cseq=0, sessionGuid=self.sessionGuid)
+ msg.setData(ctype, data)
+ msgStr = str(msg)
+ binaryFields = BinaryFields()
+ binaryFields[1] = self.seqID.next()
+ binaryFields[3] = len(msgStr)
+ binaryFields[4] = binaryFields[3]
+ binaryFields[6] = random.randint(1000, 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", self.remoteUser)
+ message.ack = MSNMessage.MESSAGE_ACK_FAT
+ self.switchboard.sendMessage(message)
+
+ def handleSLPMessage(self, slpMessage):
+ raise NotImplementedError
+
+
+
+
+
+class SLPLink_Send(SLPLink):
+ def __init__(self, remoteUser, switchboard, filesize, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.handlePacket = None
+ self.offset = 0
+ self.filesize = filesize
+ self.data = ""
+
+ def send_dataprep(self):
+ if MSNP2PDEBUG: log.msg("send_dataprep")
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ binaryFields[1] = self.seqID.next()
+ binaryFields[3] = 4
+ binaryFields[4] = 4
+ binaryFields[6] = random.randint(1000, sys.maxint)
+ binaryFields[9] = 1
+ self.sendP2PMessage(binaryFields, chr(0) * 4)
+
+ def write(self, data):
+ if MSNP2PDEBUG: log.msg("write")
+ i = 0
+ data = self.data + data
+ self.data = ""
+ length = len(data)
+ while i < length:
+ if i + 1202 < length:
+ self._writeChunk(data[i:i+1202])
+ i += 1202
+ else:
+ self.data = data[i:]
+ return
+
+ def _writeChunk(self, chunk):
+ if MSNP2PDEBUG: log.msg("writing chunk")
+ binaryFields = BinaryFields()
+ binaryFields[0] = self.sessionID
+ if self.offset == 0:
+ binaryFields[1] = self.seqID.next()
+ else:
+ binaryFields[1] = self.seqID.get()
+ binaryFields[2] = self.offset
+ binaryFields[3] = self.filesize
+ binaryFields[4] = len(chunk)
+ binaryFields[5] = self.dataFlag
+ binaryFields[6] = random.randint(1000, sys.maxint)
+ binaryFields[9] = 1
+ self.offset += len(chunk)
+ self.sendP2PMessage(binaryFields, chunk)
+
+ def close(self):
+ if self.data:
+ self._writeChunk(self.data)
+ #self.killLink()
+
+ def error(self):
+ pass
+ # FIXME, should send 601 or something
+
+class SLPLink_FileSend(SLPLink_Send):
+ def __init__(self, remoteUser, switchboard, filename, filesize):
+ SLPLink_Send.__init__(self, remoteUser=remoteUser, switchboard=switchboard, filesize=filesize)
+ self.dataFlag = BinaryFields.DATAFT
+ # Send invite & wait for 200OK before sending dataprep
+ context = FileContext()
+ context.filename = filename
+ context.filesize = filesize
+ data = {"EUF-GUID" : MSN_MSNFTP_GUID,\
+ "SessionID": self.sessionID,\
+ "AppID" : 2,\
+ "Context" : context.pack() }
+ self.sendSLPMessage("INVITE", "application/x-msnmsgr-sessionreqbody", data)
+ self.acceptDeferred = Deferred()
+
+ def handleSLPMessage(self, slpMessage):
+ if slpMessage.status == "200":
+ if slpMessage.ctype == "application/x-msnmsgr-sessionreqbody":
+ data = {"Bridges" : "TRUDPv1 TCPv1",\
+ "NetID" : "0",\
+ "Conn-Type" : "Firewall",\
+ "UPnPNat" : "false",\
+ "ICF" : "true",}
+ #"Hashed-Nonce": random_guid()}
+ self.sendSLPMessage("INVITE", "application/x-msnmsgr-transreqbody", data)
+ elif slpMessage.ctype == "application/x-msnmsgr-transrespbody":
+ self.acceptDeferred.callback((True,))
+ self.handlePacket = self.wait_data_ack
+ else:
+ if slpMessage.status == "603":
+ self.acceptDeferred.callback((False,))
+ if MSNP2PDEBUG: log.msg("SLPLink is over due to decline, error or BYE")
+ self.data = ""
+ self.killLink()
+
+ def wait_data_ack(self, packet):
+ if MSNP2PDEBUG: log.msg("wait_data_ack")
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+
+ if binaryFields[5] != BinaryFields.ACK:
+ self.warn("field5," + str(binaryFields[5]))
+ return
+
+ self.sendSLPMessage("BYE", "application/x-msnmsgr-sessionclosebody", {})
+ self.handlePacket = None
+
+ def close(self):
+ self.handlePacket = self.wait_data_ack
+ SLPLink_Send.close(self)
+
+
+class SLPLink_AvatarSend(SLPLink_Send):
+ def __init__(self, remoteUser, switchboard, filesize, sessionID=None, sessionGuid=None):
+ SLPLink_Send.__init__(self, remoteUser=remoteUser, switchboard=switchboard, filesize=filesize, sessionID=sessionID, sessionGuid=sessionGuid)
+ self.dataFlag = BinaryFields.DATA
+ self.sendSLPMessage("200", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID})
+ self.send_dataprep()
+ self.handlePacket = lambda packet: None
+
+ def handleSLPMessage(self, slpMessage):
+ if MSNP2PDEBUG: log.msg("BYE or error")
+ self.killLink()
+
+ def close(self):
+ SLPLink_Send.close(self)
+ # Keep the link open to wait for a BYE
+
+class SLPLink_Receive(SLPLink):
+ def __init__(self, remoteUser, switchboard, consumer, context=None, sessionID=None, sessionGuid=None):
+ SLPLink.__init__(self, remoteUser, switchboard, sessionID, sessionGuid)
+ self.handlePacket = None
+ self.consumer = consumer
+ self.pos = 0
+
+ def wait_dataprep(self, packet):
+ if MSNP2PDEBUG: log.msg("wait_dataprep")
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+
+ if binaryFields[3] != 4:
+ self.warn("field3," + str(binaryFields[3]))
+ return
+ if binaryFields[4] != 4:
+ self.warn("field4," + str(binaryFields[4]))
+ return
+ # Just ignore the footer
+ #if binaryFields[9] != 1:
+ # self.warn("field9," + str(binaryFields[9]))
+ # return
+
+ self.sendP2PACK(binaryFields)
+ self.handlePacket = self.wait_data
+
+ def wait_data(self, packet):
+ if MSNP2PDEBUG: log.msg("wait_data")
+ binaryFields = BinaryFields()
+ binaryFields.unpackFields(packet)
+
+ if binaryFields[5] != self.dataFlag:
+ self.warn("field5," + str(binaryFields[5]))
+ return
+ # Just ignore the footer
+ #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.warn("Received packet out of order")
+ self.consumer.error()
+ return
+ if len(data) != length:
+ self.warn("Received bad length of slp")
+ self.consumer.error()
+ return
+
+ self.pos += length
+
+ self.consumer.write(str(data))
+
+ if self.pos == total:
+ self.sendP2PACK(binaryFields)
+ self.consumer.close()
+ self.handlePacket = None
+ self.doFinished()
+
+ def doFinished(self):
+ raise NotImplementedError
+
+
+class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
+ def __init__(self, remoteUser, switchboard, filename, filesize, sessionID, sessionGuid, branch):
+ SLPLink_Receive.__init__(self, remoteUser=remoteUser, switchboard=switchboard, consumer=self, sessionID=sessionID, sessionGuid=sessionGuid)
+ self.dataFlag = BinaryFields.DATAFT
+ self.initialBranch = branch
+ FileReceive.__init__(self, filename, filesize, remoteUser)
+
+ def reject(self):
+ # Send a 603 decline
+ if not self.switchboard: return
+ self.sendSLPMessage("603", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID}, branch=self.initialBranch)
+ self.killLink()
+
+ def accept(self, consumer):
+ FileReceive.accept(self, consumer)
+ if not self.switchboard: return
+ self.sendSLPMessage("200", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID}, branch=self.initialBranch)
+ self.handlePacket = self.wait_data # Moved here because sometimes the second INVITE seems to be skipped
+
+ def handleSLPMessage(self, slpMessage):
+ if slpMessage.method == "INVITE": # The second invite
+ data = {"Bridge" : "TCPv1",\
+ "Listening" : "false",\
+ "Hashed-Nonce": "{00000000-0000-0000-0000-000000000000}"}
+ self.sendSLPMessage("200", "application/x-msnmsgr-transrespbody", data, branch=slpMessage.branch)
+# self.handlePacket = self.wait_data # Moved up
+ else:
+ if MSNP2PDEBUG: log.msg("It's either a BYE or an error")
+ self.killLink()
+ # FIXME, do some error handling if it was an error
+
+ def doFinished(self):
+ #self.sendSLPMessage("BYE", "application/x-msnmsgr-sessionclosebody", {})
+ #self.killLink()
+ # Wait for BYE? #FIXME
+ pass
+
+class SLPLink_AvatarReceive(SLPLink_Receive):
+ def __init__(self, remoteUser, switchboard, consumer, context):
+ SLPLink_Receive.__init__(self, remoteUser=remoteUser, switchboard=switchboard, consumer=consumer, context=context)
+ self.dataFlag = BinaryFields.DATA
+ data = {"EUF-GUID" : MSN_AVATAR_GUID,\
+ "SessionID": self.sessionID,\
+ "AppID" : 1,\
+ "Context" : context}
+ self.sendSLPMessage("INVITE", "application/x-msnmsgr-sessionreqbody", data)
+ self.handlePacket = self.wait_dataprep
+
+ def handleSLPMessage(self, slpMessage):
+ if slpMessage.method == "INVITE": # The second invite
+ data = {"Bridge" : "TCPv1",\
+ "Listening" : "false",\
+ "Hashed-Nonce": "{00000000-0000-0000-0000-000000000000}"}
+ self.sendSLPMessage("200", "application/x-msnmsgr-transrespbody", data, branch=slpMessage.branch)
+ elif slpMessage.status == "200":
+ pass
+ #self.handlePacket = self.wait_dataprep # Moved upwards
+ else:
+ if MSNP2PDEBUG: log.msg("SLPLink is over due to error or BYE")
+ self.killLink()
+
+ def doFinished(self):
+ self.sendSLPMessage("BYE", "application/x-msnmsgr-sessionclosebody", {})
+