//============================================================================= // // File : kvi_ircconnection.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_ircconnection.h" #include "kvi_ircconnectiontarget.h" #include "kvi_ircconnectionuserinfo.h" #include "kvi_ircconnectionserverinfo.h" #include "kvi_ircconnectionstatedata.h" #include "kvi_ircconnectionantictcpflooddata.h" #include "kvi_ircconnectionnetsplitdetectordata.h" #include "kvi_ircconnectionasyncwhoisdata.h" #include "kvi_ircconnectionstatistics.h" #include "kvi_irclink.h" #include "kvi_ircsocket.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_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_debug.h" #include "kvi_channel.h" #include "kvi_query.h" #include "kvi_app.h" #include "kvi_databuffer.h" #include "kvi_notifylist.h" #include "kvi_dns.h" #include "kvi_defaults.h" #include "kvi_sparser.h" #include "kvi_ircdatastreammonitor.h" #include "kvi_databuffer.h" #include "kvi_lagmeter.h" #include "kvi_kvs_eventtriggers.h" #include "kvi_kvs_script.h" #include "kvi_mirccntrl.h" #include "kvi_useridentity.h" #include #include extern KVIRC_API KviIrcServerDataBase * g_pIrcServerDataBase; extern KVIRC_API KviProxyDataBase * g_pProxyDataBase; extern KVIRC_API KviGarbageCollector * g_pGarbageCollector; KviIrcConnection::KviIrcConnection(KviIrcContext * pContext,KviIrcConnectionTarget * pTarget,KviUserIdentity * pIdentity) : TQObject() { m_bIdentdAttached = false; m_pContext = pContext; m_pConsole = pContext->console(); m_pFrame = m_pConsole->frame(); m_pTarget = pTarget; m_pUserIdentity = pIdentity; m_pChannelList = new KviPointerList; m_pChannelList->setAutoDelete(false); m_pQueryList = new KviPointerList; m_pQueryList->setAutoDelete(false); m_pLink = new KviIrcLink(this); m_pUserDataBase = new KviIrcUserDataBase(); m_pUserInfo = new KviIrcConnectionUserInfo(); m_pServerInfo = new KviIrcConnectionServerInfo(); m_pStateData = new KviIrcConnectionStateData(); m_pAntiCtcpFloodData = new KviIrcConnectionAntiCtcpFloodData(); m_pNetsplitDetectorData = new KviIrcConnectionNetsplitDetectorData(); m_pAsyncWhoisData = new KviIrcConnectionAsyncWhoisData(); m_pStatistics = new KviIrcConnectionStatistics(); m_pNotifyListTimer = 0; m_pNotifyListManager = 0; m_pLocalhostDns = 0; m_pLagMeter = 0; m_eState = Idle; setupTextCodec(); } KviIrcConnection::~KviIrcConnection() { if(m_bIdentdAttached) g_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_IDENT_STOP); m_bIdentdAttached = false; if(m_pLocalhostDns) { TQObject::disconnect(m_pLocalhostDns,TQT_SIGNAL(lookupDone(KviDns *)),0,0); if(m_pLocalhostDns->isRunning()) { g_pGarbageCollector->collect(m_pLocalhostDns); } else { delete m_pLocalhostDns; } } if(m_pNotifyListTimer) { delete m_pNotifyListTimer; m_pNotifyListTimer = 0; } if(m_pNotifyListManager) { delete m_pNotifyListManager; // destroy this before the userDb m_pNotifyListManager = 0; } if(m_pLagMeter) { delete m_pLagMeter; m_pLagMeter = 0; } delete m_pLink; // <-- this MAY trigger a linkTerminated() or something like this! delete m_pChannelList; delete m_pQueryList; delete m_pTarget; delete m_pUserDataBase; delete m_pUserInfo; delete m_pServerInfo; delete m_pStateData; delete m_pAntiCtcpFloodData; delete m_pNetsplitDetectorData; delete m_pAsyncWhoisData; delete m_pStatistics; delete m_pUserIdentity; } void KviIrcConnection::setEncoding(const TQString &szEncoding) { TQTextCodec * c = KviLocale::codecForName(szEncoding.latin1()); if(c == m_pTextCodec)return; if(!c) { m_pConsole->output(KVI_OUT_SYSTEMERROR,__tr2qs("Failed to set the encoding to %Q: mapping not available."),&szEncoding); return; } TQString tmp = c->name(); for(KviChannel * ch = m_pChannelList->first();ch;ch = m_pChannelList->next()) { if((ch->textCodec() != c) && (ch->textCodec() != ch->defaultTextCodec())) // actually not using the default! { ch->forceTextCodec(c); if(_OUTPUT_VERBOSE)ch->output(KVI_OUT_VERBOSE,__tr2qs("Changed text encoding to %Q"),&tmp); } } for(KviQuery * q = m_pQueryList->first();q;q = m_pQueryList->next()) { if((q->textCodec() != c) && (q->textCodec() != q->defaultTextCodec())) // actually not using the default! { q->forceTextCodec(c); if(_OUTPUT_VERBOSE)q->output(KVI_OUT_VERBOSE,__tr2qs("Changed text encoding to %Q"),&tmp); } } m_pTextCodec = c; m_pConsole->setTextEncoding(szEncoding); } void KviIrcConnection::setupTextCodec() { // grab the codec: first look it up in the server data m_pTextCodec = 0; if(!m_pTarget->server()->encoding().isEmpty()) { m_pTextCodec = KviLocale::codecForName(m_pTarget->server()->encoding().latin1()); if(!m_pTextCodec)tqDebug("KviIrcConnection: can't find TQTextCodec for encoding %s",m_pTarget->server()->encoding().utf8().data()); } if(!m_pTextCodec) { // try the network if(!m_pTarget->network()->encoding().isEmpty()) { m_pTextCodec = KviLocale::codecForName(m_pTarget->network()->encoding().latin1()); if(!m_pTextCodec)tqDebug("KviIrcConnection: can't find TQTextCodec for encoding %s",m_pTarget->network()->encoding().utf8().data()); } } if(!m_pTextCodec) { m_pTextCodec = KviApp::defaultTextCodec(); } m_pConsole->setTextEncoding(TQString(m_pTextCodec->name())); } KviTQCString KviIrcConnection::encodeText(const TQString &szText) { if(!m_pTextCodec)return szText.utf8(); return m_pTextCodec->fromUnicode(szText); } TQString KviIrcConnection::decodeText(const char * szText) { if(!m_pTextCodec)return TQString(szText); return m_pTextCodec->toUnicode(szText); } void KviIrcConnection::serverInfoReceived(const TQString &szServerName,const TQString &szUserModes,const TQString &szChanModes) { serverInfo()->setName(szServerName); serverInfo()->setSupportedUserModes(szUserModes); serverInfo()->setSupportedChannelModes(szChanModes); m_pConsole->updateCaption(); // for server name m_pFrame->childConnectionServerInfoChange(this); } const TQString & KviIrcConnection::currentServerName() { return serverInfo()->name(); } const TQString & KviIrcConnection::currentNickName() { return userInfo()->nickName(); } const TQString & KviIrcConnection::currentUserName() { return userInfo()->userName(); } KviIrcServer * KviIrcConnection::server() { return m_pTarget->server(); } KviProxy * KviIrcConnection::proxy() { return m_pTarget->proxy(); } const TQString & KviIrcConnection::networkName() { return m_pTarget->networkName(); } KviIrcSocket * KviIrcConnection::socket() { return m_pLink->socket(); } void KviIrcConnection::abort() { // this WILL trigger linkAttemptFailed() or linkTerminated() m_pLink->abort(); } void KviIrcConnection::start() { m_eState = Connecting; if(KVI_OPTION_BOOL(KviOption_boolUseIdentService) && KVI_OPTION_BOOL(KviOption_boolUseIdentServiceOnlyOnConnect)) { g_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_IDENT_START); m_bIdentdAttached=true; } m_pLink->start(); } void KviIrcConnection::linkEstabilished() { m_eState = Connected; // setup reasonable defaults before notifying anyone m_pStatistics->setConnectionStartTime(kvi_unixTime()); m_pStatistics->setLastMessageTime(kvi_unixTime()); m_pServerInfo->setName(target()->server()->m_szHostname); if(KviPointerList * l = context()->monitorList()) { for(KviIrcDataStreamMonitor *m =l->first();m;m =l->next()) m->connectionInitiated(); } context()->connectionEstabilished(); // Ok...we're loggin in now resolveLocalHost(); loginToIrcServer(); } void KviIrcConnection::linkTerminated() { if(m_bIdentdAttached) { g_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_IDENT_STOP); m_bIdentdAttached=false; } m_eState = Idle; if(m_pNotifyListManager) { delete m_pNotifyListManager; m_pNotifyListManager = 0; } if(m_pLagMeter) { delete m_pLagMeter; m_pLagMeter = 0; } if(KviPointerList * l = context()->monitorList()) { for(KviIrcDataStreamMonitor *m =l->first();m;m =l->next()) m->connectionTerminated(); } // Prepare data for an eventual reconnect context()->connectionTerminated(); } void KviIrcConnection::linkAttemptFailed(int iError) { if(m_bIdentdAttached) { g_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_IDENT_STOP); m_bIdentdAttached=false; } m_eState = Idle; context()->connectionFailed(iError); } KviChannel * KviIrcConnection::findChannel(const TQString &name) { for(KviChannel * c = m_pChannelList->first();c;c = m_pChannelList->next()) { if(KviTQString::equalCI(name,c->windowName()))return c; } return 0; } int KviIrcConnection::getCommonChannels(const TQString &nick,TQString &szChansBuffer,bool bAddEscapeSequences) { int count = 0; for(KviChannel * c = m_pChannelList->first();c;c = m_pChannelList->next()) { if(c->isOn(nick)) { if(!szChansBuffer.isEmpty())szChansBuffer.append(", "); char uFlag = c->getUserFlag(nick); if(uFlag) { KviTQString::appendFormatted(szChansBuffer,bAddEscapeSequences ? "%c\r!c\r%Q\r" : "%c%Q",uFlag,&(c->windowName())); } else { if(bAddEscapeSequences)KviTQString::appendFormatted(szChansBuffer,"\r!c\r%Q\r",&(c->windowName())); else szChansBuffer.append(c->windowName()); } count++; } } return count; } void KviIrcConnection::unhighlightAllChannels() { for(KviChannel * c = m_pChannelList->first();c;c = m_pChannelList->next()) c->unhighlight(); } void KviIrcConnection::unhighlightAllQueries() { for(KviQuery * c = m_pQueryList->first();c;c = m_pQueryList->next()) c->unhighlight(); } void KviIrcConnection::partAllChannels() { for(KviChannel * c = m_pChannelList->first();c;c = m_pChannelList->next()) { c->close(); } } void KviIrcConnection::closeAllChannels() { while(m_pChannelList->first()) { m_pFrame->closeWindow(m_pChannelList->first()); } } void KviIrcConnection::closeAllQueries() { while(m_pQueryList->first()) { m_pFrame->closeWindow(m_pQueryList->first()); } } KviChannel * KviIrcConnection::createChannel(const TQString &szName) { KviChannel * c = m_pContext->findDeadChannel(szName); if(c) { c->setAliveChan(); if(!KVI_OPTION_BOOL(KviOption_boolCreateMinimizedChannels)) { c->raise(); c->setFocus(); } } else { c = new KviChannel(m_pFrame,m_pConsole,szName); m_pFrame->addWindow(c,!KVI_OPTION_BOOL(KviOption_boolCreateMinimizedChannels)); if(KVI_OPTION_BOOL(KviOption_boolCreateMinimizedChannels)) c->minimize(); } return c; } KviQuery * KviIrcConnection::createQuery(const TQString &szNick) { KviQuery * q = m_pContext->findDeadQuery(szNick); if(!q) { q = findQuery(szNick); if(q)return q; // hm ? } if(q) { q->setAliveQuery(); if(!KVI_OPTION_BOOL(KviOption_boolCreateMinimizedQuery)) { q->raise(); q->setFocus(); } } else { q = new KviQuery(m_pFrame,m_pConsole,szNick); m_pFrame->addWindow(q,!KVI_OPTION_BOOL(KviOption_boolCreateMinimizedQuery)); if(KVI_OPTION_BOOL(KviOption_boolCreateMinimizedQuery))q->minimize(); } return q; } KviQuery * KviIrcConnection::findQuery(const TQString &name) { for(KviQuery * c = m_pQueryList->first();c;c = m_pQueryList->next()) { if(KviTQString::equalCI(name,c->windowName()))return c; } return 0; } void KviIrcConnection::registerChannel(KviChannel * c) { m_pChannelList->append(c); if(KVI_OPTION_BOOL(KviOption_boolLogChannelHistory)) g_pApp->addRecentChannel(c->windowName(),m_pTarget->networkName()); emit(channelRegistered(c)); emit(chanListChanged()); } void KviIrcConnection::unregisterChannel(KviChannel * c) { m_pChannelList->removeRef(c); emit(channelUnregistered(c)); emit(chanListChanged()); } void KviIrcConnection::registerQuery(KviQuery * c) { m_pQueryList->append(c); } void KviIrcConnection::unregisterQuery(KviQuery * c) { if(m_pQueryList->removeRef(c))return; } void KviIrcConnection::keepChannelsOpenAfterDisconnect() { while(KviChannel * c = m_pChannelList->first()) { c->outputNoFmt(KVI_OUT_SOCKETERROR,__tr2qs("Connection to server lost")); c->setDeadChan(); } } void KviIrcConnection::keepQueriesOpenAfterDisconnect() { while(KviQuery * q = m_pQueryList->first()) { q->outputNoFmt(KVI_OUT_SOCKETERROR,__tr2qs("Connection to server lost")); q->setDeadQuery(); } } void KviIrcConnection::resurrectDeadQueries() { while(KviQuery * q = m_pContext->firstDeadQuery()) { q->outputNoFmt(KVI_OUT_SOCKETMESSAGE,__tr2qs("Connection to server established")); q->setAliveQuery(); } } //=== Message send stuff ====================================================// // Max buffer that can be sent to an IRC server is 512 bytes // including CRLF. (ircd simply 'cuts' messages to 512 bytes // and discards the remainig part) // Note that 510 bytes of data is a reasonably long message :) // // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 01234567890123456789012345678901234567890123456789 // 0123456789\r\n // // We keep a list of data to send , and flush it as soon as we can. // bool KviIrcConnection::sendFmtData(const char *fmt,...) { KviDataBuffer * pData = new KviDataBuffer(512); kvi_va_list(list); kvi_va_start(list,fmt); bool bTruncated; //sprintf the buffer up to 512 chars (adds a CRLF too) int iLen = kvi_irc_vsnprintf((char *)(pData->data()),fmt,list,&bTruncated); kvi_va_end(list); //adjust the buffer size if(iLen < 512)pData->resize(iLen); if(bTruncated) { if(!_OUTPUT_MUTE) m_pConsole->outputNoFmt(KVI_OUT_SOCKETWARNING,__tr2qs("[LINK WARNING]: Socket message truncated to 512 bytes.")); } // notify the monitors if(KviPointerList * l = context()->monitorList()) { for(KviIrcDataStreamMonitor *m = l->first();m;m = l->next()) m->outgoingMessage((const char *)(pData->data()),iLen - 2); } return m_pLink->sendPacket(pData); } bool KviIrcConnection::sendData(const char *buffer,int buflen) { if(buflen < 0)buflen = (int)strlen(buffer); if(buflen > 510) { buflen = 510; if(!_OUTPUT_MUTE) m_pConsole->outputNoFmt(KVI_OUT_SOCKETWARNING,__tr2qs("[LINK WARNING]: Socket message truncated to 512 bytes.")); } KviDataBuffer * pData = new KviDataBuffer(buflen + 2); kvi_memmove(pData->data(),buffer,buflen); *(pData->data()+buflen)='\r'; *(pData->data()+buflen+1)='\n'; // notify the monitors if(KviPointerList * l = context()->monitorList()) { for(KviIrcDataStreamMonitor *m = l->first();m;m = l->next()) m->outgoingMessage((const char *)(pData->data()),buflen); } return m_pLink->sendPacket(pData); } //============================================================================================== // notify list management //============================================================================================== void KviIrcConnection::delayedStartNotifyList() { // start the notify list in 15 seconds // We have this delay to wait an eventual RPL_PROTOCTL from the server // telling us that the WATCH notify list method is supported __range_invalid(m_pNotifyListTimer); if(m_pNotifyListTimer)delete m_pNotifyListTimer; m_pNotifyListTimer = new TQTimer(); connect(m_pNotifyListTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(restartNotifyList())); m_pNotifyListTimer->start(15000,true); // This delay is large enough to fire after the MOTD has been sent, // even on the weirdest network. // If there is no MOTD, this timer will fire after 15 secs, // If there is a MOTD , restartNotifyList() will be triggered by RPL_ENDOFMOTD and // will kill the timer before it has fired. } void KviIrcConnection::endOfMotdReceived() { // if the timer is still there running then just if(m_pNotifyListTimer)restartNotifyList(); } void KviIrcConnection::restartNotifyList() { if(m_pNotifyListTimer) { delete m_pNotifyListTimer; m_pNotifyListTimer = 0; } // clear it if(m_pNotifyListManager) { m_pNotifyListManager->stop(); // may need to remove watch entries delete m_pNotifyListManager; m_pNotifyListManager = 0; } if(!KVI_OPTION_BOOL(KviOption_boolUseNotifyList))return; if(serverInfo()->supportsWatchList() && KVI_OPTION_BOOL(KviOption_boolUseWatchListIfAvailable)) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("The server seems to support the WATCH notify list method, will try to use it")); m_pNotifyListManager = new KviWatchNotifyListManager(this); } else { if(KVI_OPTION_BOOL(KviOption_boolUseIntelligentNotifyListManager)) { m_pNotifyListManager = new KviIsOnNotifyListManager(this); } else { m_pNotifyListManager = new KviStupidNotifyListManager(this); } } m_pNotifyListManager->start(); } void KviIrcConnection::restartLagMeter() { if(m_pLagMeter) { delete m_pLagMeter; m_pLagMeter = 0; } if(!KVI_OPTION_BOOL(KviOption_boolUseLagMeterEngine))return; m_pLagMeter = new KviLagMeter(this); } void KviIrcConnection::resolveLocalHost() { TQString szIp; if(!socket()->getLocalHostIp(szIp,server()->isIpV6())) { bool bGotIp = false; if(!KVI_OPTION_STRING(KviOption_stringLocalHostIp).isEmpty()) { #ifdef COMPILE_IPV6_SUPPORT if(server()->isIpV6()) { if(KviNetUtils::isValidStringIp_V6(KVI_OPTION_STRING(KviOption_stringLocalHostIp)))bGotIp = true; } else { #endif if(KviNetUtils::isValidStringIp(KVI_OPTION_STRING(KviOption_stringLocalHostIp)))bGotIp = true; #ifdef COMPILE_IPV6_SUPPORT } #endif } if(bGotIp) { m_pUserInfo->setLocalHostIp(KVI_OPTION_STRING(KviOption_stringLocalHostIp)); if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Can't resolve local host address, using user supplied one (%Q)"), &(m_pUserInfo->localHostIp())); } else { // FIXME : Maybe check for IPv6 here too ? m_pUserInfo->setLocalHostIp("127.0.0.1"); if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("Can't resolve local host address, using default 127.0.0.1"), &(m_pUserInfo->localHostIp())); } } else { m_pUserInfo->setLocalHostIp(szIp); if(!_OUTPUT_QUIET) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Local host address is %Q"), &(m_pUserInfo->localHostIp())); } // For now this is the only we know m_pUserInfo->setHostName(m_pUserInfo->localHostIp()); m_pUserInfo->setHostIp(m_pUserInfo->localHostIp()); } void KviIrcConnection::changeAwayState(bool bAway) { if(bAway)m_pUserInfo->setAway(); else m_pUserInfo->setBack(); m_pConsole->updateCaption(); m_pFrame->childConnectionAwayStateChange(this); emit awayStateChanged(); } void KviIrcConnection::userInfoReceived(const TQString &szUserName,const TQString &szHostName) { userInfo()->setUserName(szUserName); TQString szUnmaskedHost = m_pUserInfo->unmaskedHostName(); // Update the user entry KviIrcUserEntry * e = userDataBase()->find(userInfo()->nickName()); if(e) // should be there! (we have the permanent entry in the notify list view) { e->setUser(szUserName); if(!szHostName.isEmpty())e->setHost(szHostName); } // else buuug if(szHostName.isEmpty())return; // nothing to do anyway if(KviTQString::equalCS(m_pUserInfo->hostName(),szHostName))return; // again nothing to do static bool warned_once = false; if(!warned_once) { if(!(m_pUserInfo->hostName().isEmpty() || KviTQString::equalCS(m_pUserInfo->hostName(),m_pUserInfo->localHostIp()))) { // ok, something weird is probably going on // is is non-empty and it is NOT the IP address we have set // at connection startup... // ...the server (or more likely the bouncer) must have changed his mind... if(!_OUTPUT_MUTE) { m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("The server seems to have changed the idea about the local hostname")); m_pConsole->output(KVI_OUT_SYSTEMWARNING,__tr2qs("You're probably using a broken bouncer or maybe something weird is happening on the IRC server")); } warned_once = true; } } // set it m_pUserInfo->setHostName(szHostName); bool bChangeIp = true; // if we don't have any routable IP yet, then it is worth to lookup the new hostname #ifdef COMPILE_IPV6_SUPPORT if((KviNetUtils::isValidStringIp(m_pUserInfo->hostIp()) && KviNetUtils::isRoutableIpString(m_pUserInfo->hostIp())) || KviNetUtils::isValidStringIp_V6(m_pUserInfo->hostIp())) #else if((KviNetUtils::isValidStringIp(m_pUserInfo->hostIp()) && KviNetUtils::isRoutableIpString(m_pUserInfo->hostIp()))) #endif { if(KVI_OPTION_BOOL(KviOption_boolDccGuessIpFromServerWhenLocalIsUnroutable) && KVI_OPTION_BOOL(KviOption_boolDccBrokenBouncerHack)) { if(!_OUTPUT_MUTE) m_pConsole->outputNoFmt(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Here goes your \"broken bouncer hack\": The server has changed the hostname but I'll ignore the IP address change")); bChangeIp = false; } } if(bChangeIp) { // lookup the new hostname then... #ifdef COMPILE_IPV6_SUPPORT if(KviNetUtils::isValidStringIp(szHostName) || KviNetUtils::isValidStringIp_V6(szHostName)) #else if(KviNetUtils::isValidStringIp(szHostName)) #endif { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("The local IP address as seen by the IRC server is %Q"),&szHostName); m_pUserInfo->setHostIp(szHostName); } else #ifdef COMPILE_IPV6_SUPPORT if(KviNetUtils::isValidStringIp(szUnmaskedHost) || KviNetUtils::isValidStringIp_V6(szUnmaskedHost)) #else if(KviNetUtils::isValidStringIp(szUnmaskedHost)) #endif { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("The local IP address as seen by the IRC server is %Q"),&szUnmaskedHost); m_pUserInfo->setHostIp(szUnmaskedHost); } else { // look it up too if(m_pLocalhostDns)delete m_pLocalhostDns; // it could be only another local host lookup m_pLocalhostDns = new KviDns(); connect(m_pLocalhostDns,TQT_SIGNAL(lookupDone(KviDns *)),this,TQT_SLOT(hostNameLookupTerminated(KviDns *))); if(!m_pLocalhostDns->lookup(szHostName,KviDns::Any)) { if(!_OUTPUT_MUTE) { // don't change the string to aid the translators TQString szTmp = __tr2qs("Can't start the DNS slave thread"); m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Unable to resolve the local hostname as seen by the IRC server: %Q"),&szTmp); } delete m_pLocalhostDns; m_pLocalhostDns = 0; } else { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Looking up the local hostname as seen by the IRC server (%Q)"),&szHostName); } } } } void KviIrcConnection::hostNameLookupTerminated(KviDns *pDns) { // // This is called when our hostname lookup terminates // if(!m_pLocalhostDns) { tqDebug("Something weird is happening: pDns != 0 but m_pLocalhostDns == 0 :/"); return; } if(m_pLocalhostDns->state() != KviDns::Success) { TQString szErr = KviError::getDescription(m_pLocalhostDns->error()); if(!m_pUserInfo->hostIp().isEmpty()) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Unable to resolve the local hostname as seen by the IRC server: %Q, using previously resolved %Q"), &szErr,&(m_pUserInfo->hostIp())); else m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Unable to resolve the local hostname as seen by the IRC server: %Q"), &szErr); } else { TQString szIpAddr = m_pLocalhostDns->firstIpAddress(); m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Local hostname as seen by the IRC server resolved to %Q"),&szIpAddr); m_pUserInfo->setHostIp(m_pLocalhostDns->firstIpAddress()); } delete m_pLocalhostDns; m_pLocalhostDns = 0; } void KviIrcConnection::loginToIrcServer() { KviIrcServer * pServer = target()->server(); KviIrcNetwork * pNet = target()->network(); // Username pServer->m_szUser.stripWhiteSpace(); if(!pServer->m_szUser.isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using server specific username (%Q)"),&(pServer->m_szUser)); } else { if(!pNet->userName().isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using network specific username (%Q)"),&(pNet->userName())); pServer->m_szUser = pNet->userName(); } else { pServer->m_szUser = KVI_OPTION_STRING(KviOption_stringUsername); } } pServer->m_szUser.stripWhiteSpace(); if(pServer->m_szUser.isEmpty())pServer->m_szUser = KVI_DEFAULT_USERNAME; // For now this is the only we know m_pUserInfo->setUserName(pServer->m_szUser); m_pServerInfo->setName(pServer->m_szHostname); // Nick stuff pServer->m_szNick.stripWhiteSpace(); if(pServer->m_pReconnectInfo) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using reconnect specific nickname (%Q)"),&(pServer->m_pReconnectInfo->m_szNick)); m_pUserInfo->setNickName(pServer->m_pReconnectInfo->m_szNick); m_pStateData->setLoginNickIndex(0); }else if(!pServer->m_szNick.isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using server specific nickname (%Q)"),&(pServer->m_szNick)); m_pUserInfo->setNickName(pServer->m_szNick); m_pStateData->setLoginNickIndex(0); } else { if(!pNet->nickName().isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using network specific nickname (%Q)"),&(pNet->nickName())); m_pUserInfo->setNickName(pNet->nickName()); m_pStateData->setLoginNickIndex(0); } else { KVI_OPTION_STRING(KviOption_stringNickname1).stripWhiteSpace(); if(KVI_OPTION_STRING(KviOption_stringNickname1).isEmpty()) KVI_OPTION_STRING(KviOption_stringNickname1) = KVI_DEFAULT_NICKNAME1; m_pUserInfo->setNickName(KVI_OPTION_STRING(KviOption_stringNickname1)); m_pStateData->setLoginNickIndex(1); } } // Real name pServer->m_szRealName.stripWhiteSpace(); if(!pServer->m_szRealName.isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using server specific real name (%Q)"), &(pServer->m_szRealName)); m_pUserInfo->setRealName(pServer->m_szRealName); } else { if(!pNet->realName().isEmpty()) { if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Using network specific real name (%Q)"), &(pNet->realName())); m_pUserInfo->setRealName(pNet->realName()); } else { m_pUserInfo->setRealName(KVI_OPTION_STRING(KviOption_stringRealname)); } } // FIXME: The server's encoding! setupTextCodec(); KviTQCString szNick = encodeText(m_pUserInfo->nickName()); // never empty KviTQCString szUser = encodeText(m_pUserInfo->userName()); // never empty KviTQCString szReal = encodeText(m_pUserInfo->realName()); // may be empty if(!szReal.data())szReal = ""; if(!_OUTPUT_MUTE) m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("Logging in as %Q!%Q :%Q"), &(m_pUserInfo->nickName()),&(m_pUserInfo->userName()),&(m_pUserInfo->realName())); // spity, 27.03.2005: follow the RFC2812 suggested order for connection registration // first the PASS, then NICK and then USER // The pass ? pServer->m_szPass.stripWhiteSpace(); if(!pServer->m_szPass.isEmpty()) { KviStr szHidden; int pLen = pServer->m_szPass.length(); for(int i=0;ioutput(KVI_OUT_VERBOSE,__tr2qs("Sending %s as password"),szHidden.ptr()); // The colon should allow user to use passwords with whitespaces. // Non-whitespace passwords are unaffected. if(!sendFmtData("PASS :%s",encodeText(pServer->m_szPass).data())) { // disconnected in the meantime return; } } if(!sendFmtData("NICK %s",szNick.data())) { // disconnected :( return; } TQString szGenderTag; if(KVI_OPTION_BOOL(KviOption_boolPrependGenderInfoToRealname) && !KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).isEmpty()) { szGenderTag.append(KVI_TEXT_COLOR); if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("m",false)) { szGenderTag.append("1"); } else if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("f",false)) { szGenderTag.append("2"); } szGenderTag.append(KVI_TEXT_RESET); szReal.prepend(KviTQString::toUtf8(szGenderTag)); } if(!sendFmtData("USER %s 0 %s :%s",szUser.data(), KviTQString::toUtf8(pServer->m_szHostname).data(),szReal.data())) { // disconnected in the meantime! return; } // permanent info in the user database m_pConsole->notifyListView()->join(m_pUserInfo->nickName(),"*","*"); // set own avatar if we have it KviIrcUserEntry * e = userDataBase()->find(userInfo()->nickName()); if(e) // should be there! { if(!e->avatar()) { KviAvatar * av = m_pConsole->defaultAvatarFromOptions(); if(av) { e->setAvatar(av); m_pConsole->notifyListView()->avatarChanged(userInfo()->nickName()); } } } // else buuug if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("m",false)){ e->setGender(KviIrcUserEntry::Male); } else if(KVI_OPTION_STRING(KviOption_stringCtcpUserInfoGender).startsWith("f",false)){ e->setGender(KviIrcUserEntry::Female); } // on connect stuff ? TQString tmp = pNet->onConnectCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled network specific \"on connect\" commands")); KviKvsScript::run(tmp,m_pConsole); } tmp = pServer->onConnectCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled server specific \"on connect\" commands")); KviKvsScript::run(tmp,m_pConsole); } tmp = m_pUserIdentity->onConnectCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled identity specific \"on connect\" commands")); KviKvsScript::run(tmp,m_pConsole); } // and wait for the server to agree... } void KviIrcConnection::nickChange(const TQString &szNewNick) { // FIXME: should the new nickname be decoded in some way ? m_pConsole->notifyListView()->nickChange(m_pUserInfo->nickName(),szNewNick); m_pUserInfo->setNickName(szNewNick); m_pConsole->output(KVI_OUT_NICK,__tr2qs("You have changed your nickname to %Q"),&szNewNick); m_pConsole->updateCaption(); m_pFrame->childConnectionNickNameChange(this); emit nickNameChanged(); g_pApp->addRecentNickname(szNewNick); } bool KviIrcConnection::changeUserMode(char mode,bool bSet) { __range_valid(m_pConnectionInfo); if(bSet) { if(m_pUserInfo->hasUserMode(mode))return false; m_pUserInfo->addUserMode(mode); } else { if(!m_pUserInfo->hasUserMode(mode))return false; m_pUserInfo->removeUserMode(mode); } m_pConsole->updateCaption(); m_pFrame->childConnectionUserModeChange(this); emit userModeChanged(); return true; } void KviIrcConnection::loginComplete(const TQString &szNickName) { if(context()->state() == KviIrcContext::Connected)return; context()->loginComplete(); if(m_bIdentdAttached) { g_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_IDENT_STOP); m_bIdentdAttached=false; } if(szNickName != m_pUserInfo->nickName()) { m_pConsole->output(KVI_OUT_SYSTEMMESSAGE,__tr2qs("The server refused the suggested nickname (%s) and named you %s instead"), m_pUserInfo->nickName().utf8().data(),szNickName.utf8().data()); m_pConsole->notifyListView()->nickChange(m_pUserInfo->nickName(),szNickName); m_pUserInfo->setNickName(szNickName); } g_pApp->addRecentNickname(szNickName); bool bHaltOutput = false; bHaltOutput = KVS_TRIGGER_EVENT_0_HALTED(KviEvent_OnIrc,m_pConsole); if(!bHaltOutput) m_pConsole->outputNoFmt(KVI_OUT_IRC,__tr2qs("Login operations complete, happy ircing!")); resurrectDeadQueries(); // on connect stuff ? TQString tmp = target()->network()->onLoginCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled network specific \"on login\" commands")); KviKvsScript::run(tmp,m_pConsole); } tmp = target()->server()->onLoginCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled server specific \"on login\" commands")); KviKvsScript::run(tmp,m_pConsole); } tmp = m_pUserIdentity->onLoginCommand(); tmp.stripWhiteSpace(); if(!tmp.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Executing scheduled identity specific \"on login\" commands")); KviKvsScript::run(tmp,m_pConsole); } // Set the configured umode KviStr modeStr = server()->initUMode(); if(modeStr.isEmpty())modeStr = KVI_OPTION_STRING(KviOption_stringDefaultUserMode); if(!modeStr.isEmpty()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Setting configured user mode")); sendFmtData("MODE %s +%s",encodeText(TQString(m_pUserInfo->nickName())).data(),modeStr.ptr()); } delayedStartNotifyList(); restartLagMeter(); if(KVI_OPTION_BOOL(KviOption_boolShowChannelsJoinOnIrc)) m_pFrame->executeInternalCommand(KVI_INTERNALCOMMAND_CHANNELSJOIN_OPEN); // join saved channels TQString szChannels,szProtectedChannels,szPasswords,szCurPass,szCurChan; if(!(m_pStateData->commandToExecAfterConnect().isEmpty())) { KviStr tmp = m_pStateData->commandToExecAfterConnect(); KviKvsScript::run(tmp.ptr(),m_pConsole); } if(target()->server()->m_pReconnectInfo) { if(!target()->server()->m_pReconnectInfo->m_szJoinChannels.isEmpty()) sendFmtData("JOIN %s",encodeText(target()->server()->m_pReconnectInfo->m_szJoinChannels).data()); KviQuery * query; for(TQStringList::Iterator it = target()->server()->m_pReconnectInfo->m_szOpenQueryes.begin(); it != target()->server()->m_pReconnectInfo->m_szOpenQueryes.end();it++) { TQString szNick = *it; query = findQuery(szNick); if(!query) { query = createQuery(szNick); TQString user; TQString host; KviIrcUserDataBase * db = userDataBase(); if(db) { KviIrcUserEntry * e = db->find(szNick); if(e) { user = e->user(); host = e->host(); } } query->setTarget(szNick,user,host); } query->autoRaise(); query->setFocus(); } delete target()->server()->m_pReconnectInfo; target()->server()->m_pReconnectInfo=0; }else { if(target()->network()->autoJoinChannelList()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Auto-joining network specific channels")); TQStringList * l = target()->network()->autoJoinChannelList(); if(l->count()!=0) { for ( TQStringList::Iterator it = l->begin(); it != l->end(); ++it ) { szCurPass=(*it).section(':',1); if(szCurPass.isEmpty()) { if(!szChannels.isEmpty()) szChannels.append(","); szCurChan = (*it).section(':',0,0); if(!(szCurChan[0]=='#' || szCurChan[0]=='&' || szCurChan[0]=='!')) szCurChan.prepend('#'); szChannels.append(szCurChan); } else { if(!szProtectedChannels.isEmpty()) szProtectedChannels.append(","); szCurChan = (*it).section(':',0,0); if(!(szCurChan[0]=='#' || szCurChan[0]=='&' || szCurChan[0]=='!')) szCurChan.prepend('#'); szProtectedChannels.append(szCurChan); if(!szPasswords.isEmpty()) szPasswords.append(","); szPasswords.append(szCurPass); } } } } if(server()->autoJoinChannelList()) { if(_OUTPUT_VERBOSE) m_pConsole->output(KVI_OUT_VERBOSE,__tr2qs("Auto-joining server specific channels")); TQStringList * l = server()->autoJoinChannelList(); if(l->count()!=0) { for ( TQStringList::Iterator it = l->begin(); it != l->end(); ++it ) { szCurPass=(*it).section(':',1); if(szCurPass.isEmpty()) { if(!szChannels.isEmpty()) szChannels.append(","); szCurChan = (*it).section(':',0,0); if(!(szCurChan[0]=='#' || szCurChan[0]=='&' || szCurChan[0]=='!')) szCurChan.prepend(':'); szChannels.append(szCurChan); } else { if(!szProtectedChannels.isEmpty()) szProtectedChannels.append(","); szCurChan = (*it).section(':',0,0); if(!(szCurChan[0]=='#' || szCurChan[0]=='&' || szCurChan[0]=='!')) szCurChan.prepend('#'); szProtectedChannels.append(szCurChan); if(!szPasswords.isEmpty()) szPasswords.append(","); szPasswords.append(szCurPass); } } } } TQString szCommand; if( (!szChannels.isEmpty()) || (!szProtectedChannels.isEmpty()) ) { szCommand.append(szProtectedChannels); if(!szProtectedChannels.isEmpty() && !szChannels.isEmpty()) szCommand.append(','); szCommand.append(szChannels); szCommand.append(" "); szCommand.append(szPasswords); sendFmtData("JOIN %s",encodeText(szCommand).data()); } } // minimize after connect if(KVI_OPTION_BOOL(KviOption_boolMinimizeConsoleAfterConnect)) m_pConsole->minimize(); } void KviIrcConnection::incomingMessage(const char * message) { // A message has arrived from the current server // First of all , notify the monitors if(KviPointerList * l = context()->monitorList()) { for(KviIrcDataStreamMonitor *m = l->first();m;m = l->next()) { m->incomingMessage(message); } } // set the last message time m_pStatistics->setLastMessageTime(kvi_unixTime()); // and pass it to the server parser for processing g_pServerParser->parseMessage(message,this); } void KviIrcConnection::heartbeat(kvi_time_t tNow) { if(m_eState == Connected) { if(!KVI_OPTION_BOOL(KviOption_boolDisableAwayListUpdates)) { // update the channel WHO lists (fixes users away state) // first of all, we send our request not more often than every 50 secs if((tNow - stateData()->lastSentChannelWhoRequest()) > 50) { // we also make sure that the last sent request is older than // the last received reply if(stateData()->lastSentChannelWhoRequest() <= stateData()->lastReceivedChannelWhoReply()) { // find the channel that has the older list now kvi_time_t tOldest = tNow; KviChannel * pOldest = 0; for(KviChannel * pChan = m_pChannelList->first();pChan;pChan = m_pChannelList->next()) { if(pChan->lastReceivedWhoReply() < tOldest) { pOldest = pChan; tOldest = pChan->lastReceivedWhoReply(); } } // if the oldest chan who list is older than 150 secs, update it if((tNow - tOldest) > 150) { // ok, sent the request for this channel stateData()->setLastSentChannelWhoRequest(tNow); TQString szChanName = encodeText(pOldest->name()); if(_OUTPUT_PARANOIC) console()->output(KVI_OUT_VERBOSE,__tr2qs("Updating away state for channel %Q"),&szChanName); if(lagMeter()) { KviStr tmp(KviStr::Format,"WHO %s",pOldest->name()); lagMeter()->lagCheckRegister(tmp.ptr(),70); } pOldest->setSentSyncWhoRequest(); if(!sendFmtData("WHO %s",encodeText(TQString(pOldest->name())).data()))return; } } } } } }