1# -*- test-case-name: twisted.words.test -*- 2# Copyright (c) Twisted Matrix Laboratories. 3# See LICENSE for details. 4 5 6""" 7An implementation of the OSCAR protocol, which AIM and ICQ use to communcate. 8 9Maintainer: Paul Swartz 10""" 11 12import struct 13import string 14import socket 15import random 16import types 17import re 18from hashlib import md5 19 20from twisted.internet import reactor, defer, protocol 21from twisted.python import log 22 23def logPacketData(data): 24 lines = len(data)/16 25 if lines*16 != len(data): lines=lines+1 26 for i in range(lines): 27 d = tuple(data[16*i:16*i+16]) 28 hex = map(lambda x: "%02X"%ord(x),d) 29 text = map(lambda x: (len(repr(x))>3 and '.') or x, d) 30 log.msg(' '.join(hex)+ ' '*3*(16-len(d)) +''.join(text)) 31 log.msg('') 32 33def SNAC(fam,sub,id,data,flags=[0,0]): 34 header="!HHBBL" 35 head=struct.pack(header,fam,sub, 36 flags[0],flags[1], 37 id) 38 return head+str(data) 39 40def readSNAC(data): 41 header="!HHBBL" 42 head=list(struct.unpack(header,data[:10])) 43 return head+[data[10:]] 44 45def TLV(type,value): 46 header="!HH" 47 head=struct.pack(header,type,len(value)) 48 return head+str(value) 49 50def readTLVs(data,count=None): 51 header="!HH" 52 dict={} 53 while data and len(dict)!=count: 54 head=struct.unpack(header,data[:4]) 55 dict[head[0]]=data[4:4+head[1]] 56 data=data[4+head[1]:] 57 if not count: 58 return dict 59 return dict,data 60 61def encryptPasswordMD5(password,key): 62 m=md5() 63 m.update(key) 64 m.update(md5(password).digest()) 65 m.update("AOL Instant Messenger (SM)") 66 return m.digest() 67 68def encryptPasswordICQ(password): 69 key=[0xF3,0x26,0x81,0xC4,0x39,0x86,0xDB,0x92,0x71,0xA3,0xB9,0xE6,0x53,0x7A,0x95,0x7C] 70 bytes=map(ord,password) 71 r="" 72 for i in range(len(bytes)): 73 r=r+chr(bytes[i]^key[i%len(key)]) 74 return r 75 76def dehtml(text): 77 text=string.replace(text,"<br>","\n") 78 text=string.replace(text,"<BR>","\n") 79 text=string.replace(text,"<Br>","\n") # XXX make this a regexp 80 text=string.replace(text,"<bR>","\n") 81 text=re.sub('<.*?>','',text) 82 text=string.replace(text,'>','>') 83 text=string.replace(text,'<','<') 84 text=string.replace(text,' ',' ') 85 text=string.replace(text,'"','"') 86 text=string.replace(text,'&','&') 87 return text 88 89def html(text): 90 text=string.replace(text,'"','"') 91 text=string.replace(text,'&','&') 92 text=string.replace(text,'<','<') 93 text=string.replace(text,'>','>') 94 text=string.replace(text,"\n","<br>") 95 return '<html><body bgcolor="white"><font color="black">%s</font></body></html>'%text 96 97class OSCARUser: 98 def __init__(self, name, warn, tlvs): 99 self.name = name 100 self.warning = warn 101 self.flags = [] 102 self.caps = [] 103 for k,v in tlvs.items(): 104 if k == 1: # user flags 105 v=struct.unpack('!H',v)[0] 106 for o, f in [(1,'trial'), 107 (2,'unknown bit 2'), 108 (4,'aol'), 109 (8,'unknown bit 4'), 110 (16,'aim'), 111 (32,'away'), 112 (1024,'activebuddy')]: 113 if v&o: self.flags.append(f) 114 elif k == 2: # member since date 115 self.memberSince = struct.unpack('!L',v)[0] 116 elif k == 3: # on-since 117 self.onSince = struct.unpack('!L',v)[0] 118 elif k == 4: # idle time 119 self.idleTime = struct.unpack('!H',v)[0] 120 elif k == 5: # unknown 121 pass 122 elif k == 6: # icq online status 123 if v[2] == '\x00': 124 self.icqStatus = 'online' 125 elif v[2] == '\x01': 126 self.icqStatus = 'away' 127 elif v[2] == '\x02': 128 self.icqStatus = 'dnd' 129 elif v[2] == '\x04': 130 self.icqStatus = 'out' 131 elif v[2] == '\x10': 132 self.icqStatus = 'busy' 133 else: 134 self.icqStatus = 'unknown' 135 elif k == 10: # icq ip address 136 self.icqIPaddy = socket.inet_ntoa(v) 137 elif k == 12: # icq random stuff 138 self.icqRandom = v 139 elif k == 13: # capabilities 140 caps=[] 141 while v: 142 c=v[:16] 143 if c==CAP_ICON: caps.append("icon") 144 elif c==CAP_IMAGE: caps.append("image") 145 elif c==CAP_VOICE: caps.append("voice") 146 elif c==CAP_CHAT: caps.append("chat") 147 elif c==CAP_GET_FILE: caps.append("getfile") 148 elif c==CAP_SEND_FILE: caps.append("sendfile") 149 elif c==CAP_SEND_LIST: caps.append("sendlist") 150 elif c==CAP_GAMES: caps.append("games") 151 else: caps.append(("unknown",c)) 152 v=v[16:] 153 caps.sort() 154 self.caps=caps 155 elif k == 14: pass 156 elif k == 15: # session length (aim) 157 self.sessionLength = struct.unpack('!L',v)[0] 158 elif k == 16: # session length (aol) 159 self.sessionLength = struct.unpack('!L',v)[0] 160 elif k == 30: # no idea 161 pass 162 else: 163 log.msg("unknown tlv for user %s\nt: %s\nv: %s"%(self.name,k,repr(v))) 164 165 def __str__(self): 166 s = '<OSCARUser %s' % self.name 167 o = [] 168 if self.warning!=0: o.append('warning level %s'%self.warning) 169 if hasattr(self, 'flags'): o.append('flags %s'%self.flags) 170 if hasattr(self, 'sessionLength'): o.append('online for %i minutes' % (self.sessionLength/60,)) 171 if hasattr(self, 'idleTime'): o.append('idle for %i minutes' % self.idleTime) 172 if self.caps: o.append('caps %s'%self.caps) 173 if o: 174 s=s+', '+', '.join(o) 175 s=s+'>' 176 return s 177 178 179class SSIGroup: 180 def __init__(self, name, tlvs = {}): 181 self.name = name 182 #self.tlvs = [] 183 #self.userIDs = [] 184 self.usersToID = {} 185 self.users = [] 186 #if not tlvs.has_key(0xC8): return 187 #buddyIDs = tlvs[0xC8] 188 #while buddyIDs: 189 # bid = struct.unpack('!H',buddyIDs[:2])[0] 190 # buddyIDs = buddyIDs[2:] 191 # self.users.append(bid) 192 193 def findIDFor(self, user): 194 return self.usersToID[user] 195 196 def addUser(self, buddyID, user): 197 self.usersToID[user] = buddyID 198 self.users.append(user) 199 user.group = self 200 201 def oscarRep(self, groupID, buddyID): 202 tlvData = TLV(0xc8, reduce(lambda x,y:x+y, [struct.pack('!H',self.usersToID[x]) for x in self.users])) 203 return struct.pack('!H', len(self.name)) + self.name + \ 204 struct.pack('!HH', groupID, buddyID) + '\000\001' + tlvData 205 206 207class SSIBuddy: 208 def __init__(self, name, tlvs = {}): 209 self.name = name 210 self.tlvs = tlvs 211 for k,v in tlvs.items(): 212 if k == 0x013c: # buddy comment 213 self.buddyComment = v 214 elif k == 0x013d: # buddy alerts 215 actionFlag = ord(v[0]) 216 whenFlag = ord(v[1]) 217 self.alertActions = [] 218 self.alertWhen = [] 219 if actionFlag&1: 220 self.alertActions.append('popup') 221 if actionFlag&2: 222 self.alertActions.append('sound') 223 if whenFlag&1: 224 self.alertWhen.append('online') 225 if whenFlag&2: 226 self.alertWhen.append('unidle') 227 if whenFlag&4: 228 self.alertWhen.append('unaway') 229 elif k == 0x013e: 230 self.alertSound = v 231 232 def oscarRep(self, groupID, buddyID): 233 tlvData = reduce(lambda x,y: x+y, map(lambda (k,v):TLV(k,v), self.tlvs.items()), '\000\000') 234 return struct.pack('!H', len(self.name)) + self.name + \ 235 struct.pack('!HH', groupID, buddyID) + '\000\000' + tlvData 236 237 238class OscarConnection(protocol.Protocol): 239 def connectionMade(self): 240 self.state="" 241 self.seqnum=0 242 self.buf='' 243 self.stopKeepAliveID = None 244 self.setKeepAlive(4*60) # 4 minutes 245 246 def connectionLost(self, reason): 247 log.msg("Connection Lost! %s" % self) 248 self.stopKeepAlive() 249 250# def connectionFailed(self): 251# log.msg("Connection Failed! %s" % self) 252# self.stopKeepAlive() 253 254 def sendFLAP(self,data,channel = 0x02): 255 header="!cBHH" 256 self.seqnum=(self.seqnum+1)%0xFFFF 257 seqnum=self.seqnum 258 head=struct.pack(header,'*', channel, 259 seqnum, len(data)) 260 self.transport.write(head+str(data)) 261# if isinstance(self, ChatService): 262# logPacketData(head+str(data)) 263 264 def readFlap(self): 265 header="!cBHH" 266 if len(self.buf)<6: return 267 flap=struct.unpack(header,self.buf[:6]) 268 if len(self.buf)<6+flap[3]: return 269 data,self.buf=self.buf[6:6+flap[3]],self.buf[6+flap[3]:] 270 return [flap[1],data] 271 272 def dataReceived(self,data): 273# if isinstance(self, ChatService): 274# logPacketData(data) 275 self.buf=self.buf+data 276 flap=self.readFlap() 277 while flap: 278 func=getattr(self,"oscar_%s"%self.state,None) 279 if not func: 280 log.msg("no func for state: %s" % self.state) 281 state=func(flap) 282 if state: 283 self.state=state 284 flap=self.readFlap() 285 286 def setKeepAlive(self,t): 287 self.keepAliveDelay=t 288 self.stopKeepAlive() 289 self.stopKeepAliveID = reactor.callLater(t, self.sendKeepAlive) 290 291 def sendKeepAlive(self): 292 self.sendFLAP("",0x05) 293 self.stopKeepAliveID = reactor.callLater(self.keepAliveDelay, self.sendKeepAlive) 294 295 def stopKeepAlive(self): 296 if self.stopKeepAliveID: 297 self.stopKeepAliveID.cancel() 298 self.stopKeepAliveID = None 299 300 def disconnect(self): 301 """ 302 send the disconnect flap, and sever the connection 303 """ 304 self.sendFLAP('', 0x04) 305 def f(reason): pass 306 self.connectionLost = f 307 self.transport.loseConnection() 308 309 310class SNACBased(OscarConnection): 311 snacFamilies = { 312 # family : (version, toolID, toolVersion) 313 } 314 def __init__(self,cookie): 315 self.cookie=cookie 316 self.lastID=0 317 self.supportedFamilies = () 318 self.requestCallbacks={} # request id:Deferred 319 320 def sendSNAC(self,fam,sub,data,flags=[0,0]): 321 """ 322 send a snac and wait for the response by returning a Deferred. 323 """ 324 reqid=self.lastID 325 self.lastID=reqid+1 326 d = defer.Deferred() 327 d.reqid = reqid 328 329 #d.addErrback(self._ebDeferredError,fam,sub,data) # XXX for testing 330 331 self.requestCallbacks[reqid] = d 332 self.sendFLAP(SNAC(fam,sub,reqid,data)) 333 return d 334 335 def _ebDeferredError(self, error, fam, sub, data): 336 log.msg('ERROR IN DEFERRED %s' % error) 337 log.msg('on sending of message, family 0x%02x, subtype 0x%02x' % (fam, sub)) 338 log.msg('data: %s' % repr(data)) 339 340 def sendSNACnr(self,fam,sub,data,flags=[0,0]): 341 """ 342 send a snac, but don't bother adding a deferred, we don't care. 343 """ 344 self.sendFLAP(SNAC(fam,sub,0x10000*fam+sub,data)) 345 346 def oscar_(self,data): 347 self.sendFLAP("\000\000\000\001"+TLV(6,self.cookie), 0x01) 348 return "Data" 349 350 def oscar_Data(self,data): 351 snac=readSNAC(data[1]) 352 if self.requestCallbacks.has_key(snac[4]): 353 d = self.requestCallbacks[snac[4]] 354 del self.requestCallbacks[snac[4]] 355 if snac[1]!=1: 356 d.callback(snac) 357 else: 358 d.errback(snac) 359 return 360 func=getattr(self,'oscar_%02X_%02X'%(snac[0],snac[1]),None) 361 if not func: 362 self.oscar_unknown(snac) 363 else: 364 func(snac[2:]) 365 return "Data" 366 367 def oscar_unknown(self,snac): 368 log.msg("unknown for %s" % self) 369 log.msg(snac) 370 371 372 def oscar_01_03(self, snac): 373 numFamilies = len(snac[3])/2 374 self.supportedFamilies = struct.unpack("!"+str(numFamilies)+'H', snac[3]) 375 d = '' 376 for fam in self.supportedFamilies: 377 if self.snacFamilies.has_key(fam): 378 d=d+struct.pack('!2H',fam,self.snacFamilies[fam][0]) 379 self.sendSNACnr(0x01,0x17, d) 380 381 def oscar_01_0A(self,snac): 382 """ 383 change of rate information. 384 """ 385 # this can be parsed, maybe we can even work it in 386 pass 387 388 def oscar_01_18(self,snac): 389 """ 390 host versions, in the same format as we sent 391 """ 392 self.sendSNACnr(0x01,0x06,"") #pass 393 394 def clientReady(self): 395 """ 396 called when the client is ready to be online 397 """ 398 d = '' 399 for fam in self.supportedFamilies: 400 if self.snacFamilies.has_key(fam): 401 version, toolID, toolVersion = self.snacFamilies[fam] 402 d = d + struct.pack('!4H',fam,version,toolID,toolVersion) 403 self.sendSNACnr(0x01,0x02,d) 404 405class BOSConnection(SNACBased): 406 snacFamilies = { 407 0x01:(3, 0x0110, 0x059b), 408 0x13:(3, 0x0110, 0x059b), 409 0x02:(1, 0x0110, 0x059b), 410 0x03:(1, 0x0110, 0x059b), 411 0x04:(1, 0x0110, 0x059b), 412 0x06:(1, 0x0110, 0x059b), 413 0x08:(1, 0x0104, 0x0001), 414 0x09:(1, 0x0110, 0x059b), 415 0x0a:(1, 0x0110, 0x059b), 416 0x0b:(1, 0x0104, 0x0001), 417 0x0c:(1, 0x0104, 0x0001) 418 } 419 420 capabilities = None 421 422 def __init__(self,username,cookie): 423 SNACBased.__init__(self,cookie) 424 self.username=username 425 self.profile = None 426 self.awayMessage = None 427 self.services = {} 428 429 if not self.capabilities: 430 self.capabilities = [CAP_CHAT] 431 432 def parseUser(self,data,count=None): 433 l=ord(data[0]) 434 name=data[1:1+l] 435 warn,foo=struct.unpack("!HH",data[1+l:5+l]) 436 warn=int(warn/10) 437 tlvs=data[5+l:] 438 if count: 439 tlvs,rest = readTLVs(tlvs,foo) 440 else: 441 tlvs,rest = readTLVs(tlvs), None 442 u = OSCARUser(name, warn, tlvs) 443 if rest == None: 444 return u 445 else: 446 return u, rest 447 448 def oscar_01_05(self, snac, d = None): 449 """ 450 data for a new service connection 451 d might be a deferred to be called back when the service is ready 452 """ 453 tlvs = readTLVs(snac[3][2:]) 454 service = struct.unpack('!H',tlvs[0x0d])[0] 455 ip = tlvs[5] 456 cookie = tlvs[6] 457 #c = serviceClasses[service](self, cookie, d) 458 c = protocol.ClientCreator(reactor, serviceClasses[service], self, cookie, d) 459 def addService(x): 460 self.services[service] = x 461 c.connectTCP(ip, 5190).addCallback(addService) 462 #self.services[service] = c 463 464 def oscar_01_07(self,snac): 465 """ 466 rate paramaters 467 """ 468 self.sendSNACnr(0x01,0x08,"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05") # ack 469 self.initDone() 470 self.sendSNACnr(0x13,0x02,'') # SSI rights info 471 self.sendSNACnr(0x02,0x02,'') # location rights info 472 self.sendSNACnr(0x03,0x02,'') # buddy list rights 473 self.sendSNACnr(0x04,0x04,'') # ICBM parms 474 self.sendSNACnr(0x09,0x02,'') # BOS rights 475 476 def oscar_01_10(self,snac): 477 """ 478 we've been warned 479 """ 480 skip = struct.unpack('!H',snac[3][:2])[0] 481 newLevel = struct.unpack('!H',snac[3][2+skip:4+skip])[0]/10 482 if len(snac[3])>4+skip: 483 by = self.parseUser(snac[3][4+skip:]) 484 else: 485 by = None 486 self.receiveWarning(newLevel, by) 487 488 def oscar_01_13(self,snac): 489 """ 490 MOTD 491 """ 492 pass # we don't care for now 493 494 def oscar_02_03(self, snac): 495 """ 496 location rights response 497 """ 498 tlvs = readTLVs(snac[3]) 499 self.maxProfileLength = tlvs[1] 500 501 def oscar_03_03(self, snac): 502 """ 503 buddy list rights response 504 """ 505 tlvs = readTLVs(snac[3]) 506 self.maxBuddies = tlvs[1] 507 self.maxWatchers = tlvs[2] 508 509 def oscar_03_0B(self, snac): 510 """ 511 buddy update 512 """ 513 self.updateBuddy(self.parseUser(snac[3])) 514 515 def oscar_03_0C(self, snac): 516 """ 517 buddy offline 518 """ 519 self.offlineBuddy(self.parseUser(snac[3])) 520 521# def oscar_04_03(self, snac): 522 523 def oscar_04_05(self, snac): 524 """ 525 ICBM parms response 526 """ 527 self.sendSNACnr(0x04,0x02,'\x00\x00\x00\x00\x00\x0b\x1f@\x03\xe7\x03\xe7\x00\x00\x00\x00') # IM rights 528 529 def oscar_04_07(self, snac): 530 """ 531 ICBM message (instant message) 532 """ 533 data = snac[3] 534 cookie, data = data[:8], data[8:] 535 channel = struct.unpack('!H',data[:2])[0] 536 data = data[2:] 537 user, data = self.parseUser(data, 1) 538 tlvs = readTLVs(data) 539 if channel == 1: # message 540 flags = [] 541 multiparts = [] 542 for k, v in tlvs.items(): 543 if k == 2: 544 while v: 545 v = v[2:] # skip bad data 546 messageLength, charSet, charSubSet = struct.unpack('!3H', v[:6]) 547 messageLength -= 4 548 message = [v[6:6+messageLength]] 549 if charSet == 0: 550 pass # don't add anything special 551 elif charSet == 2: 552 message.append('unicode') 553 elif charSet == 3: 554 message.append('iso-8859-1') 555 elif charSet == 0xffff: 556 message.append('none') 557 if charSubSet == 0xb: 558 message.append('macintosh') 559 if messageLength > 0: multiparts.append(tuple(message)) 560 v = v[6+messageLength:] 561 elif k == 3: 562 flags.append('acknowledge') 563 elif k == 4: 564 flags.append('auto') 565 elif k == 6: 566 flags.append('offline') 567 elif k == 8: 568 iconLength, foo, iconSum, iconStamp = struct.unpack('!LHHL',v) 569 if iconLength: 570 flags.append('icon') 571 flags.append((iconLength, iconSum, iconStamp)) 572 elif k == 9: 573 flags.append('buddyrequest') 574 elif k == 0xb: # unknown 575 pass 576 elif k == 0x17: 577 flags.append('extradata') 578 flags.append(v) 579 else: 580 log.msg('unknown TLV for incoming IM, %04x, %s' % (k,repr(v))) 581 582# unknown tlv for user SNewdorf 583# t: 29 584# v: '\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x01\x10/\x8c\x8b\x8a\x1e\x94*\xbc\x80}\x8d\xc4;\x1dEM' 585# XXX what is this? 586 self.receiveMessage(user, multiparts, flags) 587 elif channel == 2: # rondevouz 588 status = struct.unpack('!H',tlvs[5][:2])[0] 589 requestClass = tlvs[5][10:26] 590 moreTLVs = readTLVs(tlvs[5][26:]) 591 if requestClass == CAP_CHAT: # a chat request 592 exchange = struct.unpack('!H',moreTLVs[10001][:2])[0] 593 name = moreTLVs[10001][3:-2] 594 instance = struct.unpack('!H',moreTLVs[10001][-2:])[0] 595 if not self.services.has_key(SERVICE_CHATNAV): 596 self.connectService(SERVICE_CHATNAV,1).addCallback(lambda x: self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\ 597 addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12])) 598 else: 599 self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\ 600 addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12]) 601 elif requestClass == CAP_SEND_FILE: 602 if moreTLVs.has_key(11): # cancel 603 log.msg('cancelled file request') 604 log.msg(status) 605 return # handle this later 606 name = moreTLVs[10001][9:-7] 607 desc = moreTLVs[12] 608 log.msg('file request from %s, %s, %s' % (user, name, desc)) 609 self.receiveSendFileRequest(user, name, desc, cookie) 610 else: 611 log.msg('unsupported rondevouz: %s' % requestClass) 612 log.msg(repr(moreTLVs)) 613 else: 614 log.msg('unknown channel %02x' % channel) 615 log.msg(tlvs) 616 617 def _cbGetChatInfoForInvite(self, info, user, message): 618 apply(self.receiveChatInvite, (user,message)+info) 619 620 def oscar_09_03(self, snac): 621 """ 622 BOS rights response 623 """ 624 tlvs = readTLVs(snac[3]) 625 self.maxPermitList = tlvs[1] 626 self.maxDenyList = tlvs[2] 627 628 def oscar_0B_02(self, snac): 629 """ 630 stats reporting interval 631 """ 632 self.reportingInterval = struct.unpack('!H',snac[3][:2])[0] 633 634 def oscar_13_03(self, snac): 635 """ 636 SSI rights response 637 """ 638 #tlvs = readTLVs(snac[3]) 639 pass # we don't know how to parse this 640 641 # methods to be called by the client, and their support methods 642 def requestSelfInfo(self): 643 """ 644 ask for the OSCARUser for ourselves 645 """ 646 d = defer.Deferred() 647 self.sendSNAC(0x01, 0x0E, '').addCallback(self._cbRequestSelfInfo, d) 648 return d 649 650 def _cbRequestSelfInfo(self, snac, d): 651 d.callback(self.parseUser(snac[5])) 652 653 def initSSI(self): 654 """ 655 this sends the rate request for family 0x13 (Server Side Information) 656 so we can then use it 657 """ 658 return self.sendSNAC(0x13, 0x02, '').addCallback(self._cbInitSSI) 659 660 def _cbInitSSI(self, snac, d): 661 return {} # don't even bother parsing this 662 663 def requestSSI(self, timestamp = 0, revision = 0): 664 """ 665 request the server side information 666 if the deferred gets None, it means the SSI is the same 667 """ 668 return self.sendSNAC(0x13, 0x05, 669 struct.pack('!LH',timestamp,revision)).addCallback(self._cbRequestSSI) 670 671 def _cbRequestSSI(self, snac, args = ()): 672 if snac[1] == 0x0f: # same SSI as we have 673 return 674 itemdata = snac[5][3:] 675 if args: 676 revision, groups, permit, deny, permitMode, visibility = args 677 else: 678 version, revision = struct.unpack('!BH', snac[5][:3]) 679 groups = {} 680 permit = [] 681 deny = [] 682 permitMode = None 683 visibility = None 684 while len(itemdata)>4: 685 nameLength = struct.unpack('!H', itemdata[:2])[0] 686 name = itemdata[2:2+nameLength] 687 groupID, buddyID, itemType, restLength = \ 688 struct.unpack('!4H', itemdata[2+nameLength:10+nameLength]) 689 tlvs = readTLVs(itemdata[10+nameLength:10+nameLength+restLength]) 690 itemdata = itemdata[10+nameLength+restLength:] 691 if itemType == 0: # buddies 692 groups[groupID].addUser(buddyID, SSIBuddy(name, tlvs)) 693 elif itemType == 1: # group 694 g = SSIGroup(name, tlvs) 695 if groups.has_key(0): groups[0].addUser(groupID, g) 696 groups[groupID] = g 697 elif itemType == 2: # permit 698 permit.append(name) 699 elif itemType == 3: # deny 700 deny.append(name) 701 elif itemType == 4: # permit deny info 702 if not tlvs.has_key(0xcb): 703 continue # this happens with ICQ 704 permitMode = {1:'permitall',2:'denyall',3:'permitsome',4:'denysome',5:'permitbuddies'}[ord(tlvs[0xca])] 705 visibility = {'\xff\xff\xff\xff':'all','\x00\x00\x00\x04':'notaim'}[tlvs[0xcb]] 706 elif itemType == 5: # unknown (perhaps idle data)? 707 pass 708 else: 709 log.msg('%s %s %s %s %s' % (name, groupID, buddyID, itemType, tlvs)) 710 timestamp = struct.unpack('!L',itemdata)[0] 711 if not timestamp: # we've got more packets coming 712 # which means add some deferred stuff 713 d = defer.Deferred() 714 self.requestCallbacks[snac[4]] = d 715 d.addCallback(self._cbRequestSSI, (revision, groups, permit, deny, permitMode, visibility)) 716 return d 717 return (groups[0].users,permit,deny,permitMode,visibility,timestamp,revision) 718 719 def activateSSI(self): 720 """ 721 active the data stored on the server (use buddy list, permit deny settings, etc.) 722 """ 723 self.sendSNACnr(0x13,0x07,'') 724 725 def startModifySSI(self): 726 """ 727 tell the OSCAR server to be on the lookout for SSI modifications 728 """ 729 self.sendSNACnr(0x13,0x11,'') 730 731 def addItemSSI(self, item, groupID = None, buddyID = None): 732 """ 733 add an item to the SSI server. if buddyID == 0, then this should be a group. 734 this gets a callback when it's finished, but you can probably ignore it. 735 """ 736 if groupID is None: 737 if isinstance(item, SSIGroup): 738 groupID = 0 739 else: 740 groupID = item.group.group.findIDFor(item.group) 741 if buddyID is None: 742 buddyID = item.group.findIDFor(item) 743 return self.sendSNAC(0x13,0x08, item.oscarRep(groupID, buddyID)) 744 745 def modifyItemSSI(self, item, groupID = None, buddyID = None): 746 if groupID is None: 747 if isinstance(item, SSIGroup): 748 groupID = 0 749 else: 750 groupID = item.group.group.findIDFor(item.group) 751 if buddyID is None: 752 buddyID = item.group.findIDFor(item) 753 return self.sendSNAC(0x13,0x09, item.oscarRep(groupID, buddyID)) 754 755 def delItemSSI(self, item, groupID = None, buddyID = None): 756 if groupID is None: 757 if isinstance(item, SSIGroup): 758 groupID = 0 759 else: 760 groupID = item.group.group.findIDFor(item.group) 761 if buddyID is None: 762 buddyID = item.group.findIDFor(item) 763 return self.sendSNAC(0x13,0x0A, item.oscarRep(groupID, buddyID)) 764 765 def endModifySSI(self): 766 self.sendSNACnr(0x13,0x12,'') 767 768 def setProfile(self, profile): 769 """ 770 set the profile. 771 send None to not set a profile (different from '' for a blank one) 772 """ 773 self.profile = profile 774 tlvs = '' 775 if self.profile is not None: 776 tlvs = TLV(1,'text/aolrtf; charset="us-ascii"') + \ 777 TLV(2,self.profile) 778 779 tlvs = tlvs + TLV(5, ''.join(self.capabilities)) 780 self.sendSNACnr(0x02, 0x04, tlvs) 781 782 def setAway(self, away = None): 783 """ 784 set the away message, or return (if away == None) 785 """ 786 self.awayMessage = away 787 tlvs = TLV(3,'text/aolrtf; charset="us-ascii"') + \ 788 TLV(4,away or '') 789 self.sendSNACnr(0x02, 0x04, tlvs) 790 791 def setIdleTime(self, idleTime): 792 """ 793 set our idle time. don't call more than once with a non-0 idle time. 794 """ 795 self.sendSNACnr(0x01, 0x11, struct.pack('!L',idleTime)) 796 797 def sendMessage(self, user, message, wantAck = 0, autoResponse = 0, offline = 0 ): \ 798 #haveIcon = 0, ): 799 """ 800 send a message to user (not an OSCARUseR). 801 message can be a string, or a multipart tuple. 802 if wantAck, we return a Deferred that gets a callback when the message is sent. 803 if autoResponse, this message is an autoResponse, as if from an away message. 804 if offline, this is an offline message (ICQ only, I think) 805 """ 806 data = ''.join([chr(random.randrange(0, 127)) for i in range(8)]) # cookie 807 data = data + '\x00\x01' + chr(len(user)) + user 808 if not type(message) in (types.TupleType, types.ListType): 809 message = [[message,]] 810 if type(message[0][0]) == types.UnicodeType: 811 message[0].append('unicode') 812 messageData = '' 813 for part in message: 814 charSet = 0 815 if 'unicode' in part[1:]: 816 charSet = 2 817 part[0] = part[0].encode('utf-8') 818 elif 'iso-8859-1' in part[1:]: 819 charSet = 3 820 part[0] = part[0].encode('iso-8859-1') 821 elif 'none' in part[1:]: 822 charSet = 0xffff 823 if 'macintosh' in part[1:]: 824 charSubSet = 0xb 825 else: 826 charSubSet = 0 827 messageData = messageData + '\x01\x01' + \ 828 struct.pack('!3H',len(part[0])+4,charSet,charSubSet) 829 messageData = messageData + part[0] 830 data = data + TLV(2, '\x05\x01\x00\x03\x01\x01\x02'+messageData) 831 if wantAck: 832 data = data + TLV(3,'') 833 if autoResponse: 834 data = data + TLV(4,'') 835 if offline: 836 data = data + TLV(6,'') 837 if wantAck: 838 return self.sendSNAC(0x04, 0x06, data).addCallback(self._cbSendMessageAck, user, message) 839 self.sendSNACnr(0x04, 0x06, data) 840 841 def _cbSendMessageAck(self, snac, user, message): 842 return user, message 843 844 def connectService(self, service, wantCallback = 0, extraData = ''): 845 """ 846 connect to another service 847 if wantCallback, we return a Deferred that gets called back when the service is online. 848 if extraData, append that to our request. 849 """ 850 if wantCallback: 851 d = defer.Deferred() 852 self.sendSNAC(0x01,0x04,struct.pack('!H',service) + extraData).addCallback(self._cbConnectService, d) 853 return d 854 else: 855 self.sendSNACnr(0x01,0x04,struct.pack('!H',service)) 856 857 def _cbConnectService(self, snac, d): 858 self.oscar_01_05(snac[2:], d) 859 860 def createChat(self, shortName): 861 """ 862 create a chat room 863 """ 864 if self.services.has_key(SERVICE_CHATNAV): 865 return self.services[SERVICE_CHATNAV].createChat(shortName) 866 else: 867 return self.connectService(SERVICE_CHATNAV,1).addCallback(lambda s: s.createChat(shortName)) 868 869 870 def joinChat(self, exchange, fullName, instance): 871 """ 872 join a chat room 873 """ 874 #d = defer.Deferred() 875 return self.connectService(0x0e, 1, TLV(0x01, struct.pack('!HB',exchange, len(fullName)) + fullName + 876 struct.pack('!H', instance))).addCallback(self._cbJoinChat) #, d) 877 #return d 878 879 def _cbJoinChat(self, chat): 880 del self.services[SERVICE_CHAT] 881 return chat 882 883 def warnUser(self, user, anon = 0): 884 return self.sendSNAC(0x04, 0x08, '\x00'+chr(anon)+chr(len(user))+user).addCallback(self._cbWarnUser) 885 886 def _cbWarnUser(self, snac): 887 oldLevel, newLevel = struct.unpack('!2H', snac[5]) 888 return oldLevel, newLevel 889 890 def getInfo(self, user): 891 #if user. 892 return self.sendSNAC(0x02, 0x05, '\x00\x01'+chr(len(user))+user).addCallback(self._cbGetInfo) 893 894 def _cbGetInfo(self, snac): 895 user, rest = self.parseUser(snac[5],1) 896 tlvs = readTLVs(rest) 897 return tlvs.get(0x02,None) 898 899 def getAway(self, user): 900 return self.sendSNAC(0x02, 0x05, '\x00\x03'+chr(len(user))+user).addCallback(self._cbGetAway) 901 902 def _cbGetAway(self, snac): 903 user, rest = self.parseUser(snac[5],1) 904 tlvs = readTLVs(rest) 905 return tlvs.get(0x04,None) # return None if there is no away message 906 907 #def acceptSendFileRequest(self, 908 909 # methods to be overriden by the client 910 def initDone(self): 911 """ 912 called when we get the rate information, which means we should do other init. stuff. 913 """ 914 log.msg('%s initDone' % self) 915 pass 916 917 def updateBuddy(self, user): 918 """ 919 called when a buddy changes status, with the OSCARUser for that buddy. 920 """ 921 log.msg('%s updateBuddy %s' % (self, user)) 922 pass 923 924 def offlineBuddy(self, user): 925 """ 926 called when a buddy goes offline 927 """ 928 log.msg('%s offlineBuddy %s' % (self, user)) 929 pass 930 931 def receiveMessage(self, user, multiparts, flags): 932 """ 933 called when someone sends us a message 934 """ 935 pass 936 937 def receiveWarning(self, newLevel, user): 938 """ 939 called when someone warns us. 940 user is either None (if it was anonymous) or an OSCARUser 941 """ 942 pass 943 944 def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime): 945 """ 946 called when someone invites us to a chat room 947 """ 948 pass 949 950 def chatReceiveMessage(self, chat, user, message): 951 """ 952 called when someone in a chatroom sends us a message in the chat 953 """ 954 pass 955 956 def chatMemberJoined(self, chat, member): 957 """ 958 called when a member joins the chat 959 """ 960 pass 961 962 def chatMemberLeft(self, chat, member): 963 """ 964 called when a member leaves the chat 965 """ 966 pass 967 968 def receiveSendFileRequest(self, user, file, description, cookie): 969 """ 970 called when someone tries to send a file to us 971 """ 972 pass 973 974class OSCARService(SNACBased): 975 def __init__(self, bos, cookie, d = None): 976 SNACBased.__init__(self, cookie) 977 self.bos = bos 978 self.d = d 979 980 def connectionLost(self, reason): 981 for k,v in self.bos.services.items(): 982 if v == self: 983 del self.bos.services[k] 984 return 985 986 def clientReady(self): 987 SNACBased.clientReady(self) 988 if self.d: 989 self.d.callback(self) 990 self.d = None 991 992class ChatNavService(OSCARService): 993 snacFamilies = { 994 0x01:(3, 0x0010, 0x059b), 995 0x0d:(1, 0x0010, 0x059b) 996 } 997 def oscar_01_07(self, snac): 998 # rate info 999 self.sendSNACnr(0x01, 0x08, '\000\001\000\002\000\003\000\004\000\005') 1000 self.sendSNACnr(0x0d, 0x02, '') 1001 1002 def oscar_0D_09(self, snac): 1003 self.clientReady() 1004 1005 def getChatInfo(self, exchange, name, instance): 1006 d = defer.Deferred() 1007 self.sendSNAC(0x0d,0x04,struct.pack('!HB',exchange,len(name)) + \ 1008 name + struct.pack('!HB',instance,2)). \ 1009 addCallback(self._cbGetChatInfo, d) 1010 return d 1011 1012 def _cbGetChatInfo(self, snac, d): 1013 data = snac[5][4:] 1014 exchange, length = struct.unpack('!HB',data[:3]) 1015 fullName = data[3:3+length] 1016 instance = struct.unpack('!H',data[3+length:5+length])[0] 1017 tlvs = readTLVs(data[8+length:]) 1018 shortName = tlvs[0x6a] 1019 inviteTime = struct.unpack('!L',tlvs[0xca])[0] 1020 info = (exchange,fullName,instance,shortName,inviteTime) 1021 d.callback(info) 1022 1023 def createChat(self, shortName): 1024 #d = defer.Deferred() 1025 data = '\x00\x04\x06create\xff\xff\x01\x00\x03' 1026 data = data + TLV(0xd7, 'en') 1027 data = data + TLV(0xd6, 'us-ascii') 1028 data = data + TLV(0xd3, shortName) 1029 return self.sendSNAC(0x0d, 0x08, data).addCallback(self._cbCreateChat) 1030 #return d 1031 1032 def _cbCreateChat(self, snac): #d): 1033 exchange, length = struct.unpack('!HB',snac[5][4:7]) 1034 fullName = snac[5][7:7+length] 1035 instance = struct.unpack('!H',snac[5][7+length:9+length])[0] 1036 #d.callback((exchange, fullName, instance)) 1037 return exchange, fullName, instance 1038 1039class ChatService(OSCARService): 1040 snacFamilies = { 1041 0x01:(3, 0x0010, 0x059b), 1042 0x0E:(1, 0x0010, 0x059b) 1043 } 1044 def __init__(self,bos,cookie, d = None): 1045 OSCARService.__init__(self,bos,cookie,d) 1046 self.exchange = None 1047 self.fullName = None 1048 self.instance = None 1049 self.name = None 1050 self.members = None 1051 1052 clientReady = SNACBased.clientReady # we'll do our own callback 1053 1054 def oscar_01_07(self,snac): 1055 self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005") 1056 self.clientReady() 1057 1058 def oscar_0E_02(self, snac): 1059# try: # this is EVIL 1060# data = snac[3][4:] 1061# self.exchange, length = struct.unpack('!HB',data[:3]) 1062# self.fullName = data[3:3+length] 1063# self.instance = struct.unpack('!H',data[3+length:5+length])[0] 1064# tlvs = readTLVs(data[8+length:]) 1065# self.name = tlvs[0xd3] 1066# self.d.callback(self) 1067# except KeyError: 1068 data = snac[3] 1069 self.exchange, length = struct.unpack('!HB',data[:3]) 1070 self.fullName = data[3:3+length] 1071 self.instance = struct.unpack('!H',data[3+length:5+length])[0] 1072 tlvs = readTLVs(data[8+length:]) 1073 self.name = tlvs[0xd3] 1074 self.d.callback(self) 1075 1076 def oscar_0E_03(self,snac): 1077 users=[] 1078 rest=snac[3] 1079 while rest: 1080 user, rest = self.bos.parseUser(rest, 1) 1081 users.append(user) 1082 if not self.fullName: 1083 self.members = users 1084 else: 1085 self.members.append(users[0]) 1086 self.bos.chatMemberJoined(self,users[0]) 1087 1088 def oscar_0E_04(self,snac): 1089 user=self.bos.parseUser(snac[3]) 1090 for u in self.members: 1091 if u.name == user.name: # same person! 1092 self.members.remove(u) 1093 self.bos.chatMemberLeft(self,user) 1094 1095 def oscar_0E_06(self,snac): 1096 data = snac[3] 1097 user,rest=self.bos.parseUser(snac[3][14:],1) 1098 tlvs = readTLVs(rest[8:]) 1099 message=tlvs[1] 1100 self.bos.chatReceiveMessage(self,user,message) 1101 1102 def sendMessage(self,message): 1103 tlvs=TLV(0x02,"us-ascii")+TLV(0x03,"en")+TLV(0x01,message) 1104 self.sendSNAC(0x0e,0x05, 1105 "\x46\x30\x38\x30\x44\x00\x63\x00\x00\x03\x00\x01\x00\x00\x00\x06\x00\x00\x00\x05"+ 1106 struct.pack("!H",len(tlvs))+ 1107 tlvs) 1108 1109 def leaveChat(self): 1110 self.disconnect() 1111 1112class OscarAuthenticator(OscarConnection): 1113 BOSClass = BOSConnection 1114 def __init__(self,username,password,deferred=None,icq=0): 1115 self.username=username 1116 self.password=password 1117 self.deferred=deferred 1118 self.icq=icq # icq mode is disabled 1119 #if icq and self.BOSClass==BOSConnection: 1120 # self.BOSClass=ICQConnection 1121 1122 def oscar_(self,flap): 1123 if not self.icq: 1124 self.sendFLAP("\000\000\000\001", 0x01) 1125 self.sendFLAP(SNAC(0x17,0x06,0, 1126 TLV(TLV_USERNAME,self.username)+ 1127 TLV(0x004B,''))) 1128 self.state="Key" 1129 else: 1130 encpass=encryptPasswordICQ(self.password) 1131 self.sendFLAP('\000\000\000\001'+ 1132 TLV(0x01,self.username)+ 1133 TLV(0x02,encpass)+ 1134 TLV(0x03,'ICQ Inc. - Product of ICQ (TM).2001b.5.18.1.3659.85')+ 1135 TLV(0x16,"\x01\x0a")+ 1136 TLV(0x17,"\x00\x05")+ 1137 TLV(0x18,"\x00\x12")+ 1138 TLV(0x19,"\000\001")+ 1139 TLV(0x1a,"\x0eK")+ 1140 TLV(0x14,"\x00\x00\x00U")+ 1141 TLV(0x0f,"en")+ 1142 TLV(0x0e,"us"),0x01) 1143 self.state="Cookie" 1144 1145 def oscar_Key(self,data): 1146 snac=readSNAC(data[1]) 1147 key=snac[5][2:] 1148 encpass=encryptPasswordMD5(self.password,key) 1149 self.sendFLAP(SNAC(0x17,0x02,0, 1150 TLV(TLV_USERNAME,self.username)+ 1151 TLV(TLV_PASSWORD,encpass)+ 1152 TLV(0x004C, '')+ # unknown 1153 TLV(TLV_CLIENTNAME,"AOL Instant Messenger (SM), version 4.8.2790/WIN32")+ 1154 TLV(0x0016,"\x01\x09")+ 1155 TLV(TLV_CLIENTMAJOR,"\000\004")+ 1156 TLV(TLV_CLIENTMINOR,"\000\010")+ 1157 TLV(0x0019,"\000\000")+ 1158 TLV(TLV_CLIENTSUB,"\x0A\xE6")+ 1159 TLV(0x0014,"\x00\x00\x00\xBB")+ 1160 TLV(TLV_LANG,"en")+ 1161 TLV(TLV_COUNTRY,"us")+ 1162 TLV(TLV_USESSI,"\001"))) 1163 return "Cookie" 1164 1165 def oscar_Cookie(self,data): 1166 snac=readSNAC(data[1]) 1167 if self.icq: 1168 i=snac[5].find("\000") 1169 snac[5]=snac[5][i:] 1170 tlvs=readTLVs(snac[5]) 1171 if tlvs.has_key(6): 1172 self.cookie=tlvs[6] 1173 server,port=string.split(tlvs[5],":") 1174 d = self.connectToBOS(server, int(port)) 1175 d.addErrback(lambda x: log.msg("Connection Failed! Reason: %s" % x)) 1176 if self.deferred: 1177 d.chainDeferred(self.deferred) 1178 self.disconnect() 1179 elif tlvs.has_key(8): 1180 errorcode=tlvs[8] 1181 errorurl=tlvs[4] 1182 if errorcode=='\000\030': 1183 error="You are attempting to sign on again too soon. Please try again later." 1184 elif errorcode=='\000\005': 1185 error="Invalid Username or Password." 1186 else: error=repr(errorcode) 1187 self.error(error,errorurl) 1188 else: 1189 log.msg('hmm, weird tlvs for %s cookie packet' % str(self)) 1190 log.msg(tlvs) 1191 log.msg('snac') 1192 log.msg(str(snac)) 1193 return "None" 1194 1195 def oscar_None(self,data): pass 1196 1197 def connectToBOS(self, server, port): 1198 c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie) 1199 return c.connectTCP(server, int(port)) 1200 1201 def error(self,error,url): 1202 log.msg("ERROR! %s %s" % (error,url)) 1203 if self.deferred: self.deferred.errback((error,url)) 1204 self.transport.loseConnection() 1205 1206FLAP_CHANNEL_NEW_CONNECTION = 0x01 1207FLAP_CHANNEL_DATA = 0x02 1208FLAP_CHANNEL_ERROR = 0x03 1209FLAP_CHANNEL_CLOSE_CONNECTION = 0x04 1210 1211SERVICE_CHATNAV = 0x0d 1212SERVICE_CHAT = 0x0e 1213serviceClasses = { 1214 SERVICE_CHATNAV:ChatNavService, 1215 SERVICE_CHAT:ChatService 1216} 1217TLV_USERNAME = 0x0001 1218TLV_CLIENTNAME = 0x0003 1219TLV_COUNTRY = 0x000E 1220TLV_LANG = 0x000F 1221TLV_CLIENTMAJOR = 0x0017 1222TLV_CLIENTMINOR = 0x0018 1223TLV_CLIENTSUB = 0x001A 1224TLV_PASSWORD = 0x0025 1225TLV_USESSI = 0x004A 1226 1227CAP_ICON = '\011F\023FL\177\021\321\202"DEST\000\000' 1228CAP_VOICE = '\011F\023AL\177\021\321\202"DEST\000\000' 1229CAP_IMAGE = '\011F\023EL\177\021\321\202"DEST\000\000' 1230CAP_CHAT = 't\217$ b\207\021\321\202"DEST\000\000' 1231CAP_GET_FILE = '\011F\023HL\177\021\321\202"DEST\000\000' 1232CAP_SEND_FILE = '\011F\023CL\177\021\321\202"DEST\000\000' 1233CAP_GAMES = '\011F\023GL\177\021\321\202"DEST\000\000' 1234CAP_SEND_LIST = '\011F\023KL\177\021\321\202"DEST\000\000' 1235CAP_SERV_REL = '\011F\023IL\177\021\321\202"DEST\000\000' 1236