Logo Search packages:      
Sourcecode: kmess version File versions

msnconnection.cpp

/***************************************************************************
                          msnconnection.cpp  -  description
                             -------------------
    begin                : Thu Jan 23 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "msnconnection.h"

#include <qregexp.h>
#include <qtimer.h>
#include <qmutex.h>
#include <qvaluelist.h>

#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kurl.h>

#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "mimemessage.h"

#ifdef KMESSDEBUG_CONNECTION // Area-specific debug statements
  #define KMESSDEBUG_CONNECTION_GENERAL
  #define KMESSDEBUG_CONNECTION_SOCKET
#endif

// The constructor
MsnConnection::MsnConnection(QString identifier)
: QObject(0, identifier),
   currentAccount_(0),
   ack_(0),
   disconnected_(false),
   initialized_(false),
   missedPings_(0),
   sendPings_(false),
   writeLocked_(false)
{
  // Create the socket
  socket_ = new KExtendedSocket();
  // Connect it up and set some defaults
  socket_->setSocketFlags( KExtendedSocket::bufferedSocket );
  socket_->enableRead( true );
  socket_->setTimeout( 30   );
  connect( socket_, SIGNAL(         readyRead()    ),
           this,    SLOT  (      dataReceived()    ) );
  connect( socket_, SIGNAL(  connectionFailed(int) ),
           this,    SLOT  (  connectionFailed(int) ) );
  connect( socket_, SIGNAL( connectionSuccess()    ),
           this,    SLOT  ( connectionSuccess()    ) );

  pingTimer_.stop();
  // Connect the ping timer to the sendPing slot
  connect( &pingTimer_,  SIGNAL(    timeout() ),
           this,         SLOT  (   sendPing() ) );

  // Configure the multi packet buffer
  multiPacketBuffer_.setAutoDelete(true);

  // Configure mutex. (TODO: check if this is really needed anymore)
  mproxywriteLocked_ = new QMutex();

#ifdef KMESSTEST
  ASSERT( socket_ != 0 );
#endif
}



// The destructor
MsnConnection::~MsnConnection()
{
  // Disconnect from the server on exit
  if(isConnected())
  {
    disconnectFromServer();
  }

  // Delete the socket
  delete socket_;
  delete mproxywriteLocked_;
#ifdef KMESSDEBUG_CONNECTION
  kdDebug() << "DESTROYED Connection" << endl;
#endif
}



// Insert "%" values for certain regular expressions, i.e. "%20" for " ".
void MsnConnection::addPercents(QString &word) const
{
  // TODO: replace with KURL::encode_string(), but test for compatibility with other clients.
  word = word.replace(QRegExp(" "),"%20");
}



// Detect a socket error
void MsnConnection::connectionFailed(int error)
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdWarning() << "Connection: Received error " << error << " from the socket"
              << " (socket status=" << socket_->status() << " systemerror=" << socket_->systemError() << ")." << endl;
#endif

  QString reason = KExtendedSocket::strError( socket_->status(), error );  // pokes sys_errlist[] / strerror()
  KMessageBox::error( 0, i18n("KMess could not connect to the MSN Messenger service.\nSystem error: %1 (code %2).").arg(reason).arg(error) );

  closeConnection();
  disconnectFromServer();
}


// If the connection is successful...
void MsnConnection::connectionSuccess()
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Socket connected successfully." << endl;
#endif
}


// Connect to the given server via the socket
bool MsnConnection::connectToServer(const QString& server, const int& port)
{
#ifdef KMESSTEST
  ASSERT( ( port >= 0 ) && ( port < 32768 ) );
#endif
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Connect to server at " << server << ":" << port << "." << endl;
  kdDebug() << "Socket status is " << socket_->socketStatus() << "." << endl;
#endif
  // Check that we are disconnected (the socket is null)
  if ( ! (    ( socket_->socketStatus() == KExtendedSocket::nothing )
           || ( socket_->socketStatus() == KExtendedSocket::done    ) ) )
  {
    kdDebug() << "Connection: Socket is not disconnected.  Already connected?  Status is " << socket_->socketStatus() << "." << endl;
    return false;
  }

  // Reset state vars
  disconnected_ = false;

  // This seams to solve some problems with re-connecting
  // to the switchboard if there is a timeout.
  socket_->reset();

  // Either connect directly or via the proxy based on the user's settings
  if (currentAccount_->getUseProxy())
  {
    return connectToServerViaProxy(server,port);
  }
  else
  {
    return connectToServerDirectly(server,port);
  }
}




// Connect to the given server, not using the proxy
bool MsnConnection::connectToServerDirectly(const QString& server, const int& port)
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Using direct connection." << endl;
#endif

  socket_->setAddress( server, port );
  // Apparently async lookups don't work correctly under Qt3
  socket_->lookup();
  int connectionSuccess = socket_->startAsyncConnect();
  return connectionSuccess == 0;
}



//Send different request(send authentication info, connect to the destination)
bool MsnConnection::connectToServerViaProxy(const QString& server, const int& port)
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Using proxy connection." << endl;
#endif

  if(currentAccount_->getProxyServer()=="")
  {
    return false;
  }
  struct hostent *hostent=NULL;
  hostent = gethostbyname( server);
  if( hostent==NULL )
  {
    return false;
  }

  //now only support ipv4

  //save server's ip address and port in struct sockaddr_in
  memcpy(&addr_destServer_.sin_addr,hostent->h_addr,4);
  addr_destServer_.sin_port=htons(port);

  //save server and port for later connection
  destServer_=server;
  destPort_=port;

  disconnect(socket_, SIGNAL(readyRead()),this, SLOT(dataReceived()) );
  disconnect( socket_, SIGNAL( connectionSuccess(void) ),this, SLOT( connectionSuccess(void)) );

  connect( socket_, SIGNAL( readyRead()             ),
           this,    SLOT  ( proxyDataReceived()          ) );
  connect( socket_, SIGNAL( connectionSuccess(void) ),
           this,    SLOT  ( proxyConnected(void)         ) );

  proxyState_=0;

  socket_->setAddress( currentAccount_->getProxyServer(), currentAccount_->getProxyPort());

  //at present, use synchronous connection
  return socket_->connect() == 0;
}



// Read data from the socket
void MsnConnection::dataReceived()
{
#ifdef KMESSTEST
  ASSERT( socket_ != 0 );
#endif

  char         rawblock[1024];
  QCString     block, commandLine;
  int          index, noBytesRemaining, noBytesRead, payloadLength = 0;
  QStringList  command;
  bool         gotPayloadCommand = false;
  bool         completePayload;
  QByteArray   payloadData;

  // For multi-packet messages
  MultiPacketMessage *multiPacketMessage = 0;
  QString messageId;


  do   // while(noBytesRemaining > 0)
  {
    // Read data from the socket
    noBytesRead      = socket_->readBlock( rawblock, 1024 );   // Read one block
    noBytesRemaining = socket_->bytesAvailable();              // Get number of available bytes

#ifdef KMESSDEBUG_CONNECTION_SOCKET
    kdDebug() << "MsnConnection - " << noBytesRead      << " bytes read. "
                                    << noBytesRemaining << " bytes remaining. " << endl;
#endif

    // Exit if it's an error code
    if( noBytesRead < 0 )
    {
      return;
    }

    // We've received some data, reset the ping timer
    resetPingTimer();

    // Add the block to the buffer
    buffer_.add( rawblock, noBytesRead );


    // Keep looping until we can't find any commands
    while(true)
    {
      // Get newline
      index = buffer_.findNewline();            // Find a newline
      if(index == -1) break;                    // Stop if it's not received yet

      // Read command and split
      commandLine = buffer_.left( index );      // Fetch one line from the buffer
      commandLine = commandLine.left( index );  // Not sure why this is needed (should already be this length)
      command     = QStringList::split(" ", QString::fromUtf8(commandLine.data(), commandLine.size()));   // Split into separate fields
      gotPayloadCommand = false;

      // If it's a payload message, detect whether the full payload is received, otherwise break.
      if( command[0] == "MSG" || isPayloadCommand(command[0]) )
      {
        // Message is followed by a payload section.
        // verify whether the full payload has arrived.
        payloadLength     = command[ command.count() - 1 ].toInt();  // Get payload length (last parameter)
        completePayload   = ( buffer_.length() >= (payloadLength + commandLine.length() + 2)); // Compare length with the size of the buffer
        gotPayloadCommand = true;

        if( ! completePayload )
        {
          // If the buffer doesn't contain the rest of the message,
          // just wait until "dataReceived" is called again.
#ifdef KMESSDEBUG_CONNECTION_SOCKET
          kdDebug() << "MsnConnection - Will wait for the rest of the message..." << endl;
#endif
          break;  // Break processing loop, return to top-reading-loop.
        }
      }


      // Handle debugging.
#ifdef KMESSDEBUG_SERVERMESSAGES
      kdDebug() << name() << " <<< " << commandLine << endl;
#endif
#ifdef KMESS_NETWORK_WINDOW
      KMESS_NET_RECEIVED( this, buffer_.left( index + 2 ) );
#endif


      // Remove command from buffer.
      // Should be done after the "! completePayload" part, keeping messages when
      buffer_.remove( index + 2 );

      // Get payload data
      if( gotPayloadCommand && payloadLength > 0 )
      {
        // Remove the command from the buffer
        // Read message from buffer,
        // delete message from buffer
        payloadData = buffer_.left(payloadLength);
        buffer_.remove( payloadLength );
      }


      // Handle debugging
#ifdef KMESS_NETWORK_WINDOW
      if( payloadLength > 0 )
      {
        KMESS_NET_RECEIVED( this, payloadData );
      }
#endif


      // Parse the messages.
      if( gotPayloadCommand  )
      {
        // It's a payload detected earlier.
        // See if it's a Mime message.
        if( command[0] != "MSG" )
        {
          // Other payload message types are parsed in the subclasses.
          parsePayloadMessage(command, payloadData);
        }
        else
        {
          // It's a mime message
          // Get the message from the buffer and create the mime message
          // Note we can't parse strings here, the message could contain binary p2p data.
          // UTF-8 characters are translated in the MimeMessage constructor itself.
          MimeMessage mimeMessage(payloadData);

          // Detect Multi-packet messages
          if( ! mimeMessage.hasField("Message-ID") )
          {
            // It's a normal message, parse it.
            parseMessage( command, mimeMessage );
          }
          else
          {
            // Handle multi-packet message parts.
#ifdef KMESSDEBUG_CONNECTION_SOCKET
            kdDebug() << "MsnConnection - Message is a multi-packet message, buffering." << endl;
#endif
            // Get associated multi-packet message buffer, create one if it doesn't exist yet.
            messageId          = mimeMessage.getValue("Message-ID");
            multiPacketMessage = multiPacketBuffer_.find(messageId);
            if( multiPacketMessage == 0 )
            {
              multiPacketMessage = new MultiPacketMessage();
              multiPacketBuffer_.insert(messageId, multiPacketMessage);
            }

            // Add the message to the handler.
            multiPacketMessage->addChunk(mimeMessage);

            // Parse message if complete multi-packet message is received.
            if( multiPacketMessage->isComplete() )
            {
              parseMessage( command, multiPacketMessage->getMessage() );
              multiPacketBuffer_.remove(messageId);   // auto-deletes item.
            }
          }
        }
      }
      else if( command[0] == "QNG" )
      {
        // Response from the internal ping timer.
        pingReceived_ = true;
        missedPings_  = 0;
      }
      else
      {
        // It's a normal command, handle it in the subclass.
        // Parse the command.
        parseCommand( command );
      }


#ifdef KMESSDEBUG_CONNECTION_SOCKET
      kdDebug() << "MsnConnection - Message parsed, buffer length is " << buffer_.length() << endl;
#endif
    }
  }
  while(noBytesRemaining > 0);
}



// Disconnect from the server, if connected
// The "isTransfer" is used if KMess moves to another Notication-server.
void MsnConnection::disconnectFromServer( bool isTransfer )
{
  disconnected_ = true;

  // stop any more pings
  setSendPings( false );

  multiPacketBuffer_.clear();

  // If the socket is connected...
  if( isConnected() )
  {
    // Send "OUT"
    writeData("OUT\r\n");
  }

#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Flush the socket." << endl;
#endif

  socket_->flush();

#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: Close the socket." << endl;
#endif

  socket_->closeNow();
  socket_->reset();

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_CLOSE(this);
#endif

  // Always fire the event to update others.
  if ( ! isTransfer )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kdDebug() << "Connection: emit 'disconnected'." << endl;
#endif
    emit disconnected();
  }
}



// Return an acknowledgement number
int MsnConnection::getAck()
{
  return ack_++;
}



// Get the IP address of this machine.
QString MsnConnection::getLocalIp() const
{
#ifdef KMESSDEBUG_SWITCHBOARD_FILETRANSFER
  kdDebug() << "Connection::getLocalIp()" << endl;
#endif
  QString ip = "";
  const KSocketAddress *address;
  address = socket_->localAddress();
  if ( address != 0 )
  {
    ip = address->pretty();
    // Local address gives something like "1.2.3.4 port 5" in the english
    //  version, and "1.2.3.4-Portnammersomething 5" in the german version.
    // So, to get the port, replace any "-"'s with " " then get everything
    //  to the left of the first space.  That should work.
    ip = ip.replace( QRegExp("-"), " " );
#ifdef KMESSDEBUG_SWITCHBOARD_FILETRANSFER
    kdDebug() << "C:    Raw IP is " << ip << endl;
#endif
    if ( ip.contains(" ") )
    {
      ip = ip.left( ip.find(" ") );
    }
#ifdef KMESSDEBUG_SWITCHBOARD_FILETRANSFER
    kdDebug() << "C:    IP is " << ip << endl;
#endif
  }
  else
  {
    kdDebug() << "Connection::getLocalIp(): WARNING - IP not found.  Local address was null?" << endl;
  }
#ifdef KMESSDEBUG_SWITCHBOARD_FILETRANSFER
  kdDebug() << "C:    Done getLocalIp()." << endl;
#endif
  return ip;
}



// Get whether we're sending pings or not
bool MsnConnection::getSendPings() const
{
  return sendPings_;
}



// Extract some information from a message (like "Name: Joe;" - extract the "Joe")
QString MsnConnection::getValueFromMessage(const QString& field, const QString& message, bool goToEndOfLine) const
{
  QString value;
  int      fieldStart, fieldEnd;
#ifdef KMESSTEST
  ASSERT( message.contains( field ) );
#endif
  if ( !message.contains( field ) )
  {
    return "";
  }
  // Find the occurance of "field" in the message
  fieldStart = message.find( field );
  fieldEnd = message.find( " ", fieldStart );
  if ( fieldEnd < 0 )
  { // For some reason, there's no space after the field.
    return "";
  }
  // Take everything to the right of the message beginning
  value = message.right( message.length() - fieldEnd - 1 );
  // The value should be anything before returns or spaces
  value = value.left( value.find("\r\n") );
  if ( !goToEndOfLine )
  {
    value = value.left( value.find(" ") );
  }
  // Strip semicolons off the end of the value
  if ( value.right(1) == ";" )
  {
    value = value.left( value.length() - 1 );
  }
  return value;
}



// Whether or not the class is connected
bool MsnConnection::isConnected() const
{
  return ( socket_->socketStatus() == KExtendedSocket::connected );
}



// Test whether the given command is a payload command
bool MsnConnection::isPayloadCommand(const QString &/*command*/) const
{
  // Return false by default, is overwritten in MsnNotificationConnection
  return false;
}


// Replace any "%" values with their regular values, i.e. "%20" to " ".
void MsnConnection::removePercents(QString &word) const
{
  word = KURL::decode_string(word);
}



// Initialize the object
bool MsnConnection::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "Connection already initialized!" << endl;
    return false;
  }
#ifdef KMESSTEST
  ASSERT( currentAccount_ == 0 );
#endif
  currentAccount_ = CurrentAccount::instance();
  if ( currentAccount_ == 0 )
  {
    kdDebug() << "Connection: Couldn't get an instance of the current account." << endl;
    return false;
  }
  initialized_ = true;
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
  ASSERT( socket_ != 0 );
#endif
  return initialized_;
}




// If the connection to proxy is successful...
void MsnConnection::proxyConnected(void)
{
  char command[9];
  bzero(command,9);

  proxyState_=1;

  switch(currentAccount_->getProxyType() )
  {
    case Account::TYPE_SOCKS4:    // Socks4 Proxy

    command[0]='\x04';  //version num
    command[1]='\x01';  //\x01 stands for "Connect"
    memcpy(&command[2],&addr_destServer_.sin_port,2);
    memcpy(&command[4],&addr_destServer_.sin_addr,4);
    writeProxyData(command,9);
      break;

    case Account::TYPE_SOCKS5:    // Socks5 Proxy

      //if both uid and password are not empty, the proxy may need authentication.
      if( currentAccount_->getProxyUID()!=""&& currentAccount_->getProxyPassword()!="")
    {
        command[0]='\x05';  // Version num
        command[1]='\x02';  // METHOD number
        command[2]='\x02';  // Need authentication
        command[3]='\x00';  // Need not authentication
        // Proxy will pick a METHOD from the upper two

        writeProxyData(command,4);
    }
    else    //We assume that no authentication is needed
    {
        command[0]='\x05';
        command[1]='\x01';
        command[2]='\x00';

        writeProxyData(command,3);
    }
      break;

    case Account::TYPE_HTTP:  //Have some problems now, I'll commit later
    break;
  }
}



// Read proxy reply from the socket
void MsnConnection::proxyDataReceived(void)
{
int nbytesRead,nbytesAvailable;
  char *buf=NULL;

  if((nbytesAvailable=socket_->bytesAvailable())<=0)
  {
    qWarning("socket_ has no information to read.\n");
    return;
  }
  if((buf=new char [nbytesAvailable+1])==NULL)
  {
    qWarning("Can't allocate space.\n");
    return;
  }

  bzero(buf, nbytesAvailable+1);

  nbytesRead=socket_->readBlock(buf, nbytesAvailable+1);
  if(nbytesRead<=0||nbytesRead>nbytesAvailable)
  {
    qWarning("Reading from socket error");
    return;
  }

  switch(currentAccount_->getProxyType())
  {
    case Account::TYPE_SOCKS4:

      if(proxyState_==1)
      {
      if( nbytesRead!=8 )
      {
          //Reply must have exact 8 bytes
          setProxyState(PROXYERROR );
       return;
      }

      if( buf[1]==90 )  //Request for connecting to the host granted
        {
          setProxyState(PROXYHOSTCONNECTED );
        }
      else
        {
          //buf[1]==91 stands for request rejected or failed
          //92 stands for request rejected becasue SOCKS server cannot connect to identd on the client
          //93 stands for request rejected because the client program and identd report different user-ids
          setProxyState(PROXYFAIL );
        }
      }
      break;

    case  Account::TYPE_HTTP:

      if(strncmp(buf,"HTTP/1.",7)==0&&strncmp(buf+9,"200",3)==0)
      {
        setProxyState(PROXYHOSTCONNECTED);
      }
      else
      {
          /*
          Status-Code    = "200"   ; OK
                      | "201"   ; Created
                      | "202"   ; Accepted
                      | "204"   ; No Content
                      | "301"   ; Moved Permanently
                      | "302"   ; Moved Temporarily
                      | "304"   ; Not Modified
                      | "400"   ; Bad Request
                      | "401"   ; Unauthorized
                      | "403"   ; Forbidden
                      | "404"   ; Not Found
                      | "500"   ; Internal Server Error
                      | "501"   ; Not Implemented
                      | "502"   ; Bad Gateway
                      | "503"   ; Service Unavailable
                      | extension-code
          */
        setProxyState(PROXYFAIL);
      }
      break;

    case  Account::TYPE_SOCKS5:

      socks5_reply(buf,nbytesRead);
      break;
    }
}



// Show a message that the proxy failed
void MsnConnection::proxyFailed()
{
  QString message;
  // Show a message to the user
  message = i18n("There was a problem connecting to the proxy.");
  KMessageBox::error( 0, message );

  // Disconnect from the server
  closeConnection();
  disconnectFromServer();
}



// Resets the ping timer
void MsnConnection::resetPingTimer()
{
  if ( sendPings_ )
  {
    pingTimer_.stop();

    // 30s ping timer
    pingTimer_.start( 1000 * 30 );
  }
}



// Send a command to the server
int MsnConnection::sendCommand(const QString& prefix, const QString &text)
{
#ifdef KMESSTEST
  ASSERT( prefix.length() == 3 );
  if ( ( prefix != "QRY" ) && ( prefix != "MSG" ) )
  {
    ASSERT( text.right( 2 ) == "\r\n" );
  }
#endif

  int      ack = getAck();
  QString ackString;
  // Get an acknowledgement number and convert it to a string
  ackString.sprintf("%d", ack);

#ifdef KMESSTEST
  ASSERT( ackString.toInt() == ack );
#endif
//#ifdef KMESSDEBUG_CONNECTION_GENERAL
//  kdDebug() << "MsnConnection::sendCommand: Sending " << prefix << " command (" << ack << ")." << endl;
//#endif

  // Send the data to the server
  QString command = prefix + " " + ackString + " " + text;
  writeData( command );

  // Return the ack used
  return ack;
}



// Send a command to the server, returning the ack
int MsnConnection::sendMimeMessage(AckType ackType, const MimeMessage &message)
{
  // Determine which type to send.
  // Normally a char[4] lookup array would be sufficient,
  // but this is easier to use in the string concatenation with Qt
  const char *ackTypeStr = "?";
  switch(ackType)
  {
    case ACK_NONE:        ackTypeStr = "U"; break;
    case ACK_NAK_ONLY:    ackTypeStr = "N"; break;
    case ACK_ALWAYS:      ackTypeStr = "A"; break;
    case ACK_ALWAYS_P2P:  ackTypeStr = "D"; break;
  }

  // Create the "MSG id type length" header
  QByteArray rawMessage = message.getMessage();
  QString    command;

  // Build the command
  int ack = getAck();
  command.sprintf("MSG %u %s %u\r\n", ack, ackTypeStr, rawMessage.size());

//#ifdef KMESSDEBUG_CONNECTION_GENERAL
//  kdDebug() << "MsnConnection::sendMimeMessage: Sending MSG command (" << ack << ")." << endl;
//#endif

  // Write the data
  writeData(command);
  writeBinaryData(rawMessage);

  // Return the ack used
  return ack;
}




// Send a payload command to the server, convenience function
int MsnConnection::sendPayloadMessage(const QString &prefix, const QString &arguments, const QString &payload)
{
  // Avoid converting data that has 0 bytes.
  if( payload.isEmpty() )
  {
    return sendPayloadMessage(prefix, arguments, QByteArray());
  }

  // Get UTF-8 encoded raw string.
  QCString utf8Payload = payload.utf8();

  // Downgrade to QByteArray.
  // If this is not done manually, otherwise a '\0' will be padded.
  QByteArray binPayload;
  binPayload.duplicate(utf8Payload.data(), utf8Payload.length());

  // Send payload
  return sendPayloadMessage(prefix, arguments, binPayload);
}



// Send a payload command to the server
int MsnConnection::sendPayloadMessage(const QString &prefix, const QString &arguments, const QByteArray &payload)
{
  QString command;
  int ack = getAck();

  // Build the command
  command = prefix + " " + QString::number(ack);
  if( ! arguments.isEmpty() )
  {
    command += " " + arguments;
  }
  command += " " + QString::number(payload.size()) + "\r\n";

  // Write the data
  writeData(command);

  if( payload.size() > 0 )
  {
    writeBinaryData(payload);
  }

  // Return the ack used
  return ack;
}



// Send a "ping" to the server
void MsnConnection::sendPing()
{
  QString message;

  if ( pingReceived_ == false )
  {
    missedPings_ ++;

    if ( missedPings_ == 1 )
    {
      message += i18n( "1 ping lost" );
    }
    else
    {
      message += i18n( "%1 pings lost" ).arg(missedPings_);
    }

    emit statusMessage( message, 2 );
  }
  else
  {
    emit statusMessage( i18n( "Connection good" ), 1 );
  }

  pingReceived_ = false;

  // Assume the connection was lost when 3 pings are missed.
  if ( missedPings_ == 3 )
  {
    KMessageBox::error( 0, i18n("The connection to the server was lost.") );
    closeConnection();
    disconnectFromServer(false);
  }

  writeData( "PNG\r\n" );
}



// Set whether we're sending pings or not (also resets ping timer)
void MsnConnection::setSendPings( bool sendPings )
{
  sendPings_ = sendPings;
  missedPings_ = 0;

  pingReceived_ = true;

  if ( sendPings_ )
  {
    sendPing();

    resetPingTimer();
  }
  else
  {
    pingTimer_.stop();
  }
}




// Dealing different proxy state, implemented in class MsnNotificationConnection
void MsnConnection::setProxyState(PROXYSTATE state)
{
  switch(state)
  {
    case PROXYCONNECTED:

      break;

    case PROXYHOSTCONNECTED:

      proxyState_=0;
      if(socket_!=0)
      {
        disconnect(socket_, SIGNAL(readyRead()),this, SLOT(proxyDataReceived()) );
        disconnect( socket_, SIGNAL( connectionSuccess(void) ),this, SLOT( proxyConnected(void)) );
        connect( socket_, SIGNAL(readyRead()),this, SLOT(dataReceived()) );
        connect( socket_, SIGNAL( connectionSuccess(void) ),this, SLOT( connectionSuccess(void)) );
      }
      connectionSuccess();  // Send version commands
      break;

    case PROXYERROR:
    case PROXYFAIL:
      proxyState_ = 0;
      if(socket_!=0)
      {
        disconnect( socket_, SIGNAL(readyRead()),this, SLOT(proxyDataReceived()) );
        disconnect( socket_, SIGNAL( connectionSuccess(void) ),this, SLOT( proxyConnected(void)) );
        connect( socket_, SIGNAL(readyRead()),this, SLOT(dataReceived()) );
        connect( socket_, SIGNAL( connectionSuccess(void) ),this, SLOT( connectionSuccess(void)) );
      }
      proxyFailed();
      break;
  }
}



// Send authentication info to the socks5 server
void MsnConnection::socks5_auth(void)
{
  QString uid=currentAccount_->getProxyUID();
  QString pwd=currentAccount_->getProxyPassword();

  int ulen = uid.length();
  int plen = pwd.length();
  char * command = new char[3+ulen+plen];
  sprintf((char *)command,"  %s %s",uid.local8Bit().data(),pwd.local8Bit().data());

  command[0]='\x01';  // Version of the subnegotiation, not the protocol
  command[1]=ulen;
  command[2+ulen] = plen;

  writeProxyData( command,3+ulen+plen );
  delete []command;
}



// Finally request to connect to the host via the socks5 proxy
void MsnConnection::socks5_connect(void)
{
  char command[10];
  bzero(command,10);

  command[0]='\x05';  // Protocol version
  command[1]='\x01';  // CONNECT
  command[2]='\x00';  // RESERVED, now is '\x00'
  command[3]='\x01';  // Indicates IP V4 address
  memcpy( command+4, &addr_destServer_.sin_addr,4 ); //4 bytes IP address
  memcpy( command+8, &addr_destServer_.sin_port,2 );  //2 bytes port

  writeProxyData(command, 10 );
}



// Send different request(send authentication info, connect to the destination)
// to the socks5 server according to its reply
void MsnConnection::socks5_reply(const char *buf,int nread)
{
  switch( proxyState_)
  {
    case 1:       // Reply indicates wheather to use authentication

      if( nread!=2 )
    {
        //Reply must have exact 2 bytes
        setProxyState(PROXYERROR );
        return;
    }

    switch( buf[1] )
    {
        case '\x00':  // No authentation needed

          socks5_connect();
          proxyState_=3;
          return;

        case '\x02':  // Authentation needed

          socks5_auth();
          proxyState_=2;
          return;

        case '\xff':    //NO ACCEPTABLE METHODS
        default:

          //'\x03' to '\x7F'      IANA ASSIGNED
          //'\x80' to '\xFE'      RESERVED FOR PRIVATE METHODS
          setProxyState(PROXYFAIL);
          return;
      }
      break;

    case 2: //Reply indicates whether the uid and password sent are correct

      if( nread!=2 )
    {
        //Reply must have exact 2 bytes
        setProxyState(PROXYERROR);
        return;
    }

    if( buf[1]!='\00')  //auth fails
    {
      setProxyState(PROXYFAIL );
      return;
    }

      // uid and password are correct, and now start a connection to the messenger's host
    socks5_connect();
    proxyState_=3;
    break;

    case 3:        // Reply indicates whether the proxy has made a connection to the host.

    if( nread!=10 )
    {
      //Reply must have exact 10 bytes
      setProxyState(PROXYERROR );
      return;
    }

    if(buf[1]!='\00' )  //connection fails
    {
      setProxyState(PROXYFAIL );
      return;
    }

      // Connection to the host is established
    setProxyState(PROXYHOSTCONNECTED );
    break;

    default:
      proxyState_=0;
    break;
  }
}



// Write data to the socket
void MsnConnection::writeData(const QString& data)
{
#ifdef KMESSTEST
  if ( ( data.left(3) != "QRY" ) && ( data.left(3) != "MSG" ) )
  {
    ASSERT( ( data.right( 2 ) ) == "\r\n" );
  }
#endif
  uint noBytesWritten;

  if( ! isConnected() )
  {
    if( ! disconnected_ )
    {
      kdWarning() << "Connection: Attempting to write data to a disconnected socket, closing socket." << endl;

      // First disable pings, KMessageBox blocks here but it's private event loop would keep processing more ping timeouts.
      setSendPings(false);
      KMessageBox::error( 0, i18n("The remote server has closed the connection.") );

      // Close connection, updates GUI.
      closeConnection();
      disconnectFromServer(false);
    }
    return;
  }

  while ( writeLocked_ )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET
    kdDebug() << "Connection: WRITE DATA IS LOCKED TO AVOID WRITING OVERLAPPING DATA TO THE SOCKET." << endl;
#endif
  }
  // Lock the writing of data.
  writeLocked_ = true;

#ifdef KMESSDEBUG_SERVERMESSAGES
  QString debugData = data;
  QRegExp removeCrLf("\r\n$");
  kdDebug() << name() << " >>> " << debugData.replace(removeCrLf, QString::null) << endl;
#endif

  QCString unicode = data.utf8();
  noBytesWritten = socket_->writeBlock( unicode.data(), unicode.length() );

  if ( noBytesWritten != unicode.length() ) // Note that the previous code used data.length instead of unicode.length(), but I
  {                                         // changed this since that is the string we send to the socket... (could be wrong here)
    kdDebug() << "Connection: WARNING - Wanted to write " << data.length() << " bytes to the socket, wrote " << noBytesWritten << "." << endl;
  }

  // Unlock the writing of data.
  writeLocked_ = false;

  // Append message to the network window
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_SENT( this, data.utf8() );
#endif
}


// Write data to the socket without conversions
void MsnConnection::writeBinaryData(const QByteArray& data)
{
  int noBytesWritten;

  while ( writeLocked_ )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET
    kdDebug() << "Connection: WRITE DATA IS LOCKED TO AVOID WRITING OVERLAPPING DATA TO THE SOCKET." << endl;
#endif
  }
  // Lock the writing of data.
  writeLocked_ = true;

  if ( isConnected() )
  {
#ifdef KMESSDEBUG_SERVERMESSAGES

//  To display the entire contents:
/*
    QByteArray debugData = data.copy();  // because QByteArray is explicitly shared
    for(int i = 0; i < debugData.size(); i++) { if(debugData[i] < 10) debugData[i] = '?'; }
    kdDebug() << name() << " >>> " << QString::fromUtf8(debugData.data(), debugData.size()) << endl;
*/
//  To display the first readable part:
    kdDebug() << name() << " >>> " << QString(data) << endl;
#endif

    int dataLength = 0;

    dataLength = data.size();
    // NOTE: Assumes the caller used the right encoding already...!
    //       using .utf8() here breaks the ascii zero chars again.
    noBytesWritten = socket_->writeBlock( data.data(), dataLength );

    if ( noBytesWritten != dataLength )
    {
      kdDebug() << "Connection: WARNING - Wanted to write " << data.size() << " bytes to the socket, wrote " << noBytesWritten << "." << endl;
    }
  }
  else
  {
    kdDebug() << "Connection: WARNING - Attempting to write data to a disconnected socket." << endl;
  }

  // Unlock the writing of data.
  writeLocked_ = false;

  // Append the message to the network window
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_SENT(this, data);
#endif
}


// Write data to the socket dealing requests and replies with the proxy
void MsnConnection::writeProxyData(const char *buf,int len)
{
  mproxywriteLocked_->lock();
  socket_->writeBlock(buf, len);
  //socket_->flush();
  mproxywriteLocked_->unlock();
}


#include "msnconnection.moc"

Generated by  Doxygen 1.6.0   Back to index