# Twisted, the Framework of Your Internet # Copyright (C) 2001 Matthew W. Lefkowitz # # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Implements a AOL Instant Messenger TOC server and client, using the Twisted framework. TODO: info,dir: see how gaim connects for this...it may never work if it tries to connect to the aim server automatically This module is stable, but deprecated. Maintainer: U{Paul Swartz} """ # twisted imports from twisted.internet import reactor, protocol from twisted.python import log # base imports import struct import string import time import base64 import os import StringIO SIGNON,DATA,ERROR,SIGNOFF,KEEP_ALIVE=range(1,6) PERMITALL,DENYALL,PERMITSOME,DENYSOME=range(1,5) def quote(s): rep=['\\','$','{','}','[',']','(',')','"'] for r in rep: s=string.replace(s,r,"\\"+r) return "\""+s+"\"" def unquote(s): if s=="": return "" if s[0]!='"': return s r=string.replace s=s[1:-1] s=r(s,"\\\\","\\") s=r(s,"\\$","$") s=r(s,"\\{","{") s=r(s,"\\}","}") s=r(s,"\\[","[") s=r(s,"\\]","]") s=r(s,"\\(","(") s=r(s,"\\)",")") s=r(s,"\\\"","\"") return s def unquotebeg(s): for i in range(1,len(s)): if s[i]=='"' and s[i-1]!='\\': q=unquote(s[:i+1]) return [q,s[i+2:]] def unroast(pw): roaststring="Tic/Toc" pw=string.lower(pw[2:]) r="" count=0 hex=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"] while pw: st,pw=pw[:2],pw[2:] value=(16*hex.index(st[0]))+hex.index(st[1]) xor=ord(roaststring[count]) count=(count+1)%len(roaststring) r=r+chr(value^xor) return r def roast(pw): # contributed by jemfinch on #python key="Tic/Toc" ro="0x" i=0 ascii=map(ord,pw) for c in ascii: ro=ro+'%02x'%(c^ord(key[i%len(key)])) i=i+1 return string.lower(ro) def checksum(b): return 0xdeadbeef # do it like gaim does, since the checksum # formula doesn't work ## # used in file transfers ## check0 = check1 = 0x00ff ## for i in range(len(b)): ## if i%2: ## if ord(b[i])>check1: ## check1=check1+0x100 # wrap ## if check0==0: ## check0=0x00ff ## if check1==0x100: ## check1=check1-1 ## else: ## check0=check0-1 ## check1=check1-ord(b[i]) ## else: ## if ord(b[i])>check0: # wrap ## check0=check0+0x100 ## if check1==0: ## check1=0x00ff ## if check0==0x100: ## check0=check0-1 ## else: ## check1=check1-1 ## check0=check0-ord(b[i]) ## check0=check0 & 0xff ## check1=check1 & 0xff ## checksum=(long(check0)*0x1000000)+(long(check1)*0x10000) ## return checksum def checksum_file(f): return 0xdeadbeef # do it like gaim does, since the checksum # formula doesn't work ## check0=check1=0x00ff ## i=0 ## while 1: ## b=f.read() ## if not b: break ## for char in b: ## i=not i ## if i: ## if ord(char)>check1: ## check1=check1+0x100 # wrap ## if check0==0: ## check0=0x00ff ## if check1==0x100: ## check1=check1-1 ## else: ## check0=check0-1 ## check1=check1-ord(char) ## else: ## if ord(char)>check0: # wrap ## check0=check0+0x100 ## if check1==0: ## check1=0x00ff ## if check0==0x100: ## check0=check0-1 ## else: ## check1=check1-1 ## check0=check0-ord(char) ## check0=check0 & 0xff ## check1=check1 & 0xff ## checksum=(long(check0)*0x1000000)+(long(check1)*0x10000) ## return checksum def normalize(s): s=string.lower(s) s=string.replace(s," ","") return s class TOCParseError(ValueError): pass class TOC(protocol.Protocol): users={} def connectionMade(self): # initialization of protocol self._buf="" self._ourseqnum=0L self._theirseqnum=0L self._mode="Flapon" self._onlyflaps=0 self._laststatus={} # the last status for a user self.username=None self.permitmode=PERMITALL self.permitlist=[] self.denylist=[] self.buddylist=[] self.signontime=0 self.idletime=0 self.userinfo="
" self.userclass=" O" self.away="" self.saved=None def _debug(self,data): log.msg(data) def connectionLost(self, reason): self._debug("dropped connection from %s" % self.username) try: del self.factory.users[self.username] except: pass for k in self.factory.chatroom.keys(): try: self.factory.chatroom[k].leave(self) except TOCParseError: pass if self.saved: self.factory.savedusers[self.username]=self.saved self.updateUsers() def sendFlap(self,type,data): """ send a FLAP to the client """ send="*" self._debug(data) if type==DATA: data=data+"\000" length=len(data) send=send+struct.pack("!BHH",type,self._ourseqnum,length) send=send+data self._ourseqnum=self._ourseqnum+1 if self._ourseqnum>(256L**4): self._ourseqnum=0 self.transport.write(send) def dataReceived(self,data): self._buf=self._buf+data try: func=getattr(self,"mode%s"%self._mode) except: return self._mode=func() if self._onlyflaps and self.isFlap(): self.dataReceived("") def isFlap(self): """ tests to see if a flap is actually on the buffer """ if self._buf=='': return 0 if self._buf[0]!="*": return 0 if len(self._buf)<6: return 0 foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6]) if type not in range(1,6): return 0 if len(self._buf)<6+length: return 0 return 1 def readFlap(self): """ read the first FLAP off self._buf, raising errors if it isn't in the right form. the FLAP is the basic TOC message format, and is logically equivilant to a packet in TCP """ if self._buf=='': return None if self._buf[0]!="*": raise TOCParseError if len(self._buf)<6: return None foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6]) if len(self._buf)<6+length: return None data=self._buf[6:6+length] self._buf=self._buf[6+length:] if data and data[-1]=="\000": data=data[:-1] self._debug([type,data]) return [type,data] #def modeWeb(self): # try: # line,rest=string.split(self._buf,"\n",1) # get,username,http=string.split(line," ",2) # except: # return "Web" # not enough data # foo,type,username=string.split(username,"/") # if type=="info": # user=self.factory.users[username] # text="User Information for %sUsername: %s
\nWarning Level: %s%
\n Online Since: %s
\nIdle Minutes: %s
\n

\n%s\n

\n"%(user.saved.nick, user.saved.nick, user.saved.evilness, time.asctime(user.signontime), int((time.time()-user.idletime)/60), user.userinfo) # self.transport.write("HTTP/1.1 200 OK\n") # self.transport.write("Content-Type: text/html\n") # self.transport.write("Content-Length: %s\n\n"%len(text)) # self.transport.write(text) # self.loseConnection() def modeFlapon(self): #if self._buf[:3]=="GET": self.modeWeb() # TODO: get this working if len(self._buf)<10: return "Flapon" # not enough bytes flapon,self._buf=self._buf[:10],self._buf[10:] if flapon!="FLAPON\r\n\r\n": raise TOCParseError self.sendFlap(SIGNON,"\000\000\000\001") self._onlyflaps=1 return "Signon" def modeSignon(self): flap=self.readFlap() if flap==None: return "Signon" if flap[0]!=SIGNON: raise TOCParseError version,tlv,unlength=struct.unpack("!LHH",flap[1][:8]) if version!=1 or tlv!=1 or unlength+8!=len(flap[1]): raise TOCParseError self.username=normalize(flap[1][8:]) if self.username in self.factory.savedusers.keys(): self.saved=self.factory.savedusers[self.username] else: self.saved=SavedUser() self.saved.nick=self.username return "TocSignon" def modeTocSignon(self): flap=self.readFlap() if flap==None: return "TocSignon" if flap[0]!=DATA: raise TOCParseError data=string.split(flap[1]," ") if data[0]!="toc_signon": raise TOCParseError for i in data: if not i:data.remove(i) password=unroast(data[4]) if not(self.authorize(data[1],int(data[2]),data[3],password)): self.sendError(BAD_NICKNAME) self.transport.loseConnection() return self.sendFlap(DATA,"SIGN_ON:TOC1.0") self.sendFlap(DATA,"NICK:%s"%self.saved.nick) self.sendFlap(DATA,"CONFIG:%s"%self.saved.config) # sending user configuration goes here return "Connected" def authorize(self,server,port,username,password): if self.saved.password=="": self.saved.password=password return 1 else: return self.saved.password==password def modeConnected(self): flap=self.readFlap() while flap!=None: if flap[0] not in [DATA,KEEP_ALIVE]: raise TOCParseError flapdata=string.split(flap[1]," ",1) tocname=flapdata[0][4:] if len(flapdata)==2: data=flapdata[1] else: data="" func=getattr(self,"toc_"+tocname,None) if func!=None: func(data) else: self.toc_unknown(tocname,data) flap=self.readFlap() return "Connected" def toc_unknown(self,tocname,data): self._debug("unknown! %s %s" % (tocname,data)) def toc_init_done(self,data): """ called when all the setup is done. toc_init_done """ self.signontime=int(time.time()) self.factory.users[self.username]=self self.updateUsers() def toc_add_permit(self,data): """ adds users to the permit list. if the list is null, then set the mode to DENYALL """ if data=="": self.permitmode=DENYALL self.permitlist=[] self.denylist=[] else: self.permitmode=PERMITSOME self.denylist=[] users=string.split(data," ") map(self.permitlist.append,users) self.updateUsers() def toc_add_deny(self,data): """ adds users to the deny list. if the list is null, then set the mode to PERMITALL """ if data=="": self.permitmode=PERMITALL self.permitlist=[] self.denylist=[] else: self.permitmode=DENYSOME self.permitlist=[] users=string.split(data," ") map(self.denylist.append,users) self.updateUsers() def toc_evil(self,data): """ warns a user. toc_evil """ username,nora=string.split(data," ") if nora=="anon": user="" else: user=self.saved.nick if not(self.factory.users.has_key(username)): self.sendError(CANT_WARN,username) return if self.factory.users[username].saved.evilness>=100: self.sendError(CANT_WARN,username) return self.factory.users[username].evilFrom(user) def toc_add_buddy(self,data): """ adds users to the buddy list toc_add_buddy [] []... """ buddies=map(normalize,string.split(data," ")) for b in buddies: if b not in self.buddylist: self.buddylist.append(b) for buddy in buddies: try: buddy=self.factory.users[buddy] except: pass else: self.buddyUpdate(buddy) def toc_remove_buddy(self,data): """ removes users from the buddy list toc_remove_buddy [] []... """ buddies=string.split(data," ") for buddy in buddies: try: self.buddylist.remove(normalize(buddy)) except: pass def toc_send_im(self,data): """ incoming instant message toc_send_im [auto] """ username,data=string.split(data," ",1) auto=0 if data[-4:]=="auto": auto=1 data=data[:-5] data=unquote(data) if not(self.factory.users.has_key(username)): self.sendError(NOT_AVAILABLE,username) return user=self.factory.users[username] if not(self.canContact(user)): self.sendError(NOT_AVAILABLE,username) return user.hearWhisper(self,data,auto) def toc_set_info(self,data): """ set the users information, retrivable with toc_get_info toc_set_info """ info=unquote(data) self._userinfo=info def toc_set_idle(self,data): """ set/unset idle toc_set_idle """ seconds=int(data) self.idletime=time.time()-seconds # time when they started being idle self.updateUsers() def toc_set_away(self,data): """ set/unset away message toc_set_away [] """ away=unquote(data) if not self.away and away: # setting an away message self.away=away self.userclass=self.userclass+'U' self.updateUsers() elif self.away and not away: # coming back self.away="" self.userclass=self.userclass[:2] self.updateUsers() else: raise TOCParseError def toc_chat_join(self,data): """ joins the chat room. toc_chat_join """ exchange,name=string.split(data," ",1) self.factory.getChatroom(int(exchange),unquote(name)).join(self) def toc_chat_invite(self,data): """ invite others to the room. toc_chat_invite []... """ id,data=string.split(data," ",1) id=int(id) message,data=unquotebeg(data) buddies=string.split(data," ") for b in buddies: room=self.factory.chatroom[id] bud=self.factory.users[b] bud.chatInvite(room,self,message) def toc_chat_accept(self,data): """ accept an invitation. toc_chat_accept """ id=int(data) self.factory.chatroom[id].join(self) def toc_chat_send(self,data): """ send a message to the chat room. toc_chat_send """ id,message=string.split(data," ",1) id=int(id) message=unquote(message) self.factory.chatroom[id].say(self,message) def toc_chat_whisper(self,data): id,user,message=string.split(data," ",2) id=int(id) room=self.factory.chatroom[id] message=unquote(message) self.factory.users[user].chatWhisper(room,self,message) def toc_chat_leave(self,data): """ leave the room. toc_chat_leave """ id=int(data) self.factory.chatroom[id].leave(self) def toc_set_config(self,data): """ set the saved config. this gets send when you log in. toc_set_config """ self.saved.config=unquote(data) def toc_get_info(self,data): """ get the user info for a user toc_get_info """ if not self.factory.users.has_key(data): self.sendError(901,data) return self.sendFlap(2,"GOTO_URL:TIC:info/%s"%data) def toc_format_nickname(self,data): """ change the format of your nickname. toc_format_nickname """ # XXX may not work nick=unquote(data) if normalize(nick)==self.username: self.saved.nick=nick self.sendFlap(2,"ADMIN_NICK_STATUS:0") else: self.sendError(BAD_INPUT) def toc_change_passwd(self,data): orig,data=unquotebeg(data) new=unquote(data) if orig==self.saved.password: self.saved.password=new self.sendFlap(2,"ADMIN_PASSWD_STATUS:0") else: self.sendError(BAD_INPUT) def sendError(self,code,*varargs): """ send an error to the user. listing of error messages is below. """ send="ERROR:%s"%code for v in varargs: send=send+":"+v self.sendFlap(DATA,send) def updateUsers(self): """ Update the users who have us on their buddylist. Called when the user changes anything (idle,away) so people can get updates. """ for user in self.factory.users.values(): if self.username in user.buddylist and self.canContact(user): user.buddyUpdate(self) def getStatus(self,user): if self.canContact(user): if self in self.factory.users.values():ol='T' else: ol='F' idle=0 if self.idletime: idle=int((time.time()-self.idletime)/60) return (self.saved.nick,ol,self.saved.evilness,self.signontime,idle,self.userclass) else: return (self.saved.nick,'F',0,0,0,self.userclass) def canContact(self,user): if self.permitmode==PERMITALL: return 1 elif self.permitmode==DENYALL: return 0 elif self.permitmode==PERMITSOME: if user.username in self.permitlist: return 1 else: return 0 elif self.permitmode==DENYSOME: if user.username in self.denylist: return 0 else: return 1 else: assert 0,"bad permitmode %s" % self.permitmode def buddyUpdate(self,user): """ Update the buddy. Called from updateUsers() """ if not self.canContact(user): return status=user.getStatus(self) if not self._laststatus.has_key(user): self._laststatus[user]=() if self._laststatus[user]!=status: send="UPDATE_BUDDY:%s:%s:%s:%s:%s:%s"%status self.sendFlap(DATA,send) self._laststatus[user]=status def hearWhisper(self,user,data,auto=0): """ Called when you get an IM. If auto=1, it's an autoreply from an away message. """ if not self.canContact(user): return if auto: auto='T' else: auto='F' send="IM_IN:%s:%s:%s"%(user.saved.nick,auto,data) self.sendFlap(DATA,send) def evilFrom(self,user): if user=="": percent=0.03 else: percent=0.1 self.saved.evilness=self.saved.evilness+int((100-self.saved.evilness)*percent) self.sendFlap(2,"EVILED:%s:%s"%(self.saved.evilness,user)) self.updateUsers() def chatJoin(self,room): self.sendFlap(2,"CHAT_JOIN:%s:%s"%(room.id,room.name)) f="CHAT_UPDATE_BUDDY:%s:T"%room.id for u in room.users: if u!=self: u.chatUserUpdate(room,self) f=f+":"+u.saved.nick self.sendFlap(2,f) def chatInvite(self,room,user,message): if not self.canContact(user): return self.sendFlap(2,"CHAT_INVITE:%s:%s:%s:%s"%(room.name,room.id,user.saved.nick,message)) def chatUserUpdate(self,room,user): if user in room.users: inroom='T' else: inroom='F' self.sendFlap(2,"CHAT_UPDATE_BUDDY:%s:%s:%s"%(room.id,inroom,user.saved.nick)) def chatMessage(self,room,user,message): if not self.canContact(user): return self.sendFlap(2,"CHAT_IN:%s:%s:F:%s"%(room.id,user.saved.nick,message)) def chatWhisper(self,room,user,message): if not self.canContact(user): return self.sendFlap(2,"CHAT_IN:%s:%s:T:%s"%(room.id,user.saved.nick,message)) def chatLeave(self,room): self.sendFlap(2,"CHAT_LEFT:%s"%(room.id)) class Chatroom: def __init__(self,fac,exchange,name,id): self.exchange=exchange self.name=name self.id=id self.factory=fac self.users=[] def join(self,user): if user in self.users: return self.users.append(user) user.chatJoin(self) def leave(self,user): if user not in self.users: raise TOCParseError self.users.remove(user) user.chatLeave(self) for u in self.users: u.chatUserUpdate(self,user) if len(self.users)==0: self.factory.remChatroom(self) def say(self,user,message): for u in self.users: u.chatMessage(self,user,message) class SavedUser: def __init__(self): self.config="" self.nick="" self.password="" self.evilness=0 class TOCFactory(protocol.Factory): def __init__(self): self.users={} self.savedusers={} self.chatroom={} self.chatroomid=0 def buildProtocol(self,addr): p=TOC() p.factory=self return p def getChatroom(self,exchange,name): for i in self.chatroom.values(): if normalize(i.name)==normalize(name): return i self.chatroom[self.chatroomid]=Chatroom(self,exchange,name,self.chatroomid) self.chatroomid=self.chatroomid+1 return self.chatroom[self.chatroomid-1] def remChatroom(self,room): id=room.id del self.chatroom[id] MAXARGS={} MAXARGS["CONFIG"]=0 MAXARGS["NICK"]=0 MAXARGS["IM_IN"]=2 MAXARGS["UPDATE_BUDDY"]=5 MAXARGS["ERROR"]=-1 MAXARGS["EVILED"]=1 MAXARGS["CHAT_JOIN"]=1 MAXARGS["CHAT_IN"]=3 MAXARGS["CHAT_UPDATE_BUDDY"]=-1 MAXARGS["CHAT_INVITE"]=3 MAXARGS["CHAT_LEFT"]=0 MAXARGS["ADMIN_NICK_STATUS"]=0 MAXARGS["ADMIN_PASSWD_STATUS"]=0 class TOCClient(protocol.Protocol): def __init__(self,username,password,authhost="login.oscar.aol.com",authport=5190): self.username=normalize(username) # our username self._password=password # our password self._mode="SendNick" # current mode self._ourseqnum=19071 # current sequence number (for sendFlap) self._authhost=authhost # authorization host self._authport=authport # authorization port self._online=0 # are we online? self._buddies=[] # the current buddy list self._privacymode=PERMITALL # current privacy mode self._permitlist=[] # list of users on the permit list self._roomnames={} # the names for each of the rooms we're in self._receivedchatmembers={} # have we gotten who's in our room yet? self._denylist=[] self._cookies={} # for file transfers self._buf='' # current data buffer self._awaymessage='' def _debug(self,data): log.msg(data) def sendFlap(self,type,data): if type==DATA: data=data+"\000" length=len(data) s="*" s=s+struct.pack("!BHH",type,self._ourseqnum,length) s=s+data self._ourseqnum=self._ourseqnum+1 if self._ourseqnum>(256*256+256): self._ourseqnum=0 self._debug(data) self.transport.write(s) def isFlap(self): """ tests to see if a flap is actually on the buffer """ if self._buf=='': return 0 if self._buf[0]!="*": return 0 if len(self._buf)<6: return 0 foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6]) if type not in range(1,6): return 0 if len(self._buf)<6+length: return 0 return 1 def readFlap(self): if self._buf=='': return None if self._buf[0]!="*": raise TOCParseError if len(self._buf)<6: return None foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6]) if len(self._buf)<6+length: return None data=self._buf[6:6+length] self._buf=self._buf[6+length:] if data and data[-1]=="\000": data=data[:-1] return [type,data] def connectionMade(self): self._debug("connection made! %s" % self.transport) self.transport.write("FLAPON\r\n\r\n") def connectionLost(self, reason): self._debug("connection lost!") self._online=0 def dataReceived(self,data): self._buf=self._buf+data while self.isFlap(): flap=self.readFlap() func=getattr(self,"mode%s"%self._mode) func(flap) def modeSendNick(self,flap): if flap!=[1,"\000\000\000\001"]: raise TOCParseError s="\000\000\000\001\000\001"+struct.pack("!H",len(self.username))+self.username self.sendFlap(1,s) s="toc_signon %s %s %s %s english \"penguin\""%(self._authhost,\ self._authport,self.username,roast(self._password)) self.sendFlap(2,s) self._mode="Data" def modeData(self,flap): if not flap[1]: return if not ':' in flap[1]: self._debug("bad SNAC:%s"%(flap[1])) return command,rest=string.split(flap[1],":",1) if MAXARGS.has_key(command): maxsplit=MAXARGS[command] else: maxsplit=-1 if maxsplit==-1: l=tuple(string.split(rest,":")) elif maxsplit==0: l=(rest,) else: l=tuple(string.split(rest,":",maxsplit)) self._debug("%s %s"%(command,l)) try: func=getattr(self,"toc%s"%command) self._debug("calling %s"%func) except: self._debug("calling %s"%self.tocUNKNOWN) self.tocUNKNOWN(command,l) return func(l) def tocUNKNOWN(self,command,data): pass def tocSIGN_ON(self,data): if data!=("TOC1.0",): raise TOCParseError self._debug("Whee, signed on!") if self._buddies: self.add_buddy(self._buddies) self._online=1 self.onLine() def tocNICK(self,data): """ NICK: """ self.username=data[0] def tocCONFIG(self,data): """ CONFIG: format of config data: - g: group. all users until next g or end of config are in this group - b: buddy - p: person on the permit list - d: person on the deny list - m: permit/deny mode (1: permit all, 2: deny all, 3: permit some, 4: deny some) """ data=data[0] if data and data[0]=="{":data=data[1:-1] lines=string.split(data,"\n") buddylist={} currentgroup="" permit=[] deny=[] mode=1 for l in lines: if l: code,data=l[0],l[2:] if code=='g': # group currentgroup=data buddylist[currentgroup]=[] elif code=='b': buddylist[currentgroup].append(data) elif code=='p': permit.append(data) elif code=='d': deny.append(data) elif code=='m': mode=int(data) self.gotConfig(mode,buddylist,permit,deny) def tocIM_IN(self,data): """ IM_IN:::message """ user=data[0] autoreply=(data[1]=='T') message=data[2] self.hearMessage(user,message,autoreply) def tocUPDATE_BUDDY(self,data): """ UPDATE_BUDDY:::::: """ data=list(data) online=(data[1]=='T') if len(data[5])==2: data[5]=data[5]+" " away=(data[5][-1]=='U') if data[5][-1]=='U': data[5]=data[5][:-1] self.updateBuddy(data[0],online,int(data[2]),int(data[3]),int(data[4]),data[5],away) def tocERROR(self,data): """ ERROR:: """ code,args=data[0],data[1:] self.hearError(int(code),args) def tocEVILED(self,data): """ EVILED:: """ self.hearWarning(data[0],data[1]) def tocCHAT_JOIN(self,data): """ CHAT_JOIN:: """ #self.chatJoined(int(data[0]),data[1]) self._roomnames[int(data[0])]=data[1] self._receivedchatmembers[int(data[0])]=0 def tocCHAT_UPDATE_BUDDY(self,data): """ CHAT_UPDATE_BUDDY::::... """ roomid=int(data[0]) inroom=(data[1]=='T') if self._receivedchatmembers[roomid]: for u in data[2:]: self.chatUpdate(roomid,u,inroom) else: self._receivedchatmembers[roomid]=1 self.chatJoined(roomid,self._roomnames[roomid],list(data[2:])) def tocCHAT_IN(self,data): """ CHAT_IN:::: whisper isn't used """ whisper=(data[2]=='T') if whisper: self.chatHearWhisper(int(data[0]),data[1],data[3]) else: self.chatHearMessage(int(data[0]),data[1],data[3]) def tocCHAT_INVITE(self,data): """ CHAT_INVITE:::: """ self.chatInvited(int(data[1]),data[0],data[2],data[3]) def tocCHAT_LEFT(self,data): """ CHAT_LEFT: """ self.chatLeft(int(data[0])) del self._receivedchatmembers[int(data[0])] del self._roomnames[int(data[0])] def tocRVOUS_PROPOSE(self,data): """ RVOUS_PROPOSE:::::::: [:tlv tag1:tlv value1[:tlv tag2:tlv value2[:...]]] """ user,uid,cookie,seq,rip,pip,vip,port=data[:8] cookie=base64.decodestring(cookie) port=int(port) tlvs={} for i in range(8,len(data),2): key=data[i] value=base64.decodestring(data[i+1]) tlvs[key]=value name=UUIDS[uid] try: func=getattr(self,"toc%s"%name) except: self._debug("no function for UID %s" % uid) return func(user,cookie,seq,pip,vip,port,tlvs) def tocSEND_FILE(self,user,cookie,seq,pip,vip,port,tlvs): if tlvs.has_key('12'): description=tlvs['12'] else: description="" subtype,numfiles,size=struct.unpack("!HHI",tlvs['10001'][:8]) name=tlvs['10001'][8:-4] while name[-1]=='\000': name=name[:-1] self._cookies[cookie]=[user,SEND_FILE_UID,pip,port,{'name':name}] self.rvousProposal("send",cookie,user,vip,port,description=description, name=name,files=numfiles,size=size) def tocGET_FILE(self,user,cookie,seq,pip,vip,port,tlvs): return # XXX add this back in #reactor.clientTCP(pip,port,GetFileTransfer(self,cookie,os.path.expanduser("~"))) #self.rvous_accept(user,cookie,GET_FILE_UID) def onLine(self): """ called when we are first online """ pass def gotConfig(self,mode,buddylist,permit,deny): """ called when we get a configuration from the server mode := permit/deny mode buddylist := current buddylist permit := permit list deny := deny list """ pass def hearError(self,code,args): """ called when an error is received code := error code args := misc. arguments (username, etc.) """ pass def hearWarning(self,newamount,username): """ called when we get warned newamount := the current warning level username := the user who warned us, or '' if it's anonymous """ pass def hearMessage(self,username,message,autoreply): """ called when you receive an IM username := the user who the IM is from message := the message autoreply := true if the message is an autoreply from an away message """ pass def updateBuddy(self,username,online,evilness,signontime,idletime,userclass,away): """ called when a buddy changes state username := the user whos state changed online := true if the user is online evilness := the users current warning level signontime := the time the user signed on (UNIX epoch) idletime := the time the user has been idle (minutes) away := true if the user is away userclass := the class of the user (generally " O") """ pass def chatJoined(self,roomid,roomname,users): """ we just joined a chat room roomid := the AIM id for the room roomname := the name for the room users := a list of the users already in the room """ pass def chatUpdate(self,roomid,username,inroom): """ a user has joined the room roomid := the AIM id for the room username := the username inroom := true if the user is in the room """ pass def chatHearMessage(self,roomid,username,message): """ a message was sent to the room roomid := the AIM id for the room username := the user who sent the message message := the message """ pass def chatHearWhisper(self,roomid,username,message): """ someone whispered to us in a chatroom roomid := the AIM for the room username := the user who whispered to us message := the message """ pass def chatInvited(self,roomid,roomname,username,message): """ we were invited to a chat room roomid := the AIM id for the room roomname := the name of the room username := the user who invited us message := the invite message """ pass def chatLeft(self,roomid): """ we left the room roomid := the AIM id for the room """ pass def rvousProposal(self,type,cookie,user,vip,port,**kw): """ we were asked for a rondevouz type := the type of rondevous. currently, one of ["send"] cookie := the cookie. pass this to rvous_accept() user := the user who asked us vip := their verified_ip port := the port they want us to conenct to kw := misc. args """ pass #self.rvous_accept(cookie) def receiveBytes(self,user,file,chunk,sofar,total): """ we received part of a file from a file transfer file := the name of the file chunk := the chunk of data sofar := how much data we've gotten so far total := the total amount of data """ pass #print user,file,sofar,total def isaway(self): """ return our away status """ return len(self._awaymessage)>0 def set_config(self,mode,buddylist,permit,deny): """ set the server configuration mode := permit mode buddylist := buddy list permit := permit list deny := deny list """ s="m %s\n"%mode for g in buddylist.keys(): s=s+"g %s\n"%g for u in buddylist[g]: s=s+"b %s\n"%u for p in permit: s=s+"p %s\n"%p for d in deny: s=s+"d %s\n"%d #s="{\n"+s+"\n}" self.sendFlap(2,"toc_set_config %s"%quote(s)) def add_buddy(self,buddies): s="" if type(buddies)==type(""): buddies=[buddies] for b in buddies: s=s+" "+normalize(b) self.sendFlap(2,"toc_add_buddy%s"%s) def del_buddy(self,buddies): s="" if type(buddies)==type(""): buddies=[buddies] for b in buddies: s=s+" "+b self.sendFlap(2,"toc_remove_buddy%s"%s) def add_permit(self,users): if type(users)==type(""): users=[users] s="" if self._privacymode!=PERMITSOME: self._privacymode=PERMITSOME self._permitlist=[] for u in users: u=normalize(u) if u not in self._permitlist:self._permitlist.append(u) s=s+" "+u if not s: self._privacymode=DENYALL self._permitlist=[] self._denylist=[] self.sendFlap(2,"toc_add_permit"+s) def del_permit(self,users): if type(users)==type(""): users=[users] p=self._permitlist[:] for u in users: u=normalize(u) if u in p: p.remove(u) self.add_permit([]) self.add_permit(p) def add_deny(self,users): if type(users)==type(""): users=[users] s="" if self._privacymode!=DENYSOME: self._privacymode=DENYSOME self._denylist=[] for u in users: u=normalize(u) if u not in self._denylist:self._denylist.append(u) s=s+" "+u if not s: self._privacymode=PERMITALL self._permitlist=[] self._denylist=[] self.sendFlap(2,"toc_add_deny"+s) def del_deny(self,users): if type(users)==type(""): users=[users] d=self._denylist[:] for u in users: u=normalize(u) if u in d: d.remove(u) self.add_deny([]) if d: self.add_deny(d) def signon(self): """ called to finish the setup, and signon to the network """ self.sendFlap(2,"toc_init_done") self.sendFlap(2,"toc_set_caps %s" % (SEND_FILE_UID,)) # GET_FILE_UID) def say(self,user,message,autoreply=0): """ send a message user := the user to send to message := the message autoreply := true if the message is an autoreply (good for away messages) """ if autoreply: a=" auto" else: a='' self.sendFlap(2,"toc_send_im %s %s%s"%(normalize(user),quote(message),a)) def idle(self,idletime=0): """ change idle state idletime := the seconds that the user has been away, or 0 if they're back """ self.sendFlap(2,"toc_set_idle %s" % int(idletime)) def evil(self,user,anon=0): """ warn a user user := the user to warn anon := if true, an anonymous warning """ self.sendFlap(2,"toc_evil %s %s"%(normalize(user), (not anon and "anon") or "norm")) def away(self,message=''): """ change away state message := the message, or '' to come back from awayness """ self._awaymessage=message if message: message=' '+quote(message) self.sendFlap(2,"toc_set_away%s"%message) def chat_join(self,exchange,roomname): """ join a chat room exchange := should almost always be 4 roomname := room name """ roomname=string.replace(roomname," ","") self.sendFlap(2,"toc_chat_join %s %s"%(int(exchange),roomname)) def chat_say(self,roomid,message): """ send a message to a chatroom roomid := the AIM id for the room message := the message to send """ self.sendFlap(2,"toc_chat_send %s %s"%(int(roomid),quote(message))) def chat_whisper(self,roomid,user,message): """ whisper to another user in a chatroom roomid := the AIM id for the room user := the user to whisper to message := the message to send """ self.sendFlap(2,"toc_chat_whisper %s %s %s"%(int(roomid),normalize(user),quote(message))) def chat_leave(self,roomid): """ leave a chat room. roomid := the AIM id for the room """ self.sendFlap(2,"toc_chat_leave %s" % int(roomid)) def chat_invite(self,roomid,usernames,message): """ invite a user[s] to the chat room roomid := the AIM id for the room usernames := either a string (one username) or a list (more than one) message := the message to invite them with """ if type(usernames)==type(""): # a string, one username users=usernames else: users="" for u in usernames: users=users+u+" " users=users[:-1] self.sendFlap(2,"toc_chat_invite %s %s %s" % (int(roomid),quote(message),users)) def chat_accept(self,roomid): """ accept an invite to a chat room roomid := the AIM id for the room """ self.sendFlap(2,"toc_chat_accept %s"%int(roomid)) def rvous_accept(self,cookie): user,uuid,pip,port,d=self._cookies[cookie] self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user), cookie,uuid)) if uuid==SEND_FILE_UID: protocol.ClientConnector(reactor, SendFileTransfer,self,cookie,user,d["name"]).connectTCP(pip,port) def rvous_cancel(self,cookie): user,uuid,pip,port,d=self._cookies[cookie] self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user), cookie,uuid)) del self._cookies[cookie] class SendFileTransfer(protocol.Protocol): header_fmt="!4s2H8s6H10I32s3c69s16s2H64s" def __init__(self,client,cookie,user,filename): self.client=client self.cookie=cookie self.user=user self.filename=filename self.hdr=[0,0,0] self.sofar=0 def dataReceived(self,data): if not self.hdr[2]==0x202: self.hdr=list(struct.unpack(self.header_fmt,data[:256])) self.hdr[2]=0x202 self.hdr[3]=self.cookie self.hdr[4]=0 self.hdr[5]=0 self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr)) data=data[256:] if self.hdr[6]==1: self.name=self.filename else: self.name=self.filename+self.hdr[-1] while self.name[-1]=="\000": self.name=self.name[:-1] if not data: return self.sofar=self.sofar+len(data) self.client.receiveBytes(self.user,self.name,data,self.sofar,self.hdr[11]) if self.sofar==self.hdr[11]: # end of this file self.hdr[2]=0x204 self.hdr[7]=self.hdr[7]-1 self.hdr[9]=self.hdr[9]-1 self.hdr[19]=0xdeadbeef # XXX really calculate this self.hdr[18]=self.hdr[18]+1 self.hdr[21]="\000" self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr)) self.sofar=0 if self.hdr[7]==0: self.transport.loseConnection() class GetFileTransfer(protocol.Protocol): header_fmt="!4s 2H 8s 6H 10I 32s 3c 69s 16s 2H 64s" def __init__(self,client,cookie,dir): self.client=client self.cookie=cookie self.dir=dir self.buf="" def connectionMade(self): def func(f,path,names): names.sort(lambda x,y:cmp(string.lower(x),string.lower(y))) for n in names: name=os.path.join(path,n) lt=time.localtime(os.path.getmtime(name)) size=os.path.getsize(name) f[1]=f[1]+size f.append("%02d/%02d/%4d %02d:%02d %8d %s" % (lt[1],lt[2],lt[0],lt[3],lt[4],size,name[f[0]:])) f=[len(self.dir)+1,0] os.path.walk(self.dir,func,f) size=f[1] self.listing=string.join(f[2:],"\r\n")+"\r\n" open("\\listing.txt","w").write(self.listing) hdr=["OFT2",256,0x1108,self.cookie,0,0,len(f)-2,len(f)-2,1,1,size, len(self.listing),os.path.getmtime(self.dir), checksum(self.listing),0,0,0,0,0,0,"OFT_Windows ICBMFT V1.1 32", "\002",chr(0x1a),chr(0x10),"","",0,0,""] self.transport.write(apply(struct.pack,[self.header_fmt]+hdr)) def dataReceived(self,data): self.buf=self.buf+data while len(self.buf)>=256: hdr=list(struct.unpack(self.header_fmt,self.buf[:256])) self.buf=self.buf[256:] if hdr[2]==0x1209: self.file=StringIO.StringIO(self.listing) self.transport.registerProducer(self,0) elif hdr[2]==0x120b: pass elif hdr[2]==0x120c: # file request file=hdr[-1] for k,v in [["\000",""],["\001",os.sep]]: file=string.replace(file,k,v) self.name=os.path.join(self.dir,file) self.file=open(self.name,'rb') hdr[2]=0x0101 hdr[6]=hdr[7]=1 hdr[10]=hdr[11]=os.path.getsize(self.name) hdr[12]=os.path.getmtime(self.name) hdr[13]=checksum_file(self.file) self.file.seek(0) hdr[18]=hdr[19]=0 hdr[21]=chr(0x20) self.transport.write(apply(struct.pack,[self.header_fmt]+hdr)) log.msg("got file request for %s"%file,hex(hdr[13])) elif hdr[2]==0x0202: log.msg("sending file") self.transport.registerProducer(self,0) elif hdr[2]==0x0204: log.msg("real checksum: %s"%hex(hdr[19])) del self.file elif hdr[2]==0x0205: # resume already=hdr[18] if already: data=self.file.read(already) else: data="" log.msg("restarting at %s"%already) hdr[2]=0x0106 hdr[19]=checksum(data) self.transport.write(apply(struct.pack,[self.header_fmt]+hdr)) elif hdr[2]==0x0207: self.transport.registerProducer(self,0) else: log.msg("don't understand 0x%04x"%hdr[2]) log.msg(hdr) def resumeProducing(self): data=self.file.read(4096) log.msg(len(data)) if not data: self.transport.unregisterProducer() self.transport.write(data) def pauseProducing(self): pass def stopProducing(self): del self.file # UUIDs SEND_FILE_UID = "09461343-4C7F-11D1-8222-444553540000" GET_FILE_UID = "09461348-4C7F-11D1-8222-444553540000" UUIDS={ SEND_FILE_UID:"SEND_FILE", GET_FILE_UID:"GET_FILE" } # ERRORS # general NOT_AVAILABLE=901 CANT_WARN=902 MESSAGES_TOO_FAST=903 # admin BAD_INPUT=911 BAD_ACCOUNT=912 REQUEST_ERROR=913 SERVICE_UNAVAILABLE=914 # chat NO_CHAT_IN=950 # im and info SEND_TOO_FAST=960 MISSED_BIG_IM=961 MISSED_FAST_IM=962 # directory DIR_FAILURE=970 TOO_MANY_MATCHES=971 NEED_MORE_QUALIFIERS=972 DIR_UNAVAILABLE=973 NO_EMAIL_LOOKUP=974 KEYWORD_IGNORED=975 NO_KEYWORDS=976 BAD_LANGUAGE=977 BAD_COUNTRY=978 DIR_FAIL_UNKNOWN=979 # authorization BAD_NICKNAME=980 SERVICE_TEMP_UNAVAILABLE=981 WARNING_TOO_HIGH=982 CONNECTING_TOO_QUICK=983 UNKNOWN_SIGNON=989 STD_MESSAGE={} STD_MESSAGE[NOT_AVAILABLE]="%s not currently available" STD_MESSAGE[CANT_WARN]="Warning of %s not currently available" STD_MESSAGE[MESSAGES_TOO_FAST]="A message has been dropped, you are exceeding the server speed limit" STD_MESSAGE[BAD_INPUT]="Error validating input" STD_MESSAGE[BAD_ACCOUNT]="Invalid account" STD_MESSAGE[REQUEST_ERROR]="Error encountered while processing request" STD_MESSAGE[SERVICE_UNAVAILABLE]="Service unavailable" STD_MESSAGE[NO_CHAT_IN]="Chat in %s is unavailable" STD_MESSAGE[SEND_TOO_FAST]="You are sending messages too fast to %s" STD_MESSAGE[MISSED_BIG_IM]="You missed an IM from %s because it was too big" STD_MESSAGE[MISSED_FAST_IM]="You missed an IM from %s because it was sent too fast" # skipping directory for now STD_MESSAGE[BAD_NICKNAME]="Incorrect nickname or password" STD_MESSAGE[SERVICE_TEMP_UNAVAILABLE]="The service is temporarily unavailable" STD_MESSAGE[WARNING_TOO_HIGH]="Your warning level is currently too high to sign on" STD_MESSAGE[CONNECTING_TOO_QUICK]="You have been connecting and disconnecting too frequently. Wait 10 minutes and try again. If you continue to try, you will need to wait even longer." STD_MESSAGE[UNKNOWN_SIGNON]="An unknown signon error has occured %s"