X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/8988dfb72fdd4e99581138b5fcfe1e610f5e4ff2..976c6fe023f9f1c93e19de17d0c4a6a32b915cd0:/src/tlib/msn/msn.py diff --git a/src/tlib/msn/msn.py b/src/tlib/msn/msn.py index cc665ad..13ec091 100644 --- a/src/tlib/msn/msn.py +++ b/src/tlib/msn/msn.py @@ -99,10 +99,13 @@ 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 @@ -151,16 +154,10 @@ STATUS_LUNCH = 'LUN' PINGSPEED = 50.0 -DEBUGALL = False LINEDEBUG = False MESSAGEDEBUG = False MSNP2PDEBUG = False -if DEBUGALL: - LINEDEBUG = True - MESSAGEDEBUG = True - MSNP2PDEBUG = True - P2PSEQ = [-3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8] def p2pseq(n): @@ -343,7 +340,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 = {} @@ -430,7 +427,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 @@ -441,6 +438,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 @@ -479,10 +481,9 @@ class MSNObject: """ def __init__(self, s=""): """ Pass a XML MSNObject string to parse it, or pass no arguments for a null MSNObject to be created. """ + self.setNull() if s: self.parse(s) - else: - self.setNull() def setData(self, creator, imageData): """ Set the creator and imageData for this object """ @@ -778,11 +779,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: @@ -803,8 +805,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 @@ -819,6 +823,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 @@ -839,9 +844,8 @@ class MSNEventBase(LineReceiver): self.setLineMode(extra) return except Exception, e: - log.msg("Traceback - ERROR in checkMessage: " + str(e)) self.setLineMode(extra) - return + raise self.gotMessage(m) self.setLineMode(extra) @@ -966,6 +970,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') @@ -1045,16 +1050,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): @@ -1368,7 +1374,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') @@ -1378,6 +1384,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 @@ -1983,10 +1990,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 @@ -2009,6 +2017,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 @@ -2016,8 +2026,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): @@ -2108,14 +2119,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) @@ -2161,15 +2174,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 @@ -2327,11 +2340,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) @@ -2355,6 +2370,7 @@ 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) self.slpLinks[slpLink.sessionID] = slpLink return d @@ -2708,7 +2724,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 @@ -2851,7 +2869,8 @@ 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.data = "" self.killLink() def wait_data_ack(self, packet): @@ -2880,7 +2899,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) @@ -2961,12 +2981,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 @@ -2974,9 +2997,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): @@ -2994,12 +3018,19 @@ 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 + 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: - # 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): @@ -3034,6 +3065,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",