//============================================================================= // // 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 #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 // // 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 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 0 // // and we reply with // // DCC CHAT chat // // when a zero port request is initiated by us we send out // // DCC CHAT chat 0 // // and we get // // DCC CHAT chat // // Thus if there is a 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 // // 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 // ACCEPT // // There is a mIrc extension that allows 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 0 // if (and only if) we want to resume we reply with // DCC RESUME 0 // in this case the remote part replies again with // DCC ACCEPT 0 // and we finally reply with // DCC SEND // // when a zero port request is initiated by us we send out // DCC SEND 0 // and if the remote party wants to resume then we get // DCC RESUME 0 // and we eventually reply with // DCC ACCEPT 0 // and we finally get // DCC SEND // // Thus if there is a 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 // zero port acknowledge: treat as a RECV that should look like // DCC [TS]RECV // but since we have stored the sharedfile with the name // 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("","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 // but may be also // DCC ACCEPT 0 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 // when a zero port request is initiated by us we send out // DCC SEND 0 // and if the remote party wants to resume then we get // DCC RESUME 0 // and we eventually reply with // DCC ACCEPT 0 // and we finally get // DCC SEND 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 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 //#warning "Ignore files depending on file type ? (MediaType ?)" // // We have received a DCC RSEND request in the following form // // DCC [ST]RSEND // 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("","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 [filesize] // -> DCC [TS]SEND // ... 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 // // 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 // // 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 // 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;iszType.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()); } }