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/modules/dcc/requests.cpp

1155 lines
40 KiB

//=============================================================================
//
// File : requests.cpp
// Creation date : Tue Jul 23 02:44:38 2002 GMT by Szymon Stefanek
//
// This file is part of the KVirc irc client distribution
// Copyright (C) 2002 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 _KVI_DEBUG_CHECK_RANGE_
#include "kvi_debug.h"
#include "kvi_settings.h"
#include "kvi_string.h"
#include "kvi_module.h"
#include "kvi_sparser.h"
#include "kvi_locale.h"
#include "kvi_out.h"
#include "kvi_console.h"
#include "kvi_netutils.h"
#include "kvi_frame.h"
#include "kvi_console.h"
#include "kvi_error.h"
#include "kvi_options.h"
#include "kvi_defaults.h"
#include "kvi_sharedfiles.h"
#include "kvi_mirccntrl.h"
#include "kvi_app.h"
#include "kvi_ircconnection.h"
#include "kvi_ircconnectionuserinfo.h"
#include "gsmcodec.h"
#include "broker.h"
#include "voice.h"
#include "utils.h"
#include "send.h"
#include <tqfileinfo.h>
#ifdef COMPILE_ON_WINDOWS
// Ugly Windoze compiler...
#include "dialogs.h"
#endif
//#warning "KviOption_boolIgnoreDccChat and other types too"
extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager;
extern KviDccBroker * g_pDccBroker;
static void dcc_module_reply_errmsg(KviDccRequest * dcc,const TQString& errText)
{
dcc->ctcpMsg->msg->console()->connection()->sendFmtData(
"NOTICE %s :%cERRMSG %s%c",
dcc->ctcpMsg->msg->console()->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(),0x01,
dcc->ctcpMsg->msg->console()->connection()->encodeText(errText).data()
,0x01);
}
static void dcc_module_request_error(KviDccRequest * dcc,const TQString& errText)
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR,
__tr2qs_ctx("Unable to process the above request: %Q, %Q","dcc"),
&errText,
KVI_OPTION_BOOL(KviOption_boolNotifyFailedDccHandshakes) ? &(__tr2qs_ctx("Ignoring and notifying failure","dcc")) : &(__tr2qs_ctx("Ignoring","dcc")));
if(KVI_OPTION_BOOL(KviOption_boolNotifyFailedDccHandshakes))
{
TQString szError = TQString("Sorry, your DCC %1 request can't be satisfied: %2").arg(dcc->szType.ptr()).arg(errText);
dcc_module_reply_errmsg(dcc,szError);
}
}
static bool dcc_module_check_concurrent_transfers_limit(KviDccRequest * dcc)
{
if(KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers) > 0)
{
unsigned int uTransfers = KviDccFileTransfer::runningTransfersCount();
if(uTransfers >= KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers))
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Concurrent transfer limit reached (%u of %u transfers running)","dcc"),
uTransfers,KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers));
dcc_module_request_error(dcc,szError.ptr());
return false;
}
}
return true;
}
static bool dcc_module_check_limits(KviDccRequest * dcc)
{
if(KVI_OPTION_UINT(KviOption_uintMaxDccSlots) > 0)
{
unsigned int uWindows = g_pDccBroker->dccWindowsCount();
if(uWindows >= KVI_OPTION_UINT(KviOption_uintMaxDccSlots))
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Slot limit reached (%u slots of %u)","dcc"),
uWindows,KVI_OPTION_UINT(KviOption_uintMaxDccSlots));
dcc_module_request_error(dcc,szError.ptr());
return false;
}
}
if(g_pDccBroker->dccBoxCount() >= 32)
{
// there are too many pending dcc requests: the user isn't watching....
dcc_module_request_error(dcc,__tr2qs_ctx("Too many pending connections","dcc"));
return false;
}
return true;
}
static void dcc_fill_local_nick_user_host(KviDccDescriptor * d,KviDccRequest * dcc)
{
if(dcc->pConsole->connection())
{
d->szLocalNick = dcc->pConsole->connection()->userInfo()->nickName();
d->szLocalUser = dcc->pConsole->connection()->userInfo()->userName();
d->szLocalHost = dcc->pConsole->connection()->userInfo()->hostName();
} else {
d->szLocalNick = __tr_ctx("unknown","dcc");
d->szLocalUser = __tr2qs_ctx("unknown","dcc");
d->szLocalHost = __tr2qs_ctx("unknown","dcc");
}
}
static void dcc_module_set_dcc_type(KviDccDescriptor * d,const char * szBaseType)
{
d->szType = szBaseType;
#ifdef COMPILE_SSL_SUPPORT
if(d->bIsSSL)d->szType.prepend('S');
#endif
if(d->bIsTdcc)d->szType.prepend('T');
}
static bool dcc_module_normalize_target_data(KviDccRequest * dcc,KviStr &ipaddr,KviStr &port)
{
if(!port.isUnsignedNum())
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid port number %s","dcc"),port.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
return false;
}
struct in_addr addr;
if(ipaddr.isUnsignedNum())
{
addr.s_addr = htonl((unsigned long)ipaddr.toULong());
TQString tmp;
if(!kvi_binaryIpToStringIp(addr,tmp))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid IP address in old format %s","dcc"),ipaddr.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
return false;
}
ipaddr = tmp;
} else {
if(!kvi_stringIpToBinaryIp(ipaddr,&addr))
{
#ifdef COMPILE_IPV6_SUPPORT
struct in6_addr addr6;
if(kvi_stringIpToBinaryIp_V6(ipaddr,&addr6))
{
dcc->bIpV6 = true;
return true; // IPV6 address.
}
#endif
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid IP address %s","dcc"),ipaddr.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CHAT
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccChat(KviDccRequest *dcc)
{
//
// We have received a DCC CHAT request in the following form:
//
// DCC CHAT chat <ipaddress> <port>
//
// This means that we're requested to setup an ACTIVE chat connection
// ... Easy task :)
//
// Anybody understands the meaning of the secondo "chat" in there ?
// It was meant to simplify the parsing ? :DDD
//
// There is a mIrc extension that allows <port> to be 0
// and adds a last parameter that seems to be a random number (thnx YaP :)
// that is used to keep track of the connection.
// This extension is used by firewalled machines to initiate a DCC CHAT:
// the receiving side should respond with a DCC CHAT offer
// with the same random number appended, and then should listen for a connection.
//
// when a zero port request is initiated by another party we get
//
// DCC CHAT chat <fakeipaddress> 0 <tag>
//
// and we reply with
//
// DCC CHAT chat <ourip> <ourport> <tag>
//
// when a zero port request is initiated by us we send out
//
// DCC CHAT chat <fakeipaddress> 0 <tag>
//
// and we get
//
// DCC CHAT chat <remoteip> <remoteport> <tag>
//
// Thus if there is a <tag> and the port is 0, then the remote party
// wanted to estabilish a dcc with us and wants us to listen, but if the port is nonzero then
// we have sent out a zero port request and the remote party acked it
// thus we have to connect instead!
//
// First of all we check the dcc slot limits
if(!dcc_module_check_limits(dcc))return;
// Then we check the target host data
if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return;
if(!kvi_strEqualCI(dcc->szParam1.ptr(),"chat"))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The second parameter is '%s' and should be 'chat', trying to continue","dcc"),dcc->szParam1.ptr());
}
}
KviStr szExtensions = dcc->szType;
szExtensions.cutRight(4); // cut off CHAT
#ifdef COMPILE_SSL_SUPPORT
bool bSSLExtension = szExtensions.contains('S',false);
#else //!COMPILE_SSL_SUPPORT
if(szExtensions.contains('S',false))
{
dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC CHAT is not available","dcc"));
return;
}
#endif //!COMPILE_SSL_SUPPORT
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
dcc_fill_local_nick_user_host(d,dcc);
d->szIp = dcc->szParam2.ptr();
d->szPort = dcc->szParam3.ptr();
if(dcc->szParam4.hasData())
{
// zero port tag ?
if(d->szPort == "0")
{
// zero port request
if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault))
{
d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress);
if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false;
}
d->setZeroPortRequestTag(dcc->szParam4.ptr());
TQString tmp;
if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))d->szListenIp = "0.0.0.0";
else d->szListenIp=tmp;
d->szListenPort = "0"; // any port is OK
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccChat);
d->bActive = false; // we must listen then...
} else {
// zero port acknowledge
// check if this is a tag that we have sent out
TQString szTag = TQString(dcc->szParam4.ptr());
KviDccZeroPortTag * t = g_pDccBroker->findZeroPortTag(szTag);
if(!t)
{
// hum.. not our tag
// FIXME: As segnaled by PRAEDO, ezbounce seems to send a fourth parameter in response to /quote ezb log
// Pragma: That's a bug in ezbounce, it sends the filesize of the log as a DCC CHAT parameter...
// The author probably copied and pasted the CTCP line from DCC SEND and forgot to remove the filesize.
// We *could* add an option to ignore the last parameter and treat it as a standard dcc chat
// request, but since we don't encourage bugs, we don't do it :D
// Mail me at pragma at kvirc dot net if you really think it's necessary.
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: it looks like a zero port tag acknowledge but I have either never seen this tag or it was sent more than 120 seconds ago","dcc"));
dcc_module_request_error(dcc,__tr2qs_ctx("It seems that I haven't requested this dcc chat","dcc"));
delete d;
return;
} else {
g_pDccBroker->removeZeroPortTag(szTag);
}
d->bAutoAccept = true; // auto-accept it (we have sent it out)
d->bActive = true;
}
} else {
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccChat);
d->bActive = true; // we have to connct (standard active chat)
}
#ifdef COMPILE_SSL_SUPPORT
d->bIsSSL = bSSLExtension;
#endif
dcc_module_set_dcc_type(d,"CHAT");
d->triggerCreationEvent();
g_pDccBroker->handleChatRequest(d);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SEND
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccRecv(KviDccRequest * dcc);
static void dccModuleParseDccSend(KviDccRequest *dcc)
{
//#warning "Ignore files depending on file type ? (MediaType ?)"
//
// We have received a DCC SEND request in the following form
//
// DCC [ST]SEND <filename> <ipaddress> <port> <filesize>
//
// Now the things are a bit tricky... we eventually can
// reply with a DCC RESUME and receive a DCC ACCEPT then
// The format of these requests is:
//
// DCC RESUME <filename> <port> <resumepos>
// ACCEPT <filename> <port> <resumepos>
//
// There is a mIrc extension that allows <port> to be 0
// and adds a last parameter that seems to be a random number (thnx YaP :)
// that is used to keep track of the connection.
// This extension is used by firewalled machines to initiate a DCC SEND:
// the receiving side should respond with a DCC SEND offer
// with the same random number appended, listen for a connection, and receive the file
// instead of sending it.
//
// when a zero port request is initiated by another party we get
// DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag>
// if (and only if) we want to resume we reply with
// DCC RESUME <filename> 0 <resumesize> <tag>
// in this case the remote part replies again with
// DCC ACCEPT <filename> 0 <resumesize> <tag>
// and we finally reply with
// DCC SEND <filename> <ourip> <ourport> <filesize> <tag>
//
// when a zero port request is initiated by us we send out
// DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag>
// and if the remote party wants to resume then we get
// DCC RESUME <filename> 0 <resumesize> <tag>
// and we eventually reply with
// DCC ACCEPT <filename> 0 <resumesize> <tag>
// and we finally get
// DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag>
//
// Thus if there is a <tag> and the port is 0, then the remote party
// is trying to send a file to us, but if the port is nonzero then
// we have sent out a zero port request and the remote party acked it
//
if((!kvi_strEqualCS(dcc->szParam3.ptr(),"0")) && dcc->szParam5.hasData())
{
// DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag>
// zero port acknowledge: treat as a RECV that should look like
// DCC [TS]RECV <filename> <remoteip> <remoteport> <resume-filesize>
// but since we have stored the sharedfile with the name <tag>
// we do exchange the params :)
KviDccZeroPortTag * t = g_pDccBroker->findZeroPortTag(dcc->szParam5.ptr());
if(t)
{
dcc->szParam4.sprintf("%u",t->m_uResumePosition);
g_pDccBroker->removeZeroPortTag(dcc->szParam5.ptr());
} else {
// this should never happen since we always add
// a zero port tag for out outgoing requests
// but well... maybe the user did something behing our back...
dcc->szParam4 = "0"; // no resume possible in this case
}
// swap the tag and the filename (we have added a fileoffer with this tag)
dcc->szParam1 = dcc->szParam5;
dcc->szParam5 = "";
dccModuleParseDccRecv(dcc);
return;
}
// First of all we check the transfer limits
dcc->szParam1=dcc->pConsole->decodeText(dcc->szParam1);
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_check_concurrent_transfers_limit(dcc))return;
// Then we ensure that the data that the remote end has sent are valid
if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return;
if(!(dcc->szParam4.isUnsignedNum()))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The fourth parameter should be the file size but does not appear to be an unsigned number, trying to continue","dcc"),dcc->szParam4.ptr());
}
dcc->szParam4 = __tr2qs_ctx("<unknown size>","dcc");
}
if(dcc->szParam1.contains('/'))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr());
}
dcc->szParam1.cutToLast('/');
}
KviStr szExtensions = dcc->szType;
szExtensions.cutRight(4); // cut off SEND
bool bTurboExtension = szExtensions.contains('T',false);
#ifdef COMPILE_SSL_SUPPORT
bool bSSLExtension = szExtensions.contains('S',false);
#else //!COMPILE_SSL_SUPPORT
if(szExtensions.contains('S',false))
{
dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC SEND is not available","dcc"));
return;
}
#endif //!COMPILE_SSL_SUPPORT
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
dcc_fill_local_nick_user_host(d,dcc);
d->szIp = dcc->szParam2.ptr();
d->szPort = dcc->szParam3.ptr();
d->szFileName = dcc->szParam1.ptr();
d->szFileSize = dcc->szParam4.ptr();
if(d->szPort=="0" && dcc->szParam5.hasData())
{
if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault))
{
d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress);
if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false;
}
d->setZeroPortRequestTag(dcc->szParam5.ptr());
TQString tmp;
if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))d->szListenIp = "0.0.0.0";
else d->szListenIp=TQString(tmp);
d->szListenPort = "0"; // any port is OK
d->bSendRequest = true;
d->szLocalFileSize = d->szFileSize;
}
d->bActive = !d->isZeroPortRequest(); // we have to connect unless it is a zero port request
d->bResume = false;
d->bRecvFile = true;
d->bIsTdcc = bTurboExtension;
d->bNoAcks = d->bIsTdcc;
#ifdef COMPILE_SSL_SUPPORT
d->bIsSSL = bSSLExtension;
#endif
d->bOverrideMinimize = false;
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccSend);
d->bIsIncomingAvatar = g_pApp->findPendingAvatarChange(dcc->pConsole,d->szNick,d->szFileName);
dcc_module_set_dcc_type(d,"RECV");
if(KVI_OPTION_BOOL(KviOption_boolAutoAcceptIncomingAvatars))d->bAutoAccept = d->bAutoAccept || d->bIsIncomingAvatar;
d->triggerCreationEvent();
g_pDccBroker->recvFileManage(d);
}
static void dccModuleParseDccAccept(KviDccRequest *dcc)
{
// this is usually DCC ACCEPT <filename> <port> <resumesize>
// but may be also
// DCC ACCEPT <filename> 0 <resumesize> <tag>
if(!g_pDccBroker->handleResumeAccepted(dcc->szParam1.ptr(),dcc->szParam2.ptr(),dcc->szParam4.ptr()))
{
//#warning "IF KviOption_boolReplyCtcpErrmsgOnInvalidAccept..."
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Can't proceed with DCC RECV: Transfer not initiated for file %s on port %s","dcc"),dcc->szParam1.ptr(),dcc->szParam2.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
}
}
static void dccModuleParseDccResume(KviDccRequest *dcc)
{
// This is usually RESUME <filename> <port> <resumesize>
// when a zero port request is initiated by us we send out
// DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag>
// and if the remote party wants to resume then we get
// DCC RESUME <filename> 0 <resumesize> <tag>
// and we eventually reply with
// DCC ACCEPT <filename> 0 <resumesize> <tag>
// and we finally get
// DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag>
bool bOk;
unsigned int filePos = dcc->szParam3.toUInt(&bOk);
if(!bOk)
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid resume position argument '%s'","dcc"),dcc->szParam3.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
return;
}
if(!g_pDccBroker->handleResumeRequest(dcc,dcc->szParam1.ptr(),dcc->szParam2.ptr(),filePos,dcc->szParam4.ptr()))
{
//#warning "IF KviOption_boolReplyCtcpErrmsgOnInvalidResume..."
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,
__tr2qs_ctx("Can't proceed with DCC SEND: Transfer not initiated for file %s on port %s, or invalid resume size","dcc"),
dcc->szParam1.ptr(),dcc->szParam2.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RECV
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccRecv(KviDccRequest * dcc)
{
// DCC [TS]RECV <filename> <ipaddr> <port> <resume-filesize>
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_check_concurrent_transfers_limit(dcc))return;
if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return;
if(!(dcc->szParam4.isUnsignedNum()))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->outputNoFmt(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request has resume file size missing, assuming a resume file size of 0","dcc"));
}
dcc->szParam4 = "0";
}
if(dcc->szParam1.contains('/'))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr());
}
dcc->szParam1.cutToLast('/');
}
KviStr szExtensions = dcc->szType;
szExtensions.cutRight(4); // cut off RECV
bool bTurboExtension = szExtensions.contains('T',false);
#ifdef COMPILE_SSL_SUPPORT
bool bSSLExtension = szExtensions.contains('S',false);
#else //!COMPILE_SSL_SUPPORT
if(szExtensions.contains('S',false))
{
dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC RECV is not available","dcc"));
return;
}
#endif //!COMPILE_SSL_SUPPORT
// If we have a file offer for this...do it automatically
KviSharedFile * o = g_pSharedFilesManager->lookupSharedFile(dcc->szParam1.ptr(),dcc->ctcpMsg->pSource,0);
if(o)
{
unsigned int uResumeSize = dcc->szParam4.toUInt(); // this will NEVER fail
if(uResumeSize >= o->fileSize())
{
// senseless request
KviStr szError(KviStr::Format,
__tr2qs_ctx("Invalid RECV request: Position %u is is larger than file size","dcc"),uResumeSize);
dcc_module_request_error(dcc,szError.ptr());
return;
}
// ok...we have requested this send
// #warning "Maybe remove this file offer now ?"
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->user();
d->szHost = dcc->ctcpMsg->pSource->host();
d->szFileName = dcc->szParam1.ptr();
d->szFileSize = dcc->szParam4.ptr();
//d->bResume = false; // This is actually useless
d->szLocalFileName = o->absFilePath();
d->szLocalFileSize.setNum(o->fileSize()); // Should we look it up again ?
d->bRecvFile = false;
d->bNoAcks = bTurboExtension;
d->bAutoAccept = true;
d->bIsIncomingAvatar = false;
d->bIsTdcc = bTurboExtension;
#ifdef COMPILE_SSL_SUPPORT
d->bIsSSL = bSSLExtension;
#endif
d->bOverrideMinimize = false;
// We know everything
dcc_fill_local_nick_user_host(d,dcc);
d->bDoTimeout = true;
d->szIp = dcc->szParam2.ptr();
d->szPort = dcc->szParam3.ptr();
d->bActive = true;
dcc_module_set_dcc_type(d,"SEND");
d->triggerCreationEvent();
g_pDccBroker->sendFileExecute(0,d);
return;
} else {
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("%Q [%Q@%Q] is ready to receive the file \"%s\"","dcc"),
&(dcc->ctcpMsg->pSource->nick()),
&(dcc->ctcpMsg->pSource->username()),
&(dcc->ctcpMsg->pSource->host()),
dcc->szParam1.ptr());
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The remote client is listening on interface %s and port %s","dcc"),dcc->szParam2.ptr(),dcc->szParam3.ptr());
KviStr szSwitches = "-c";
if(bTurboExtension)szSwitches.prepend("-t ");
#ifdef COMPILE_SSL_SUPPORT
if(bSSLExtension)szSwitches.prepend("-s ");
#endif
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("Use %c\r![!dbl]dcc.send %s -i=%s -p=%s %Q\r/dcc.send %s -i=%s -p=%s %Q\r%c to send the file (or double-click on the socket)","dcc"),
KVI_TEXT_BOLD,
szSwitches.ptr(),
dcc->szParam2.ptr(),dcc->szParam3.ptr(),&(dcc->ctcpMsg->pSource->nick()),
szSwitches.ptr(),
dcc->szParam2.ptr(),dcc->szParam3.ptr(),&(dcc->ctcpMsg->pSource->nick()),
KVI_TEXT_BOLD);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RSEND
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccRSend(KviDccRequest *dcc)
{
// DCC RSEND <filename> <filesize>
//#warning "Ignore files depending on file type ? (MediaType ?)"
//
// We have received a DCC RSEND request in the following form
//
// DCC [ST]RSEND <filename> <filesize>
//
dcc->szParam1 = dcc->pConsole->decodeText(dcc->szParam1);
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_check_concurrent_transfers_limit(dcc))return;
if(!(dcc->szParam2.isUnsignedNum()))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The fourth parameter should be the file size but does not appear to be an unsigned number; trying to continue","dcc"),dcc->szParam2.ptr());
}
dcc->szParam2 = __tr_ctx("<unknown size>","dcc");
}
if(dcc->szParam1.contains('/'))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr());
}
dcc->szParam1.cutToLast('/');
}
KviStr szExtensions = dcc->szType;
szExtensions.cutRight(4); // cut off SEND
bool bTurboExtension = szExtensions.contains('T',false);
#ifdef COMPILE_SSL_SUPPORT
bool bSSLExtension = szExtensions.contains('S',false);
#else //!COMPILE_SSL_SUPPORT
if(szExtensions.contains('S',false))
{
dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC RSEND is not available","dcc"));
return;
}
#endif //!COMPILE_SSL_SUPPORT
//#warning "When behind a firewall, we should reply an error message and avoid setting up the listening connection"
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
d->szIp = __tr2qs_ctx("(unknown)","dcc");
d->szPort = d->szIp;
TQString tmp;
if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))
{
d->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("No suitable interface to listen on, trying to continue anyway...","dcc"));
d->szListenIp = "0.0.0.0";
} else
d->szListenIp=TQString(tmp);
d->szListenPort = "0";
dcc_fill_local_nick_user_host(d,dcc);
d->szFileName = dcc->szParam1.ptr();
d->szFileSize = dcc->szParam2.ptr();
d->bActive = false; // we have to listen!
d->bResume = false;
d->bRecvFile = true; // we have to receive the file!
#ifdef COMPILE_SSL_SUPPORT
d->bIsSSL = bSSLExtension;
#endif
d->bIsTdcc = bTurboExtension;
d->bSendRequest = true; // we have to send the [ST]RECV request back
d->bNoAcks = d->bIsTdcc;
d->bOverrideMinimize = false;
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccSend);
d->bIsIncomingAvatar = g_pApp->findPendingAvatarChange(dcc->pConsole,d->szNick.utf8().data(),d->szFileName.utf8().data());
if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault))
{
d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress);
if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false;
}
if(KVI_OPTION_BOOL(KviOption_boolAutoAcceptIncomingAvatars))d->bAutoAccept = d->bAutoAccept || d->bIsIncomingAvatar;
dcc_module_set_dcc_type(d,"RECV");
d->triggerCreationEvent();
g_pDccBroker->recvFileManage(d);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GET
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccGet(KviDccRequest *dcc)
{
// DCC [TS]GET <filename> [filesize]
// -> DCC [TS]SEND <filename> <ipaddr> <port> <filesize>
// ...
dcc->szParam1=dcc->pConsole->decodeText(dcc->szParam1);
bool bOk;
unsigned int uSize = dcc->szParam2.toUInt(&bOk);
if(!bOk)uSize = 0;
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_check_concurrent_transfers_limit(dcc))return;
KviStr szExtensions = dcc->szType;
szExtensions.cutRight(3); // cut off GET
bool bTurboExtension = szExtensions.contains('T',false);
#ifdef COMPILE_SSL_SUPPORT
bool bSSLExtension = szExtensions.contains('S',false);
#else //!COMPILE_SSL_SUPPORT
if(szExtensions.contains('S',false))
{
dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC GET is not available","dcc"));
return;
}
#endif //!COMPILE_SSL_SUPPORT
KviSharedFile * o = g_pSharedFilesManager->lookupSharedFile(dcc->szParam1.ptr(),dcc->ctcpMsg->pSource,uSize);
if(!o)
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,
__tr2qs_ctx("No file offer named '%s' (with size %s) available for %Q [%Q@%Q]","dcc"),
dcc->szParam1.ptr(),uSize > 0 ? dcc->szParam2.ptr() : __tr_ctx("\"any\"","dcc"),
&(dcc->ctcpMsg->pSource->nick()),
&(dcc->ctcpMsg->pSource->username()),
&(dcc->ctcpMsg->pSource->host()));
dcc_module_request_error(dcc,szError.ptr());
}
return;
}
//#warning "IF NOT IGNORE DCC GET!"
//#warning "CREATE IT MINIMIZED ETC..."
//#warning "MAYBE USE A DIALOG TO ACCEPT THE REQUEST ?"
//#warning "DO NOT ACCEPT /etc/* requests..."
if(KVI_OPTION_BOOL(KviOption_boolCantAcceptIncomingDccConnections))
{
// we have to use DCC RSEND , otherwise it will not work
KviStr szSubproto("RSEND");
szSubproto.prepend(szExtensions);
TQString szFileName = TQFileInfo(o->absFilePath()).fileName();
if(o->name() != szFileName)
{
// BUG
// If the file offer was added with a name that is senseless (like "mediaXYZ" for an *.mp3 file)
// then we would be going to RSEND that name here: the remote user woulnd't be
// able to recognize the file.
// Here we add another temporary offer with the right filename.
// now add a file offer , so he we will accept it automatically
// 120 secs is a reasonable timeout
TQString szMask;
dcc->ctcpMsg->pSource->mask(szMask,KviIrcMask::NickUserHost);
KviSharedFile * pOld = o;
o = g_pSharedFilesManager->addSharedFile(szFileName,o->absFilePath(),szMask,120);
if(!o)o = pOld; // give up (FIXME: should we notify that ?)
}
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("Accepting file request from %Q [%Q@%Q] for '%s' (real file: %Q), offering DCC %s since we can't accept incoming connections (user option)","dcc"),
&(dcc->ctcpMsg->pSource->nick()),
&(dcc->ctcpMsg->pSource->username()),
&(dcc->ctcpMsg->pSource->host()),dcc->szParam1.ptr(),
&(o->absFilePath()),szSubproto.ptr());
}
dcc->pConsole->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %u%c",
dcc->pConsole->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(),
0x01,szSubproto.ptr(),
dcc->pConsole->connection()->encodeText(dcc->szParam1.ptr()).data(),o->fileSize(),0x01);
return;
}
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szLocalFileName = o->absFilePath();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
d->bRecvFile = false;
dcc_fill_local_nick_user_host(d,dcc);
TQString tmp;
if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))
{
d->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("No suitable interface to listen on, trying to continue anyway...","dcc"));
d->szListenIp = "0.0.0.0";
} else
d->szListenIp=TQString(tmp);
//#warning "DO STH WITH THIS PORT (HOW TO SPECIFY IT ?)"
d->szListenPort = "0"; // any port is ok
if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault))
{
d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress);
if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false;
}
d->bDoTimeout = true;
d->szIp = __tr2qs_ctx("(unknown)","dcc");
d->szPort = d->szIp;
d->bActive = false;
d->bSendRequest = true;
d->bIsTdcc = bTurboExtension;
#ifdef COMPILE_SSL_SUPPORT
d->bIsSSL = bSSLExtension;
#endif
d->bNoAcks = d->bIsTdcc;
d->bOverrideMinimize = false;
dcc_module_set_dcc_type(d,"SEND");
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("Accepting file request from %Q [%Q@%Q] for '%s' (real file: %Q), offering DCC %Q","dcc"),
&(dcc->ctcpMsg->pSource->nick()),
&(dcc->ctcpMsg->pSource->username()),
&(dcc->ctcpMsg->pSource->host()),
dcc->szParam1.ptr(),
&(o->absFilePath()),&(d->szType));
}
d->triggerCreationEvent();
g_pDccBroker->sendFileExecute(0,d);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VOICE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccVoice(KviDccRequest *dcc)
{
//
// We have received a DCC VOICE request in the following form:
//
// DCC VOICE codec <ipaddress> <port> <sample-rate>
//
// This means that we're requested to setup an ACTIVE voice connection
// ... Easy task :)
//
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return;
#ifdef COMPILE_DISABLE_DCC_VOICE
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR,
__tr2qs_ctx("The above request cannot be accepted: DCC VOICE support not enabled at compilation time ","dcc"));
return;
}
#endif
// Actually unused parameter
if(!kvi_dcc_voice_is_valid_codec(dcc->szParam1.ptr()))
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR,
__tr2qs_ctx("The above request cannot be accepted: Unsupported codec '%s'","dcc"),dcc->szParam1.ptr());
return;
}
}
bool bOk;
int iSampleRate = dcc->szParam4.toInt(&bOk);
if(!bOk)
{
if(!dcc->ctcpMsg->msg->haltOutput())
{
dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
__tr2qs_ctx("The above request appears to be broken: Invalid sample-rate '%s', defaulting to 8000","dcc"),dcc->szParam4.ptr());
}
iSampleRate = 8000;
}
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
dcc_fill_local_nick_user_host(d,dcc);
d->szIp = dcc->szParam2.ptr();
d->szPort = dcc->szParam3.ptr();
d->bActive = true; // we have to connect
d->bIsTdcc = false;
d->bNoAcks = false; // this has no meaning in voice
d->szCodec = dcc->szParam1;
d->iSampleRate = iSampleRate;
d->bOverrideMinimize = false;
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccVoice);
dcc_module_set_dcc_type(d,"VOICE");
d->triggerCreationEvent();
g_pDccBroker->activeVoiceManage(d);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CANVAS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void dccModuleParseDccCanvas(KviDccRequest *dcc)
{
//
// We have received a DCC CANVAS request in the following form:
//
// DCC CANVAS unused <ipaddress> <port>
//
// This means that we're requested to setup an ACTIVE canvas connection
// ... Easy task :)
//
if(!dcc_module_check_limits(dcc))return;
if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return;
// Actually unused parameter
// if(!(kvi_strEqualCI("canvas",dcc->szParam1.ptr())))
// {
// if(!dcc->ctcpMsg->msg->haltOutput())
// {
// dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG,
// __tr("The above request is broken: the second parameter is '%s' and shoud be 'chat'; trying to continue"),dcc->szParam1.ptr());
// }
// }
#ifdef COMPILE_DCC_CANVAS
KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole);
d->szNick = dcc->ctcpMsg->pSource->nick();
d->szUser = dcc->ctcpMsg->pSource->username();
d->szHost = dcc->ctcpMsg->pSource->host();
dcc_fill_local_nick_user_host(d,dcc);
d->szIp = dcc->szParam2.ptr();
d->szPort = dcc->szParam3.ptr();
d->bActive = true; // we have to connect
d->bIsTdcc = false;
d->bNoAcks = false; // this has no meaning in canvas
d->bOverrideMinimize = false;
d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccCanvas);
dcc_module_set_dcc_type(d,"CANVAS");
d->triggerCreationEvent();
g_pDccBroker->activeCanvasManage(d);
#endif
}
static void dccModuleParseDccList(KviDccRequest *dcc)
{
// DCC LIST <mask> <ipaddr> <port>
// FIXME!
}
typedef void (*dccParseProc)(KviDccRequest *);
typedef struct _dccParseProcEntry
{
const char * type;
dccParseProc proc;
} dccParseProcEntry;
#define KVI_NUM_KNOWN_DCC_TYPES 27
static dccParseProcEntry dccParseProcTable[KVI_NUM_KNOWN_DCC_TYPES]=
{
{ "CHAT" , dccModuleParseDccChat },
{ "SCHAT" , dccModuleParseDccChat },
{ "SEND" , dccModuleParseDccSend },
{ "TSEND" , dccModuleParseDccSend },
{ "SSEND" , dccModuleParseDccSend },
{ "TSSEND" , dccModuleParseDccSend },
{ "STSEND" , dccModuleParseDccSend },
{ "GET" , dccModuleParseDccGet },
{ "SGET" , dccModuleParseDccGet },
{ "TGET" , dccModuleParseDccGet },
{ "STGET" , dccModuleParseDccGet },
{ "TSGET" , dccModuleParseDccGet },
{ "LIST" , dccModuleParseDccList },
{ "ACCEPT" , dccModuleParseDccAccept },
{ "RESUME" , dccModuleParseDccResume },
{ "RECV" , dccModuleParseDccRecv },
{ "SRECV" , dccModuleParseDccRecv },
{ "TRECV" , dccModuleParseDccRecv },
{ "TSRECV" , dccModuleParseDccRecv },
{ "STRECV" , dccModuleParseDccRecv },
{ "RSEND" , dccModuleParseDccRSend },
{ "SRSEND" , dccModuleParseDccRSend },
{ "TRSEND" , dccModuleParseDccRSend },
{ "STRSEND", dccModuleParseDccRSend },
{ "TSRSEND", dccModuleParseDccRSend },
{ "CANVAS" , dccModuleParseDccCanvas },
{ "VOICE" , dccModuleParseDccVoice }
};
// We want C linkage on this one: we want to be able to dlsym() it with a simple name
// FIXME: Is this portable enough ? Or is better to have a table entry ?
KVIMODULEEXPORTFUNC void dccModuleCtcpDccParseRoutine(KviDccRequest *dcc)
{
dcc->szType.toUpper();
for(int i=0;i<KVI_NUM_KNOWN_DCC_TYPES;i++)
{
if(kvi_strEqualCS(dccParseProcTable[i].type,dcc->szType.ptr()))
{
(dccParseProcTable[i].proc)(dcc);
return;
}
}
// ops...we don't know this dcc type
if(!dcc->ctcpMsg->msg->haltOutput())
{
KviStr szError(KviStr::Format,
__tr2qs_ctx("Unknown DCC type '%s'","dcc"),dcc->szType.ptr());
dcc_module_request_error(dcc,szError.ptr());
}
}