You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kvirc/src/kvirc/kernel/kvi_ircconnection.cpp

1355 lines
40 KiB

//=============================================================================
//
// 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 <pragma at kvirc dot net>
//
// 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 <tqtimer.h>
#include <tqtextcodec.h>
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<KviChannel>;
m_pChannelList->setAutoDelete(false);
m_pQueryList = new KviPointerList<KviQuery>;
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)debug("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)debug("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<KviIrcDataStreamMonitor> * 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<KviIrcDataStreamMonitor> * 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<KviIrcDataStreamMonitor> * 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<KviIrcDataStreamMonitor> * 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_TQUIET)
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)
{
debug("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;i<pLen;i++)szHidden.append('*');
if(!_OUTPUT_MUTE)
m_pConsole->output(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<KviIrcDataStreamMonitor> * 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;
}
}
}
}
}
}