# Copyright (c) 2001-2005 Twisted Matrix Laboratories. # See LICENSE for details. # # vim: set sts=4 ts=4 expandtab : """An implementation of the OSCAR protocol, which AIM and ICQ use to communcate. This module is unstable. Maintainer: U{Daniel Henninger} Previous Maintainer: U{Paul Swartz} SMS Related code by Uri Shaked """ from __future__ import nested_scopes from twisted.internet import reactor, defer, protocol from twisted.python import log from scheduler import Scheduler import struct import md5 import string import socket import random import time import types import re import binascii import threading import socks5, sockserror import countrycodes def logPacketData(data): # Comment out to display packet log data return lines = len(data)/16 if lines*16 != len(data): lines=lines+1 for i in range(lines): d = tuple(data[16*i:16*i+16]) hex = map(lambda x: "%02X"%ord(x),d) text = map(lambda x: (len(repr(x))>3 and '.') or x, d) log.msg(' '.join(hex)+ ' '*3*(16-len(d)) +''.join(text)) log.msg('') def bitstostr(num, size): bitstring = '' if num < 0: return if num == 0: return '0'*size cnt = 0 while cnt < size: bitstring = str(num % 2) + bitstring num = num >> 1 cnt = cnt + 1 return bitstring def SNAC(fam,sub,id,data,flags=[0,0]): head=struct.pack("!HHBBL",fam,sub, flags[0],flags[1], id) return head+str(data) def readSNAC(data): try: if len(data) < 10: return None head=list(struct.unpack("!HHBBL",data[:10])) datapos = 10 if 0x80 & head[2]: # Ah flag 0x8000, this is some sort of family indicator, skip it, # we don't care. sLen,id,length = struct.unpack(">HHH", data[datapos:datapos+6]) datapos = datapos + 6 + length return head+[data[datapos:]] except struct.error: return None def oldICQCommand(commandCode, commandData, username, sequence): """ Packs a command for the old ICQ server. commandCode (int) - the code of the command, commandData - the data payload of the command. username (str) - The UIN of the sender sequence (int) - The lower word of the SNAC ID that encapsulates the command. """ header = "]*>',"\n",text) text=re.sub(']*>',"\n",text) text=re.sub(']*>',"\n",text) # Convert inline images to their alt-tags text=re.sub('<[Ii][Mm][Gg] +[^>]*alt=["\']([^"\']*)["\'][^>]*>',r"\1",text) # Turn bold into stars text=re.sub('<[Bb]>(.*?)',r"*\1*",text) text=re.sub('(.*?)',r"*\1*",text) # Turn italics into underscores text=re.sub('<[Ii]>(.*?)',r"_\1_",text) # Turn quotes into, um quotes. text=re.sub(']*>(.*?)',r'"\1"',text) # Extract links text=re.sub('<[Aa] +[^>]*href=["\']([^"\']*)["\'][^>]*>(.*?)',r"\2 <\1> ",text) # Convert more than two linebreaks into just two text=re.sub("\n\n+","\n\n",text) # Get rid of any leading or trailing whitespace text=re.sub("^[ \n]+","",text) text=re.sub("[ \n]+$","",text) # Convert clumps of whitespace into just one space text=re.sub(" +"," ",text) # Remove any remaining HTML elements. text=re.sub('<[^>]*>','',text) # Convert the entities text=string.replace(text,'>','>') text=string.replace(text,'<','<') text=string.replace(text,' ',' ') text=string.replace(text,'&','&') text=string.replace(text,'"',"'") return text def html(text): if (not text): text = "" text=string.replace(text,'&','&') text=string.replace(text,'<','<') text=string.replace(text,'>','>') text=string.replace(text,"\n","
") return '%s'%text def getIconSum(buf): sum = 0L i = 0 buflen = len(buf) while i+1 < buflen: sum += (ord(buf[i+1]) << 8) + ord(buf[i]) i += 2 if i < buflen: sum += ord(buf[i]) sum = ((sum & 0xffff0000L) >> 16) + (sum & 0x0000ffffL) return sum # Originally taken from: # http://www.pyzine.com/Issue008/Section_Articles/article_Encodings.html # which was adapted from io.py # in the docutils extension module # see http://docutils.sourceforge.net # modified for better use here def guess_encoding(data, defaultencoding='iso-8859-1'): """ Given a byte string, attempt to decode it. Tries 'utf-16be, 'utf-8' and 'iso-8859-1' (or something else) encodings. If successful it returns (decoded_unicode, successful_encoding) If unsuccessful it raises a ``UnicodeError`` """ successful_encoding = None #encodings = ['utf-8', 'utf-16be', defaultencoding] encodings = ['utf-8', defaultencoding] for enc in encodings: # some of the locale calls # may have returned None if not enc: continue try: decoded = unicode(data, enc) #decoded = data.decode(enc) successful_encoding = enc except (UnicodeError, LookupError): pass else: break if not successful_encoding: raise UnicodeError( 'Unable to decode input data. Tried the following encodings: %s.' % ', '.join([repr(enc) for enc in encodings if enc])) else: return (decoded, successful_encoding) class OSCARUser: def __init__(self, name, warn, tlvs): self.name = name self.warning = warn self.flags = [] self.caps = [] self.icqStatus = [] self.icqFlags = [] self.icqIPaddy = None self.icqLANIPaddy = None self.icqLANIPport = None self.icqProtocolVersion = None self.status = "" self.url = "" self.statusencoding = None self.idleTime = 0 self.iconmd5sum = None self.icontype = None self.iconcksum = None self.iconlen = None self.iconstamp = None for k,v in tlvs.items(): if k == 0x0001: # user flags v=struct.unpack('!H',v)[0] for o, f in [(0x0001,'unconfirmed'), (0x0002,'admin'), (0x0004,'staff'), (0x0008,'commercial'), (0x0010,'free'), (0x0020,'away'), (0x0040,'icq'), (0x0080,'wireless'), (0x0100,'unknown'), (0x0200,'unknown'), (0x0400,'active'), (0x0800,'unknown'), (0x1000,'abinternal')]: if v&o: self.flags.append(f) elif k == 0x0002: # account creation time self.createdOn = struct.unpack('!L',v)[0] elif k == 0x0003: # on-since self.onSince = struct.unpack('!L',v)[0] elif k == 0x0004: # idle time self.idleTime = struct.unpack('!H',v)[0] elif k == 0x0005: # member since self.memberSince = struct.unpack('!L',v)[0] elif k == 0x0006: # icq online status and flags # Flags first mv=struct.unpack('!H',v[0:2])[0] for o, f in [(0x0001,'webaware'), (0x0002,'showip'), (0x0008,'birthday'), (0x0020,'webfront'), (0x0100,'dcdisabled'), (0x1000,'dcauth'), (0x2000,'dccont')]: if mv&o: self.icqFlags.append(f) # Status flags next mv=struct.unpack('!H',v[2:4])[0] for o, f in [(0x0000,'online'), (0x0001,'away'), (0x0002,'dnd'), (0x0004,'xa'), (0x0010,'busy'), (0x0020,'chat'), (0x0100,'invisible')]: if mv&o: self.icqStatus.append(f) elif k == 0x0008: # client type? pass elif k == 0x000a: # icq user ip address self.icqIPaddy = socket.inet_ntoa(v) elif k == 0x000c: # icq random stuff # from http://iserverd1.khstu.ru/oscar/info_block.html self.icqRandom = struct.unpack('!4sLBHLLLLLLH',v) self.icqLANIPaddy = socket.inet_ntoa(self.icqRandom[0]) self.icqLANIPport = self.icqRandom[1] self.icqProtocolVersion = self.icqRandom[3] elif k == 0x000d: # capabilities caps=[] while v: c=v[:16] if CAPS.has_key(c): caps.append(CAPS[c]) else: caps.append(("unknown",c)) v=v[16:] caps.sort() self.caps=caps elif k == 0x000e: # AOL capability information pass elif k == 0x000f: # session length (aim) self.sessionLength = struct.unpack('!L',v)[0] elif k == 0x0010: # session length (aol) self.sessionLength = struct.unpack('!L',v)[0] elif k == 0x0019: # OSCAR short capabilities pass elif k == 0x001a: # AOL short capabilities pass elif k == 0x001b: # encryption certification MD5 checksum pass elif k == 0x001d: # AIM Extended Status log.msg("AIM Extended Status: user %s\nv: %s"%(self.name,repr(v))) while len(v)>4 and ord(v[0]) == 0 and ord(v[3]) != 0: exttype,extflags,extlen = struct.unpack('!HBB',v[0:4]) if exttype == 0x00: # Gaim skips this, so will we pass elif exttype == 0x01: # Actual interesting buddy icon if extlen > 0 and (extflags == 0x00 or extflags == 0x01): self.iconmd5sum = v[4:4+extlen] self.icontype = extflags log.msg(" extracted icon hash: extflags = %s, iconhash = %s" % (str(hex(extflags)), binascii.hexlify(self.iconmd5sum))) elif exttype == 0x02: # Extended Status Message if extlen >= 4: # Why? Gaim does this availlen = (struct.unpack('!H', v[4:6]))[0] self.status = v[6:6+availlen] pos = 6+availlen if pos < extlen+4: hasencoding = (struct.unpack('!H',v[pos:pos+2]))[0] pos = pos+2 if hasencoding == 0x0001: enclen = (struct.unpack('!HH',v[pos:pos+4]))[1] self.statusencoding = v[pos+4:pos+4+enclen] log.msg(" extracted status message: %s"%(self.status)) if self.statusencoding: log.msg(" status message encoding: %s"%(str(self.statusencoding))) elif exttype == 0x09: # iTunes URL statlen = (struct.unpack('!H', v[4:6]))[0] #statlen=int((struct.unpack('!H', v[2:4]))[0])-4 if statlen>2 and v[6+statlen-1:6+statlen] != "\x00": self.url=v[6:6+statlen] else: self.url=None log.msg(" extracted itunes URL: %s"%(repr(self.url))) else: log.msg(" unknown extended status type: %d\ndata: %s"%(ord(v[1]), repr(v[:ord(v[3])+4]))) #v=v[ord(v[3])+4:] v=v[extlen+4:] elif k == 0x001e: # unknown pass elif k == 0x001f: # unknown pass else: log.msg("unknown tlv for user %s\nt: %s\nv: %s"%(self.name,str(hex(k)),repr(v))) def __str__(self): s = '' return s def __repr__(self): return self.__str__() class SSIGroup: def __init__(self, name, groupID, buddyID, tlvs = {}): self.name = name self.groupID = groupID self.buddyID = buddyID #self.tlvs = [] #self.userIDs = [] self.usersToID = {} self.users = [] #if not tlvs.has_key(0xC8): return #buddyIDs = tlvs[0xC8] #while buddyIDs: # bid = struct.unpack('!H',buddyIDs[:2])[0] # buddyIDs = buddyIDs[2:] # self.users.append(bid) def findIDFor(self, user): return self.usersToID[user] def addUser(self, buddyID, user): self.usersToID[user] = buddyID self.users.append(user) user.group = self def delUser(self, user): buddyID = self.usersToID[user] self.users.remove(user) del self.usersToID[user] user.group = None def oscarRep(self): data = struct.pack(">H", len(self.name)) +self.name tlvs = TLV(0xc8, struct.pack(">H",len(self.users))) data += struct.pack(">4H", self.groupID, self.buddyID, 1, len(tlvs)) return data+tlvs #if len(self.users) > 0: # tlvData = TLV(0xc8, reduce(lambda x,y:x+y, [struct.pack('!H',self.usersToID[x]) for x in self.users])) #else: # tlvData = "" # return struct.pack('!H', len(self.name)) + self.name + \ # struct.pack('!HH', groupID, buddyID) + '\000\001' + \ # struct.pack(">H", len(tlvData)) + tlvData def __str__(self): s = ' 0: # s=s+' (Members:'+', '.join(self.users)+')' s=s+'>' return s def __repr__(self): return self.__str__() class SSIBuddy: def __init__(self, name, groupID, buddyID, tlvs = {}): self.name = name self.nick = None self.groupID = groupID self.buddyID = buddyID self.tlvs = tlvs self.authorizationRequestSent = False self.authorized = True self.sms = None self.email = None self.buddyComment = None self.alertSound = None self.firstMessage = None for k,v in tlvs.items(): if k == 0x0066: # awaiting authorization self.authorized = False elif k == 0x0131: # buddy nick self.nick = v elif k == 0x0137: # buddy email self.email = v elif k == 0x013a: # sms number self.sms = v elif k == 0x013c: # buddy comment self.buddyComment = v elif k == 0x013d: # buddy alerts actionFlag = ord(v[0]) whenFlag = ord(v[1]) self.alertActions = [] self.alertWhen = [] if actionFlag&1: self.alertActions.append('popup') if actionFlag&2: self.alertActions.append('sound') if whenFlag&1: self.alertWhen.append('online') if whenFlag&2: self.alertWhen.append('unidle') if whenFlag&4: self.alertWhen.append('unaway') elif k == 0x013e: self.alertSound = v elif k == 0x0145: # first time we sent a message to this person self.firstMessage = v # unix timestamp def oscarRep(self): data = struct.pack(">H", len(self.name)) + self.name tlvs = "" if not self.authorized: tlvs += TLV(0x0066) # awaiting authorization if self.nick: tlvs += TLV(0x0131, self.nick) if self.email: tlvs += TLV(0x0137, self.email) if self.sms: tlvs += TLV(0x013a, self.sms) if self.buddyComment: tlvs += TLV(0x013c, self.buddyComment) # Should do buddy alerts here too if self.alertSound: tlvs += TLV(0x013e, self.alertSound) if self.firstMessage: tlvs += TLV(0x0145, self.firstMessage) data += struct.pack(">4H", self.groupID, self.buddyID, 0, len(tlvs)) return data+tlvs #tlvData = reduce(lambda x,y: x+y, map(lambda (k,v):TLV(k,v), self.tlvs.items()), '\000\000') #return struct.pack('!H', len(self.name)) + self.name + \ # struct.pack('!HH', groupID, buddyID) + '\000\000' + tlvData def __str__(self): s = '' return s def __repr__(self): return self.__str__() class SSIIconSum: def __init__(self, name="1", groupID=0x0000, buddyID=0x51f4, tlvs = {}): self.name = name self.buddyID = buddyID self.groupID = groupID self.iconSum = tlvs.get(0xd5,"") def updateIcon(self, iconData): m=md5.new() m.update(iconData) self.iconSum = m.digest() log.msg("icon sum is %s" % binascii.hexlify(self.iconSum)) def oscarRep(self): data = struct.pack(">H", len(self.name)) + self.name tlvs = TLV(0x00d5,struct.pack('!BB', 0x00, len(self.iconSum))+self.iconSum)+TLV(0x0131, "") data += struct.pack(">4H", self.groupID, self.buddyID, AIM_SSI_TYPE_ICONINFO, len(tlvs)) return data+tlvs def __str__(self): s = '' return s def __repr__(self): return self.__str__() class SSIPDInfo: def __init__(self, name="", groupID=0x0000, buddyID=0xffff, tlvs = {}): self.name = name self.groupID = groupID self.buddyID = buddyID self.permitMode = tlvs.get(0xca, None) self.visibility = tlvs.get(0xcb, None) def oscarRep(self): data = struct.pack(">H", len(self.name)) + self.name tlvs = "" if self.permitMode: tlvs += TLV(0xca,struct.pack('!B', self.permitMode)) if self.visibility: tlvs += TLV(0xcb,self.visibility) data += struct.pack(">4H", self.groupID, self.buddyID, AIM_SSI_TYPE_PDINFO, len(tlvs)) return data+tlvs def __str__(self): s = '' return s def __repr__(self): return self.__str__() class OscarConnection(protocol.Protocol): def connectionMade(self): self.state="" self.seqnum=0 self.buf='' self.outRate=6000 self.outTime=time.time() self.stopKeepAliveID = None self.setKeepAlive(240) # 240 seconds = 4 minutes self.transport.setTcpNoDelay(True) def connectionLost(self, reason): log.msg("Connection Lost! %s" % self) self.stopKeepAlive() self.transport.loseConnection() def connectionFailed(self): log.msg("Connection Failed! %s" % self) self.stopKeepAlive() def sendFLAP(self,data,channel = 0x02): if not hasattr(self, "seqnum"): self.seqnum = 0 self.seqnum=(self.seqnum+1)%0xFFFF seqnum=self.seqnum head=struct.pack("!BBHH", 0x2a, channel, seqnum, len(data)) reactor.callFromThread(self.transport.write,head+str(data)) #if isinstance(self, ChatService): # logPacketData(head+str(data)) def readFlap(self): if len(self.buf)<6: return # We don't have a whole FLAP yet flap=struct.unpack("!BBHH",self.buf[:6]) if len(self.buf)<6+flap[3]: return # We don't have a whole FLAP yet if flap[0] != 0x2a: log.msg("WHOA! Illegal FLAP id! %x" % flap[0]) return data,self.buf=self.buf[6:6+flap[3]],self.buf[6+flap[3]:] return [flap[1],data] def dataReceived(self,data): logPacketData(data) self.buf=self.buf+data flap=self.readFlap() while flap: if flap[0] == 0x04: # We failed to connect properly self.connectionLost("Connection rejected.") func=getattr(self,"oscar_%s"%self.state,None) if not func: log.msg("no func for state: %s" % self.state) return state=func(flap) if state: self.state=state flap=self.readFlap() def setKeepAlive(self,t): self.keepAliveDelay=t if hasattr(self,"stopKeepAliveID") and self.stopKeepAliveID: self.stopKeepAlive() self.stopKeepAliveID = reactor.callLater(t, self.sendKeepAlive) def sendKeepAlive(self): self.sendFLAP("",0x05) self.stopKeepAliveID = reactor.callLater(self.keepAliveDelay, self.sendKeepAlive) def stopKeepAlive(self): if hasattr(self,"stopKeepAliveID") and self.stopKeepAliveID: self.stopKeepAliveID.cancel() self.stopKeepAliveID = None def disconnect(self): """ send the disconnect flap, and sever the connection """ self.sendFLAP('', 0x04) def f(reason): pass self.connectionLost = f self.transport.loseConnection() class SNACBased(OscarConnection): snacFamilies = { # family : (version, toolID, toolVersion) } def __init__(self,cookie): self.cookie=cookie self.lastID=0 self.supportedFamilies = {} self.requestCallbacks={} # request id:Deferred self.scheduler=Scheduler(self.sendFLAP) def sendSNAC(self,fam,sub,data,flags=[0,0]): """ send a snac and wait for the response by returning a Deferred. """ if not self.supportedFamilies.has_key(fam): log.msg("Ignoring attempt to send unsupported SNAC family %s." % (str(hex(fam)))) return defer.fail("Attempted to send unsupported SNAC family.") reqid=self.lastID self.lastID=reqid+1 d = defer.Deferred() d.reqid = reqid d.addErrback(self._ebDeferredError,fam,sub,data) # XXX for testing self.requestCallbacks[reqid] = d snac=SNAC(fam,sub,reqid,data) self.scheduler.enqueue(fam,sub,snac) return d def _ebDeferredError(self, error, fam, sub, data): log.msg('ERROR IN DEFERRED %s' % error) log.msg('on sending of message, family 0x%02x, subtype 0x%02x' % (fam, sub)) log.msg('data: %s' % repr(data)) def sendSNACnr(self,fam,sub,data,flags=[0,0]): """ send a snac, but don't bother adding a deferred, we don't care. """ if not self.supportedFamilies.has_key(fam): log.msg("Ignoring attempt to send unsupported SNAC family %s." % (str(hex(fam)))) return snac=SNAC(fam,sub,0x10000*fam+sub,data) self.scheduler.enqueue(fam,sub,snac) def sendOldICQCommand(self,commandCode,commandData): """ Sends a command to the old ICQ server. commandCode - the code of the command to be sent commandData - data payload. """ reqid=self.lastID self.lastID=reqid+1 d = defer.Deferred() d.reqid = reqid # Prepare the ICQ Command data data = oldICQCommand(commandCode, commandData, self.username, reqid) self.requestCallbacks[reqid] = d snac=SNAC(0x15, 0x2, reqid, TLV(1, data)) self.scheduler.enqueue(0x15,0x2,snac) return d def oscar_(self,data): self.sendFLAP("\000\000\000\001"+TLV(6,self.cookie), 0x01) return "Data" def oscar_Data(self,data): snac=readSNAC(data[1]) if not snac: log.msg("Illegal SNAC data received in oscar_Data: %s" % data) return if self.requestCallbacks.has_key(snac[4]): d = self.requestCallbacks[snac[4]] del self.requestCallbacks[snac[4]] if snac[1]!=1: d.callback(snac) else: d.errback(snac) return func=getattr(self,'oscar_%02X_%02X'%(snac[0],snac[1]),None) if not func: self.oscar_unknown(snac) else: func(snac) return "Data" def oscar_unknown(self,snac): log.msg("unknown for %s" % self) log.msg(snac) def oscar_01_03(self, snac): numFamilies = len(snac[5])/2 serverFamilies = struct.unpack("!"+str(numFamilies)+'H', snac[5]) d = '' for fam in serverFamilies: log.msg("Server supports SNAC family %s" % (str(hex(fam)))) self.supportedFamilies[fam] = True if self.snacFamilies.has_key(fam): d=d+struct.pack('!2H',fam,self.snacFamilies[fam][0]) self.sendSNACnr(0x01,0x17, d) def oscar_01_0A(self,snac): """ change of rate information. """ # this can be parsed, maybe we can even work it in try: info=struct.unpack('!HHLLLLLLL',snac[5][8:40]) except struct.error: return code=info[0] rateclass=info[1] window=info[2] clear=info[3] alert=info[4] limit=info[5] disconnect=info[6] current=info[7] maxrate=info[8] self.scheduler.setStat(rateclass,window=window,clear=clear,alert=alert,limit=limit,disconnect=disconnect,rate=current,maxrate=maxrate) #need to figure out a better way to do this #if (code==3): # import sys # sys.exit() def oscar_01_18(self,snac): """ host versions, in the same format as we sent """ self.sendSNACnr(0x01,0x06,"") #pass def clientReady(self): """ called when the client is ready to be online """ d = '' for fam in self.supportedFamilies: log.msg("Checking for client SNAC family support %s" % str(hex(fam))) if self.snacFamilies.has_key(fam): version, toolID, toolVersion = self.snacFamilies[fam] log.msg(" We do support at %s %s %s" % (str(version), str(hex(toolID)), str(hex(toolVersion)))) d = d + struct.pack('!4H',fam,version,toolID,toolVersion) self.sendSNACnr(0x01,0x02,d) class BOSConnection(SNACBased): #snacFamilies = { # 0x01:(3, 0x0110, 0x0629), # 0x02:(1, 0x0110, 0x0629), # 0x03:(1, 0x0110, 0x0629), # 0x04:(1, 0x0110, 0x0629), # 0x06:(1, 0x0110, 0x0629), # 0x08:(1, 0x0104, 0x0001), # 0x09:(1, 0x0110, 0x0629), # 0x0a:(1, 0x0110, 0x0629), # 0x0b:(1, 0x0104, 0x0001), # 0x0c:(1, 0x0104, 0x0001), # 0x13:(3, 0x0110, 0x0629), # 0x15:(1, 0x0110, 0x047c) #} snacFamilies = { 0x01:(4, 0x0110, 0x08e4), 0x02:(1, 0x0110, 0x08e4), 0x03:(1, 0x0110, 0x08e4), 0x04:(1, 0x0110, 0x08e4), 0x06:(1, 0x0110, 0x08e4), 0x08:(1, 0x0104, 0x0001), 0x09:(1, 0x0110, 0x08e4), 0x0a:(1, 0x0110, 0x08e4), 0x0b:(1, 0x0104, 0x08e4), 0x0c:(1, 0x0104, 0x0001), 0x13:(4, 0x0110, 0x08e4), 0x15:(1, 0x0110, 0x08e4) } capabilities = None statusindicators = 0x0000 def __init__(self,username,cookie): SNACBased.__init__(self,cookie) self.username=username self.profile = None self.awayMessage = None self.services = {} self.socksProxyServer = None self.socksProxyPort = None self.connectPort = 5190 # Note that this is "no unicode" default encoding # We use unicode if it's there self.defaultEncoding = 'iso-8859-1' if not self.capabilities: self.capabilities = [CAP_CHAT] def parseUser(self,data,wantRest=0): l=ord(data[0]) name=data[1:1+l] warn,tlvcnt=struct.unpack("!HH",data[1+l:5+l]) warn=int(warn/10) #if count == None: # tlvs,rest = readTLVs(data[5+l:]), None #else: # tlvs,rest = readTLVs(data[5+l:],tlvcnt) tlvs,rest = readTLVs(data[5+l:],tlvcnt) u = OSCARUser(name, warn, tlvs) if wantRest: return u, rest else: return u def parseProfile(self, data): l=ord(data[0]) warn, tlvcnt = struct.unpack("!HH",data[1+l:5+l]) return readTLVs(data[5+l:], tlvcnt)[0] def parseAway(self, data): l=ord(data[0]) warn, tlvcnt = struct.unpack("!HH",data[1+l:5+l]) return readTLVs(data[5+l:], tlvcnt)[0] def parseMoreInfo(self, data): # why did i have this here and why did dsh remove it #result = ord(data[0]) #if result != 0xa: # return pos = 3 homepagelen = struct.unpack("H",data[pos:pos+2])[0] if countrycode in countrycodes.countryCodes: country = countrycodes.countryCodes[countrycode] else: country = "" pos += 2 companylen = struct.unpack(" 0): info=struct.unpack('!HH',snac[5][:4]) classid=info[0] count=info[1] info=struct.unpack('!'+str(2*count)+'H',snac[5][4:4+count*4]) while (len(info)>0): fam,sub=str(info[0]),str(info[1]) self.scheduler.bindIntoClass(fam,sub,classid) info=info[2:] snac[5]=snac[5][4+count*4:] self.sendSNACnr(0x01,0x08,"\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05") # ack self.initDone() self.sendSNACnr(0x13,0x02,'') # SSI rights info self.sendSNACnr(0x02,0x02,'') # location rights info self.sendSNACnr(0x03,0x02,'') # buddy list rights self.sendSNACnr(0x04,0x04,'') # ICBM parms self.sendSNACnr(0x09,0x02,'') # BOS rights def oscar_01_0F(self,snac): """ Receive Self User Info """ log.msg('Received Self User Info %s' % str(snac)) self.receivedSelfInfo(self.parseUser(snac[5])) def oscar_01_10(self,snac): """ we've been warned """ skip = struct.unpack('!H',snac[5][:2])[0] newLevel = struct.unpack('!H',snac[5][2+skip:4+skip])[0]/10 if len(snac[5])>4+skip: by = self.parseUser(snac[5][4+skip:]) else: by = None self.receiveWarning(newLevel, by) def oscar_01_13(self,snac): """ MOTD """ motd_msg_type = struct.unpack('!H', snac[5][:2])[0] if MOTDS.has_key(motd_msg_type): tlvs = readTLVs(snac[5][2:]) motd_msg_string = tlvs[0x0b] def oscar_01_21(self,snac): """ Receive extended status info """ v = snac[5] log.msg('Received extended status info for %s: %s' % (self.username, str(snac))) while len(v)>4 and ord(v[0]) == 0 and ord(v[3]) != 0: exttype = (struct.unpack('!H',v[0:2]))[0] if exttype == 0x00 or exttype == 0x01: # Why are there two? iconflags, iconhashlen = struct.unpack('!BB',v[2:4]) iconhash = v[4:4+iconhashlen] log.msg(" extracted icon hash: flags = %s, flags-as-hex = %s, iconhash = %s" % (bitstostr(iconflags, 8), str(hex(iconflags)), binascii.hexlify(iconhash))) if iconflags == 0x41: self.receivedIconUploadRequest(iconhash) elif exttype == 0x02: # Extended Status Message # I'm not sure if we should do something about this here? statlen=int((struct.unpack('!H', v[2:4]))[0]) status=v[4:4+statlen] log.msg(" extracted status message: %s"%(status)) else: log.msg(" unknown extended status type: %d\ndata: %s"%(ord(v[1]), repr(v[:ord(v[3])+4]))) v=v[ord(v[3])+4:] def oscar_02_03(self, snac): """ location rights response """ tlvs = readTLVs(snac[5]) self.maxProfileLength = tlvs[1] def oscar_03_03(self, snac): """ buddy list rights response """ tlvs = readTLVs(snac[5]) self.maxBuddies = tlvs[1] self.maxWatchers = tlvs[2] def oscar_03_0B(self, snac): """ buddy update """ self.updateBuddy(self.parseUser(snac[5])) def oscar_03_0C(self, snac): """ buddy offline """ self.offlineBuddy(self.parseUser(snac[5])) def oscar_04_01(self, snac): """ ICBM Error """ data = snac[5] errorcode = struct.unpack('!H',data[:2])[0] data = data[2:] if errorcode==0x04: errortxt="client is offline" elif errorcode==0x09: errortxt="this message not supported by client" elif errorcode==0x0e: errortxt="invalid (incorrectly formatted) message" elif errorcode==0x10: errortxt="the receiver or sender is blocked" else: errortxt="an unknown error has occured. (0x%02x)"%(errorcode) log.msg('ICBM Error: %s' % (errortxt)) self.errorMessage('Unable to deliver message because %s' % (errortxt)) log.msg(snac) def oscar_04_05(self, snac): """ ICBM parms response """ self.sendSNACnr(0x04,0x02,'\x00\x00\x00\x00\x00\x0b\x1f@\x03\xe7\x03\xe7\x00\x00\x00\x00') # IM rights def oscar_04_07(self, snac): """ ICBM message (instant message) """ data = snac[5] cookie, data = data[:8], data[8:] channel = struct.unpack('!H',data[:2])[0] log.msg("channel = %d" % (channel)) data = data[2:] user, data = self.parseUser(data, 1) log.msg("user = %s, data = %s" % (user, binascii.hexlify(data))) tlvs = readTLVs(data) log.msg("tlvs = %s" % (tlvs)) if channel == 1: # message flags = [] multiparts = [] for k, v in tlvs.items(): if k == 0x02: # message data log.msg("Message data: %s" % (repr(v))) while v: #2005/09/25 13:55 EDT [B,client] Message data: '\x05\x01\x00\x01\x01\x01\x01\x00\xaf\x00\x03\x00\x00test\xe4ng the transport for fun and profit' fragtype,fragver,fraglen = struct.unpack('!BBH', v[:4]) if fragtype == 0x05: # This is a required capabilities list # We really have no idea what to do with this... # actual capabilities seen have been 0x01... text? # we shall move on with our lives pass elif fragtype == 0x01: # This is what we're realllly after.. message data. charSet, charSubSet = struct.unpack('!HH', v[4:8]) messageLength = fraglen - 4 # ditch the charsets message = [v[8:8+messageLength]] if charSet == 0x0000: message.append('ascii') elif charSet == 0x0002: message.append('unicode') elif charSet == 0x0003: message.append('custom') # iso-8859-1? elif charSet == 0xffff: message.append('none') else: message.append('unknown') if charSubSet == 0x0000: message.append('standard') elif charSubSet == 0x000b: message.append('macintosh') elif charSubSet == 0xffff: message.append('none') else: message.append('unknown') if messageLength > 0: multiparts.append(tuple(message)) else: # Uh... what is this??? log.msg("unknown message fragment %d %d: %v" % (fragtype, fragver, str(v))) v = v[4+fraglen:] elif k == 0x03: # server ack requested flags.append('acknowledge') elif k == 0x04: # message is auto response flags.append('auto') elif k == 0x06: # message received offline flags.append('offline') elif k == 0x08: # has a buddy icon iconLength, foo, iconSum, iconStamp = struct.unpack('!LHHL',v) if iconLength: flags.append('icon') # why exactly was I doing it like this? #flags.append((iconLength, iconSum, iconStamp)) user.iconcksum = iconSum user.iconlen = iconLength user.iconstamp = iconStamp elif k == 0x09: # request for buddy icon flags.append('iconrequest') elif k == 0x0b: # non-direct connect typing notification flags.append('typingnot') elif k == 0x17: # extra data.. wonder what this is? flags.append('extradata') flags.append(v) else: log.msg('unknown TLV for incoming IM, %04x, %s' % (k,repr(v))) # unknown tlv for user SNewdorf # t: 29 # v: '\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x01\x10/\x8c\x8b\x8a\x1e\x94*\xbc\x80}\x8d\xc4;\x1dEM' # XXX what is this? self.receiveMessage(user, multiparts, flags) elif channel == 2: # rendezvous status = struct.unpack('!H',tlvs[5][:2])[0] cookie2 = tlvs[5][2:10] requestClass = tlvs[5][10:26] moreTLVs = readTLVs(tlvs[5][26:]) if requestClass == CAP_CHAT: # a chat request exchange = None name = None instance = None if moreTLVs.has_key(10001): exchange = struct.unpack('!H',moreTLVs[10001][:2])[0] name = moreTLVs[10001][3:-2] instance = struct.unpack('!H',moreTLVs[10001][-2:])[0] if not exchange or not name or not instance: self.chatInvitationAccepted(user) return if not self.services.has_key(SERVICE_CHATNAV): self.connectService(SERVICE_CHATNAV,1).addCallback(lambda x: self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\ addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12])) else: self.services[SERVICE_CHATNAV].getChatInfo(exchange, name, instance).\ addCallback(self._cbGetChatInfoForInvite, user, moreTLVs[12]) elif requestClass == CAP_SEND_FILE: if moreTLVs.has_key(11): # cancel log.msg('cancelled file request') log.msg(status) return # handle this later if moreTLVs.has_key(10001): name = moreTLVs[10001][9:-7] desc = moreTLVs[12] log.msg('file request from %s, %s, %s' % (user, name, desc)) self.receiveSendFileRequest(user, name, desc, cookie) elif requestClass == CAP_ICON: if moreTLVs.has_key(10001): checksum,length,timestamp = struct.unpack('!III',moreTLVs[10001][:12]) length = int(length) icondata = moreTLVs[10001][12:12+length+1] user.iconcksum = checksum user.iconlen = length user.iconstamp = timestamp log.msg('received icbm icon, length %d' % (length)) self.receivedIconDirect(user, icondata) elif requestClass == CAP_SEND_LIST: pass elif requestClass == CAP_SERV_REL: pass else: log.msg('unsupported rendezvous: %s' % requestClass) log.msg(repr(moreTLVs)) elif channel == 4: for k,v in tlvs.items(): if k == 5: # message data uinHandle = struct.unpack(" 0: multiparts.append(tuple(message)) self.receiveMessage(user, multiparts, flags) elif messageType == 0x02: # chat request message log.msg("received chat request message") pass elif messageType == 0x03: # file request/file ok message log.msg("received file request message") pass elif messageType == 0x04: # url message log.msg("received url message") pass elif messageType == 0x06: # authorization request self.gotAuthorizationRequest(uin) elif messageType == 0x07: # authorization denied self.gotAuthorizationResponse(uin, False) elif messageType == 0x08: # authorization ok self.gotAuthorizationResponse(uin, True) elif messageType == 0x09: # message from oscar server log.msg("received oscar server message") pass elif messageType == 0x0c: # you were added message log.msg("received you were added message") pass elif messageType == 0x0d: # web pager message log.msg("received web pager message") flags = [] multiparts = [] msg = "ICQ page from %s [%s]\n%s" % (messageParts[0], messageParts[3], messageParts[5]) if messageStringLength > 0: multiparts.append(tuple([msg])) self.receiveMessage(user, multiparts, flags) elif messageType == 0x0e: # email express message log.msg("received email express message") flags = [] multiparts = [] msg = "ICQ e-mail from %s [%s]\n%s" % (messageParts[0], messageParts[3], messageParts[5]) if messageStringLength > 0: multiparts.append(tuple([msg])) self.receiveMessage(user, multiparts, flags) elif messageType == 0x13: # contact list message (send contacts for buddy list) log.msg("received contact list message") pass elif messageType == 0x1a: # plugin message log.msg("received plugin message") pass elif messageType == 0xe8: # automatic away message log.msg("received autoaway message") pass elif messageType == 0xe9: # automatic busy message log.msg("received autobusy message") pass elif messageType == 0xea: # automatic not available message log.msg("received auton/a message") pass elif messageType == 0xeb: # automatic do not disturb message log.msg("received autodnd message") pass elif messageType == 0xec: # automatic free for chat message log.msg("received autoffc message") pass else: log.msg('unknown channel %02x' % channel) log.msg(tlvs) def oscar_04_0C(self, snac): """ ICBM message ack """ log.msg("Received message ack: %s" % (snac)) pass def oscar_04_14(self, snac): """ client/server typing notifications """ data = snac[5] scrnnamelen = int(struct.unpack('B',data[10:11])[0]) scrnname = str(data[11:11+scrnnamelen]) typestart = 11+scrnnamelen+1 type = struct.unpack('B', data[typestart])[0] tlvs = dict() user = OSCARUser(scrnname, None, tlvs) if (type == 0x02): self.receiveTypingNotify("begin", user) elif (type == 0x01): self.receiveTypingNotify("idle", user) elif (type == 0x00): self.receiveTypingNotify("finish", user) def _cbGetChatInfoForInvite(self, info, user, message): apply(self.receiveChatInvite, (user,message)+info) def oscar_09_03(self, snac): """ BOS rights response """ tlvs = readTLVs(snac[5]) self.maxPermitList = tlvs[1] self.maxDenyList = tlvs[2] def oscar_0B_02(self, snac): """ stats reporting interval """ self.reportingInterval = struct.unpack('!H',snac[5][:2])[0] def oscar_13_03(self, snac): """ SSI rights response """ #tlvs = readTLVs(snac[5]) pass # we don't know how to parse this def oscar_13_08(self, snac): # SSI Edit: add items # Why does this come to the client? pass #uinLen = ord(snac[5][pos]) #uin = snac[5][pos+1:pos+1+uinLen] def oscar_13_0E(self, snac): """ SSI modification response """ #tlvs = readTLVs(snac[5]) pass # we don't know how to parse this def oscar_13_19(self, snac): """ Got authorization request """ pos = 0 #if 0x80 & snac[2] or 0x80 & snac[3]: # sLen,id,length = struct.unpack(">HHH", snac[5][:6]) # pos = 6 + length uinlen = ord(snac[5][pos]) pos += 1 uin = snac[5][pos:pos+uinlen] pos += uinlen self.gotAuthorizationRequest(uin) def oscar_13_1B(self, snac): """ Got authorization response """ pos = 0 #if 0x80 & snac[2] or 0x80 & snac[3]: # sLen,id,length = struct.unpack(">HHH", snac[5][:6]) # pos = 6 + length uinlen = ord(snac[5][pos]) pos += 1 uin = snac[5][pos:pos+uinlen] pos += uinlen success = ord(snac[5][pos]) pos += 1 reasonlen = struct.unpack(">H", snac[5][pos:pos+2])[0] pos += 2 reason = snac[5][pos:] if success: # authorization request successfully granted self.gotAuthorizationResponse(uin, True) else: # authorization request was not granted self.gotAuthorizationResponse(uin, False) def oscar_13_1C(self, snac): """ SSI Your were added to someone's buddylist """ pos = 0 #if 0x80 & snac[2] or 0x80 & snac[3]: # sLen,id,length = struct.unpack(">HHH", snac[5][:6]) # pos = 6 + length # val = snac[5][4:pos] uinLen = ord(snac[5][pos]) uin = snac[5][pos+1:pos+1+uinLen] self.youWereAdded(uin) # Methods to be called by the client, and their support methods def requestSelfInfo(self): """ ask for the OSCARUser for ourselves """ d = defer.Deferred() d.addErrback(self._ebDeferredSelfInfoError) self.sendSNAC(0x01, 0x0E, '').addCallback(self._cbRequestSelfInfo, d) return d def _ebDeferredSelfInfoError(self, error): log.msg('ERROR IN SELFINFO DEFERRED %s' % error) def _cbRequestSelfInfo(self, snac, d): self.receivedSelfInfo(self.parseUser(snac[5])) #d.callback(self.parseUser(snac[5])) def oscar_15_03(self, snac): """ Meta information (Offline messages, extended info about users) """ tlvs = readTLVs(snac[5]) for k, v in tlvs.items(): if (k == 1): targetuin,type = struct.unpack(' 0): flags = [] multiparts = [] tlvs = dict() multiparts.append(tuple(message)) user = OSCARUser(str(senderuin), None, tlvs) self.receiveMessage(user, multiparts, flags) elif (type == 0x42): # End of offline messages reqdata = '\x08\x00'+struct.pack("4: nameLength = struct.unpack('!H', itemdata[:2])[0] name = itemdata[2:2+nameLength] groupID, buddyID, itemType, restLength = \ struct.unpack('!4H', itemdata[2+nameLength:10+nameLength]) tlvs = readTLVs(itemdata[10+nameLength:10+nameLength+restLength]) itemdata = itemdata[10+nameLength+restLength:] if itemType == AIM_SSI_TYPE_BUDDY: # buddies groups[groupID].addUser(buddyID, SSIBuddy(name, groupID, buddyID, tlvs)) elif itemType == AIM_SSI_TYPE_GROUP: # group g = SSIGroup(name, groupID, buddyID, tlvs) if groups.has_key(0): groups[0].addUser(groupID, g) groups[groupID] = g elif itemType == AIM_SSI_TYPE_PERMIT: # permit permit.append(name) elif itemType == AIM_SSI_TYPE_DENY: # deny deny.append(name) elif itemType == AIM_SSI_TYPE_PDINFO: # permit deny info permitDenyInfo = SSIPDInfo(name, groupID, buddyID, tlvs) if tlvs.has_key(0xca): permitMode = {AIM_SSI_PERMDENY_PERMIT_ALL:'permitall',AIM_SSI_PERMDENY_DENY_ALL:'denyall',AIM_SSI_PERMDENY_PERMIT_SOME:'permitsome',AIM_SSI_PERMDENY_DENY_SOME:'denysome',AIM_SSI_PERMDENY_PERMIT_BUDDIES:'permitbuddies'}.get(ord(tlvs[0xca]),None) if tlvs.has_key(0xcb): visibility = {AIM_SSI_VISIBILITY_ALL:'all',AIM_SSI_VISIBILITY_NOTAIM:'notaim'}.get(tlvs[0xcb],None) elif itemType == AIM_SSI_TYPE_PRESENCEPREFS: # presence preferences pass elif itemType == AIM_SSI_TYPE_ICQSHORTCUT: # ICQ2K shortcuts bar? pass elif itemType == AIM_SSI_TYPE_IGNORE: # Ignore list record pass elif itemType == AIM_SSI_TYPE_LASTUPDATE: # Last update time pass elif itemType == AIM_SSI_TYPE_SMS: # SMS contact. Like 1#EXT, 2#EXT, etc pass elif itemType == AIM_SSI_TYPE_IMPORTTIME: # Roster import time pass elif itemType == AIM_SSI_TYPE_ICONINFO: # icon information # I'm not sure why there are multiple of these sometimes # We're going to return all of them though... iconcksum.append(SSIIconSum(name, groupID, buddyID, tlvs)) elif itemType == AIM_SSI_TYPE_LOCALBUDDYNAME: # locally stored buddy name pass else: log.msg('unknown SSI entry: %s %s %s %s %s' % (name, groupID, buddyID, itemType, tlvs)) timestamp = struct.unpack('!L',itemdata)[0] if not timestamp: # we've got more packets coming # which means add some deferred stuff d = defer.Deferred() self.requestCallbacks[snac[4]] = d d.addCallback(self._cbRequestSSI, (revision, groups, permit, deny, permitMode, visibility, iconcksum, permitDenyInfo)) d.addErrback(self._ebDeferredRequestSSIError, revision, groups, permit, deny, permitMode, visibility, iconcksum, permitDenyInfo) return d if (len(groups) <= 0): gusers = None else: gusers = groups[0].users return (gusers,permit,deny,permitMode,visibility,iconcksum,timestamp,revision,permitDenyInfo) def _ebDeferredRequestSSIError(self, error, revision, groups, permit, deny, permitMode, visibility, iconcksum, permitDenyInfo): log.msg('ERROR IN REQUEST SSI DEFERRED %s' % error) def activateSSI(self): """ activate the data stored on the server (use buddy list, permit deny settings, etc.) """ self.sendSNACnr(0x13,0x07,'') def startModifySSI(self): """ tell the OSCAR server to be on the lookout for SSI modifications """ self.sendSNACnr(0x13,0x11,'') def addItemSSI(self, item): """ add an item to the SSI server. if buddyID == 0, then this should be a group. this gets a callback when it's finished, but you can probably ignore it. """ d = self.sendSNAC(0x13,0x08, item.oscarRep()) log.msg("addItemSSI: adding %s, g:%d, u:%d"%(item.name, item.groupID, item.buddyID)) d.addCallback(self._cbAddItemSSI, item) return d def _cbAddItemSSI(self, snac, item): pos = 0 #if snac[2] & 0x80 or snac[3] & 0x80: # sLen,id,length = struct.unpack(">HHH", snac[5][:6]) # pos = 6 + length if snac[5][pos:pos+2] == "\00\00": #success #data = struct.pack(">H", len(groupName))+groupName #data += struct.pack(">HH", 0, 1) #tlvData = TLV(0xc8, struct.pack(">H", buddyID)) #data += struct.pack(">H", len(tlvData))+tlvData #self.sendSNACnr(0x13,0x09, data) if item.buddyID != 0: # is it a buddy or a group? self.buddyAdded(item.name) elif snac[5][pos:pos+2] == "\00\x0a": # invalid, error while adding pass elif snac[5][pos:pos+2] == "\00\x0c": # limit exceeded self.errorMessage("Contact list limit exceeded") elif snac[5][pos:pos+2] == "\00\x0d": # Trying to add ICQ contact to an AIM list self.errorMessage("Trying to add ICQ contact to an AIM list") elif snac[5][pos:pos+2] == "\00\x0e": # requires authorization log.msg("Authorization needed... requesting") self.sendAuthorizationRequest(item.name, "Please authorize me") item.authorizationRequestSent = True item.authorized = False self.addItemSSI(item) def modifyItemSSI(self, item, groupID = None, buddyID = None): if groupID is None: if isinstance(item, SSIIconSum): groupID = 0 elif isinstance(item, SSIPDInfo): groupID = 0 elif isinstance(item, SSIGroup): groupID = 0 else: groupID = item.group.group.findIDFor(item.group) if buddyID is None: if isinstance(item, SSIIconSum): buddyID = 0x5dd6 elif isinstance(item, SSIPDInfo): buddyID = 0xffff elif hasattr(item, "group"): buddyID = item.group.findIDFor(item) else: buddyID = 0 return self.sendSNAC(0x13,0x09, item.oscarRep()) def delItemSSI(self, item): return self.sendSNAC(0x13,0x0A, item.oscarRep()) def endModifySSI(self): self.sendSNACnr(0x13,0x12,'') def setProfile(self, profile=None): """ set the profile. send None to not set a profile (different from '' for a blank one) """ self.profile = profile tlvs = '' if self.profile is not None: tlvs = TLV(1,'text/aolrtf; charset="us-ascii"') + \ TLV(2,self.profile) tlvs = tlvs + TLV(5, ''.join(self.capabilities)) self.sendSNACnr(0x02, 0x04, tlvs) def setAway(self, away = None): """ set the away message, or return (if away == None) """ self.awayMessage = away tlvs = TLV(3,'text/aolrtf; charset="us-ascii"') + \ TLV(4,away or '') self.sendSNACnr(0x02, 0x04, tlvs) def setBack(self, status=None): """ set the extended status message """ # If our away message is set, clear it. if self.awayMessage: self.setAway() if not status: status = "" else: status = status[:220] log.msg("Setting extended status message to \"%s\""%status) self.backMessage = status packet = struct.pack( "!HHHbbH", 0x001d, # H len(status)+8, # H 0x0002, # H 0x04, # b len(status)+4, # b len(status) # H ) + str(status) + struct.pack("H",0x0000) self.sendSNACnr(0x01, 0x1e, packet) def setURL(self, status=None): """ set the extended status URL """ if not status: status = "" else: status = status[:220] log.msg("Setting extended status URL to \"%s\""%status) self.backMessage = status packet = struct.pack( "!HHHbbH", 0x001d, # H len(status)+8, # H 0x0006, # H 0x04, # b len(status)+4, # b len(status) # H ) + str(status) + struct.pack("H",0x0000) self.sendSNACnr(0x01, 0x1e, packet) def sendAuthorizationRequest(self, uin, authString): """ send an authorization request """ packet = struct.pack("b", len(uin)) packet += uin packet += struct.pack(">H", len(authString)) packet += authString packet += struct.pack("H", 0x00) log.msg("sending authorization request to %s"%uin) self.sendSNACnr(0x13, 0x18, packet) def sendAuthorizationResponse(self, uin, success, responsString): """ send an authorization response """ packet = struct.pack("b", len(uin)) + uin if success: packet += struct.pack("b", 1) else: packet += struct.pack("b", 0) packet += struct.pack(">H", len(responsString)) + responsString self.sendSNACnr(0x13, 0x1a, packet) def setICQStatus(self, status): """ set status of user: online, away, xa, dnd or chat """ if status == "away": icqStatus = 0x01 elif status == "dnd": icqStatus = 0x02 elif status == "xa": icqStatus = 0x04 elif status == "chat": icqStatus = 0x20 else: icqStatus = 0x00 self.sendSNACnr(0x01, 0x1e, TLV(0x06, struct.pack(">HH", self.statusindicators, icqStatus))) def setIdleTime(self, idleTime): """ set our idle time. don't call more than once with a non-0 idle time. """ self.sendSNACnr(0x01, 0x11, struct.pack('!L',idleTime)) def sendMessage(self, user, message, wantAck = 0, autoResponse = 0, offline = 0, wantIcon = 0, iconSum = None, iconLen = None, iconStamp = None ): """ send a message to user (not an OSCARUseR). message can be a string, or a multipart tuple. if wantAck, we return a Deferred that gets a callback when the message is sent. if autoResponse, this message is an autoResponse, as if from an away message. if offline, this is an offline message (ICQ only, I think) if iconLen, iconSum, and iconStamp, we have a buddy icon and want user to know if wantIcon, we want their buddy icon, tell us if you have it """ cookie = ''.join([chr(random.randrange(0, 127)) for i in range(8)]) # cookie data = cookie + struct.pack("!HB", 0x0001, len(user)) + user if not type(message) in (types.TupleType, types.ListType): message = [[message,]] if type(message[0][0]) == types.UnicodeType: message[0].append('unicode') messageData = '' for part in message: charSet = 0x0000 if 'none' in part[1:]: charSet = 0xffff else: try: part[0] = part[0].encode('ascii') charSet = 0x0000 except: try: part[0] = part[0].encode('iso-8859-1') charSet = 0x0003 except: try: part[0] = part[0].encode('utf-16be', 'replace') charSet = 0x0002 except: part[0] = part[0].encode('iso-8859-1', 'replace') charSet = 0x0003 if 'macintosh' in part[1:]: charSubSet = 0x000b elif 'none' in part[1:]: charSubSet = 0xffff else: charSubSet = 0x0000 messageData = messageData + struct.pack('!HHHH',0x0101,len(part[0])+4,charSet,charSubSet) + part[0] # We'll investigate this in more detail later. features = '\x01\x01\x02' # Why do i need to encode this? I shouldn't .. it's data. data = data.encode('iso-8859-1', 'replace') + TLV(2, TLV(0x0501, features)+messageData) if wantAck: log.msg("sendMessage: Sending wanting ACK") data = data + TLV(3) if autoResponse: log.msg("sendMessage: Sending as an auto-response") data = data + TLV(4) if offline: log.msg("sendMessage: Sending offline") data = data + TLV(6) if iconSum and iconLen and iconStamp: log.msg("sendMessage: Sending info about our icon") data = data + TLV(8,struct.pack('!IHHI', iconLen, 0x0001, iconSum, iconStamp)) if wantIcon: log.msg("sendMessage: Sending request for their icon") data = data + TLV(9) if wantAck: return self.sendSNAC(0x04, 0x06, data).addCallback(self._cbSendMessageAck, user, message) self.sendSNACnr(0x04, 0x06, data) def _cbSendMessageAck(self, snac, user, message): return user, message def sendSMS(self, phone, message, senderName = "Auto"): """ Sends an SMS message through the ICQ server. phone (str) - Internation phone number to send to, digits only message (str or unicode) - The message to send senderName (str or unicode) - The sender name """ message = u""" %s %s utf-8 %s %s Yes """ % (phone, message, self.username, senderName, time.strftime("%a, %d %b %Y %T %Z")) commandData = struct.pack('H', data[0:2])[0]) tlvs,data = readTLVs(data[2:], count=numpieces) log.msg(" Entry %s" % (repr(tlvs))) result = {} if tlvs.has_key(0x0001): result['first'] = tlvs[0x0001] if tlvs.has_key(0x0002): result['last'] = tlvs[0x0002] if tlvs.has_key(0x0003): result['middle'] = tlvs[0x0003] if tlvs.has_key(0x0004): result['maiden'] = tlvs[0x0004] if tlvs.has_key(0x0005): result['email'] = tlvs[0x0005] if tlvs.has_key(0x0006): result['country'] = tlvs[0x0006] if tlvs.has_key(0x0007): result['state'] = tlvs[0x0007] if tlvs.has_key(0x0008): result['city'] = tlvs[0x0008] if tlvs.has_key(0x0009): result['screenname'] = tlvs[0x0009] if tlvs.has_key(0x000b): result['interest'] = tlvs[0x000b] if tlvs.has_key(0x000c): result['nickname'] = tlvs[0x000c] if tlvs.has_key(0x000d): result['zip'] = tlvs[0x000d] if tlvs.has_key(0x001c): result['region'] = tlvs[0x001c] if tlvs.has_key(0x0021): result['address'] = tlvs[0x0021] results.append(result) cnt = cnt + 1 self.disconnect() return results def _cbGetDirectoryError(self, error): log.msg("Got directory error %s" % error) return error def sendInterestsRequest(self): return self.sendSNAC(0x0f, 0x04, "").addCallback(self._cbGetInterests).addErrback(self._cbGetInterestsError) def _cbGetInterests(self, snac): log.msg("Got interests %s" % snac) pass def _cbGetInterestsError(self, error): log.msg("Got interests error %s" % error) pass def disconnect(self): """ send the disconnect flap, and sever the connection """ self.sendFLAP('', 0x04) self.transport.loseConnection() class EmailService(OSCARService): snacFamilies = { 0x01:(4, 0x0110, 0x08e4), 0x18:(1, 0x0110, 0x08e4) } def oscar_01_07(self,snac): self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005") cookie1 = "\xb3\x80\x9a\xd8\x0d\xba\x11\xd5\x9f\x8a\x00\x60\xb0\xee\x06\x31" cookie2 = "\x5d\x5e\x17\x08\x55\xaa\x11\xd3\xb1\x43\x00\x60\xb0\xfb\x1e\xcb" self.sendSNAC(0x18, 0x06, "\x00\x02"+cookie1+cookie2) self.sendEmailRequest() self.nummessages = 0 self.clientReady() def oscar_18_07(self,snac): snacData = snac[5] cookie1 = snacData[8:16] cookie2 = snacData[16:24] cnt = int(struct.unpack('>H', snacData[24:26])[0]) tlvs,foo = readTLVs(snacData[26:], count=cnt) #0x80 = number of unread messages #0x81 = have new messages #0x82 = domain #0x84 = flag #0x07 = url to access #0x09 = username #0x1b = something about gateway #0x1d = some odd string #0x05 = apparantly an alert title #0x0d = apparantly an alert url domain = tlvs[0x82] username = tlvs[0x09] url = tlvs[0x07] unreadnum = int(struct.unpack('>H', tlvs[0x80])[0]) hasunread = int(struct.unpack('B', tlvs[0x81])[0]) log.msg("received email notify: tlvs = %s" % (str(tlvs))) self.bos.emailNotificationReceived('@'.join([username,domain]), str(url), unreadnum, hasunread) def sendEmailRequest(self): log.msg("Activating email notifications") self.sendSNAC(0x18, 0x16, "\x02\x04\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00") def disconnect(self): """ send the disconnect flap, and sever the connection """ self.sendFLAP('', 0x04) self.transport.loseConnection() class AdminService(OSCARService): snacFamilies = { 0x01:(4, 0x0110, 0x08e4), 0x07:(1, 0x0110, 0x08e4) } def oscar_01_07(self,snac): self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005") self.clientReady() def requestFormattedScreenName(self): return self.sendSNAC(0x07, 0x02, TLV(0x01)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def requestEmailAddress(self): return self.sendSNAC(0x07, 0x02, TLV(0x11)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def requestRegistrationStatus(self): return self.sendSNAC(0x07, 0x02, TLV(0x13)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def changePassword(self, oldpassword, newpassword): return self.sendSNAC(0x07, 0x04, TLV(0x02, newpassword)+TLV(0x12, oldpassword)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def formatScreenName(self, fmtscreenname): """ Note that the new screen name must be the same as the official one with only changes to spacing and capitalization """ return self.sendSNAC(0x07, 0x04, TLV(0x01, fmtscreenname)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def setEmailAddress(self, email): return self.sendSNAC(0x07, 0x04, TLV(0x11, email)).addCallback(self._cbInfoResponse).addErrback(self._cbInfoResponseError) def _cbInfoResponse(self, snac): """ This is sent for both changes and requests """ log.msg("Got info change %s" % (snac)) snacData = snac[5] perms = int(struct.unpack(">H", snacData[0:2])[0]) tlvcnt = int(struct.unpack(">H", snacData[2:4])[0]) tlvs,foo = readTLVs(snacData[4:], count=tlvcnt) log.msg("TLVS are %s" % str(tlvs)) sn = tlvs.get(0x01, None) url = tlvs.get(0x04, None) error = tlvs.get(0x08, None) email = tlvs.get(0x11, None) if not error: errorret = None elif error == '\x00\x01': errorret = (error, "Unable to format screen name because the requested screen name differs from the original.") elif error == '\x00\x06': #errorret = (error, "Unable to format screen name because the requested screen name ends in a space.") errorret = (error, "Unable to format screen name because the requested screen name is too long.") elif error == '\x00\x0b': #I get the above on a 'too long' screen name.. so what's this really? errorret = (error, "Unable to format screen name because the requested screen name is too long.") elif error == '\x00\x1d': errorret = (error, "Unable to change email address because there is already a request pending for this screen name.") elif error == '\x00\x21': errorret = (error, "Unable to change email address because the given address has too many screen names associated with it.") elif error == '\x00\x23': errorret = (error, "Unable to change email address because the given address is invalid.") else: errorret = (error, "Unknown error code %d" % int(error)) self.disconnect() return (perms, sn, url, errorret, email) def _cbInfoResponseError(self, error): log.msg("GOT INFO CHANGE ERROR %s" % error) self.disconnect() pass def requestAccountConfirm(self): """ Causes an email message to be sent to the registered email address. By following the instructions in the email, you can get the trial/unconfirmed flag removed from your account. """ return self.sendSNAC(0x07, 0x06, "").addCallback(self._cbAccountConfirm).addErrback(self._cbAccountConfirmError) def _cbAccountConfirm(self, snac): log.msg("Got account confirmation %s" % snac) status = int(struct.unpack(">H", snac[5][0:2])[0]) # Returns whether it failed or not self.disconnect() if (status == "\x00\x13"): return 1 else: return 0 def _cbAccountConfirmError(self, error): log.msg("GOT ACCOUNT CONFIRMATION ERROR %s" % error) self.disconnect() def disconnect(self): """ send the disconnect flap, and sever the connection """ self.sendFLAP('', 0x04) self.transport.loseConnection() class SSBIService(OSCARService): #snacFamilies = { # 0x01:(3, 0x0010, 0x0629), # 0x10:(1, 0x0010, 0x0629) #} snacFamilies = { 0x01:(4, 0x0110, 0x08e4), 0x10:(1, 0x0110, 0x08e4) } def oscar_01_07(self,snac): self.sendSNAC(0x01,0x08,"\000\001\000\002\000\003\000\004\000\005") self.clientReady() def uploadIcon(self, iconData, iconLen): return self.sendSNAC(0x10, 0x02, struct.pack('!HH', 0x0001, iconLen)+iconData).addCallback(self._cbIconResponse).addErrback(self._cbIconResponseError) def _cbIconResponse(self, snac): log.msg("GOT ICON RESPONSE: %s" % str(snac)) #\x05\x00\x00\x00\x00 - bad format? #\x04\x00\x00\x00\x00 - too large? #\x00\x00\x01\x01\x10 - ok, this is a hash, last one is length self.disconnect() # FIXME, This needs to be done like... correctly. =D resultcode = snac[5][0] if resultcode == 0x04: return "Icon too large." if resultcode == 0x05: return "Icon not in accepted format." if resultcode == 0x01: # Success checksumlen = struct.unpack('!B', snac[5][4])[0] checksum = snac[5:5+checksumlen] return checksum return "Unknown result from buddy icon retrieval." def _cbIconResponseError(self, error): log.msg("GOT UPLOAD ICON ERROR %s" % error) self.disconnect() def retrieveAIMIcon(self, contact, iconhash, iconflags): log.msg("Requesting icon for %s with hash %s" % (contact, binascii.hexlify(iconhash))) return self.sendSNAC(0x10, 0x04, struct.pack('!B', len(contact))+contact+"\x01\x00\x01"+struct.pack('!B', iconflags)+struct.pack('!B', len(iconhash))+iconhash).addCallback(self._cbAIMIconRequest).addErrback(self._cbAIMIconRequestError) def _cbAIMIconRequest(self, snac): v = snac[5] scrnnamelen = int((struct.unpack('!B', v[0]))[0]) scrnname = v[1:1+scrnnamelen] p = 1+scrnnamelen flags,iconcsumtype,iconcsumlen = struct.unpack('!HBB', v[p:p+4]) iconcsumlen = int(iconcsumlen) p = p+4 iconcsum = v[p:p+iconcsumlen] p = p+iconcsumlen iconlen = int((struct.unpack('!H', v[p:p+2]))[0]) p = p + 2 log.msg("Got Icon Request (AIM): %s, %s, %d" % (scrnname, binascii.hexlify(iconcsum), iconlen)) if iconlen > 0 and iconlen != 90: icondata = v[p:p+iconlen] else: icondata = None self.disconnect() return scrnname,iconcsumtype,iconcsum,iconlen,icondata def _cbAIMIconRequestError(self, error): log.msg("GOT AIM ICON REQUEST ERROR %s" % error) self.disconnect() def disconnect(self): """ send the disconnect flap, and sever the connection """ self.sendFLAP('', 0x04) self.transport.loseConnection() class OscarAuthenticator(OscarConnection): BOSClass = BOSConnection def __init__(self,username,password,deferred=None,icq=0): self.username=username self.password=password self.deferred=deferred self.icq=icq # icq mode is disabled #if icq and self.BOSClass==BOSConnection: # self.BOSClass=ICQConnection def oscar_(self,flap): if not self.icq: self.sendFLAP("\000\000\000\001", 0x01) self.sendFLAP(SNAC(0x17,0x06,0, TLV(TLV_USERNAME,self.username)+ TLV(0x004B))) self.state="Key" else: # stupid max password length... encpass=encryptPasswordICQ(self.password[:8]) #self.sendFLAP('\000\000\000\001'+ # TLV(0x01,self.username)+ # TLV(0x02,encpass)+ # TLV(0x03,'ICQ Inc. - Product of ICQ (TM).2001b.5.18.1.3659.85')+ # TLV(0x16,"\x01\x0a")+ # TLV(0x17,"\x00\x05")+ # TLV(0x18,"\x00\x12")+ # TLV(0x19,"\000\001")+ # TLV(0x1a,"\x0eK")+ # TLV(0x14,"\x00\x00\x00U")+ # TLV(0x0f,"en")+ # TLV(0x0e,"us"),0x01) #self.sendFLAP('\000\000\000\001'+ # TLV(0x01,self.username)+ # TLV(0x02,encpass)+ # TLV(0x03,'ICQ Inc. - Product of ICQ (TM).2003a.5.45.1.3777.85')+ # TLV(0x16,"\x01\x0a")+ # TLV(TLV_CLIENTMAJOR,"\x00\x05")+ # TLV(TLV_CLIENTMINOR,"\x00\x2d")+ # TLV(0x19,"\000\001")+ # TLV(TLV_CLIENTSUB,"\x0e\xc1")+ # TLV(0x14,"\x00\x00\x00\x55")+ # TLV(0x0f,"en")+ # TLV(0x0e,"us"),0x01) self.sendFLAP('\000\000\000\001'+ TLV(0x01,self.username)+ TLV(0x02,encpass)+ TLV(0x03,'ICQBasic')+ TLV(0x16,"\x01\x0a")+ TLV(TLV_CLIENTMAJOR,"\x00\x14")+ TLV(TLV_CLIENTMINOR,"\x00\x22")+ TLV(0x19,"\x00\x00")+ TLV(TLV_CLIENTSUB,"\x09\x11")+ TLV(0x14,"\x00\x00\x04\x3d")+ TLV(0x0f,"en")+ TLV(0x0e,"us"),0x01) self.state="Cookie" def oscar_Key(self,data): snac=readSNAC(data[1]) if not snac: log.msg("Illegal SNAC data received in oscar_Key: %s" % data) return len=ord(snac[5][0]) * 256 + ord(snac[5][1]) key=snac[5][2:2+len] encpass=encryptPasswordMD5(self.password,key) self.sendFLAP(SNAC(0x17,0x02,0, TLV(TLV_USERNAME,self.username)+ TLV(TLV_PASSWORD,encpass)+ TLV(0x004C)+ # unknown TLV(TLV_CLIENTNAME,"AOL Instant Messenger (SM), version 5.1.3036/WIN32")+ TLV(0x0016,"\x01\x09")+ TLV(TLV_CLIENTMAJOR,"\000\005")+ TLV(TLV_CLIENTMINOR,"\000\001")+ TLV(0x0019,"\000\000")+ TLV(TLV_CLIENTSUB,"\x0B\xDC")+ TLV(0x0014,"\x00\x00\x00\xD2")+ TLV(TLV_LANG,"en")+ TLV(TLV_COUNTRY,"us")+ TLV(TLV_USESSI,"\001"))) return "Cookie" def oscar_Cookie(self,data): snac=readSNAC(data[1]) if not snac: log.msg("Illegal SNAC data received in oscar_Cookie: %s" % data) return if self.icq: i=snac[5].find("\000") snac[5]=snac[5][i:] tlvs=readTLVs(snac[5]) log.msg(tlvs) if tlvs.has_key(6): self.cookie=tlvs[6] server,port=string.split(tlvs[5],":") d = self.connectToBOS(server, int(port)) d.addErrback(lambda x: log.msg("Connection Failed! Reason: %s" % x)) if self.deferred: d.chainDeferred(self.deferred) self.disconnect() elif tlvs.has_key(8): errorcode=tlvs[8] if tlvs.has_key(4): errorurl=tlvs[4] else: errorurl=None if errorcode=='\x00\x02': error="The instant messenger server is temporarily unavailable" elif errorcode=='\x00\x05': error="Incorrect username or password." elif errorcode=='\x00\x11': error="Your account is currently suspended." elif errorcode=='\x00\x14': error="The instant messenger server is temporarily unavailable" elif errorcode=='\x00\x18': error="You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer." elif errorcode=='\x00\x1c': error="The client version you are using is too old. Please contact the maintainer of this software if you see this message so that the problem can be resolved." else: error=repr(errorcode) self.error(error,errorurl) else: log.msg('hmm, weird tlvs for %s cookie packet' % str(self)) log.msg(tlvs) log.msg('snac') log.msg(str(snac)) return "None" def oscar_None(self,data): pass def connectToBOS(self, server, port): c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie) return c.connectTCP(server, int(port)) def error(self,error,url): log.msg("ERROR! %s %s" % (error,url)) if self.deferred: self.deferred.errback((error,url)) self.transport.loseConnection() FLAP_CHANNEL_NEW_CONNECTION = 0x01 FLAP_CHANNEL_DATA = 0x02 FLAP_CHANNEL_ERROR = 0x03 FLAP_CHANNEL_CLOSE_CONNECTION = 0x04 SERVICE_ADMIN = 0x07 SERVICE_CHATNAV = 0x0d SERVICE_CHAT = 0x0e SERVICE_DIRECTORY = 0x0f SERVICE_SSBI = 0x10 SERVICE_EMAIL = 0x18 serviceClasses = { SERVICE_ADMIN:AdminService, SERVICE_CHATNAV:ChatNavService, SERVICE_CHAT:ChatService, SERVICE_DIRECTORY:DirectoryService, SERVICE_SSBI:SSBIService, SERVICE_EMAIL:EmailService } TLV_USERNAME = 0x0001 TLV_CLIENTNAME = 0x0003 TLV_COUNTRY = 0x000E TLV_LANG = 0x000F TLV_CLIENTMAJOR = 0x0017 TLV_CLIENTMINOR = 0x0018 TLV_CLIENTSUB = 0x001A TLV_PASSWORD = 0x0025 TLV_USESSI = 0x004A ### # Capabilities ### # Supports avatars/buddy icons CAP_ICON = '\x09\x46\x13\x46\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # User is using iChat CAP_ICHAT = '\x09\x46\x00\x00\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # ... and has audio-video support CAP_ICHATAV = '\x09\x46\x01\x05\x4C\x7F\x11\xD1\x82\x22\x44\x45\x45\x53\x54\x00' # Supports voice chat CAP_VOICE = '\x09\x46\x13\x41\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports direct image/direct im CAP_IMAGE = '\x09\x46\x13\x45\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports chat CAP_CHAT = '\x74\x8F\x24\x20\x62\x87\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports file transfers (can accept files) CAP_GET_FILE = '\x09\x46\x13\x48\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports file transfers (can send files) CAP_SEND_FILE = '\x09\x46\x13\x43\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports games CAP_GAMES = '\x09\x46\x13\x4A\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports buddy list transfer CAP_SEND_LIST = '\x09\x46\x13\x4B\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports channel 2 extended CAP_SERV_REL = '\x09\x46\x13\x49\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Allow communication between ICQ and AIM CAP_CROSS_CHAT = '\x09\x46\x13\x4D\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports UTF-8 encoded messages, only used with ICQ CAP_UTF = '\x09\x46\x13\x4E\x4C\x7F\x11\xD1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports RTF messages CAP_RTF = '\x97\xB1\x27\x51\x24\x3C\x43\x34\xAD\x22\xD6\xAB\xF7\x3F\x14\x92' # Is Apple iChat (probably indicates that it supports iChat features) CAP_ICHAT = '\x09\x46\x00\x00\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports some sort of secure instant messaging. (not trillian) CAP_SECUREIM = '\x09\x46\x00\x01\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports video chat? (other caps seem to indicate this as well) CAP_VIDEO = '\x09\x46\x01\x00\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # "Live Video" support in Windows AIM 5.5.3501 and newer CAP_LIVE_VIDEO = '\x09\x46\x01\x01\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # "Camera" support in Windows AIM 5.5.3501 and newer CAP_CAMERA = '\x09\x46\x01\x02\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Not really sure about this one. In an email from 26 Sep 2003, # Matthew Sachs suggested that, "this * is probably the capability # for the SMS features." CAP_SMS = '\x09\x46\x01\xff\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # In Windows AIM 5.5.3501 and newer CAP_GENERICUNKNOWN1 = '\x09\x46\x01\x03\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Total unknowns CAP_GENERICUNKNOWN2 = '\x09\x46\xf0\x03\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' CAP_GENERICUNKNOWN3 = '\x09\x46\xf0\x04\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' CAP_GENERICUNKNOWN4 = '\x09\x46\xf0\x05\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' CAP_GENERICUNKNOWN5 = '\x97\xb1\x27\x51\x24\x3c\x43\x34\xad\x22\xd6\xab\xf7\x3f\x14\x09' # Is a Hiptop device? CAP_HIPTOP = '\x09\x46\x13\x23\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports ICQ direct connections CAP_ICQ_DIRECT = '\x09\x46\x13\x44\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Supports some sort of add-ins/extras? This seems different than ICQ Xtraz. CAP_ADDINS = '\x09\x46\x13\x47\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' # Another games related one. CAP_GAMES2 = '\x09\x46\x13\x4a\x4c\x7f\x11\xd1\x22\x82\x44\x45\x53\x54\x00\x00' # Supports old style ICQ utf-8. CAP_ICQUTF8OLD = '\x2e\x7a\x64\x75\xfa\xdf\x4d\xc8\x88\x6f\xea\x35\x95\xfd\xb6\xdf' # Supports ICQ2GO extensions CAP_ICQ2GO = '\x56\x3f\xc8\x09\x0b\x6f\x41\xbd\x9f\x79\x42\x26\x09\xdf\xa2\xf3' # No idea CAP_APINFO = '\xaa\x4a\x32\xb5\xf8\x84\x48\xc6\xa3\xd7\x8c\x50\x97\x19\xfd\x5b' # Supports Trillian style encrypted messages CAP_TRILLIANCRYPT = '\xf2\xe7\xc7\xf4\xfe\xad\x4d\xfb\xb2\x35\x36\x79\x8b\xdf\x00\x00' # Unknown ICQ5 capabilities, probably related to Xtras CAP_ICQ5UNKNOWN1 = '\x09\x46\x13\x4c\x4c\x7f\x11\xd1\x82\x22\x44\x45\x53\x54\x00\x00' CAP_ICQ5UNKNOWN2 = '\xb9\x97\x08\xb5\x3a\x92\x42\x02\xb0\x69\xf1\xe7\x57\xbb\x2e\x17' # Supports ICQ 5 video chat CAP_ICQVIDEO = '\x17\x8c\x2d\x9b\xda\xa5\x45\xbb\x8d\xdb\xf3\xbd\xbd\x53\xa1\x0a' # Supports ICQ 5 Xtraz (includes multi-user chat) CAP_ICQXTRAZ = '\x1a\x09\x3c\x6c\xd7\xfd\x4e\xc5\x9d\x51\xa6\x47\x4e\x34\xf5\xa0' # Supports ICQ 5 voice chat (also push to talk gets listed as supported?) CAP_ICQVOICE = '\x67\x36\x15\x15\x61\x2d\x4c\x07\x8f\x3d\xbd\xe6\x40\x8e\xa0\x41' # Causes a push to talk icon to be displayed, why is this different? CAP_ICQPUSHTOTALK = '\xe3\x62\xc1\xe9\x12\x1a\x4b\x94\xa6\x26\x7a\x74\xde\x24\x27\x0d' # Empty capability ... ? CAP_EMPTY = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Mappings of capabilities back to identifier strings. CAPS = dict( [ (CAP_ICON, 'icon'), (CAP_VOICE, 'voice'), (CAP_IMAGE, 'image'), (CAP_CHAT, 'chat'), (CAP_GET_FILE, 'getfile'), (CAP_SEND_FILE, 'sendfile'), (CAP_SEND_LIST, 'sendlist'), (CAP_GAMES, 'games'), (CAP_SERV_REL, 'serv_rel'), (CAP_CROSS_CHAT, 'cross_chat'), (CAP_UTF, 'unicode'), (CAP_RTF, 'rtf'), (CAP_ICHAT, 'ichat'), (CAP_SECUREIM, 'secureim'), (CAP_VIDEO, 'video'), (CAP_LIVE_VIDEO, 'live_video'), (CAP_CAMERA, 'camera'), (CAP_GENERICUNKNOWN1, 'genericunknown1'), (CAP_GENERICUNKNOWN2, 'genericunknown2'), (CAP_GENERICUNKNOWN3, 'genericunknown3'), (CAP_GENERICUNKNOWN4, 'genericunknown4'), (CAP_GENERICUNKNOWN5, 'genericunknown5'), (CAP_ICHATAV, 'ichatav'), (CAP_SMS, 'sms'), (CAP_HIPTOP, 'hiptop'), (CAP_ICQ_DIRECT, 'icq_direct'), (CAP_ADDINS, 'addins'), (CAP_GAMES2, 'games2'), (CAP_ICQUTF8OLD, 'icqutf8old'), (CAP_ICQ2GO, 'icq2go'), (CAP_APINFO, 'apinfo'), (CAP_TRILLIANCRYPT, 'trilliancrypt'), (CAP_ICQ5UNKNOWN1, 'icq5unknown1'), (CAP_ICQ5UNKNOWN2, 'icq5unknown2'), (CAP_ICQVIDEO, 'icqvideochat'), (CAP_ICQVOICE, 'icqvoicechat'), (CAP_ICQXTRAZ, 'icqxtraz'), (CAP_ICQPUSHTOTALK, 'icqpushtotalk'), (CAP_EMPTY, 'empty') ] ) ### # Status indicators ### # Web status icons should be updated to show status STATUS_WEBAWARE = 0x0001 # IP address should be provided to requestors STATUS_SHOWIP = 0x0002 # Indicate that it is the user's birthday STATUS_BIRTHDAY = 0x0008 # "User active webfront flag"... no idea STATUS_WEBFRONT = 0x0020 # Client does not support direct connections STATUS_DCDISABLED = 0x0100 # Client will do direct connections upon authorization STATUS_DCAUTH = 0x1000 # Client will only do direct connections with contact users STATUS_DCCONT = 0x2000 ### # Typing notification status codes ### MTN_FINISH = '\x00\x00' MTN_IDLE = '\x00\x01' MTN_BEGIN = '\x00\x02' # Motd types list MOTDS = dict( [ (0x01, "Mandatory upgrade needed notice"), (0x02, "Advisable upgrade notice"), (0x03, "AIM/ICQ service system announcements"), (0x04, "Standard notice"), (0x06, "Some news from AOL service") ] ) ### # SSI Types ### AIM_SSI_TYPE_BUDDY = 0x0000 AIM_SSI_TYPE_GROUP = 0x0001 AIM_SSI_TYPE_PERMIT = 0x0002 AIM_SSI_TYPE_DENY = 0x0003 AIM_SSI_TYPE_PDINFO = 0x0004 AIM_SSI_TYPE_PRESENCEPREFS = 0x0005 AIM_SSI_TYPE_ICQSHORTCUT = 0x0009 # Not sure if this is true AIM_SSI_TYPE_IGNORE = 0x000e AIM_SSI_TYPE_LASTUPDATE = 0x000f AIM_SSI_TYPE_SMS = 0x0010 AIM_SSI_TYPE_IMPORTTIME = 0x0013 AIM_SSI_TYPE_ICONINFO = 0x0014 AIM_SSI_TYPE_LOCALBUDDYNAME = 0x0131 ### # Permission Types ### AIM_SSI_PERMDENY_PERMIT_ALL = 0x01 AIM_SSI_PERMDENY_DENY_ALL = 0x02 AIM_SSI_PERMDENY_PERMIT_SOME = 0x03 AIM_SSI_PERMDENY_DENY_SOME = 0x04 AIM_SSI_PERMDENY_PERMIT_BUDDIES = 0x05 ### # Visibility Masks ### AIM_SSI_VISIBILITY_ALL = '\xff\xff\xff\xff' AIM_SSI_VISIBILITY_NOTAIM = '\x00\x00\x00\x04'