]> code.delx.au - pymsnt/blobdiff - src/tlib/msn/msn.py
Handle MSN error conditions
[pymsnt] / src / tlib / msn / msn.py
index cd7d6772617d6351cd25970aff8b735e775e2b7a..fcab164707075d7226403463c5bf87c342695b07 100644 (file)
@@ -99,16 +99,19 @@ import msnp11chl
 # Twisted imports
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
-from twisted.internet.protocol import ClientFactory
-from twisted.internet.ssl import ClientContextFactory
+from twisted.internet.protocol import ReconnectingClientFactory, ClientFactory
+try:
+    from twisted.internet.ssl import ClientContextFactory
+except ImportError:
+    print "You must install pycrypto and pyopenssl."
+    raise
 from twisted.python import failure, log
-from twisted.xish.domish import unescapeFromXml
 
 # Compat stuff
 from tlib import xmlw
 
 # System imports
-import types, operator, os, sys, base64, random, struct, random, sha, base64, StringIO
+import types, operator, os, sys, base64, random, struct, random, sha, base64, StringIO, array, codecs, binascii
 from urllib import quote, unquote
 
 
@@ -118,7 +121,6 @@ MSN_MAX_MESSAGE      = 1664               # max message length
 MSN_CVR_STR          = "0x040c winnt 5.1 i386 MSNMSGR 7.0.0777 msmsgs"
 MSN_AVATAR_GUID      = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}"
 MSN_MSNFTP_GUID      = "{5D3E02AB-6190-11D3-BBBB-00C04F795683}"
-P2PSEQ = [-3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8]
 
 # auth constants
 LOGIN_SUCCESS  = 1
@@ -162,6 +164,15 @@ if DEBUGALL:
     MESSAGEDEBUG = True
     MSNP2PDEBUG = True
 
+
+P2PSEQ = [-3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8]
+def p2pseq(n):
+    if n > 5:
+        return n - 3
+    else:
+        return P2PSEQ[n]
+
+
 def getVal(inp):
     return inp.split('=')[1]
 
@@ -190,9 +201,32 @@ def getVals(params):
 
     return userHandle, screenName, userGuid, lists, groups
 
+def ljust(s, n, c):
+    """ Needed for Python 2.3 compatibility """
+    return s + (n-len(s))*c
+
+if sys.byteorder == "little":
+    def utf16net(s):
+        """ Encodes to utf-16 and ensures network byte order. Strips the BOM """
+        a = array.array("h", s.encode("utf-16")[2:])
+        a.byteswap()
+        return a.tostring()
+else:
+    def utf16net(s):
+        """ Encodes to utf-16 and ensures network byte order. Strips the BOM """
+        return s.encode("utf-16")[2:]
+
 def b64enc(s):
     return base64.encodestring(s).replace("\n", "")
 
+def b64dec(s):
+    for pad in ["", "=", "==", "A", "A=", "A=="]: # Stupid MSN client!
+        try:
+            return base64.decodestring(s + pad)
+        except:
+            pass
+    raise ValueError("Got some very bad base64!")
+
 def random_guid():
     format = "{%4X%4X-%4X-%4X-%4X-%4X%4X%4X}"
     data = []
@@ -312,7 +346,7 @@ class PassportLogin(HTTPClient):
     def connectionMade(self):
         self.sendCommand('GET', self.path)
         self.sendHeader('Authorization', 'Passport1.4 OrgVerb=GET,OrgURL=http://messenger.msn.com,' +
-                                         'sign-in=%s,pwd=%s,%s' % (quote(self.userHandle), self.passwd,self.authData))
+                                         'sign-in=%s,pwd=%s,%s' % (quote(self.userHandle), quote(self.passwd), self.authData))
         self.sendHeader('Host', self.host)
         self.endHeaders()
         self.headers = {}
@@ -399,7 +433,7 @@ class MSNMessage:
         self.screenName = screenName
         self.specialMessage = specialMessage
         self.message = message
-        self.headers = {'MIME-Version' : '1.0', 'Content-Type' : 'text/plain'}
+        self.headers = {'MIME-Version' : '1.0', 'Content-Type' : 'text/plain; charset=UTF-8'}
         self.length = length
         self.readPos = 0
 
@@ -410,6 +444,11 @@ class MSNMessage:
         """
         return reduce(operator.add, [len(x[0]) + len(x[1]) + 4  for x in self.headers.items()]) + len(self.message) + 2
 
+    def delHeader(self, header):
+        """ delete the desired header """
+        if self.headers.has_key(header):
+            del self.headers[header]
+
     def setHeader(self, header, value):
         """ set the desired header """
         self.headers[header] = value
@@ -747,11 +786,12 @@ class MSNEventBase(LineReceiver):
         raise NotImplementedError
 
     def sendLine(self, line):
-        if LINEDEBUG: log.msg(">> " + line)
+        if LINEDEBUG: log.msg("<< " + line)
         LineReceiver.sendLine(self, line)
 
     def lineReceived(self, line):
-        if LINEDEBUG: log.msg("<< " + line)
+        if LINEDEBUG: log.msg(">> " + line)
+        if not self.connected: return
         if self.currentMessage:
             self.currentMessage.readPos += len(line+"\r\n")
             try:
@@ -772,8 +812,10 @@ class MSNEventBase(LineReceiver):
 
         if len(cmd) != 3: raise MSNProtocolError, "Invalid Command, %s" % repr(cmd)
         if cmd.isdigit():
-            if self.ids.has_key(params.split(' ')[0]):
-                self.ids[id].errback(int(cmd))
+            id = params.split(' ')[0]
+            if id.isdigit() and self.ids.has_key(int(id)):
+                id = int(id)
+                self.ids[id][0].errback(int(cmd))
                 del self.ids[id]
                 return
             else:       # we received an error which doesn't map to a sent command
@@ -788,6 +830,7 @@ class MSNEventBase(LineReceiver):
             self.handle_UNKNOWN(cmd, params.split(' '))
 
     def rawDataReceived(self, data):
+        if not self.connected: return
         extra = ""
         self.currentMessage.readPos += len(data)
         diff = self.currentMessage.readPos - self.currentMessage.length
@@ -803,9 +846,13 @@ class MSNEventBase(LineReceiver):
         m = self.currentMessage
         self.currentMessage = None
         if MESSAGEDEBUG: log.msg(m.message)
-        if not self.checkMessage(m):
+        try:
+            if not self.checkMessage(m):
+                self.setLineMode(extra)
+                return
+        except Exception, e:
             self.setLineMode(extra)
-            return
+            raise
         self.gotMessage(m)
         self.setLineMode(extra)
 
@@ -930,6 +977,7 @@ class NotificationClient(MSNEventBase):
         MSNEventBase.connectionMade(self)
         self._setState('CONNECTED')
         self.sendLine("VER %s %s" % (self._nextTransactionID(), MSN_PROTOCOL_VERSION))
+        self.factory.resetDelay()
 
     def connectionLost(self, reason):
         self._setState('DISCONNECTED')
@@ -1009,16 +1057,17 @@ class NotificationClient(MSNEventBase):
         self.gotMSNAlert(bodytext, actionurl, subscrurl)
 
     def _gotUBX(self, message):
+        msnContact = self.factory.contacts.getContact(message.userHandle)
+        if not msnContact: return
         lm = message.message.lower()
         p1 = lm.find("<psm>") + 5
         p2 = lm.find("</psm>")
         if p1 >= 0 and p2 >= 0:
-            personal = unescapeFromXml(message.message[p1:p2])
-            msnContact = self.factory.contacts.getContact(message.userHandle)
-            if not msnContact: return
+            personal = xmlw.unescapeFromXml(message.message[p1:p2])
             msnContact.personal = personal
             self.contactPersonalChanged(message.userHandle, personal)
         else:
+            msnContact.personal = ''
             self.contactPersonalChanged(message.userHandle, '')
 
     def checkMessage(self, message):
@@ -1112,7 +1161,7 @@ class NotificationClient(MSNEventBase):
             if MSNP2PDEBUG: log.msg("Updated MSNObject received!" + msnobjStr)
             msnContact.msnobj = msnobj
             msnContact.msnobjGot = False
-            self.contactAvatarChanged(msnContact.userHandle, msnContact.msnobj.sha1d)
+            self.contactAvatarChanged(msnContact.userHandle, binascii.hexlify(b64dec(msnContact.msnobj.sha1d)))
 
     def handle_CHL(self, params):
         checkParamLen(len(params), 2, 'CHL')
@@ -1214,18 +1263,18 @@ class NotificationClient(MSNEventBase):
 
     def handle_PRP(self, params):
         if params[1] == "MFN":
-            self._fireCallback(int(params[0]), unquote(params[2]))
+            self._fireCallback(int(params[0]))
         elif self._getState() == 'SYNC':
             self._getStateData('phone').append((params[0], unquote(params[1])))
         else:
             self._fireCallback(int(params[0]), int(params[1]), unquote(params[3]))
 
     def handle_BPR(self, params):
-        if not self.factory.contacts: raise MSNProtocolError, "handle_BPR called with no contact list" # debug
         numParams = len(params)
         if numParams == 2: # part of a syn
             self._getStateData('last_contact').setPhone(params[0], unquote(params[1]))
         elif numParams == 4:
+            if not self.factory.contacts: raise MSNProtocolError, "handle_BPR called with no contact list" # debug
             self.factory.contacts.version = int(params[0])
             userHandle, phoneType, number = params[1], params[2], unquote(params[3])
             self.factory.contacts.getContact(userHandle).setPhone(phoneType, number)
@@ -1332,7 +1381,7 @@ class NotificationClient(MSNEventBase):
             self.currentMessage = MSNMessage(length=messageLen, userHandle=params[0], screenName="UBX", specialMessage=True)
             self.setRawMode()
         else:
-            self.contactPersonalChanged(params[0], '')
+            self._gotUBX(MSNMessage(userHandle=params[0]))
 
     def handle_UUX(self, params):
         checkParamLen(len(params), 2, 'UUX')
@@ -1342,6 +1391,7 @@ class NotificationClient(MSNEventBase):
 
     def handle_OUT(self, params):
         checkParamLen(len(params), 1, 'OUT')
+        self.factory.stopTrying()
         if params[0] == "OTH": self.multipleLogin()
         elif params[0] == "SSD": self.serverGoingDown()
         else: raise MSNProtocolError, "Invalid Parameters received for OUT" # debug
@@ -1414,7 +1464,7 @@ class NotificationClient(MSNEventBase):
         contact.
 
         @param userHandle: contact who's msnobj has been changed
-        @param hash: sha1 hash of their avatar
+        @param hash: sha1 hash of their avatar as hex string
         """
 
     def statusChanged(self, statusCode):
@@ -1867,14 +1917,13 @@ class NotificationClient(MSNEventBase):
 
         @return: A Deferred, the callback for which will be called
                  when the server acknowledges the change.
-                 The callback argument will be a tuple of 1 element,
-                 the new screen name.
+                 The callback argument will be an empty tuple.
         """
 
         id, d = self._createIDMapping()
         self.sendLine("PRP %s MFN %s" % (id, quote(newName)))
         def _cb(r):
-            self.factory.screenName = r[0]
+            self.factory.screenName = newName
             return r
         return d.addCallback(_cb)
 
@@ -1948,10 +1997,11 @@ class NotificationClient(MSNEventBase):
         if self.pingCheckTask:
             self.pingCheckTask.stop()
             self.pingCheckTask = None
+        self.factory.stopTrying()
         self.sendLine("OUT")
         self.transport.loseConnection()
 
-class NotificationFactory(ClientFactory):
+class NotificationFactory(ReconnectingClientFactory):
     """
     Factory for the NotificationClient protocol.
     This is basically responsible for keeping
@@ -1974,6 +2024,8 @@ class NotificationFactory(ClientFactory):
                           (the whole URL is required)
     @ivar status: The status of the client -- this is generally kept
                   up to date by the default command handlers
+    @ivar maxRetries: The number of times the factory will reconnect
+                      if the connection dies because of a network error.
     """
 
     contacts = None
@@ -1981,8 +2033,9 @@ class NotificationFactory(ClientFactory):
     screenName = ''
     password = ''
     passportServer = 'https://nexus.passport.com/rdr/pprdr.asp'
-    status = 'FLN'
+    status = 'NLN'
     protocol = NotificationClient
+    maxRetries = 5
 
 
 class SwitchboardClient(MSNEventBase):
@@ -2060,7 +2113,7 @@ class SwitchboardClient(MSNEventBase):
             cookie = info['Invitation-Cookie']
             filename = info['Application-File']
             filesize = int(info['Application-FileSize'])
-            connectivity = (info.get('Connectivity').lower() == 'y')
+            connectivity = (info.get('Connectivity', 'n').lower() == 'y')
         except KeyError:
             log.msg('Received munged file transfer request ... ignoring.')
             return 0
@@ -2073,14 +2126,16 @@ class SwitchboardClient(MSNEventBase):
         if not message.getHeader("P2P-Dest") == self.userHandle: return
         packet = message.message
         binaryFields = BinaryFields(packet=packet)
-        if binaryFields[0] != 0:
+        if binaryFields[5] == BinaryFields.BYEGOT:
+            pass # Ignore the ACKs to SLP messages
+        elif binaryFields[0] != 0:
             slpLink = self.slpLinks.get(binaryFields[0])
             if not slpLink:
                 # Link has been killed. Ignore
                 return
             if slpLink.remoteUser == message.userHandle:
                 slpLink.handlePacket(packet)
-        if binaryFields[5] == BinaryFields.ACK or binaryFields[5] == BinaryFields.BYEGOT:
+        elif binaryFields[5] == BinaryFields.ACK:
             pass # Ignore the ACKs to SLP messages
         else:
             slpMessage = MSNSLPMessage(packet)
@@ -2098,7 +2153,7 @@ class SwitchboardClient(MSNEventBase):
             if not slpLink and slpMessage.method == "INVITE":
                 if slpMessage.euf_guid == MSN_MSNFTP_GUID:
                     context = FileContext(slpMessage.context)
-                    slpLink = SLPLink_FileReceive(remoteUser=slpMessage.fro, switchboard=self, filename=context.filename, filesize=context.filesize, sessionID=slpMessage.sessionID, sessionGuid=slpMessage.sessionGuid)
+                    slpLink = SLPLink_FileReceive(remoteUser=slpMessage.fro, switchboard=self, filename=context.filename, filesize=context.filesize, sessionID=slpMessage.sessionID, sessionGuid=slpMessage.sessionGuid, branch=slpMessage.branch)
                     self.slpLinks[slpMessage.sessionID] = slpLink
                     self.gotFileReceive(slpLink)
                 elif slpMessage.euf_guid == MSN_AVATAR_GUID:
@@ -2110,6 +2165,7 @@ class SwitchboardClient(MSNEventBase):
                     else:
                         # They shouldn't have sent a request if we have
                         # no avatar. So we'll just ignore them.
+                        # FIXME We should really send an error
                         pass
                 if slpLink:
                     self.slpLinks[slpMessage.sessionID] = slpLink
@@ -2125,15 +2181,15 @@ class SwitchboardClient(MSNEventBase):
         """
         cTypes = [s.lstrip() for s in message.getHeader('Content-Type').split(';')]
         if self._checkTyping(message, cTypes): return 0
-        if 'text/x-msmsgsinvite' in cTypes:
+#        if 'text/x-msmsgsinvite' in cTypes:
             # header like info is sent as part of the message body.
-            info = {}
-            for line in message.message.split('\r\n'):
-                try:
-                    key, val = line.split(':')
-                    info[key] = val.lstrip()
-                except ValueError: continue
-            if self._checkFileInvitation(message, info): return 0
+#            info = {}
+#            for line in message.message.split('\r\n'):
+#                try:
+#                    key, val = line.split(':')
+#                    info[key] = val.lstrip()
+#                except ValueError: continue
+#            if self._checkFileInvitation(message, info): return 0
         elif 'application/x-msnmsgrp2p' in cTypes:
             self._handleP2PMessage(message)
             return 0
@@ -2228,11 +2284,12 @@ class SwitchboardClient(MSNEventBase):
 
     def gotFileReceive(self, fileReceive):
         """
-        called when we receive a file send request from a contact
+        called when we receive a file send request from a contact.
+        Default action is to reject the file.
 
         @param fileReceive: msnft.MSNFTReceive_Base instance
         """
-        pass
+        fileReceive.reject()
 
 
     def gotSendRequest(self, fileReceive):
@@ -2290,11 +2347,13 @@ class SwitchboardClient(MSNEventBase):
         else: id, d = self._createIDMapping()
         if message.length == 0: message.length = message._calcMessageLen()
         self.sendLine("MSG %s %s %s" % (id, message.ack, message.length))
-        # apparently order matters with at least MIME-Version and Content-Type
-        self.sendLine('MIME-Version: %s' % message.getHeader('MIME-Version'))
-        self.sendLine('Content-Type: %s' % message.getHeader('Content-Type'))
+        # Apparently order matters with these
+        orderMatters = ("MIME-Version", "Content-Type", "Message-ID")
+        for header in orderMatters:
+            if message.hasHeader(header):
+                self.sendLine("%s: %s" % (header, message.getHeader(header)))
         # send the rest of the headers
-        for header in [h for h in message.headers.items() if h[0].lower() not in ('mime-version','content-type')]:
+        for header in [h for h in message.headers.items() if h[0] not in orderMatters]:
             self.sendLine("%s: %s" % (header[0], header[1]))
         self.transport.write("\r\n")
         self.transport.write(message.message)
@@ -2318,8 +2377,8 @@ class SwitchboardClient(MSNEventBase):
         def bufferClosed(data):
             d.callback((data,))
         buffer = StringBuffer(bufferClosed)
+        buffer.error = lambda: None
         slpLink = SLPLink_AvatarReceive(remoteUser=msnContact.userHandle, switchboard=self, consumer=buffer, context=msnContact.msnobj.text)
-        slpLink.avatarDataBuffer = buffer
         self.slpLinks[slpLink.sessionID] = slpLink
         return d
 
@@ -2465,21 +2524,21 @@ class FileContext:
             self.filesize = 0
 
     def pack(self):
-        if MSNP2PDEBUG: print "FileContext packing:", self.filename, self.filesize
+        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 += self.filename.encode("utf-16")[2:] # Strip off the utf16 BOM
-        data = data.ljust(570, '\0')
-        data += struct.pack("<L", 0xFFFFFFFF)
-        data = data.ljust(638, '\0')
+        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(chunk.decode("utf-16"))
-        if MSNP2PDEBUG: print "FileContext parsed:", self.filesize, self.filename
+        self.filename = unicode((codecs.BOM_UTF16_BE + chunk).decode("utf-16"))
+        if MSNP2PDEBUG: log.msg("FileContext parsed:", self.filesize, self.filename)
 
 
 class BinaryFields:
@@ -2510,18 +2569,18 @@ class BinaryFields:
         self.fields = struct.unpack("<LLQQLLLLQ", packet[0:48])
         self.fields += struct.unpack(">L", packet[len(packet)-4:])
         if MSNP2PDEBUG:
-            print "Unpacked fields:",
+            out = "Unpacked fields: "
             for i in self.fields:
-                print hex(i),
-            print
+                out += hex(i) + ' '
+            log.msg(out)
     
     def packHeaders(self):
         f = tuple(self.fields)
         if MSNP2PDEBUG:
-            print "Packed fields:",
+            out = "Packed fields: "
             for i in self.fields:
-                print hex(i),
-            print
+                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):
@@ -2535,6 +2594,7 @@ class MSNSLPMessage:
         self.status = ""
         self.to = ""
         self.fro = ""
+        self.branch = ""
         self.cseq = 0
         self.sessionGuid = ""
         self.sessionID = None
@@ -2543,11 +2603,12 @@ class MSNSLPMessage:
         if packet:
             self.parse(packet)
 
-    def create(self, method=None, status=None, to=None, fro=None, cseq=0, sessionGuid=None, data=None):
+    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
@@ -2573,7 +2634,7 @@ class MSNSLPMessage:
         
         # Get the MSNSLP method or status
         msnslp = lines[0].split(" ")
-        if MSNP2PDEBUG: print "Parsing MSNSLPMessage", len(s), s
+        if MSNP2PDEBUG: log.msg("Parsing MSNSLPMessage %s %s" % (len(s), s))
         if msnslp[0] in ("INVITE", "BYE"):
             self.method = msnslp[0].strip()
         else:
@@ -2583,7 +2644,8 @@ class MSNSLPMessage:
         
         for line in lines:
             line = line.split(":")
-            if len(line) > 1:
+            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":
@@ -2599,7 +2661,13 @@ class MSNSLPMessage:
                 elif line[0] == "Content-Type":
                     self.ctype = line[1].strip()
                 elif line[0] == "Context":
-                    self.context = base64.decodestring(line[1])
+                    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 = []
@@ -2611,7 +2679,7 @@ class MSNSLPMessage:
             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" % random_guid())
+        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")
@@ -2630,7 +2698,7 @@ class SeqID:
         self.pos = -1
 
     def get(self):
-        return P2PSEQ[self.pos] + self.baseID
+        return p2pseq(self.pos) + self.baseID
     
     def next(self):
         self.pos += 1
@@ -2663,7 +2731,9 @@ class SLPLink:
         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
@@ -2672,8 +2742,7 @@ class SLPLink:
         reactor.callLater(0, kill)
 
     def warn(self, text):
-        if MSNP2PDEBUG:
-            print "Warning in transfer: ", self, text
+        log.msg("Warning in transfer: %s %s" % (self, text))
 
     def sendP2PACK(self, ackHeaders):
         binaryFields = BinaryFields()
@@ -2686,12 +2755,12 @@ class SLPLink:
         binaryFields[8] = ackHeaders[3]
         self.sendP2PMessage(binaryFields, "")
 
-    def sendSLPMessage(self, cmd, ctype, data):
+    def sendSLPMessage(self, cmd, ctype, data, branch=None):
         msg = MSNSLPMessage()
         if cmd.isdigit():
-            msg.create(status=cmd, to=self.remoteUser, fro=self.switchboard.userHandle, cseq=1, sessionGuid=self.sessionGuid)
+            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, cseq=0, sessionGuid=self.sessionGuid)
+            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()
@@ -2726,7 +2795,7 @@ class SLPLink_Send(SLPLink):
         self.data = ""
     
     def send_dataprep(self):
-        if MSNP2PDEBUG: print "send_dataprep"
+        if MSNP2PDEBUG: log.msg("send_dataprep")
         binaryFields = BinaryFields()
         binaryFields[0] = self.sessionID
         binaryFields[1] = self.seqID.next()
@@ -2737,23 +2806,21 @@ class SLPLink_Send(SLPLink):
         self.sendP2PMessage(binaryFields, chr(0) * 4)
 
     def write(self, data):
-        if MSNP2PDEBUG: print "write"
+        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:]
-                if len(self.data) >= 1202:
-                    data = self.data
-                    self.data = ""
-                    self.write(data)
+                self.data = data[i:]
                 return
 
     def _writeChunk(self, chunk):
-        print "writing chunk"
+        if MSNP2PDEBUG: log.msg("writing chunk")
         binaryFields = BinaryFields()
         binaryFields[0] = self.sessionID
         if self.offset == 0:
@@ -2804,28 +2871,16 @@ class SLPLink_FileSend(SLPLink_Send):
                         #"Hashed-Nonce": random_guid()}
                 self.sendSLPMessage("INVITE", "application/x-msnmsgr-transreqbody", data)
             elif slpMessage.ctype == "application/x-msnmsgr-transrespbody":
-                self.send_dataprep()
-                self.handlePacket = self.wait_dataprep_ack
+                self.acceptDeferred.callback((True,))
+                self.handlePacket = self.wait_data_ack
         else:
             if slpMessage.status == "603":
                 self.acceptDeferred.callback((False,))
-            # SLPLink is over due to decline, error or BYE
+            if MSNP2PDEBUG: log.msg("SLPLink is over due to decline, error or BYE")
             self.killLink()
 
-    def wait_dataprep_ack(self, packet):
-        if MSNP2PDEBUG: print "wait_dataprep_ack"
-        binaryFields = BinaryFields()
-        binaryFields.unpackFields(packet)
-
-        if binaryFields[5] != BinaryFields.ACK:
-            self.warn("field5," + str(binaryFields[5]))
-            return
-
-        self.handlePacket = None
-        self.acceptDeferred.callback((True,))
-
     def wait_data_ack(self, packet):
-        if MSNP2PDEBUG: print "wait_data_ack"
+        if MSNP2PDEBUG: log.msg("wait_data_ack")
         binaryFields = BinaryFields()
         binaryFields.unpackFields(packet)
 
@@ -2850,7 +2905,8 @@ class SLPLink_AvatarSend(SLPLink_Send):
         self.handlePacket = lambda packet: None
 
     def handleSLPMessage(self, slpMessage):
-        self.killLink() # BYE or error
+        if MSNP2PDEBUG: log.msg("BYE or error")
+        self.killLink()
 
     def close(self):
         SLPLink_Send.close(self)
@@ -2864,7 +2920,7 @@ class SLPLink_Receive(SLPLink):
         self.pos = 0
 
     def wait_dataprep(self, packet):
-        if MSNP2PDEBUG: print "wait_dataprep"
+        if MSNP2PDEBUG: log.msg("wait_dataprep")
         binaryFields = BinaryFields()
         binaryFields.unpackFields(packet)
 
@@ -2874,23 +2930,25 @@ class SLPLink_Receive(SLPLink):
         if binaryFields[4] != 4:
             self.warn("field4," + str(binaryFields[4]))
             return
-        if binaryFields[9] != 1:
-            self.warn("field9," + str(binaryFields[9]))
+        # 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: print "wait_data"
+        if MSNP2PDEBUG: log.msg("wait_data")
         binaryFields = BinaryFields()
         binaryFields.unpackFields(packet)
 
         if binaryFields[5] != self.dataFlag:
             self.warn("field5," + str(binaryFields[5]))
             return
-        if binaryFields[9] != 1:
-            self.warn("field9," + str(binaryFields[9]))
+        # Just ignore the footer
+        #if binaryFields[9] != 1:
+        #    self.warn("field9," + str(binaryFields[9]))
         #   return
         offset = binaryFields[2]
         total = binaryFields[3]
@@ -2908,7 +2966,7 @@ class SLPLink_Receive(SLPLink):
         
         self.pos += length
 
-        self.consumer.write(data)
+        self.consumer.write(str(data))
 
         if self.pos == total:
             self.sendP2PACK(binaryFields)
@@ -2921,29 +2979,34 @@ class SLPLink_Receive(SLPLink):
 
 
 class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
-    def __init__(self, remoteUser, switchboard, filename, filesize, sessionID, sessionGuid):
+    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
-        self.sendSLPMessage("603", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID})
+        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)
-        self.sendSLPMessage("200", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID})
+        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)
-            self.handlePacket = self.wait_dataprep
+            self.sendSLPMessage("200", "application/x-msnmsgr-transrespbody", data, branch=slpMessage.branch)
+#            self.handlePacket = self.wait_data # Moved up
         else:
-            self.killLink() # It's either a BYE or an error
+            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):
@@ -2961,12 +3024,14 @@ class SLPLink_AvatarReceive(SLPLink_Receive):
                 "AppID"    : 1,\
                 "Context"  : context}
         self.sendSLPMessage("INVITE", "application/x-msnmsgr-sessionreqbody", data)
+        self.handlePacket = self.wait_dataprep
 
     def handleSLPMessage(self, slpMessage):
         if slpMessage.status == "200":
-            self.handlePacket = self.wait_dataprep
+            pass
+            #self.handlePacket = self.wait_dataprep # Moved upwards
         else:
-            # SLPLink is over due to error or BYE
+            if MSNP2PDEBUG: log.msg("SLPLink is over due to error or BYE")
             self.killLink()
 
     def doFinished(self):
@@ -3001,6 +3066,7 @@ errorCodes = {
     301 : "Too many FND responses",
     302 : "Not logged in",
 
+    400 : "Message not allowed",
     402 : "Error accessing contact list",
     403 : "Error accessing contact list",