X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/7cbd2961a00825475f7f94f842fd2a1612d8f2e2..8731c6903026cab0a69cf54592ac2778cb575043:/src/tlib/msn/msn.py
diff --git a/src/tlib/msn/msn.py b/src/tlib/msn/msn.py
index b0d37b8..373c551 100644
--- a/src/tlib/msn/msn.py
+++ b/src/tlib/msn/msn.py
@@ -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
@@ -152,7 +154,7 @@ STATUS_LUNCH = 'LUN'
PINGSPEED = 50.0
-DEBUGALL = True
+DEBUGALL = False
LINEDEBUG = False
MESSAGEDEBUG = False
MSNP2PDEBUG = False
@@ -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]
@@ -194,6 +205,17 @@ 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", "")
@@ -411,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
@@ -422,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
@@ -759,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:
@@ -800,6 +828,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
@@ -815,9 +844,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)
@@ -942,6 +975,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')
@@ -1021,16 +1055,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("") + 5
p2 = lm.find("")
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):
@@ -1124,7 +1159,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')
@@ -1226,18 +1261,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)
@@ -1344,7 +1379,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')
@@ -1354,6 +1389,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
@@ -1426,7 +1462,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):
@@ -1879,14 +1915,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)
@@ -1960,10 +1995,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
@@ -1986,6 +2022,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
@@ -1995,6 +2033,7 @@ class NotificationFactory(ClientFactory):
passportServer = 'https://nexus.passport.com/rdr/pprdr.asp'
status = 'FLN'
protocol = NotificationClient
+ maxRetries = 5
class SwitchboardClient(MSNEventBase):
@@ -2072,7 +2111,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
@@ -2085,14 +2124,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)
@@ -2241,11 +2282,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):
@@ -2303,11 +2345,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)
@@ -2331,8 +2375,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
@@ -2478,12 +2522,12 @@ 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("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("= 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:
@@ -2831,11 +2874,11 @@ class SLPLink_FileSend(SLPLink_Send):
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_data_ack(self, packet):
- if MSNP2PDEBUG: print "wait_data_ack"
+ if MSNP2PDEBUG: log.msg("wait_data_ack")
binaryFields = BinaryFields()
binaryFields.unpackFields(packet)
@@ -2860,7 +2903,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)
@@ -2874,7 +2918,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)
@@ -2884,23 +2928,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]
@@ -2918,7 +2964,7 @@ class SLPLink_Receive(SLPLink):
self.pos += length
- self.consumer.write(data)
+ self.consumer.write(str(data))
if self.pos == total:
self.sendP2PACK(binaryFields)
@@ -2939,12 +2985,15 @@ class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
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
@@ -2952,9 +3001,10 @@ class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
"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
+# 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):
@@ -2972,12 +3022,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):