/*************************************************************************** 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"