Hello all, Here's a patch against the latest versions of ylib.py and yahoo_helper.py. This is enough to make my transport behave in the tests I've run; hopefully it's complete enough to work for everyone. Please test it and see how it works for you.
I hope my mail client won't botch this... Good luck! -- -TimS Tim Stewart Stoo Research [email protected] diff --git a/yahoo-transport/yahoo_helpers.py b/yahoo-transport/ yahoo_helpers.py index 2760c08..61a89b6 100644 --- a/yahoo-transport/yahoo_helpers.py +++ b/yahoo-transport/yahoo_helpers.py @@ -46,6 +46,7 @@ Y_chtmsg = 168 # Chat Message Y_avatar = 188 # Avatar Image update Y_statusupdate = 198 #update of status (like away/back etc) Y_advstatusupdate = 199 #update of advanced status (like avatar etc) +Y_statusupdate15 = 240 # Protocol 15: status update (away/back, etc.) Y_cloud = 241 # 0 = Yahoo!, 2 = LiveID (WLM) Yahoosep = '\xc0\x80' diff --git a/yahoo-transport/ylib.py b/yahoo-transport/ylib.py index 9731057..fccdb24 100644 --- a/yahoo-transport/ylib.py +++ b/yahoo-transport/ylib.py @@ -7,6 +7,9 @@ import socket, time import avatar import re import random +import httplib +import md5 +import base64 def printpacket(packet): @@ -30,7 +33,7 @@ class YahooCon: hostlist = socket.gethostbyname_ex('scs.msg.yahoo.com')[2] #hostlist = ['cs1 .msg.dcn.yahoo.com ','cs2 .msg.dcn.yahoo.com ','cs3 .msg.dcn.yahoo.com ','cs4 .msg.dcn.yahoo.com ','cs5 .msg.dcn.yahoo.com ','cs6 .msg.dcn.yahoo.com ','cs7 .msg.dcn.yahoo.com ','cs8 .msg.dcn.yahoo.com ','cs9 .msg.dcn.yahoo.com ','cs10 .msg.dcn.yahoo.com ','cs11 .msg.dcn.yahoo.com ','cs12 .msg.dcn.yahoo.com ','cs13 .msg.dcn.yahoo.com ','cs14 .msg.dcn.yahoo.com ','cs15 .msg.dcn.yahoo.com ','cs16 .msg.dcn.yahoo.com ','cs17 .msg.dcn.yahoo.com ','cs18 .msg.dcn.yahoo.com ','cs40 .msg.dcn.yahoo.com ','cs41 .msg.dcn.yahoo.com ','cs42 .msg.dcn.yahoo.com ','cs43 .msg.dcn.yahoo.com ','cs44 .msg.dcn.yahoo.com ','cs45 .msg.dcn.yahoo.com ','cs46 .msg.dcn.yahoo.com ','cs50 .msg.dcn.yahoo.com','cs51.msg.dcn.yahoo.com','cs52.msg.dcn.yahoo.com'] port = 5050 - version = 0x000c0000 + version = 0x00100000 sock = None # a dictionary of groups and members buddylist = {} @@ -111,8 +114,74 @@ class YahooCon: session = hdr[5] self.session=session chalstr = pay[0][94] - (crypt1, crypt2) = YahooMD5.curphoo_process_auth (self.username,self.password,chalstr) - npay = ymsg_mkargu({6:crypt1,96:crypt2,0:self.username, 1:self.username,2:self.username,135:'5,6,0,1358',148:'360'}) + + # Do HTTPS login. + h = httplib.HTTPSConnection('login.yahoo.com') + h.request('GET', '/config/pwtoken_get?src=ymsgr&ts=&login= %s&passwd=%s&chal=%s' % (self.username, self.password, chalstr)) + resp = h.getresponse().read().splitlines() + code = resp[0] + if code != '0': + # Interpret login problems. These should broken out into individual conditionals and given + # more specific messages in yahoo.py. + if code == '100' or code == '1212' or code == '1235': + # Username or password is missing (100), username or password is incorrect (1212), + # or username does not exist (1235). + if self.handlers.has_key('loginfail'): + self.handlers['loginfail'](self,'badpassword') + return None + + elif code == '1013': + # Username contains @yahoo.com or similar which needs removing. + if self.handlers.has_key('loginfail'): + self.handlers['loginfail'](self,'badusername') + return None + + elif code == '1213' or code == '1214' or code == '1236' or code == '1218': + # Security lock on account due to failed login attempts (1213 and 1236), general security + # lock (1214), or account deactivated by Yahoo! (1218). + if self.handlers.has_key('loginfail'): + self.handlers['loginfail'](self,'locked') + return None + + else: + # Other error not listed. + if self.handlers.has_key('loginfail'): + self.handlers['loginfail'](self) + return None + + # No error in code, so get remaining fields. + ymsgr = resp[1][resp[1].index('=') + 1:] + partnerid = resp[2][resp[2].index('=') + 1:] + if self.dumpProtocol: print "HTTPS pwtoken_get response {code: %s, ymsgr: %s, partnerid: %s}" % (code, ymsgr, partnerid) + + # Login successful, grab our crumb. + h.request('GET', '/config/pwtoken_login?src=ymsgr&ts=&token= %s' % ymsgr) + resp = h.getresponse().read().splitlines() + code = resp[0] + crumb = resp[1][resp[1].index('=') + 1:] + y_crumb = resp[2][resp[2].index('=') + 1:] + t_crumb = resp[3][resp[3].index('=') + 1:] + validfor = resp[4][resp[4].index('=') + 1:] + if self.dumpProtocol: print "HTTPS pwtoken_login response {code: %s, crumb: %s, y_crumb: %s, t_crumb: %s, validfor: %s}" % (code, crumb, y_crumb, t_crumb, validfor) + + # Calculate hash of crumb and challenge string. + mhash = md5.new() + mhash.update(crumb) + mhash.update(chalstr) + bhash = base64.encodestring(mhash.digest()).replace('+', '.').replace('/', '_').replace('=', '-').strip() + + # Assemble response packet. + npay = ymsg_mkargu({ + 1: self.username, + 0: self.username, + 277: y_crumb, + 278: t_crumb, + 307: bhash, + 244: '2097087', + 2: self.username, + 2: '1', + 98: 'us', + 135: '9.0.0.1389'}) nhdr = ymsg_mkhdr(self.version,len(npay),Y_challenge, 0x5a55aa55,self.session) return nhdr+npay @@ -224,6 +293,9 @@ class YahooCon: self.handlers['offline'] (self,pay[each][7]) if not self.resources.has_key(pay[each][7]) or self.resources[pay[each][7]] == []: self.roster[pay[each][7]]=('unavailable', None, None) + if self.handlers.has_key('offline'): + self.handlers['offline'](self,pay[each][7]) + elif len(pay[0].keys()) == 0: if self.handlers.has_key('closed'): self.handlers['closed'](self) @@ -443,6 +515,76 @@ class YahooCon: if self.handlers.has_key('roommessagefail'): self.handlers['roommessagefail'](self, pay[0] [109], pay[0][104], msg) + def ymsg_cloud(self, hdr, pay): + # Loop through the payload entries and pull out groups and buddy names. The odd loop construct is to make + # sure the entries are parsed in order; I'm not convinced (yet) that dict's .values() will always iterate + # in the desired order. + group = 'Top Level' + i = 0 + while pay.has_key(i): + entry = pay[i] + i = i + 1 + + # Entry is a group. + if entry.has_key(65): + group = entry[65] + self.buddylist[group] = [] + + # Entry is a buddy. + if entry.has_key(300) and entry.has_key(7): + buddy = entry[7] + self.buddylist[group].append(buddy) + if not self.roster.has_key(buddy): + self.roster[buddy]=('unavailable',None, None) + if self.handlers.has_key('subscribe'): + self.handlers['subscribe'](self, buddy, 'XXX Test message XXX') + + def ymsg_statusupdate15(self, hdr, pay): + # If we're here then buddy is online in some sense. Let's figure out how online they are. This message may + # contain more than one buddy status (this seems to happen right after login). + for entry in pay.values(): + if not entry.has_key(7): + continue + + buddy = entry[7] + typ = int(entry[10]) + + # Grab status message, if any. + status = '' + if entry.has_key(19): + status = entry[19] + + # Determine idle/away status. + away = 0 + idle = 0 + if typ > 0 and typ < 12: + away = 1 + if entry.has_key(47) and int(entry[47]) > 0: + away = 1 + if typ == 999: + idle = 1 + + # Update roster and presence. + if typ < 1000 and typ != 12: + # Buddy is online... + if away == 1: + # ... but away. + self.roster[buddy] = ('available', 'dnd', status) + elif idle == 1: + # ... but idle. + self.roster[buddy] = ('available', 'away', status) + else: + # ... and not idle or away. + self.roster[buddy] = ('available', None, status) + + if self.handlers.has_key('online'): + self.handlers['online'](self, buddy, '') + else: + # Buddy is offline. + self.roster[buddy] = ('unavailable', None, None) + if self.handlers.has_key('offline'): + self.handlers['offline'](self, buddy) + def ymsg_init(self): try: challenge = self.ymsg_send_challenge() @@ -688,8 +830,9 @@ class YahooCon: if s[3] == Y_chalreq: #87 # give salt challenge = self.ymsg_challenge(s,t) - if self.dumpProtocol: printpacket(challenge) - self.sock.send(challenge) + if challenge: + if self.dumpProtocol: printpacket(challenge) + self.sock.send(challenge) elif s[3] == Y_login: #85 # login ok self.ymsg_login(s,t) @@ -748,6 +891,10 @@ class YahooCon: self.ymsg_init() elif s[3] == Y_chatlogout: self.chatlogin = False + elif s[3] == Y_statusupdate15: #240 + self.ymsg_statusupdate15(s, t) + elif s[3] == Y_cloud: #241 + self.ymsg_cloud(s,t) else: pass #print "remove packet" --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "py-transports" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/py-transports?hl=en -~----------~----~----~----~------~----~------~--~---
