#!/usr/bin/env python import sys import os import re import socket import md5 import urllib import select import errno import time import locale import random from string import * import gettext try: # If we are executing from source just try to load locales sourceLocales= sys.path[0] + '/i18n' if os.access( sourceLocales, os.F_OK ): gettext.install( 'pebrot', sourceLocales ) else: # We are executing from a correct install (so using default locales # dir) gettext.install( 'pebrot' ) except IOError: # Lets try to not crash ;) c= gettext.NullTranslations() c.install() ######## Options ######## DEBUG= 0 VERBOSE= 0 CLOSE_EMPTY= 0 # 1 -> Close chat session when no buddies there SELECT_TIMEOUT= 0.01 PAL_CONNECT_TIMEOUT= 20 DOWNLOAD_DIR= os.getenv('HOME') + '/' LOG_CHATS= 0 LOG_DIR= os.getenv('HOME') + '/' MY_IP= None ######################### SEND_PORT= 6891 # Lets use system's encoding (if not just use 'latin-1') ENCODING= 'latin-1' loc= locale.getdefaultlocale()[1] if loc: ENCODING= loc MSG_HEADER= 'MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n' TYPING_HEADER= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgscontrol\r\nTypingUser: ' SEND_HEADER= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nApplication-Name: File Transfer\r\nApplication-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\nInvitation-Command: INVITE\r\nInvitation-Cookie: %d\r\nApplication-File: %s\r\nApplication-FileSize: %d\r\n\r\n' SEND_HEADER2= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: ACCEPT\r\nInvitation-Cookie: %d\r\nIP-Address: %s\r\nPort: %d\r\nAuthCookie: %d\r\nLaunch-Application: FALSE\r\nRequest-Data: IP-Address:\r\n\r\n' SEND_ACCEPT= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: ACCEPT\r\nInvitation-Cookie: %d\r\nLaunch-Application: FALSE\r\nRequest-Data: IP-Address:\r\n\r\n' SEND_REJECT= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: CANCEL\r\nInvitation-Cookie: %d\r\nCancel-Code: REJECT\r\n\r\n' SEND_CANCEL= 'MIME-Version: 1.0\r\nContent-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\nInvitation-Command: CANCEL\r\nInvitation-Cookie: %d\r\nCancel-Code: TIMEOUT\r\n\r\n' FILE_CHUNK_SIZE= 2048 BUFF_LEN= 80192 HOST= 'messenger.hotmail.com' PORT= 1863 MAX_LEN= 1664 - len(MSG_HEADER) #Message's max length DISPLAYNAME_MAX= 386 LOG_CHAT_INIT= _('-- Starting chat session with %s on %s --') LOG_CHAT_END= _('-- Closing chat session on %s --') MSGS= { 'VER':['id', 'proto1', 'proto2', 'proto3', 'proto4'], 'INF':['id', 'secPack'], 'XFR':['id', 'type', 'ipPort1', 'param2', 'value2'], 'USR':['id', 'type', 'arg1', 'arg2', 'arg3', 'arg4'], 'SYN':['id', 'version', 'numPeople', 'numGroups'], 'GTC':['value'], 'BLP':['list'], 'CHG':['id', 'state'], 'LSG':['id', 'group', 'number'], 'ILN':['id', 'state', 'passport', 'name'], 'CHL':['id', 'hash'], 'QRY':['id'], 'MSG':['arg1', 'arg2', 'arg3', 'arg4'], 'NLN':['state', 'passport', 'name'], 'FLN':['passport'], 'OUT':['type'], 'CAL':['id', 'ringing', 'sesionId'], 'RNG':['sessionId', 'ipPort', 'cki', 'hash', 'passport', 'name'], 'JOI':['passport', 'name'], 'ACK':['id'], 'NAK':['id'], 'IRO':['id', 'num', 'total', 'passport', 'name'], 'ANS':['id', 'ok'], 'ADD':['id', 'list', 'number', 'passport1', 'passport2'], 'REM':['id', 'list', 'number', 'passport'], 'BYE':['arg', 'idleclose'], 'REA':['id', 'listNum', 'passport', 'name'], 'PRP':['type', 'phone'], 'LST':['passport', 'nick', 'lists', 'groups'], 'CVR':['id', 'num1', 'num2', 'num3', 'url1', 'url2'], 'TFR':[], 'CCL':[], 'FIL':['size'], 'QNG':[], 'NOT':['size'], } ERRORS= { 200: _('Invalid syntax'), 201: _('Invalid parameter'), 205: _('Invalid user'), 206: _('Domain name missing'), 207: _('Already logged in'), 208: _('Invalid username'), 209: _('Nickname change illegal'), 210: _('User list full'), 215: _('User already on list'), 216: _('User already not on list'), 217: _('User not online'), 218: _('Already in mode'), 219: _('User is in the opposite list'), 223: _('Too many groups'), 224: _('Invalid group'), 225: _('User not in group'), 229: _('Group name too long'), 230: _('Cannot remove group zero'), 231: _('Invalid group'), 280: _('Switchboard failed'), 281: _('Transfer to switchboard failed'), 300: _('Required field missing'), 301: _('Too many hits to a FND'), 302: _('Not logged in'), 500: _('Internal server error'), 501: _('Database server error'), 502: _('Command disabled'), 510: _('File operation failed'), 520: _('Memory allocation failed'), 540: _('Challenge response failed'), 600: _('Server is busy'), 601: _('Server is unavaliable'), 602: _('Peer nameserver is down'), 603: _('Database connection failed'), 604: _('Server is going down'), 605: _('Server unavaliable'), 707: _('Could not create connection'), 710: _('Bad CVR parameters sent'), 711: _('Write is blocking'), 712: _('Session is overloaded'), 713: _('Too many active users'), 714: _('Too many sessions'), 715: _('Not expected'), 717: _('Bad friend file'), 731: _('Not expected'), 800: _('Friendly name changes too rapidly'), 910: _('Server too busy'), 911: _('Authentication failed'), 912: _('Authentication failed'), 913: _('Not allowed when offline'), 914: _('Server unavailable'), 915: _('Server unavailable'), 916: _('Server unavailable'), 917: _('Authentication failed'), 918: _('Server too busy'), 919: _('Server too busy'), 920: _('Not accepting new users'), 921: _('Server too busy'), 922: _('Server too busy'), 923: _('Kids Passport without parental consent'), 924: _('Passport account not yet verified'), } STATES= { 'NLN': _('Online'), 'FLN': _('Offline'), 'HDN': _('Appear Offline'), 'IDL': _('Idle'), 'AWY': _('Away'), 'BSY': _('Busy'), 'BRB': _('Be Right Back'), 'PHN': _('On the Phone'), 'LUN': _('Out to Lunch'), } LISTS= { 'AL': _('ALLOW LIST'), 'BL': _('BLOCK LIST'), 'FL': _('FORWARD LIST'), 'RL': _('REVERSE LIST'), } # Different socket types SOCK_MAIN= 0 SOCK_CHAT= 1 SOCK_SEND= 2 SOCK_RECV= 3 # States of a file send object SEND_CREATED= 0 SEND_LISTEN= 1 SEND_CONNECTED= 2 SEND_SENDING= 3 SEND_FINISHED= 4 # States of a file recv socket RECV_CREATED= 0 RECV_CONNECTING= 1 RECV_CONNECTED= 2 RECV_ACCEPTED= 3 # Chat states CHAT_CONNECTING= 0 CHAT_WAITING_REPLY= 1 CHAT_WAITING_PALS= 2 CHAT_OK= 3 # Some print funcs # Should be overriden by descending classes (or not...) class ExParrot: def pVerbose( self, level, st ): if level >= VERBOSE: sys.stdout.write( st ) sys.stdout.flush() def pDebug( self, st ): if DEBUG: print >>sys.stderr, ( str(st) + '\n' ) def pError( self, st ): print >>sys.stderr, ( _('\nError: %s\n') % st ) # Basic communication class # Provides some basic for functions for sending MSN's protocol messages class Comm(ExParrot): def __init__( self, id=0 ): self.s= socket.socket( socket.AF_INET, socket.SOCK_STREAM ) self.ready= 0 self.id= id self.host= '' self.port= -1 def renewSocket( self ): self.s= socket.socket( socket.AF_INET, socket.SOCK_STREAM ) def sendSock( self, sock, cmd, args ): 'send msn command to socket' try: if self.ready: sock.send( '%s %s %s' % ( cmd, self.id, args ) ) self.pDebug( 'send: %s %s %s' % (cmd, self.id, args) ) #rep= self.inputs.readFrom( sock ) #msg= Msg( rep ) self.id= self.id + 1 #return msg except socket.error, e: raise SocketError, (sock, e) def send( self, cmd, args ): 'send msn command via default socket' self.sendSock( self.s, cmd, args ) def sendRaw( self, cmd ): 'send msn commands without id, send cmd as it is' try: if self.ready: self.s.send( cmd ) self.pDebug( 'send: ' + cmd ) except socket.error, e: raise SocketError, (self.s, e) def connect_ex( self ): try: self.s.setblocking( 0 ) return self.s.connect_ex( (self.host, self.port) ) except socket.error, e: raise SocketError, (self.s, e) ################################################################################ # Class for MSN protocol errors class MSNError(Exception): def __init__( self, code, id ): self.code= int(code) self.id= int(id) self.text= ERRORS[self.code] #print >>sys.stderr, "MSNError: %d %d" % (self.code, self.id) def __str__( self ): return self.text # Exception raised when other side closed socket class SocketClosed(Exception): def __init__( self, socket ): self.socket= socket def __str__( self ): return str(self.socket) # Exception raised on socket error class SocketError(Exception): def __init__( self, socket, error ): self.socket= socket self.error= error def errno( self ): return self.error[0] def errorMsg( self ): return self.error[1] def __str__( self ): return str(self.socket) + ' - Error: ' + str(self.error) ################################################################################ # Allows waiting for input on several sockets. class Inputs(ExParrot): def __init__( self, list ): self.fileDescs= {} for f in list: self.fileDescs[f]= ('', SOCK_MAIN) def add( self, fileDesc, type ): #"type can be MAIN, CHAT, FILE_SEND" self.fileDescs[fileDesc]= ('', type) def has_key( self, key ): return self.fileDescs.has_key( key ) def remove( self, fileDesc ): if self.fileDescs.has_key( fileDesc ): del( self.fileDescs[fileDesc] ) def line( self, fd, peek=0 ): self.pDebug( 'line' ) text= self.fileDescs[fd][0] # avoid inf. loop when no data ready if ( len(text)>0 and find(text, '\r\n') != -1 ): self.pDebug( 'Try to fill= ' + text ) self.pDebug( len(text) ) #text+= fd.recv( BUFF_LEN ) #self.fileDescs[fd]+= fd.recv( BUFF_LEN ) #self.pDebug( 'LEIDO en while=' + text ) l= split( text, '\r\n', 1 )[0] if not peek: remain= text[len(l)+2:] self.fileDescs[fd]= (remain, self.fileDescs[fd][1]) return l else: return None def selectRead( self ): #self.pDebug( 'fds= ' + str(self.fileDescs) ) try: res= select.select( self.fileDescs.keys(), [], [], SELECT_TIMEOUT )[0] except select.error: return None #self.pDebug( 'RES= ' + str(res) ) if len(res) == 0: return None #if res[0] == sys.stdin: #return ( sys.stdin, raw_input()) #else: try: st= res[0].recv( BUFF_LEN ) except socket.error, e: raise SocketError, (res[0], e) if st == '': # peer closed socket res[0].close() raise SocketClosed, res[0] self.pDebug( 'sock=%s, 1st=%s' % (str(res[0]), st) ) self.fileDescs[res[0]]= (self.fileDescs[res[0]][0] + st , self.fileDescs[res[0]][1] ) return res def read( self ): global BUFF_LEN #self.pDebug( "self.fileDescs= " + str(self.fileDescs) ) for f in self.fileDescs.items(): if f[1][0]: if f[1][0][0] in ['\0', '\1']: size1= ord( f[1][0][1] ) size2= ord( f[1][0][2] ) self.pDebug( 'ANTES 1' ) l= self.getChars( f[0], size1+size2*256 + 3 ) self.pDebug( 'DESPUES 1' ) else: l= self.line( f[0] ) if l: self.pDebug( "getChars1= " + l ) tuple= ( f[0], l ) return tuple res= self.selectRead() if res== None: return None buff= self.fileDescs[res[0]][0] # This is a chunk from a filesend (dont try to read a line) if buff[0] in ['\0', '\1']: self.pDebug( 'estoy en el if' ) size1= ord( buff[1] ) size2= ord( buff[2] ) #self.pDebug( 'ANTES' ) l= self.getChars( res[0], size1+size2*256 + 3 ) #self.pDebug( 'DESPUES' ) else: #self.pDebug( 'en line' ) l= self.line( res[0] ) if l: #self.pDebug( "readed in getChars2= %d" % len(l) ) #self.pDebug( "getChars2= " + l ) return ( res[0], l ) else: return None def type( self, fDesc ): return self.fileDescs[fDesc][1] def getChars( self, fd, numChars ): self.pDebug( 'getChars!!!!, fd=%s, numChars= %d' % (fd, numChars) ) self.selectRead() text= self.fileDescs[fd][0] if len(text) < numChars: # not enough chars self.pDebug( 'there are only %d chars' % len(text) ) self.pDebug( 'they are= %s' % text ) #self.fileDescs[fd]+= fd.recv( BUFF_LEN ) #text+= fd.recv( BUFF_LEN ) return None else: chars= text[:numChars] self.fileDescs[fd]= (text[numChars:], self.fileDescs[fd][1]) return chars def hasSubstr( self, fd, subst ): return find( self.fileDescs[fd][0], subst ) def readFrom( self, fd, timeout= -1 ): global BUFF_LEN if ( self.fileDescs[fd][0] ): l= self.line( fd ) return l if timeout == -1: res= select.select( [fd], [], [], SELECT_TIMEOUT )[0] else: res= select.select( [fd], [], [], timeout )[0] if fd not in res: return None st= fd.recv( BUFF_LEN ) self.pDebug( '2st=' + st ) self.fileDescs[fd][0]= self.fileDescs[fd][0] + st l= self.line( fd ) return l def peekFrom( self, fd ): global BUFF_LEN if ( self.fileDescs[fd][0] ): l= self.line( fd, 1 ) return l st= fd.recv( BUFF_LEN ) self.pDebug( 'st=' + st ) self.fileDescs[fd][0]= self.fileDescs[fd][0] + st l= self.line( fd, 1 ) self.pDebug( 'postST=' + self.fileDescs[fd][0] ) return l def strReturn( self, fd, string ): self.fileDescs[fd]=( string + self.fileDescs[fd][0], self.fileDescs[fd][1] ) ################################################################################ # Does the MSN message parsing class Msg(ExParrot): def __init__( self, st ): self.pDebug( 'rec: ' + st ) self.st= st if not st: self.type= '' return self.fields= {} parts= split( st, ' ' ) self.type= parts[0] if self.type.isdigit() and len(self.type) == 3: raise MSNError, (self.type, int(parts[1]) ) elif ( self.type not in ['GTC', 'BPR'] ): maxLen= len( parts ) expLen= len( MSGS[self.type] )+1 if ( expLen < maxLen ): maxLen= expLen for i in range( 1, maxLen ): self.fields[MSGS[self.type][i-1]]= parts[i] def __str__( self ): return self.st def __repr__(): return self.type + '=' + str( self.fields ) ################################################################################ # Main class. Represents an MSN session. class Session( Comm ): def __init__( self ): Comm.__init__( self ) self.user= None self.name= None self.inputs= Inputs( [self.s]) self.usersOnline= {} self.chats= {} self.sendFile= {} self.recvFile= {} self.pendingChats= {} self.connectingChats= {} self.connectingFileRecvs= {} self.requestedList= {} self.running= 1 self.listShown= None self.lists= { 'FL':{}, 'AL':{}, 'BL':{}, 'RL':{} } self.listOps= {} self.lstNum= 0 self.lstTotal= 0 # For hiding socket error when server closes idle chats self.hideSocketError= 0 def connect( self, user, passwd, state ): self.user= user self.passwd= passwd self.state= state self.pVerbose( 1, _('Connecting to %s... ') % HOST ) self.pDebug( 'CONNECTING %s:%d' % ( HOST, PORT) ) self.s.connect((HOST, PORT)) self.ready= 1 #self.s.setblocking( 0 ) self.send( 'VER', 'MSNP8 CVRO\r\n' ) def reconnect( self ): self.inputs.remove( self.s ) self.renewSocket() self.inputs.add( self.s, SOCK_MAIN ) self.connect( self.user, self.passwd, self.state ) # Must be connected for this to work def myIP( self ): if MY_IP: return MY_IP else: return self.s.getsockname()[0] def addInput( self, input, type ): self.inputs.add( input, type ) def socketType( self, sock ): if sock == self.s: return SOCK_MAIN elif self.chats.has_key( sock ): return SOCK_CHAT elif self.sendFile.has_key( sock ): return SOCK_SEND elif self.recvFile.has_key( sock ): return SOCK_RECV else: return None def removeInput( self, input ): if self.inputs.has_key( input ): self.inputs.remove( input ) def newChat( self, passport=None ): chat= Chat( self, int(self.id) ) chat.created= 1 chat.state= CHAT_CONNECTING self.chats[chat.s]= chat self.pendingChats[str(self.id)]= chat if passport: chat.addUser( passport ) self.send( 'XFR', 'SB\r\n' ) return chat def sendChatMsg( self, chat, msg ): if chat.state == CHAT_OK: try: if chat.listInactiveUsers(): self.addConnectingChat( chat, 'NORMAL' ) chat.inviteInactive() chat.addAction( 'MSG', msg ) else: self.writeTo( chat, msg ) self.msgFrom( chat, self.user, msg ) self.isTyping= 0 except SocketError: self.delConnectingChat( chat ) self.reconnectChat( chat ) chat.addAction( 'MSG', msg ) else: chat.addAction( 'MSG', msg ) def sendChatFile( self, chat, file ): if chat.state == CHAT_OK: try: if chat.listInactiveUsers(): self.addConnectingChat( chat, 'NORMAL' ) chat.inviteInactive() chat.addAction( 'FILE_SEND', file ) else: self.sendFileRequest( chat, file ) self.pInfo( _('Request for sending \'%s\'...') % file ) except SocketError: self.delConnectingChat( chat ) self.reconnectChat( chat ) chat.addAction( 'FILE_SEND', file ) else: chat.addAction( 'FILE_SEND', file ) #def delConnectingSocket( self, sock ): #if self.connectingChats.has_key( sock ): #del( self.connectingChats[sock] ) #elif self.connectingFileRecvs.has_key( sock ): #del( self.connectingFileRecvs[sock] ) def addConnectingChat( self, chat, type ): self.pDebug( 'addConnectingChat: %s %s' % (str(chat), type) ) chat.state= CHAT_CONNECTING self.connectingChats[chat.s]= (chat, type) def delConnectingChat( self, chat ): self.pDebug( 'connectingChats: %s' % str(self.connectingChats) ) if self.connectingChats.has_key( chat.s ): del( self.connectingChats[chat.s] ) def addConnectingFileRecv( self, fRecv ): self.pDebug( 'addConnectingFileRecv: %s' % (str(fRecv)) ) fRecv.state= RECV_CONNECTING self.connectingFileRecvs[fRecv.s]= fRecv def delConnectingFileRecv( self, fRecv ): #self.pDebug( 'connectingfRecv: %s' % str(self.connectingChats) ) if self.connectingFileRecvs.has_key( fRecv.s ): del( self.connectingFileRecvs[fRecv.s] ) def reconnectChat( self, chat ): #self.inputs.remove( chat.s ) self.pDebug( 'reconnectChat') del self.chats[chat.s] chat.renewSocket() self.chats[chat.s]= chat self.pendingChats[str(self.id)]= chat self.send( 'XFR', 'SB\r\n' ) chat.connected= None def closeChat( self, chat ): chat.logText( LOG_CHAT_END % (time.asctime()) ) self.pDebug( 'chat.sendFile: %s' % str(chat.sendFile) ) # Get rid of uploads/downloads for fSend in chat.sendFile.values(): self.removeFileSend( fSend ) for fRecv in chat.recvFile.values(): self.removeFileRecv( fRecv ) chat.send( 'OUT', '\r\n' ) del( self.chats[chat.s] ) self.delConnectingChat( chat ) self.inputs.remove( chat.s ) chat.s.close() def hasChat( self, passport ): for chat in self.chats.items(): if chat[1].users == passport: return chat[1].active return 0 #def chatUser( self, passport ): #for chat in self.chats.items(): #if chat[1].user == passport: #return chat[1] def changeState( self, state ): ## Added to allow Pebrot to stay alive offline if state == "FLN": self.send ( 'OUT', '\r\n' ) self.state = 'FLN' self.pInfo( 'You have been logged out. Type \\q to exit or \\s nln to reconnect.' ) else: self.send( 'CHG', '%s\r\n' % state ) def challenge( self, msg ): chunk= md5.new( msg.fields['hash']+ 'Q1P7W2E4J9R8U3S5' ).hexdigest() self.send( 'QRY', 'msmsgs@msnmsgr.com 32\r\n%s' % chunk ) def ping( self ): self.sendRaw( 'PNG\r\n' ) def requestInfo( self, infoId= 0 ): self.send( 'SYN', '%d\r\n' % infoId ); def addUser( self, passport, list ): '''Add user to list. List can be one from: AL: allow list BL: block list FL: forward list RL: reverse list''' self.listOps[int(self.id)]= (passport, list, 'ADD') self.send( 'ADD', ' %s %s %s\r\n' % (list, passport, passport) ) def delUser( self, passport, list ): self.listOps[int(self.id)]= (passport, list, 'REM') self.send( 'REM', ' %s %s\r\n' % (list, passport) ) def acceptChat( self, msg ): chat= Chat( self ) self.inputs.add( chat.s, SOCK_CHAT ) self.pDebug( "self.inputs=" + str(self.inputs.fileDescs) ) chat.addUser( msg.fields['passport']) chat.accept( msg ) self.chats[chat.s]= chat def writeTo( self, chat, msg ): chat.write( msg ) def changeName( self, name ): name= name[:DISPLAYNAME_MAX] name= unicode( name, ENCODING, 'replace' ) name= urllib.quote(name.encode( 'utf-8' )) self.send( 'REA', ' %s %s\r\n' % (self.user, name) ) def onlyOnline( self, x ): #print >>sys.stderr, x, 'esta a', self.usersOnline.has_key( x ) return self.usersOnline.has_key( x ) def listInactiveUsers( self, chat ): list= chat.listInactiveUsers() #print >>sys.stderr, 'list2= ' + str(list) return filter( self.onlyOnline, list ) def treatProtoError( self, exc ): if exc.code == 201: info= self.listOps[exc.id] self.addUserError( info[0], info[1] ) elif exc.code == 215: info= self.listOps[exc.id] self.userOnListError( info[0], info[1] ) elif exc.code == 216: info= self.listOps[exc.id] self.delUserError( info[0], info[1] ) elif exc.code in [205, 209, 217, 219, 224, 231, 500]: self.protoError( exc.code ) #fix crash when adding invalid usernames elif exc.code == 208: self.pError( _('The user may not be added because no account exists by that name.') ) else: self.fatalProtoError( exc.code ) def whatMSG( self, text ): m= re.compile(r'Content-Type: *[a-z]+/([^; \r]+)[; \r]').search( text ) if not m: return type= m.group( 1 ) self.pDebug( 'type= %s' % type ) if type == 'x-msmsgsinitialemailnotification': m= re.compile(r'Inbox-Unread: *([0-9]+)').search( text ) if m: self.gotMail( m.group(1) ) elif type == 'x-msmsgsemailnotification': if find( text, 'From:') != -1: m= re.compile(r'From: *(.+)\r\n').search( text ) if m: self.incomingMail( m.group(1) ) def addFileSend( self, fSend ): self.sendFile[fSend.s]= fSend fSend.chat.addFileSend( fSend ) def addFileRecv( self, fRecv ): self.recvFile[fRecv.s]= fRecv fRecv.chat.addFileRecv( fRecv ) def sendFileRequest( self, chat, fileName ): fSend= chat.sendFileRequest( fileName ) self.addFileSend( fSend ) def initSendFile( self, fSend ): fSend.state= SEND_LISTEN #self.addInput( fSend.s, SOCK_SEND ) fSend.init() def removeFileSend( self, fSend ): self.pDebug( 'self.sendFile: %s' % str(self.sendFile) ) #if fSend.s.fileno() != -1 and fSend.state \ #in [SEND_CONNECTED, SEND_SENDING]: #fSend.cancel() del( self.sendFile[fSend.s] ) fSend.chat.removeFileSend( fSend ) if self.inputs.has_key( fSend.s ): self.inputs.remove( fSend.s ) self.pDebug( 'FIN self.sendFile: %s' % str(self.sendFile) ) def removeFileRecv( self, fRecv ): del( self.recvFile[fRecv.s] ) fRecv.chat.removeFileRecv( fRecv ) if self.inputs.has_key( fRecv.s ): self.inputs.remove( fRecv.s ) fRecv.s.close() self.pDebug( 'self.recvFile: %s' % str(self.recvFile) ) # Let's check if it was still connecting if self.connectingFileRecvs.has_key( fRecv.s ): del( self.connectingFileRecvs[fRecv.s] ) def crash( self ): self.send( 'asdas', 'kk' ) def step( self ): if len(self.sendFile) > 0: for fSend in self.sendFile.values(): if fSend.state == SEND_LISTEN: #self.pDebug( 'en el listen' ) self.pDebug( 'en el listen, socket = %s, fSend=%s' % (str(fSend.s), str(fSend)) ) try: fSend.s.setblocking( 0 ) self.pDebug( 'antes' ) sock, dir= fSend.s.accept() self.pDebug( 'despues' ) self.pDebug( 'sock= %s, dir= %s' % (str(sock), str(dir)) ) #del( self.sendFile[fSend.s] ) self.removeFileSend( fSend ) fSend.s= sock self.addInput( fSend.s, SOCK_SEND ) self.addFileSend( fSend ) fSend.state= SEND_CONNECTED except socket.error, e: self.pDebug( 'Esperando1... %s' % (str(e)) ) elif fSend.state == SEND_SENDING: try: fSend.s.setblocking( 0 ) resul= fSend.sendChunk() if resul: self.chunkSent( fSend ) except socket.error, e: self.pDebug( 'Esperando2... %s' % (str(e)) ) if len(self.connectingChats) > 0: for elem in self.connectingChats.values(): chat= elem[0] self.pDebug( 'Chat: %s - state: %d' % (str(chat), chat.state) ) if chat.state == CHAT_CONNECTING: resul= chat.connect_ex() if resul != 0: self.pDebug( 'connecting chat resul= %d, %s' % (resul, errno.errorcode[resul]) ) if resul in [0, errno.EISCONN]: chat.ready= 1 if elem[1] == 'NORMAL': self.inputs.add( chat.s, SOCK_CHAT ) chat.send( 'USR', '%s %s\r\n' % (self.user, chat.hash) ) self.pDebug( 'USR sent' ) else: #self.s.setblocking( 0 ) chat.send( 'ANS', '%s %s %s\r\n' % (self.user , chat.hash, chat.sessionId) ) chat.state= CHAT_WAITING_REPLY #self.delConnectingChats( chat ) elif chat.state == CHAT_WAITING_PALS: chat.checkInactive() elif chat.state == CHAT_OK: self.delConnectingChat( chat ) chat.processActions() if len(self.connectingFileRecvs) > 0: self.pDebug( 'self.connectingFileRecvs: %s' % str(self.connectingFileRecvs) ) for fRecv in self.connectingFileRecvs.values(): self.pDebug( 'fRecv: %s, state: %d' % (str(fRecv), fRecv.state) ) if fRecv.state == RECV_CONNECTING: self.pDebug( 'antes' ) resul= fRecv.connect_ex() self.pDebug( 'despues' ) if resul != 0: self.pDebug( 'connecting file recv resul= %d, %s' % (resul, errno.errorcode[resul]) ) if resul in [0, errno.EISCONN]: fRecv.ready= 1 fRecv.s.setblocking( 1 ) fRecv.state= RECV_CONNECTED fRecv.sendRaw( 'VER MSNFTP\r\n' ) self.delConnectingFileRecv( fRecv ) self.processMsgs() def sslSendRecv( self, host, port, st ): sock= socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect( (host, port) ) ssl= socket.ssl( sock ) self.pDebug( 'request: ' + st ) ssl.write( st ) self.pDebug( 'AFTER SEND' ) recv= '' try: while (1): recv+= ssl.read() except socket.sslerror, e: if e[0] not in [5,8]: raise e return recv def sslLogin( self, params ): recv= self.sslSendRecv( 'nexus.passport.com', 443, 'GET /rdr/pprdr.asp HTTP/1.0\r\n\r\n' ) self.pDebug( 'SSL response: ' + recv ) resul= re.compile(r'DALogin=([^,]+),').search( recv ) dir= resul.group(1) self.pDebug( 'daLogin: ' + str(dir) ) elem= split( dir, '/' ) user= urllib.quote( self.user ) header= 'Authorization: Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in=' + user + ',pwd=' + self.passwd + ',' + params req= 'GET /%s HTTP/1.0\r\n%s\r\n\r\n' % (elem[1], header) retry= 1 while (retry): recv= self.sslSendRecv( elem[0], 443, req ) self.pDebug( 'AFTER SEND' ) self.pDebug( 'SSL response: ' + str(recv) ) if recv[:12] == 'HTTP/1.1 302': resul= re.compile(r'Location: https://([^/]+)/([^ ]+)\r\n').search( recv ) elem[0]= resul.group(1) elem[1]= resul.group(2) else: retry= 0 resul= re.search( "from-PP='([^']+)'", recv ) param='t=&p=' if resul: param= resul.group( 1 ) self.pDebug( 'PARAM: ' + param ) return param def processMsgs( self ): try: res= self.inputs.read() except SocketClosed, e: # the other side closed socket self.pDebug( 'Socket error' ) self.pDebug( 'Error from: %s' % str(e.socket) ) if self.sendFile.has_key( e.socket ): self.pDebug( 'UNO' ) self.removeFileSend( self.sendFile[e.socket] ) elif self.recvFile.has_key( e.socket ): self.pDebug( 'DOS' ) self.removeFileRecv( self.recvFile[e.socket] ) else: self.pDebug( 'TRES' ) self.removeInput( e.socket ) self.pDebug( 'peer closed socket - ' + str(e) ) return None if res == None: return else: sockType= self.inputs.type(res[0]) #print >>sys.stderr, 'RECIBIDO= ', res #print >>sys.stderr, 'SOCK_TYPE= ', sockType if sockType == SOCK_MAIN: # Message from Main server try: msg= Msg( res[1] ) except MSNError, e: self.treatProtoError( e ) self.pDebug( 'debug= ' + str(res[0]) ) return if ( msg.type == 'CHL' ): self.challenge( msg ) elif ( msg.type == 'QNG' ): self.qngReceived() if not self.listShown: self.palListComplete() self.listShown= 1 elif ( msg.type == 'VER' ): self.send( 'CVR', '0x0409 winnt 5.1 i386 MSNMSGR 5.0.0540 MSMSGS %s\r\n' % self.user ) elif ( msg.type == 'INF' ): self.send( 'USR', 'MD5 I %s\r\n' % self.user ) elif ( msg.type == 'ILN' ): name= unicode( urllib.unquote(msg.fields['name']), 'utf-8' , 'replace' ) name= name.encode( ENCODING, 'replace' ) self.usersOnline[msg.fields['passport']]= { 'name': name , 'state':msg.fields['state']} self.userOn( msg.fields['passport'] ) elif ( msg.type == 'CVR' ): self.send( 'USR', 'TWN I %s\r\n' % self.user ) elif ( msg.type == 'NLN' ): name= unicode( urllib.unquote(msg.fields['name']), 'utf-8' , 'replace' ) name= name.encode( ENCODING, 'replace' ) wasOnline = self.usersOnline.has_key( msg.fields['passport'] ) self.usersOnline[msg.fields['passport']]= { 'name': name , 'state':msg.fields['state']} if wasOnline: self.userChangesState( msg ) else: self.userConnected( msg ) elif ( msg.type == 'FLN' ): if self.usersOnline.has_key( msg.fields['passport'] ): del( self.usersOnline[msg.fields['passport']] ) self.userOff( msg.fields['passport'] ) elif ( msg.type == 'QRY' ): pass elif ( msg.type == 'XFR' ): host= split( msg.fields['ipPort1'], ':' ) self.pDebug( 'CONNECTING %s:%s' % (host[0], host[1]) ) # Main server (Notification server) if msg.fields['type'] == 'NS': self.pVerbose( 1, _('done\n') ) self.pVerbose( 1, _('Connecting to %s... ') % host[0] ) self.inputs.remove( self.s ) self.s= socket.socket( socket.AF_INET, socket.SOCK_STREAM ) self.inputs.add( self.s, SOCK_MAIN ) self.s.connect( (host[0], int( host[1])) ) self.ready= 1 #self.pDebug( 'Socket: ' + str(self.s) ) #self.pDebug( 'Inputs: ' + str(self.inputs.fileDescs) ) #self.send( 'USR', 'MD5 I %s\r\n' % self.user ) self.send( 'VER', 'MSNP8 CVRO\r\n' ) else: # Per chat server (Switchboard server) c= self.pendingChats[msg.fields['id']] del( self.pendingChats[msg.fields['id']] ) #c.hash= msg.fields['value2'] self.addConnectingChat( c, 'NORMAL' ) c.host= host[0] c.port= int(host[1]) c.hash= msg.fields['value2'] c.connect_ex() #if resul == 0: #self.chatConnectError( c ) #return elif ( msg.type == 'USR' ): if msg.fields['type'] == 'MD5': hash= msg.fields['arg2'] chunk= md5.new( hash + self.passwd ).hexdigest() self.send( 'USR', 'MD5 S %s\r\n' % chunk ) elif msg.fields['type'] == 'TWN': text= split(msg.st)[4] #text= urllib.unquote( text ) #self.pageParams= replace( text, ',', '&' ) self.pDebug( 'PARAMS: ' + text ) param= self.sslLogin( text ) self.send( 'USR', 'TWN S %s\r\n' % param ) else: name= unicode( urllib.unquote(msg.fields['arg2']), 'utf-8' , 'replace' ) name= name.encode( ENCODING, 'replace' ) self.name= name self.pVerbose( 1, _('done\n') ) #self.changeState( self.state ) self.requestInfo() #self.ping() elif ( msg.type == 'ADD' ): l = msg.fields['list'] p = msg.fields['passport1'] self.lists[l][p]= '' self.userAdded( p, l ) elif ( msg.type == 'REM' ): l = msg.fields['list'] p = msg.fields['passport'] if self.lists[l].has_key( p ): del( self.lists[l][p] ) self.userRemoved( p, l ) elif ( msg.type == 'OUT' ): if msg.fields['type'] == 'OTH': self.otherLogin() elif msg.fields['type'] == 'SSD': self.serverDown() elif ( msg.type == 'RNG' ): self.invitedBy( msg ) elif ( msg.type == 'CHG' ): self.state= msg.fields['state'] self.selfStateChanged( msg.fields['state'] ) elif ( msg.type == 'REA' ): name= unicode( urllib.unquote(msg.fields['name']), 'utf-8' , 'replace' ) name= name.encode( ENCODING, 'replace' ) self.nameChanged( name ) elif ( msg.type == 'SYN' ): self.lstTotal= int( msg.fields['numPeople'] ) self.lstNum= 0 self.pDebug( 'lstTotal= %d' % self.lstTotal ) elif ( msg.type == 'LST' ): listNums= int( msg.fields['lists'] ) if listNums == 8: self.userAddedYou( msg.fields['passport'] ) if listNums & 1: self.lists['FL'][msg.fields['passport']]= '' if listNums & 2: self.lists['AL'][msg.fields['passport']]= '' if listNums & 4: self.lists['BL'][msg.fields['passport']]= '' if listNums & 8: self.lists['RL'][msg.fields['passport']]= '' # This tries to fix a problem with no receiving ILNs # Just doing the initial CHG after receiving the complete list self.lstNum += 1 self.pDebug( 'lstNum= %d' % self.lstNum ) if self.lstNum == self.lstTotal: self.changeState( self.state ) self.ping() #if msg.fields.has_key( 'passport' ): #self.requestedList[msg.fields['passport']]= '' #if msg.fields['num'] == msg.fields['listSize']: #self.listUpdated( self.requestedList, msg.fields['list'] ) elif ( msg.type == 'NOT' ): # Notification (MSN Mobile, alert...) more= self.inputs.getChars( res[0], int(msg.fields['size']) ) # there's only part of notification, return what we read # (NOT line) if not more: self.inputs.strReturn( res[0], msg.st + '\r\n' ) elif ( msg.type == 'MSG' ): # Mail notification more= self.inputs.getChars( res[0], int(msg.fields['arg3']) ) # there's only part of msg, return what we read (MSG line) if not more: self.inputs.strReturn( res[0], msg.st + '\r\n' ) return self.pDebug( "more="+ more ) self.whatMSG( more ) elif sockType == SOCK_CHAT: # Message from any chat server #print >>sys.stderr, 'AUA' try: msg= Msg( res[1] ) except MSNError, e: if e.code == '216': # When a user disconnects during chat res[0].close() self.inputs.remove( res[0] ) self.treatProtoError( e ) self.pDebug( 'debug2= ' + str(res[0]) ) return #print >>sys.stderr, 'MSG=', msg if ( msg.type == 'BYE' ): if self.chats.has_key( res[0] ): chat= self.chats[res[0]] if msg.fields.has_key('idleclose'): chat.delUser( msg.fields['arg'], msg.fields['idleclose'] ) else: chat.delUser( msg.fields['arg'] ) if len(chat.users) == 0 and CLOSE_EMPTY: self.pDebug( 'chat borrado!' ) self.inputs.remove( res[0] ) del( self.chats[res[0]] ) self.userLeavesChat( chat, msg.fields['arg'] ) elif ( msg.type == 'IRO' ): chat= self.chats[res[0]] chat.activateUser( msg.fields['passport'] ) elif ( msg.type == 'ANS' ): chat= self.chats[res[0]] chat.active= 1 chat.state= CHAT_OK self.joinToChat( chat ) elif ( msg.type == 'MSG' ): chat= self.chats[res[0]] more= self.inputs.getChars( res[0] , int(msg.fields['arg3']) ) # there's only part of msg, return what we read (MSG line) if not more: self.inputs.strReturn( res[0], msg.st + '\r\n' ) return parts= split(more, '\r\n\r\n', 1) heads= parts[0] #self.pDebug( 'heads=' + heads ) if len(parts) > 1: text= unicode( parts[1], 'utf-8', 'replace' ) resul= re.compile(r'Content-Type: *[a-z]+/([^; \r]+)[; \r]?').search( heads ) if not resul: return type= resul.group( 1 ) self.pDebug( 'type= ' + type ) # Typing notification if type == 'x-msmsgscontrol': self.typing( chat, msg.fields['arg1'] ) # Message elif type == 'plain': #chat.activateUser( msg.fields['arg1'] ) self.msgFrom( chat, msg.fields['arg1'] , text.encode( ENCODING, 'replace' ) ) # Response for file send elif type == 'x-msmsgsinvite': search= re.compile(r'Invitation-Command: (\w+)').search(text) if not search: return result= search.group(1) search= re.compile(r'Invitation-Cookie: (\d+)').search(text) if not search: return cookie= int(search.group(1)) cancelCode= re.compile(r'Cancel-Code: FTTIMEOUT').search(text) self.pDebug( 'result= ' + result ) if result == 'ACCEPT': # Peer sends info for file downloading search= re.compile(r'AuthCookie: (\d+)').search(text) if search: authCookie= int(search.group(1)) search= re.compile(r'IP-Address: ([^\r]+)\r').search(text) if not search: return ip= search.group(1) search= re.compile(r'Port: (\d+)').search(text) if not search: return port= int(search.group(1)) fRecv= self.chats[res[0]].getFileRecv( cookie ) fRecv.init( authCookie, ip, port ) self.addInput( fRecv.s, SOCK_RECV ) self.pDebug( 'AuthCookie= %d, IP= %s, port= %d' % (authCookie, ip, port) ) # Peer has accepted file send else: self.pDebug( 'Accepted cookie=' + str(cookie) ) fSend= self.chats[res[0]].getFileSend( cookie ) if fSend: self.initSendFile( fSend ) self.sendFileAccepted( fSend ) elif result == 'CANCEL': if cancelCode: pass else: self.pDebug( 'File cancelled' ) chat= self.chats[res[0]] if chat.sendFile.has_key( cookie ): fSend= chat.getFileSend( cookie ) self.removeFileSend( fSend ) self.sendFileRejected( fSend ) elif chat.recvFile.has_key( cookie ): fRecv= chat.getFileRecv( cookie ) self.removeFileRecv( fRecv ) self.recvFileCancelled( fRecv ) elif result == 'INVITE': chat= self.chats[res[0]] search= re.compile(r'Application-File: ([^\r]+)\r').search(text) if not search: return fileName= search.group(1) fileName= fileName.encode( ENCODING, 'replace' ) search= re.compile(r'Application-FileSize: (\d+)').search(text) if not search: return size= int(search.group(1)) self.pDebug( 'MSG INVITE= %s %s' % (fileName, size) ) user= msg.fields['arg1'] #print >>sys.stderr, str(msg.fields) fRecv= FileRecv( self, chat, user, cookie, fileName , size ) self.addFileRecv( fRecv ) self.recvFileRequest( fRecv ) elif ( msg.type == 'USR' ): self.pDebug( 'USR received' ) chat= self.chats[res[0]] chat.inviteInactive() #chat.processActions() #print >>sys.stderr, 'CHAT=', chat #self.pDebug( 'CHATS:' + str(self.chats) ) #self.pDebug( 'CHAT:' + str(chat) ) #self.pDebug( 'USERS:' + str(chat.users) ) #chat.send( 'CAL', '%s\n' % chat.nextNotActive() ) elif ( msg.type == 'CAL' ): pass elif ( msg.type == 'JOI' ): chat= self.chats[res[0]] chat.activateUser( msg.fields['passport'] ) self.userAcceptChat( chat, msg.fields['passport'] ) elif ( msg.type == 'NAK' ): self.msgNotReceived( self.chats[res[0]] ) else: self.pDebug( 'NOT IMPLEMENTED' ) elif sockType == SOCK_SEND: # Message from file send #self.pDebug( 'lala=' + str(res)) try: msg= Msg( res[1] ) except MSNError, e: self.treatProtoError( e ) self.pDebug( 'debug= ' + str(res[0]) ) return fSend= self.sendFile[res[0]] if ( msg.type == 'VER' ): fSend.sendRaw( 'VER MSNFTP\r\n' ) elif ( msg.type == 'USR' ): fSend.sendRaw( 'FIL %d\r\n' % (fSend.fileSize()) ) elif ( msg.type == 'TFR' ): fSend.state= SEND_SENDING elif ( msg.type == 'CCL' ): self.removeFileSend( fSend ) ##self.inputs.remove( fSend.s ) self.sendFileCancelled( fSend ) elif ( msg.type == 'BYE'): ##self.removeInput( fSend.s ) self.removeFileSend( fSend ) self.sendFileFinished( fSend ) else: self.pDebug( 'NOT IMPLEMENTED' ) elif sockType == SOCK_RECV: # Message from file recv self.pDebug( 'SOCK_RECV' ) fRecv= self.recvFile[res[0]] if res[1][:3] == 'VER': fRecv.sendRaw( 'USR %s %d\r\n' % (self.user, fRecv.authCookie) ) elif res[1][:3] == 'FIL': parts= split( res[1] ) fRecv.size= int(parts[1]) self.pDebug( 'size= %d' % fRecv.size ) fRecv.sendRaw( 'TFR\r\n' ) elif res[1][0] == '\0': self.pDebug( 'chunk size= %d' % (len(res[1])) ) fRecv.recvChunk( res[1] ) self.chunkReceived( fRecv ) if fRecv.received == fRecv.size: self.pDebug( 'Full file received' ) fRecv.close() self.removeFileRecv( fRecv ) self.recvFileFinished( fRecv ) elif res[1][0] == '\1': self.removeFileRecv( fRecv ) self.recvFileCancelled( fRecv ) else: self.pDebug( 'NOT IMPLEMENTED' ) def loopMsg( self ): while self.running: self.processMsgs() ########## CALLBACKS ############ def userOn( self, passport ): self.pDebug( 'userOn -> NO CALLBACK' ) def selfStateChanged( self, state ): self.pDebug( 'selfStateChanged-> NO CALLBACK' ) def userChangesState( self, msg ): self.pDebug( 'userChangesState -> NO CALLBACK' ) def userConnected( self, msg ): self.pDebug( 'userConnected -> NO CALLBACK' ) def userAdded( self, passport, list ): self.pDebug( 'userAdded -> NO CALLBACK' ) def userRemoved( self, passport, list ): self.pDebug( 'userRemoved -> NO CALLBACK' ) def userOff( self, passport ): self.pDebug( 'userOff -> NO CALLBACK' ) def serverDown( self ): self.pDebug( 'serverDown -> NO CALLBACK' ) def otherLogin( self ): self.pDebug( 'otherLogin -> NO CALLBACK' ) def userAcceptChat( self, chat, passport ): self.pDebug( 'userAcceptChat-> NO CALLBACK' ) def invitedBy( self, msg ): self.pDebug( 'invitedBy -> NO CALLBACK' ) def userLeavesChat( self, chat, user ): self.pDebug( 'userLeavesChat -> NO CALLBACK' ) def msgFrom( self, chat, passport, msg ): self.pDebug( 'msgFrom -> NO CALLBACK' ) def typing( self, chat, passport ): self.pDebug( 'typing -> NO CALLBACK' ) def gotMail( self, numUnread ): self.pDebug( 'gotMail -> NO CALLBACK' ) def incomingMail( self, mailFrom ): self.pDebug( 'incomingMail -> NO CALLBACK' ) def protoError( self, code ): self.pDebug( 'protoError -> NO CALLBACK' ) def fatalProtoError( self, code ): self.pDebug( 'fatalProtoError -> NO CALLBACK' ) def addUserError( self, passport, list ): self.pDebug( 'addUserError -> NO CALLBACK' ) def delUserError( self, passport, list ): self.pDebug( 'delUserError -> NO CALLBACK' ) def userOnListError( self, passport, list ): self.pDebug( 'userOnListError -> NO CALLBACK' ) def nameChanged( self, name ): self.pDebug( 'nameChanged-> NO CALLBACK' ) def msgNotReceived( self, chat ): self.pDebug( 'msgNotReceived -> NO CALLBACK' ) def joinToChat( self, chat ): self.pDebug( 'joinToChat -> NO CALLBACK' ) def chatConnectError( self, chat ): self.pDebug( 'errorChatConnect -> NO CALLBACK' ) def sendFileAccepted( self, fSend ): self.pDebug( 'sendFileAccepted -> NO CALLBACK' ) def sendFileRejected( self, fSend ): self.pDebug( 'sendFileRejected -> NO CALLBACK' ) def sendFileCancelled( self, fSend ): self.pDebug( 'sendFileCancelled -> NO CALLBACK' ) def sendFileFinished( self, fSend ): self.pDebug( 'sendFileFinished -> NO CALLBACK' ) def chunkSent( self, fSend ): self.pDebug( 'chunkSent -> NO CALLBACK' ) def recvFileRequest( self, fRecv ): self.pDebug( 'recvFileRequest -> NO CALLBACK' ) def recvFileFinished( self, fRecv ): self.pDebug( 'recvFileFinished -> NO CALLBACK' ) def recvFileCancelled( self, fRecv ): self.pDebug( 'recvFileCancelled -> NO CALLBACK' ) def chunkReceived( self, fRecv ): self.pDebug( 'chunkReceived -> NO CALLBACK' ) def listUpdated( self, list, type ): self.pDebug( 'listUpdated -> NO CALLBACK' ) def palListComplete( self ): self.pDebug( 'palListComplete-> NO CALLBACK' ) def qngReceived( self ): self.pDebug( 'qngReceived -> NO CALLBACK' ) def userAddedYou( self, passport ): self.pDebug( 'userAddedYou -> NO CALLBACK' ) ################################################################################ class Chat( Comm ): def __init__( self, session, id=0 ): self.id= id Comm.__init__( self ) self.session= session self.users= {} self.active= None self.connected= None self.written= 0 self.created= 0 self.sendFile= {} self.recvFile= {} self.logFile= None self.actions= [] def logText( self, text='' ): if LOG_CHATS and self.logFile != None: self.logFile.write( text + '\n' ) self.logFile.flush() def addUser( self, user ): # Create log file with first pal name if LOG_CHATS: if self.logFile == None: self.logFile= open( LOG_DIR + user, 'a' ) self.logText() self.logText( LOG_CHAT_INIT % (user, time.asctime()) ) self.users[user]= '' self.hideSocketError= 1 def nextNotActive( self ): for f in self.users.keys(): if not self.users[f]: return f return None def activateUser( self, user ): self.users[user]= 'ONLINE' def deactivateUser( self, user ): self.users[user]= '' def typingUser( self, user ): self.users[user]= 'TYPING' def isTyping( self, user ): return self.users[user] == 'TYPING' # idleClose is 1 when server closed socket when idle after 5 min. def delUser( self, user, idleClose=0 ): del( self.users[user] ) self.pDebug('self.delUser: ' + str(self.listUsers())) if len(self.listUsers()) == 0 or idleClose: self.hideSocketError= 1 else: self.hideSocketError= 0 def getUsers( self ): return self.users.items() def listUsers( self ): return self.users.keys() def listInactiveUsers( self ): l= [] for u in self.users.items(): if not u[1]: l.append( u[0] ) return l def addFileSend( self, fSend ): self.sendFile[fSend.cookie]= fSend def getFileSend( self, cookie ): cookie= int(cookie) if self.sendFile.has_key( cookie ): return self.sendFile[cookie] else: return None def getFileRecv( self, cookie ): cookie= int(cookie) if self.recvFile.has_key( cookie ): return self.recvFile[int(cookie)] else: return None def removeFileSend( self, fSend ): del( self.sendFile[fSend.cookie] ) def addFileRecv( self, fRecv ): self.recvFile[fRecv.cookie]= fRecv def removeFileRecv( self, fRecv ): del( self.recvFile[fRecv.cookie] ) def closeFileRecv( self, fRecv ): fRecv.s.close() self.session.removeFileRecv( fRecv ) def cancelFileSend( self, fSend ): fSend.cancel() self.session.removeFileSend( fSend ) def sendFileRequest( self, fileName ): rand= random.randrange( sys.maxint ) stats= os.stat( fileName ) msg= SEND_HEADER % (rand, os.path.basename(fileName), stats[6] ) nBytes= len(msg) self.send( 'MSG', 'N %d\r\n%s' % (nBytes, msg) ) fSend= FileSend( self.session, self, rand, fileName ) self.addFileSend( fSend ) return fSend def initSendFile( self, cookie ): fSend= self.sendFile[cookie] fSend.init() def invite( self, passport ): self.send( 'CAL', '%s\r\n' % (passport) ) def accept( self, msg ): self.user= msg.fields['passport'] self.id= 1 self.active= None host= split( msg.fields['ipPort'], ':' ) self.pDebug( 'CONNECTING %s:%s' % (host[0], host[1]) ) self.host= host[0] self.port= int( host[1] ) self.hash= msg.fields['hash'] self.sessionId= msg.fields['sessionId'] self.session.addConnectingChat( self, 'INVITED' ) self.connect_ex() # Invite pals who are out from chat (e.g. timeout kicked) def inviteInactive( self ): self.state= CHAT_WAITING_PALS self.t0Invited= time.time() list= self.listInactiveUsers() for user in list: self.invite( user ) def checkInactive( self ): t= time.time() if (t - self.t0Invited) < PAL_CONNECT_TIMEOUT: list= self.listInactiveUsers() if not list: self.state= CHAT_OK else: self.session.pError( _('Timeout while waiting for pals to connect')) self.session.delConnectingChat( self ) def typingNotification( self ): self.pDebug('typingNotification') self.pDebug(self.ready) if self.numOnlineUsers() > 0: chunk= TYPING_HEADER + self.session.user + '\r\n\r\n\r\n' self.send( 'MSG', 'U %d\r\n%s' % ( len( chunk ), chunk ) ) def write( self, text ): pieces= [] if len(text) > MAX_LEN: while len(text) > 0: pieces.append( text[:MAX_LEN] ) text= text[MAX_LEN:] else: pieces.append( text ) for part in pieces: part= unicode( part, ENCODING, 'replace' ) part= part.encode( 'utf-8' ) chunk= MSG_HEADER + part self.send( 'MSG', 'N %d\r\n%s' % ( len( chunk ), chunk )) def addAction( self, type, arg ): self.actions.append( (type, arg) ) def processActions( self ): for elem in self.actions: if elem[0] == 'FILE_SEND': self.session.sendFileRequest( self, elem[1] ) self.session.pInfo( _('Request for sending \'%s\'...') % elem[1] ) elif elem[0] == 'MSG': try: self.session.writeTo( self, elem[1] ) except SocketError, e: if e.errno() == errno.EAGAIN: pass else: raise e self.session.msgFrom( self, self.session.user, elem[1] ) self.session.isTyping= 0 self.actions=[] def numOnlineUsers( self ): n = 0 for state in self.users.values(): if state == 'ONLINE': n += 1 return n ############################################################################### class FileSend( Comm ): def __init__( self, session, chat, cookie, fileName ): Comm.__init__( self ) self.ready= 1 self.cookie= cookie self.session= session self.chat= chat self.fileName= fileName self.file= None self.size= 0 self.sent= 0 self.port= SEND_PORT self.state= SEND_CREATED self.s= socket.socket( socket.AF_INET, socket.SOCK_STREAM ) def init( self ): self.file= open( self.fileName ) self.size= os.stat( self.fileName )[6] self.s.setblocking( 0 ) trying= 1 while trying: try: self.pDebug( 'Trying port %d...' % (self.port) ) self.s.bind( ('', self.port) ) trying= 0 except socket.error, e: if e[0] == errno.EADDRINUSE: self.pDebug( str(e) ) self.port+= 1 else: raise SocketError, (self.s, e) self.pDebug( 'Using port= %d' % (self.port) ) self.s.setblocking( 1 ) self.s.listen( 1 ) myIP= self.session.myIP() #self.session.pMsg( 'ME %s:%d' % (myIP, myPort) ) authCookie= random.randrange( sys.maxint ) msg= SEND_HEADER2 % (self.cookie, myIP, self.port, authCookie) nBytes= len(msg) self.chat.send( 'MSG', 'N %d\r\n%s' % (nBytes, msg) ) #self.session.pMsg( 'ip= %s:%d' % (self.s.) ) def fileSize( self ): return self.size def sendChunk( self ): self.pDebug( 'sendChunk, state= %d' % self.state ) try: res= select.select( [], [self.s], [], 0 )[1] except select.error: return None self.pDebug( 'res= %s' % str(res) ) if not res: self.pDebug( 'Socket not ready for sending' ) return None chunkSize= FILE_CHUNK_SIZE if self.sent+(FILE_CHUNK_SIZE-3) > self.size: self.pDebug( 'en el if' ) chunkSize= (self.size - self.sent) + 3 self.state= SEND_FINISHED chunk= self.file.read( chunkSize - 3 ) div= (chunkSize - 3) / 256 mod= (chunkSize - 3) % 256 #self.pDebug( 'sending Chunk: ' + chunk ) self.sent= self.sent + chunkSize - 3 try: self.sendRaw( '\0' + chr(mod) + chr(div) + chunk ) return 1 except SocketError, e: self.session.pError( _('Error while sending \'%s\' (%s)') % (self.fileName, str(e)) ) self.session.removeFileSend( self ) return None def getPeer( self ): return self.s.getpeername() def cancel( self ): if self.state == SEND_SENDING: self.pDebug( 'CORTADO' ) self.sendRaw( '\1\0\0' ) else: msg= SEND_CANCEL % (self.cookie) nBytes= len(msg) self.chat.send( 'MSG', 'N %d\r\n%s' % (nBytes, msg) ) ################################################################################ class FileRecv( Comm ): def __init__( self, session, chat, user, cookie, fileName, size ): Comm.__init__( self ) self.user= user self.session= session self.chat= chat self.cookie= cookie self.fileName= fileName self.size= size self.file= None self.received= 0 self.state= RECV_CREATED self.s= socket.socket( socket.AF_INET, socket.SOCK_STREAM ) def init( self, authCookie, host, port ): self.authCookie= authCookie self.host= host self.port= port try: self.file= open( DOWNLOAD_DIR + self.fileName, 'w' ) except IOError, e: self.session.pError( e[1] ) self.ready= 1 self.session.addConnectingFileRecv( self ) def accept( self ): msg= SEND_ACCEPT % (self.cookie) nBytes= len(msg) self.chat.send( 'MSG', 'N %d\r\n%s' % (nBytes, msg) ) self.state= RECV_ACCEPTED def reject( self ): msg= SEND_REJECT % (self.cookie) nBytes= len(msg) self.chat.send( 'MSG', 'N %d\r\n%s' % (nBytes, msg) ) self.session.removeFileRecv( self ) def recvChunk( self, chunk ): size1= ord( chunk[1] ) size2= ord( chunk[2] ) self.received= self.received + (size1 + size2*256) self.pDebug( 'size1= %d, size2= %d' % (size1, size2) ) self.file.write( chunk[3:] ) def close( self ): self.sendRaw( 'BYE 16777989\r\n' ) self.s.close() self.file.close()