| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 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") |
|---|
| 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 |
|
|---|
| 89 |
def 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 |
|
|---|
| 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: |
|---|
| 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: |
|---|
| 115 |
self.memberSince = struct.unpack('!L',v)[0] |
|---|
| 116 |
elif k == 3: |
|---|
| 117 |
self.onSince = struct.unpack('!L',v)[0] |
|---|
| 118 |
elif k == 4: |
|---|
| 119 |
self.idleTime = struct.unpack('!H',v)[0] |
|---|
| 120 |
elif k == 5: |
|---|
| 121 |
pass |
|---|
| 122 |
elif k == 6: |
|---|
| 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: |
|---|
| 136 |
self.icqIPaddy = socket.inet_ntoa(v) |
|---|
| 137 |
elif k == 12: |
|---|
| 138 |
self.icqRandom = v |
|---|
| 139 |
elif k == 13: |
|---|
| 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: |
|---|
| 157 |
self.sessionLength = struct.unpack('!L',v)[0] |
|---|
| 158 |
elif k == 16: |
|---|
| 159 |
self.sessionLength = struct.unpack('!L',v)[0] |
|---|
| 160 |
elif k == 30: |
|---|
| 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 |
|
|---|
| 183 |
|
|---|
| 184 |
self.usersToID = {} |
|---|
| 185 |
self.users = [] |
|---|
| 186 |
|
|---|
| 187 |
|
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 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: |
|---|
| 213 |
self.buddyComment = v |
|---|
| 214 |
elif k == 0x013d: |
|---|
| 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) |
|---|
| 245 |
|
|---|
| 246 |
def connectionLost(self, reason): |
|---|
| 247 |
log.msg("Connection Lost! %s" % self) |
|---|
| 248 |
self.stopKeepAlive() |
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
|
|---|
| 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 |
|
|---|
| 262 |
|
|---|
| 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 |
|
|---|
| 274 |
|
|---|
| 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 |
|
|---|
| 313 |
} |
|---|
| 314 |
def __init__(self,cookie): |
|---|
| 315 |
self.cookie=cookie |
|---|
| 316 |
self.lastID=0 |
|---|
| 317 |
self.supportedFamilies = () |
|---|
| 318 |
self.requestCallbacks={} |
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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,"") |
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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") |
|---|
| 469 |
self.initDone() |
|---|
| 470 |
self.sendSNACnr(0x13,0x02,'') |
|---|
| 471 |
self.sendSNACnr(0x02,0x02,'') |
|---|
| 472 |
self.sendSNACnr(0x03,0x02,'') |
|---|
| 473 |
self.sendSNACnr(0x04,0x04,'') |
|---|
| 474 |
self.sendSNACnr(0x09,0x02,'') |
|---|
| 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 |
|---|
| 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 |
|
|---|
| 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') |
|---|
| 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: |
|---|
| 540 |
flags = [] |
|---|
| 541 |
multiparts = [] |
|---|
| 542 |
for k, v in tlvs.items(): |
|---|
| 543 |
if k == 2: |
|---|
| 544 |
while v: |
|---|
| 545 |
v = v[2:] |
|---|
| 546 |
messageLength, charSet, charSubSet = struct.unpack('!3H', v[:6]) |
|---|
| 547 |
messageLength -= 4 |
|---|
| 548 |
message = [v[6:6+messageLength]] |
|---|
| 549 |
if charSet == 0: |
|---|
| 550 |
pass |
|---|
| 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: |
|---|
| 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 |
|
|---|
| 583 |
|
|---|
| 584 |
|
|---|
| 585 |
|
|---|
| 586 |
self.receiveMessage(user, multiparts, flags) |
|---|
| 587 |
elif channel == 2: |
|---|
| 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: |
|---|
| 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): |
|---|
| 603 |
log.msg('cancelled file request') |
|---|
| 604 |
log.msg(status) |
|---|
| 605 |
return |
|---|
| 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 |
|
|---|
| 639 |
pass |
|---|
| 640 |
|
|---|
| 641 |
|
|---|
| 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 {} |
|---|
| 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: |
|---|
| 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: |
|---|
| 692 |
groups[groupID].addUser(buddyID, SSIBuddy(name, tlvs)) |
|---|
| 693 |
elif itemType == 1: |
|---|
| 694 |
g = SSIGroup(name, tlvs) |
|---|
| 695 |
if groups.has_key(0): groups[0].addUser(groupID, g) |
|---|
| 696 |
groups[groupID] = g |
|---|
| 697 |
elif itemType == 2: |
|---|
| 698 |
permit.append(name) |
|---|
| 699 |
elif itemType == 3: |
|---|
| 700 |
deny.append(name) |
|---|
| 701 |
elif itemType == 4: |
|---|
| 702 |
if not tlvs.has_key(0xcb): |
|---|
| 703 |
continue |
|---|
| 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: |
|---|
| 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: |
|---|
| 712 |
|
|---|
| 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 |
|
|---|
| 799 |
""" |
|---|
| 800 |
|
|---|
| 801 |
|
|---|
| 802 |
|
|---|
| 803 |
|
|---|
| 804 |
|
|---|
| 805 |
|
|---|
| 806 |
data = ''.join([chr(random.randrange(0, 127)) for i in range(8)]) |
|---|
| 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 |
|
|---|
| 875 |
return self.connectService(0x0e, 1, TLV(0x01, struct.pack('!HB',exchange, len(fullName)) + fullName + |
|---|
| 876 |
struct.pack('!H', instance))).addCallback(self._cbJoinChat) |
|---|
| 877 |
|
|---|
| 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 |
|
|---|
| 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) |
|---|
| 906 |
|
|---|
| 907 |
|
|---|
| 908 |
|
|---|
| 909 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 1031 |
|
|---|
| 1032 |
def _cbCreateChat(self, snac): |
|---|
| 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 |
|
|---|
| 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 |
|---|
| 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 |
|
|---|
| 1060 |
|
|---|
| 1061 |
|
|---|
| 1062 |
|
|---|
| 1063 |
|
|---|
| 1064 |
|
|---|
| 1065 |
|
|---|
| 1066 |
|
|---|
| 1067 |
|
|---|
| 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: |
|---|
| 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 |
|---|
| 1119 |
|
|---|
| 1120 |
|
|---|
| 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, '')+ |
|---|
| 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' |
|---|