#!/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: 
                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()

