root / trunk / twisted / words / protocols / oscar.py

Revision 25457, 42.2 kB (checked in by exarkun, 8 months ago)

Merge hashlib-2763-3

Author: wsanchez, exarkun
Reviewer: exarkun, mwhudson
Fixes: #2763

Replace uses of md5 and sha modules in Twisted with use of a new twisted.python.hashlib
module which transparently uses the new hashlib standard library module if it is available
or falls back to md5 and sha if not.

Line 
1 # -*- test-case-name: twisted.words.test -*-
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 An implementation of the OSCAR protocol, which AIM and ICQ use to communcate.
8
9 Maintainer: Paul Swartz
10 """
11
12 import struct
13 import string
14 import socket
15 import random
16 import types
17 import re
18
19 from twisted.internet import reactor, defer, protocol
20 from twisted.python import log
21 from twisted.python.hashlib import md5
22
23 def 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
33 def 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
40 def readSNAC(data):
41     header="!HHBBL"
42     head=list(struct.unpack(header,data[:10]))
43     return head+[data[10:]]
44
45 def TLV(type,value):
46     header="!HH"
47     head=struct.pack(header,type,len(value))
48     return head+str(value)
49
50 def 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
61 def 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
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)
71     r=""
72     for i in range(len(bytes)):
73         r=r+chr(bytes[i]^key[i%len(key)])
74     return r
75
76 def 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,'&gt;','>')
83     text=string.replace(text,'&lt;','<')
84     text=string.replace(text,'&nbsp;',' ')
85     text=string.replace(text,'&#34;','"')
86     text=string.replace(text,'&amp;','&')
87     return text
88
89 def html(text):
90     text=string.replace(text,'"','&#34;')
91     text=string.replace(text,'&','&amp;')
92     text=string.replace(text,'<','&lt;')
93     text=string.replace(text,'>','&gt;')
94     text=string.replace(text,"\n","<br>")
95     return '<html><body bgcolor="white"><font color="black">%s</font></body></html>'%text
96
97 class 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
179 class 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
207 class 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
238 class 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
310 class 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
405 class 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
974 class 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
992 class 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
1039 class 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
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
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
1206 FLAP_CHANNEL_NEW_CONNECTION = 0x01
1207 FLAP_CHANNEL_DATA = 0x02
1208 FLAP_CHANNEL_ERROR = 0x03
1209 FLAP_CHANNEL_CLOSE_CONNECTION = 0x04
1210
1211 SERVICE_CHATNAV = 0x0d
1212 SERVICE_CHAT = 0x0e
1213 serviceClasses = {
1214     SERVICE_CHATNAV:ChatNavService,
1215     SERVICE_CHAT:ChatService
1216 }
1217 TLV_USERNAME = 0x0001
1218 TLV_CLIENTNAME = 0x0003
1219 TLV_COUNTRY = 0x000E
1220 TLV_LANG = 0x000F
1221 TLV_CLIENTMAJOR = 0x0017
1222 TLV_CLIENTMINOR = 0x0018
1223 TLV_CLIENTSUB = 0x001A
1224 TLV_PASSWORD = 0x0025
1225 TLV_USESSI = 0x004A
1226
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'
Note: See TracBrowser for help on using the browser.