//============================================================================= // // File : kvi_irclink.cpp // Created on Mon 03 May 2004 01:45:42 by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2004 Szymon Stefanek // // 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 opinion) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #define __KVIRC__ #include "kvi_irclink.h" #include "kvi_dns.h" #include "kvi_locale.h" #include "kvi_ircserverdb.h" #include "kvi_proxydb.h" #include "kvi_error.h" #include "kvi_out.h" #include "kvi_options.h" #include "kvi_ircsocket.h" #include "kvi_console.h" #include "kvi_netutils.h" #include "kvi_internalcmd.h" #include "kvi_frame.h" #include "kvi_mexlinkfilter.h" #include "kvi_garbage.h" #include "kvi_malloc.h" #include "kvi_memmove.h" #include "kvi_ircconnection.h" #include "kvi_ircconnectiontarget.h" #include "kvi_ircconnectiontargetresolver.h" #include "kvi_ircsocket.h" #include "kvi_databuffer.h" #define __KVI_DEBUG__ #include "kvi_debug.h" #include extern KVIRC_API KviIrcServerDataBase * g_pIrcServerDataBase; extern KVIRC_API KviProxyDataBase * g_pProxyDataBase; extern KVIRC_API KviGarbageCollector * g_pGarbageCollector; KviIrcLink::KviIrcLink(KviIrcConnection * pConnection) : TQObject() { m_pConnection = pConnection; m_pTarget = pConnection->target(); m_pConsole = m_pConnection->console(); m_pSocket = 0; m_pLinkFilter = 0; m_pResolver = 0; m_pReadBuffer = 0; // incoming data buffer m_uReadBufferLen = 0; // incoming data buffer length m_uReadPackets = 0; // total packets read per session m_eState = Idle; } KviIrcLink::~KviIrcLink() { if(m_pResolver)delete m_pResolver; destroySocket(); if(m_pReadBuffer)kvi_free(m_pReadBuffer); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // KviIrcSocket management // void KviIrcLink::linkFilterDestroyed() { // ops.. the link filter has been destroyed without permission :D // this should NEVER happen (?) m_pLinkFilter = 0; m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Ops... for some reason the link filter object has been destroyed")); } void KviIrcLink::destroySocket() { if(m_pLinkFilter) { TQObject::disconnect(m_pLinkFilter,0,this,0); // the module extension server links must be destroyed in the module that provided it m_pLinkFilter->die(); m_pLinkFilter = 0; } if(m_pSocket) { delete m_pSocket; m_pSocket = 0; } } void KviIrcLink::createSocket(const TQString &szLinkFilterName) { destroySocket(); // make sure we do not leak memory m_pSocket = new KviIrcSocket(this); if(szLinkFilterName.isEmpty())return; if(KviTQString::equalCI(szLinkFilterName,"irc"))return; m_pLinkFilter = (KviMexLinkFilter *)g_pModuleExtensionManager->allocateExtension("linkfilter", szLinkFilterName.utf8().data(),m_pConsole,0,this,szLinkFilterName.utf8().data()); if(m_pLinkFilter) { connect(m_pLinkFilter,TQT_SIGNAL(destroyed()),this,TQT_SLOT(linkFilterDestroyed())); m_pConsole->output(KVI_OUT_SYSTEMMESSAGE, __tr2qs("Using filtered IRC protocol: Link filter is \"%Q\""),&szLinkFilterName); return; } m_pConsole->output(KVI_OUT_SYSTEMWARNING, __tr2qs("Failed to set up the link filter \"%Q\", will try with plain IRC"),&szLinkFilterName); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Connection related operations // void KviIrcLink::abort() { if(m_pSocket) { m_pSocket->abort(); return; } if(m_pResolver) { m_pResolver->abort(); return; } } void KviIrcLink::start() { m_eState = Connecting; if(m_pResolver)delete m_pResolver; // this should never happen m_pResolver = new KviIrcConnectionTargetResolver(m_pConnection); connect(m_pResolver,TQT_SIGNAL(terminated()),this,TQT_SLOT(resolverTerminated())); m_pResolver->start(m_pTarget); } void KviIrcLink::resolverTerminated() { if(!m_pResolver) { tqDebug("Oops... resoverTerminated() triggered without a resolver ?"); return; } if(m_pResolver->status() != KviIrcConnectionTargetResolver::Success) { m_eState = Idle; m_pConnection->linkAttemptFailed(m_pResolver->lastError()); return; } // resolver terminated succesfully delete m_pResolver; m_pResolver = 0; createSocket(m_pTarget->server()->linkFilter()); int iErr = m_pSocket->startConnection(m_pTarget->server(),m_pTarget->proxy(), m_pTarget->bindAddress().isEmpty() ? 0 : m_pTarget->bindAddress().utf8().data()); if(iErr != KviError_success) { TQString strDescription(KviError::getDescription(iErr)); m_pConsole->output(KVI_OUT_SYSTEMERROR, __tr2qs("Failed to start the connection: %Q"), &strDescription); // &(KviError::getDescription(iErr))); m_eState = Idle; m_pConnection->linkAttemptFailed(iErr); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // // Incoming data processing // void KviIrcLink::processData(char * buffer,int len) { if(m_pLinkFilter) { m_pLinkFilter->processData(buffer,len); return; } register char *p=buffer; char *beginOfCurData = buffer; int bufLen = 0; char *messageBuffer = (char *)kvi_malloc(1); while(*p) { if((*p == '\r' )||(*p == '\n')) { //found a CR or LF... //prepare a message buffer bufLen = p - beginOfCurData; //check for previous unterminated data if(m_uReadBufferLen > 0) { __range_valid(m_pReadBuffer); messageBuffer = (char *)kvi_realloc(messageBuffer,bufLen + m_uReadBufferLen + 1); kvi_memmove(messageBuffer,m_pReadBuffer,m_uReadBufferLen); kvi_memmove((void *)(messageBuffer + m_uReadBufferLen),beginOfCurData,bufLen); *(messageBuffer + bufLen + m_uReadBufferLen) = '\0'; m_uReadBufferLen = 0; kvi_free(m_pReadBuffer); m_pReadBuffer = 0; } else { __range_invalid(m_pReadBuffer); messageBuffer = (char *)kvi_realloc(messageBuffer,bufLen + 1); kvi_memmove(messageBuffer,beginOfCurData,bufLen); *(messageBuffer + bufLen) = '\0'; } m_uReadPackets++; // FIXME: actually it can happen that the socket gets disconnected // in a incomingMessage() call. // The problem might be that some other parts of kvirc assume // that the irc context still exists after a failed write to the socket // (some parts don't even check the return value!) // If the problem presents itself again then the solution is: // disable queue flushing for the "incomingMessage" call // and just call queue_insertMessage() // then after the call terminates flush the queue (eventually detecting // the disconnect and thus destroying the irc context). // For now we try to rely on the remaining parts to handle correctly // such conditions. Let's see... if(strlen(messageBuffer)>0) m_pConnection->incomingMessage(messageBuffer); if(m_pSocket->state() != KviIrcSocket::Connected) { // Disconnected in KviConsole::incomingMessage() call. // This may happen for several reasons (local event loop // with the user hitting the disconnect button, a scripting // handler event that disconnects explicitly) // // We handle it by simply returning control to readData() which // will return immediately (and safely) control to TQt kvi_free(messageBuffer); return; } while(*p && ((*p=='\r')||(*p=='\n')) )p++; beginOfCurData = p; } else p++; } //now *p == '\0' //beginOfCurData points to '\0' if we have //no more stuff to parse , or points to something //different than '\r' or '\n'... if(*beginOfCurData) { //Have remaining data...in the local buffer bufLen = p - beginOfCurData; if(m_uReadBufferLen > 0) { //and there was more stuff saved... (really slow connection) __range_valid(m_pReadBuffer); m_pReadBuffer =(char *)kvi_realloc(m_pReadBuffer,m_uReadBufferLen + bufLen); kvi_memmove((void *)(m_pReadBuffer+m_uReadBufferLen),beginOfCurData,bufLen); m_uReadBufferLen += bufLen; } else { // __range_invalid(m_pReadBuffer); m_uReadBufferLen = bufLen; m_pReadBuffer =(char *)kvi_malloc(m_uReadBufferLen); kvi_memmove(m_pReadBuffer,beginOfCurData,m_uReadBufferLen); } //The m_pReadBuffer contains at max 1 irc message... //that can not be longer than 510 bytes (the message is not CRLF terminated) // FIXME: Is this limit *really* valid on all servers ? if(m_uReadBufferLen > 510)tqDebug("WARNING : Receiving an invalid irc message from server."); } kvi_free(messageBuffer); } ///////////////////////////////////////////////////////////////////////////////////////////////////////// // // Outgoing data processing // bool KviIrcLink::sendPacket(KviDataBuffer * pData) { if(!m_pSocket) { delete pData; pData = 0; return false; } // if we have a filter, let it do its job if(m_pLinkFilter) return m_pLinkFilter->sendPacket(pData); return m_pSocket->sendPacket(pData); } void KviIrcLink::socketStateChange() { switch(m_pSocket->state()) { case KviIrcSocket::Connected: m_eState = Connected; m_pConnection->linkEstabilished(); break; case KviIrcSocket::Idle: { State old = m_eState; m_eState = Idle; switch(old) { case Connecting: m_pConnection->linkAttemptFailed(m_pSocket->lastError()); break; case Connected: m_pConnection->linkTerminated(); break; default: // currently can be only Idle tqDebug("Ooops... got a KviIrcSocket::Idle state change when KviIrcLink::m_eState was Idle"); break; } } break; case KviIrcSocket::Connecting: m_pConsole->output(KVI_OUT_CONNECTION,__tr2qs("Contacting %Q %s (%s) on port %u"), connection()->proxy() ? &(__tr2qs("proxy host")) : &(__tr2qs("IRC server")), connection()->proxy() ? connection()->proxy()->m_szHostname.ptr() : connection()->server()->m_szHostname.utf8().data(), connection()->proxy() ? connection()->proxy()->m_szIp.ptr() : connection()->server()->m_szIp.utf8().data(), connection()->proxy() ? connection()->proxy()->m_uPort : connection()->server()->m_uPort); break; case KviIrcSocket::SSLHandshake: m_pConsole->output(KVI_OUT_CONNECTION,__tr2qs("Low-level transport connection established [%s (%s:%u)]"), connection()->proxy() ? connection()->proxy()->m_szHostname.ptr() : connection()->server()->m_szHostname.utf8().data(), connection()->proxy() ? connection()->proxy()->m_szIp.ptr() : connection()->server()->m_szIp.utf8().data(), connection()->proxy() ? connection()->proxy()->m_uPort : connection()->server()->m_uPort); m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Starting Secure Socket Layer handshake")); break; case KviIrcSocket::ProxyLogin: m_pConsole->output(KVI_OUT_CONNECTION,__tr2qs("%Q established [%s (%s:%u)]"), connection()->socket()->usingSSL() ? &(__tr2qs("Secure proxy connection")) : &(__tr2qs("Proxy connection")), connection()->proxy()->m_szHostname.ptr(), connection()->proxy()->m_szIp.ptr(), connection()->proxy()->m_uPort); m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Negotiating relay information")); break; case KviIrcSocket::ProxyFinalV4: m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Sent connection request, waiting for acknowledgement")); break; case KviIrcSocket::ProxyFinalV5: m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Sent target host data, waiting for acknowledgement")); break; case KviIrcSocket::ProxySelectAuthMethodV5: m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Sent auth method request, waiting for acknowledgement")); break; case KviIrcSocket::ProxyUserPassV5: m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Sent username and password, waiting for acknowledgement")); break; case KviIrcSocket::ProxyFinalHttp: m_pConsole->outputNoFmt(KVI_OUT_CONNECTION,__tr2qs("Sent connection request, waiting for \"HTTP 200\" acknowledgement")); break; } }