diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
commit | a6d58bb6052ac8cb01805a48c4ad2f129126116f (patch) | |
tree | dd867a099fcbb263a8009a9fb22695b87855dad6 /src/modules/dcc/requests.cpp | |
download | kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.tar.gz kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.zip |
Added KDE3 version of kvirc
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kvirc@1095341 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/modules/dcc/requests.cpp')
-rw-r--r-- | src/modules/dcc/requests.cpp | 1154 |
1 files changed, 1154 insertions, 0 deletions
diff --git a/src/modules/dcc/requests.cpp b/src/modules/dcc/requests.cpp new file mode 100644 index 00000000..e1e18143 --- /dev/null +++ b/src/modules/dcc/requests.cpp @@ -0,0 +1,1154 @@ +//============================================================================= +// +// 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 <qfileinfo.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 QString& 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 QString& 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)) + { + QString szError = QString("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()); + QString 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()); + QString 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 + QString szTag = QString(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()); + QString tmp; + if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))d->szListenIp = "0.0.0.0"; + else d->szListenIp=QString(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; + QString 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=QString(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); + + + QString szFileName = QFileInfo(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 + QString 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); + + QString 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=QString(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()); + } +} |