1
# -*- test-case-name: twisted.words.test -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
An implementation of the OSCAR protocol, which AIM and ICQ use to communcate.
9
Maintainer: Paul Swartz
19
from twisted.internet import reactor, defer, protocol
20
from twisted.python import log
21
from twisted.python.hashlib import md5
23
def logPacketData(data):
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))
33
def SNAC(fam,sub,id,data,flags=[0,0]):
35
head=struct.pack(header,fam,sub,
42
head=list(struct.unpack(header,data[:10]))
43
return head+[data[10:]]
47
head=struct.pack(header,type,len(value))
48
return head+str(value)
50
def readTLVs(data,count=None):
53
while data and len(dict)!=count:
54
head=struct.unpack(header,data[:4])
55
dict[head[0]]=data[4:4+head[1]]
61
def encryptPasswordMD5(password,key):
64
m.update(md5(password).digest())
65
m.update("AOL Instant Messenger (SM)")
68
def 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)
72
for i in range(len(bytes)):
73
r=r+chr(bytes[i]^key[i%len(key)])
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,'&','&')
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
98
def __init__(self, name, warn, tlvs):
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'),
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
122
elif k == 6: # icq online status
124
self.icqStatus = 'online'
126
self.icqStatus = 'away'
128
self.icqStatus = 'dnd'
130
self.icqStatus = 'out'
132
self.icqStatus = 'busy'
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
139
elif k == 13: # capabilities
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))
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
163
log.msg("unknown tlv for user %s\nt: %s\nv: %s"%(self.name,k,repr(v)))
166
s = '<OSCARUser %s' % self.name
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)
174
s=s+', '+', '.join(o)
180
def __init__(self, name, tlvs = {}):
186
#if not tlvs.has_key(0xC8): return
187
#buddyIDs = tlvs[0xC8]
189
# bid = struct.unpack('!H',buddyIDs[:2])[0]
190
# buddyIDs = buddyIDs[2:]
191
# self.users.append(bid)
193
def findIDFor(self, user):
194
return self.usersToID[user]
196
def addUser(self, buddyID, user):
197
self.usersToID[user] = buddyID
198
self.users.append(user)
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
208
def __init__(self, name, 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])
217
self.alertActions = []
220
self.alertActions.append('popup')
222
self.alertActions.append('sound')
224
self.alertWhen.append('online')
226
self.alertWhen.append('unidle')
228
self.alertWhen.append('unaway')
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
238
class OscarConnection(protocol.Protocol):
239
def connectionMade(self):
243
self.stopKeepAliveID = None
244
self.setKeepAlive(4*60) # 4 minutes
246
def connectionLost(self, reason):
247
log.msg("Connection Lost! %s" % self)
250
# def connectionFailed(self):
251
# log.msg("Connection Failed! %s" % self)
252
# self.stopKeepAlive()
254
def sendFLAP(self,data,channel = 0x02):
256
self.seqnum=(self.seqnum+1)%0xFFFF
258
head=struct.pack(header,'*', channel,
260
self.transport.write(head+str(data))
261
# if isinstance(self, ChatService):
262
# logPacketData(head+str(data))
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]
272
def dataReceived(self,data):
273
# if isinstance(self, ChatService):
274
# logPacketData(data)
275
self.buf=self.buf+data
278
func=getattr(self,"oscar_%s"%self.state,None)
280
log.msg("no func for state: %s" % self.state)
286
def setKeepAlive(self,t):
287
self.keepAliveDelay=t
289
self.stopKeepAliveID = reactor.callLater(t, self.sendKeepAlive)
291
def sendKeepAlive(self):
292
self.sendFLAP("",0x05)
293
self.stopKeepAliveID = reactor.callLater(self.keepAliveDelay, self.sendKeepAlive)
295
def stopKeepAlive(self):
296
if self.stopKeepAliveID:
297
self.stopKeepAliveID.cancel()
298
self.stopKeepAliveID = None
300
def disconnect(self):
302
send the disconnect flap, and sever the connection
304
self.sendFLAP('', 0x04)
306
self.connectionLost = f
307
self.transport.loseConnection()
310
class SNACBased(OscarConnection):
312
# family : (version, toolID, toolVersion)
314
def __init__(self,cookie):
317
self.supportedFamilies = ()
318
self.requestCallbacks={} # request id:Deferred
320
def sendSNAC(self,fam,sub,data,flags=[0,0]):
322
send a snac and wait for the response by returning a Deferred.
329
#d.addErrback(self._ebDeferredError,fam,sub,data) # XXX for testing
331
self.requestCallbacks[reqid] = d
332
self.sendFLAP(SNAC(fam,sub,reqid,data))
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))
340
def sendSNACnr(self,fam,sub,data,flags=[0,0]):
342
send a snac, but don't bother adding a deferred, we don't care.
344
self.sendFLAP(SNAC(fam,sub,0x10000*fam+sub,data))
346
def oscar_(self,data):
347
self.sendFLAP("\000\000\000\001"+TLV(6,self.cookie), 0x01)
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]]
360
func=getattr(self,'oscar_%02X_%02X'%(snac[0],snac[1]),None)
362
self.oscar_unknown(snac)
367
def oscar_unknown(self,snac):
368
log.msg("unknown for %s" % self)
372
def oscar_01_03(self, snac):
373
numFamilies = len(snac[3])/2
374
self.supportedFamilies = struct.unpack("!"+str(numFamilies)+'H', snac[3])
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)
381
def oscar_01_0A(self,snac):
383
change of rate information.
385
# this can be parsed, maybe we can even work it in
388
def oscar_01_18(self,snac):
390
host versions, in the same format as we sent
392
self.sendSNACnr(0x01,0x06,"") #pass
394
def clientReady(self):
396
called when the client is ready to be online
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)
405
class BOSConnection(SNACBased):
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)
422
def __init__(self,username,cookie):
423
SNACBased.__init__(self,cookie)
424
self.username=username
426
self.awayMessage = None
429
if not self.capabilities:
430
self.capabilities = [CAP_CHAT]
432
def parseUser(self,data,count=None):
435
warn,foo=struct.unpack("!HH",data[1+l:5+l])
439
tlvs,rest = readTLVs(tlvs,foo)
441
tlvs,rest = readTLVs(tlvs), None
442
u = OSCARUser(name, warn, tlvs)
448
def oscar_01_05(self, snac, d = None):
450
data for a new service connection
451
d might be a deferred to be called back when the service is ready
453
tlvs = readTLVs(snac[3][2:])
454
service = struct.unpack('!H',tlvs[0x0d])[0]
457
#c = serviceClasses[service](self, cookie, d)
458
c = protocol.ClientCreator(reactor, serviceClasses[service], self, cookie, d)
460
self.services[service] = x
461
c.connectTCP(ip, 5190).addCallback(addService)
462
#self.services[service] = c
464
def oscar_01_07(self,snac):
468
self.sendSNACnr(0x01,0x08,"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05") # ack
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
476
def oscar_01_10(self,snac):
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:])
486
self.receiveWarning(newLevel, by)
488
def oscar_01_13(self,snac):
492
pass # we don't care for now
494
def oscar_02_03(self, snac):
496
location rights response
498
tlvs = readTLVs(snac[3])
499
self.maxProfileLength = tlvs[1]
501
def oscar_03_03(self, snac):
503
buddy list rights response
505
tlvs = readTLVs(snac[3])
506
self.maxBuddies = tlvs[1]
507
self.maxWatchers = tlvs[2]
509
def oscar_03_0B(self, snac):
513
self.updateBuddy(self.parseUser(snac[3]))
515
def oscar_03_0C(self, snac):
519
self.offlineBuddy(self.parseUser(snac[3]))
521
# def oscar_04_03(self, snac):
523
def oscar_04_05(self, snac):
527
self.sendSNACnr(0x04,0x02,'\x00\x00\x00\x00\x00\x0b\x1f@\x03\xe7\x03\xe7\x00\x00\x00\x00') # IM rights
529
def oscar_04_07(self, snac):
531
ICBM message (instant message)
534
cookie, data = data[:8], data[8:]
535
channel = struct.unpack('!H',data[:2])[0]
537
user, data = self.parseUser(data, 1)
538
tlvs = readTLVs(data)
539
if channel == 1: # message
542
for k, v in tlvs.items():
545
v = v[2:] # skip bad data
546
messageLength, charSet, charSubSet = struct.unpack('!3H', v[:6])
548
message = [v[6:6+messageLength]]
550
pass # don't add anything special
552
message.append('unicode')
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:]
562
flags.append('acknowledge')
566
flags.append('offline')
568
iconLength, foo, iconSum, iconStamp = struct.unpack('!LHHL',v)
571
flags.append((iconLength, iconSum, iconStamp))
573
flags.append('buddyrequest')
574
elif k == 0xb: # unknown
577
flags.append('extradata')
580
log.msg('unknown TLV for incoming IM, %04x, %s' % (k,repr(v)))
582
# unknown tlv for user SNewdorf
584
# v: '\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x01\x10/\x8c\x8b\x8a\x1e\x94*\xbc\x80}\x8d\xc4;\x1dEM'
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]))
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')
605
return # handle this later
606
name = moreTLVs[10001][9:-7]
608
log.msg('file request from %s, %s, %s' % (user, name, desc))
609
self.receiveSendFileRequest(user, name, desc, cookie)
611
log.msg('unsupported rondevouz: %s' % requestClass)
612
log.msg(repr(moreTLVs))
614
log.msg('unknown channel %02x' % channel)
617
def _cbGetChatInfoForInvite(self, info, user, message):
618
apply(self.receiveChatInvite, (user,message)+info)
620
def oscar_09_03(self, snac):
624
tlvs = readTLVs(snac[3])
625
self.maxPermitList = tlvs[1]
626
self.maxDenyList = tlvs[2]
628
def oscar_0B_02(self, snac):
630
stats reporting interval
632
self.reportingInterval = struct.unpack('!H',snac[3][:2])[0]
634
def oscar_13_03(self, snac):
638
#tlvs = readTLVs(snac[3])
639
pass # we don't know how to parse this
641
# methods to be called by the client, and their support methods
642
def requestSelfInfo(self):
644
ask for the OSCARUser for ourselves
647
self.sendSNAC(0x01, 0x0E, '').addCallback(self._cbRequestSelfInfo, d)
650
def _cbRequestSelfInfo(self, snac, d):
651
d.callback(self.parseUser(snac[5]))
655
this sends the rate request for family 0x13 (Server Side Information)
656
so we can then use it
658
return self.sendSNAC(0x13, 0x02, '').addCallback(self._cbInitSSI)
660
def _cbInitSSI(self, snac, d):
661
return {} # don't even bother parsing this
663
def requestSSI(self, timestamp = 0, revision = 0):
665
request the server side information
666
if the deferred gets None, it means the SSI is the same
668
return self.sendSNAC(0x13, 0x05,
669
struct.pack('!LH',timestamp,revision)).addCallback(self._cbRequestSSI)
671
def _cbRequestSSI(self, snac, args = ()):
672
if snac[1] == 0x0f: # same SSI as we have
674
itemdata = snac[5][3:]
676
revision, groups, permit, deny, permitMode, visibility = args
678
version, revision = struct.unpack('!BH', snac[5][:3])
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)
697
elif itemType == 2: # permit
699
elif itemType == 3: # deny
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)?
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
714
self.requestCallbacks[snac[4]] = d
715
d.addCallback(self._cbRequestSSI, (revision, groups, permit, deny, permitMode, visibility))
717
return (groups[0].users,permit,deny,permitMode,visibility,timestamp,revision)
719
def activateSSI(self):
721
active the data stored on the server (use buddy list, permit deny settings, etc.)
723
self.sendSNACnr(0x13,0x07,'')
725
def startModifySSI(self):
727
tell the OSCAR server to be on the lookout for SSI modifications
729
self.sendSNACnr(0x13,0x11,'')
731
def addItemSSI(self, item, groupID = None, buddyID = None):
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.
737
if isinstance(item, SSIGroup):
740
groupID = item.group.group.findIDFor(item.group)
742
buddyID = item.group.findIDFor(item)
743
return self.sendSNAC(0x13,0x08, item.oscarRep(groupID, buddyID))
745
def modifyItemSSI(self, item, groupID = None, buddyID = None):
747
if isinstance(item, SSIGroup):
750
groupID = item.group.group.findIDFor(item.group)
752
buddyID = item.group.findIDFor(item)
753
return self.sendSNAC(0x13,0x09, item.oscarRep(groupID, buddyID))
755
def delItemSSI(self, item, groupID = None, buddyID = None):
757
if isinstance(item, SSIGroup):
760
groupID = item.group.group.findIDFor(item.group)
762
buddyID = item.group.findIDFor(item)
763
return self.sendSNAC(0x13,0x0A, item.oscarRep(groupID, buddyID))
765
def endModifySSI(self):
766
self.sendSNACnr(0x13,0x12,'')
768
def setProfile(self, profile):
771
send None to not set a profile (different from '' for a blank one)
773
self.profile = profile
775
if self.profile is not None:
776
tlvs = TLV(1,'text/aolrtf; charset="us-ascii"') + \
779
tlvs = tlvs + TLV(5, ''.join(self.capabilities))
780
self.sendSNACnr(0x02, 0x04, tlvs)
782
def setAway(self, away = None):
784
set the away message, or return (if away == None)
786
self.awayMessage = away
787
tlvs = TLV(3,'text/aolrtf; charset="us-ascii"') + \
789
self.sendSNACnr(0x02, 0x04, tlvs)
791
def setIdleTime(self, idleTime):
793
set our idle time. don't call more than once with a non-0 idle time.
795
self.sendSNACnr(0x01, 0x11, struct.pack('!L',idleTime))
797
def sendMessage(self, user, message, wantAck = 0, autoResponse = 0, offline = 0 ): \
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)
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')
815
if 'unicode' in part[1:]:
817
part[0] = part[0].encode('utf-8')
818
elif 'iso-8859-1' in part[1:]:
820
part[0] = part[0].encode('iso-8859-1')
821
elif 'none' in part[1:]:
823
if 'macintosh' in part[1:]:
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)
832
data = data + TLV(3,'')
834
data = data + TLV(4,'')
836
data = data + TLV(6,'')
838
return self.sendSNAC(0x04, 0x06, data).addCallback(self._cbSendMessageAck, user, message)
839
self.sendSNACnr(0x04, 0x06, data)
841
def _cbSendMessageAck(self, snac, user, message):
844
def connectService(self, service, wantCallback = 0, extraData = ''):
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.
852
self.sendSNAC(0x01,0x04,struct.pack('!H',service) + extraData).addCallback(self._cbConnectService, d)
855
self.sendSNACnr(0x01,0x04,struct.pack('!H',service))
857
def _cbConnectService(self, snac, d):
858
self.oscar_01_05(snac[2:], d)
860
def createChat(self, shortName):
864
if self.services.has_key(SERVICE_CHATNAV):
865
return self.services[SERVICE_CHATNAV].createChat(shortName)
867
return self.connectService(SERVICE_CHATNAV,1).addCallback(lambda s: s.createChat(shortName))
870
def joinChat(self, exchange, fullName, instance):
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)
879
def _cbJoinChat(self, chat):
880
del self.services[SERVICE_CHAT]
883
def warnUser(self, user, anon = 0):
884
return self.sendSNAC(0x04, 0x08, '\x00'+chr(anon)+chr(len(user))+user).addCallback(self._cbWarnUser)
886
def _cbWarnUser(self, snac):
887
oldLevel, newLevel = struct.unpack('!2H', snac[5])
888
return oldLevel, newLevel
890
def getInfo(self, user):
892
return self.sendSNAC(0x02, 0x05, '\x00\x01'+chr(len(user))+user).addCallback(self._cbGetInfo)
894
def _cbGetInfo(self, snac):
895
user, rest = self.parseUser(snac[5],1)
896
tlvs = readTLVs(rest)
897
return tlvs.get(0x02,None)
899
def getAway(self, user):
900
return self.sendSNAC(0x02, 0x05, '\x00\x03'+chr(len(user))+user).addCallback(self._cbGetAway)
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
907
#def acceptSendFileRequest(self,
909
# methods to be overriden by the client
912
called when we get the rate information, which means we should do other init. stuff.
914
log.msg('%s initDone' % self)
917
def updateBuddy(self, user):
919
called when a buddy changes status, with the OSCARUser for that buddy.
921
log.msg('%s updateBuddy %s' % (self, user))
924
def offlineBuddy(self, user):
926
called when a buddy goes offline
928
log.msg('%s offlineBuddy %s' % (self, user))
931
def receiveMessage(self, user, multiparts, flags):
933
called when someone sends us a message
937
def receiveWarning(self, newLevel, user):
939
called when someone warns us.
940
user is either None (if it was anonymous) or an OSCARUser
944
def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
946
called when someone invites us to a chat room
950
def chatReceiveMessage(self, chat, user, message):
952
called when someone in a chatroom sends us a message in the chat
956
def chatMemberJoined(self, chat, member):
958
called when a member joins the chat
962
def chatMemberLeft(self, chat, member):
964
called when a member leaves the chat
968
def receiveSendFileRequest(self, user, file, description, cookie):
970
called when someone tries to send a file to us
974
class OSCARService(SNACBased):
975
def __init__(self, bos, cookie, d = None):
976
SNACBased.__init__(self, cookie)
980
def connectionLost(self, reason):
981
for k,v in self.bos.services.items():
983
del self.bos.services[k]
986
def clientReady(self):
987
SNACBased.clientReady(self)
989
self.d.callback(self)
992
class ChatNavService(OSCARService):
994
0x01:(3, 0x0010, 0x059b),
995
0x0d:(1, 0x0010, 0x059b)
997
def oscar_01_07(self, snac):
999
self.sendSNACnr(0x01, 0x08, '\000\001\000\002\000\003\000\004\000\005')
1000
self.sendSNACnr(0x0d, 0x02, '')
1002
def oscar_0D_09(self, snac):
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)
1012
def _cbGetChatInfo(self, snac, d):
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)
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)
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
1039
class ChatService(OSCARService):
1041
0x01:(3, 0x0010, 0x059b),
1042
0x0E:(1, 0x0010, 0x059b)
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
1052
clientReady = SNACBased.clientReady # we'll do our own callback
1054
def oscar_01_07(self,snac):
1055
self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005")
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)
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)
1076
def oscar_0E_03(self,snac):
1080
user, rest = self.bos.parseUser(rest, 1)
1082
if not self.fullName:
1083
self.members = users
1085
self.members.append(users[0])
1086
self.bos.chatMemberJoined(self,users[0])
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)
1095
def oscar_0E_06(self,snac):
1097
user,rest=self.bos.parseUser(snac[3][14:],1)
1098
tlvs = readTLVs(rest[8:])
1100
self.bos.chatReceiveMessage(self,user,message)
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))+
1109
def leaveChat(self):
1112
class 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
1122
def oscar_(self,flap):
1124
self.sendFLAP("\000\000\000\001", 0x01)
1125
self.sendFLAP(SNAC(0x17,0x06,0,
1126
TLV(TLV_USERNAME,self.username)+
1130
encpass=encryptPasswordICQ(self.password)
1131
self.sendFLAP('\000\000\000\001'+
1132
TLV(0x01,self.username)+
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")+
1140
TLV(0x14,"\x00\x00\x00U")+
1142
TLV(0x0e,"us"),0x01)
1145
def oscar_Key(self,data):
1146
snac=readSNAC(data[1])
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")+
1161
TLV(TLV_COUNTRY,"us")+
1162
TLV(TLV_USESSI,"\001")))
1165
def oscar_Cookie(self,data):
1166
snac=readSNAC(data[1])
1168
i=snac[5].find("\000")
1170
tlvs=readTLVs(snac[5])
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))
1177
d.chainDeferred(self.deferred)
1179
elif tlvs.has_key(8):
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)
1189
log.msg('hmm, weird tlvs for %s cookie packet' % str(self))
1195
def oscar_None(self,data): pass
1197
def connectToBOS(self, server, port):
1198
c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie)
1199
return c.connectTCP(server, int(port))
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()
1206
FLAP_CHANNEL_NEW_CONNECTION = 0x01
1207
FLAP_CHANNEL_DATA = 0x02
1208
FLAP_CHANNEL_ERROR = 0x03
1209
FLAP_CHANNEL_CLOSE_CONNECTION = 0x04
1211
SERVICE_CHATNAV = 0x0d
1214
SERVICE_CHATNAV:ChatNavService,
1215
SERVICE_CHAT:ChatService
1217
TLV_USERNAME = 0x0001
1218
TLV_CLIENTNAME = 0x0003
1219
TLV_COUNTRY = 0x000E
1221
TLV_CLIENTMAJOR = 0x0017
1222
TLV_CLIENTMINOR = 0x0018
1223
TLV_CLIENTSUB = 0x001A
1224
TLV_PASSWORD = 0x0025
1227
CAP_ICON = '\011F\023FL\177\021\321\202"DEST\000\000'
1228
CAP_VOICE = '\011F\023AL\177\021\321\202"DEST\000\000'
1229
CAP_IMAGE = '\011F\023EL\177\021\321\202"DEST\000\000'
1230
CAP_CHAT = 't\217$ b\207\021\321\202"DEST\000\000'
1231
CAP_GET_FILE = '\011F\023HL\177\021\321\202"DEST\000\000'
1232
CAP_SEND_FILE = '\011F\023CL\177\021\321\202"DEST\000\000'
1233
CAP_GAMES = '\011F\023GL\177\021\321\202"DEST\000\000'
1234
CAP_SEND_LIST = '\011F\023KL\177\021\321\202"DEST\000\000'
1235
CAP_SERV_REL = '\011F\023IL\177\021\321\202"DEST\000\000'