#!/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'],
'UUX':['id', 'length'],
}
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 )
if self.type in MSGS:
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]
else:
self.pDebug( 'Unknown message type: %s' % (self.type) )
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' )
self.delChat(chat)
def delChat( self, chat ):
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 import
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:
mail= re.compile(r'From: *(.+)\r\n').search( text )
name= re.compile(r'From-Addr: *(.+)\r\n').search( text )
self.incomingMail( mail.group(1), name.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' ):
if msg.fields.has_key('numPeople'): # Ok, we are gonna receive some LSTs
self.lstTotal= int( msg.fields['numPeople'] )
self.lstNum= 0
self.pDebug( 'lstTotal= %d' % self.lstTotal )
#else: # List is up-to-date
#self.changeState( self.state )
#self.ping()
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, nameFrom ):
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()
|