/* * Copyright (C) 2004-2013 See the AUTHORS file for details. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. */ #include #include #include #include #include using std::set; using std::vector; using std::map; #define IRCSOCKMODULECALL(macFUNC, macEXITER) NETWORKMODULECALL(macFUNC, m_pNetwork->GetUser(), m_pNetwork, NULL, macEXITER) // These are used in OnGeneralCTCP() const time_t CIRCSock::m_uCTCPFloodTime = 5; const unsigned int CIRCSock::m_uCTCPFloodCount = 5; // It will be bad if user sets it to 0.00000000000001 // If you want no flood protection, set network's flood rate to -1 // TODO move this constant to CIRCNetwork? static const double FLOOD_MINIMAL_RATE = 0.3; class CIRCFloodTimer : public CCron { CIRCSock* m_pSock; public: CIRCFloodTimer(CIRCSock* pSock) { m_pSock = pSock; StartMaxCycles(m_pSock->m_fFloodRate, 0); } virtual void RunJob() { if (m_pSock->m_iSendsAllowed < m_pSock->m_uFloodBurst) { m_pSock->m_iSendsAllowed++; } m_pSock->TrySend(); } }; bool CIRCSock::IsFloodProtected(double fRate) { return fRate > FLOOD_MINIMAL_RATE; } CIRCSock::CIRCSock(CIRCNetwork* pNetwork) : CZNCSock() { m_pNetwork = pNetwork; m_bAuthed = false; m_bNamesx = false; m_bUHNames = false; m_fFloodRate = m_pNetwork->GetFloodRate(); m_uFloodBurst = m_pNetwork->GetFloodBurst(); m_bFloodProtection = IsFloodProtected(m_fFloodRate); m_iSendsAllowed = m_uFloodBurst; EnableReadLine(); m_Nick.SetIdent(m_pNetwork->GetIdent()); m_Nick.SetHost(m_pNetwork->GetBindHost()); m_uMaxNickLen = 9; m_uCapPaused = 0; m_lastCTCP = 0; m_uNumCTCP = 0; m_sPerms = "*!@%+"; m_sPermModes = "qaohv"; m_mueChanModes['b'] = ListArg; m_mueChanModes['e'] = ListArg; m_mueChanModes['I'] = ListArg; m_mueChanModes['k'] = HasArg; m_mueChanModes['l'] = ArgWhenSet; m_mueChanModes['p'] = NoArg; m_mueChanModes['s'] = NoArg; m_mueChanModes['t'] = NoArg; m_mueChanModes['i'] = NoArg; m_mueChanModes['n'] = NoArg; pNetwork->SetIRCSocket(this); // RFC says a line can have 512 chars max, but we don't care ;) SetMaxBufferThreshold(1024); if (m_bFloodProtection) { AddCron(new CIRCFloodTimer(this)); } } CIRCSock::~CIRCSock() { if (!m_bAuthed) { IRCSOCKMODULECALL(OnIRCConnectionError(this), NOTHING); } const vector& vChans = m_pNetwork->GetChans(); for (unsigned int a = 0; a < vChans.size(); a++) { vChans[a]->Reset(); } m_pNetwork->IRCDisconnected(); for (map::iterator a = m_msChans.begin(); a != m_msChans.end(); ++a) { delete a->second; } Quit(); m_msChans.clear(); m_pNetwork->GetUser()->AddBytesRead(GetBytesRead()); m_pNetwork->GetUser()->AddBytesWritten(GetBytesWritten()); } void CIRCSock::Quit(const CString& sQuitMsg) { if (!m_bAuthed) { Close(CLT_NOW); return; } if (!sQuitMsg.empty()) { PutIRC("QUIT :" + sQuitMsg); } else { PutIRC("QUIT :" + m_pNetwork->ExpandString(m_pNetwork->GetUser()->GetQuitMsg())); } Close(CLT_AFTERWRITE); } void CIRCSock::ReadLine(const CString& sData) { CString sLine = sData; sLine.TrimRight("\n\r"); DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" << m_pNetwork->GetName() << ") IRC -> ZNC [" << sLine << "]"); bool bReturn = false; IRCSOCKMODULECALL(OnRaw(sLine), &bReturn); if (bReturn) return; if (sLine.Equals("PING ", false, 5)) { // Generate a reply and don't forward this to any user, // we don't want any PING forwarded PutIRCQuick("PONG " + sLine.substr(5)); return; } else if (sLine.Token(1).Equals("PONG")) { // Block PONGs, we already responded to the pings return; } else if (sLine.Equals("ERROR ", false, 6)) { //ERROR :Closing Link: nick[24.24.24.24] (Excess Flood) CString sError(sLine.substr(6)); sError.TrimPrefix(); m_pNetwork->PutStatus("Error from Server [" + sError + "]"); return; } CString sCmd = sLine.Token(1); if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) { CString sServer = sLine.Token(0).LeftChomp_n(); unsigned int uRaw = sCmd.ToUInt(); CString sNick = sLine.Token(2); CString sRest = sLine.Token(3, true); CString sTmp; switch (uRaw) { case 1: { // :irc.server.com 001 nick :Welcome to the Internet Relay Network nick if (m_bAuthed && sServer == "irc.znc.in") { // m_bAuthed == true => we already received another 001 => we might be in a traffic loop m_pNetwork->PutStatus("ZNC seems to be connected to itself, disconnecting..."); Quit(); return; } m_pNetwork->SetIRCServer(sServer); SetTimeout(540, TMO_READ); // Now that we are connected, let nature take its course PutIRC("WHO " + sNick); m_bAuthed = true; m_pNetwork->PutStatus("Connected!"); vector& vClients = m_pNetwork->GetClients(); for (unsigned int a = 0; a < vClients.size(); a++) { CClient* pClient = vClients[a]; CString sClientNick = pClient->GetNick(false); if (!sClientNick.Equals(sNick)) { // If they connected with a nick that doesn't match the one we got on irc, then we need to update them pClient->PutClient(":" + sClientNick + "!" + m_Nick.GetIdent() + "@" + m_Nick.GetHost() + " NICK :" + sNick); } } SetNick(sNick); IRCSOCKMODULECALL(OnIRCConnected(), NOTHING); m_pNetwork->ClearRawBuffer(); m_pNetwork->AddRawBuffer(":" + _NAMEDFMT(sServer) + " " + sCmd + " {target} " + _NAMEDFMT(sRest)); break; } case 5: ParseISupport(sRest); m_pNetwork->UpdateExactRawBuffer(":" + _NAMEDFMT(sServer) + " " + sCmd + " {target} " + _NAMEDFMT(sRest)); break; case 10: { // :irc.server.com 010 nick : CString sHost = sRest.Token(0); CString sPort = sRest.Token(1); CString sInfo = sRest.Token(2, true).TrimPrefix_n(); m_pNetwork->PutStatus("Server [" + m_pNetwork->GetCurrentServer()->GetString(false) + "] redirects us to [" + sHost + ":" + sPort + "] with reason [" + sInfo + "]"); m_pNetwork->PutStatus("Perhaps you want to add it as a new server."); // Don't send server redirects to the client return; } case 2: case 3: case 4: case 250: // highest connection count case 251: // user count case 252: // oper count case 254: // channel count case 255: // client count case 265: // local users case 266: // global users sTmp = ":" + _NAMEDFMT(sServer) + " " + sCmd; m_pNetwork->UpdateRawBuffer(sTmp, sTmp + " {target} " + _NAMEDFMT(sRest)); break; case 305: m_pNetwork->SetIRCAway(false); break; case 306: m_pNetwork->SetIRCAway(true); break; case 324: { // MODE sRest.Trim(); CChan* pChan = m_pNetwork->FindChan(sRest.Token(0)); if (pChan) { pChan->SetModes(sRest.Token(1, true)); // We don't SetModeKnown(true) here, // because a 329 will follow if (!pChan->IsModeKnown()) { // When we JOIN, we send a MODE // request. This makes sure the // reply isn't forwarded. return; } if (pChan->IsDetached()) { return; } } } break; case 329: { sRest.Trim(); CChan* pChan = m_pNetwork->FindChan(sRest.Token(0)); if (pChan) { unsigned long ulDate = sLine.Token(4).ToULong(); pChan->SetCreationDate(ulDate); if (!pChan->IsModeKnown()) { pChan->SetModeKnown(true); // When we JOIN, we send a MODE // request. This makes sure the // reply isn't forwarded. return; } if (pChan->IsDetached()) { return; } } } break; case 331: { // :irc.server.com 331 yournick #chan :No topic is set. CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); if (pChan) { pChan->SetTopic(""); if (pChan->IsDetached()) { return; } } break; } case 332: { // :irc.server.com 332 yournick #chan :This is a topic CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); if (pChan) { CString sTopic = sLine.Token(4, true); sTopic.LeftChomp(); pChan->SetTopic(sTopic); if (pChan->IsDetached()) { return; } } break; } case 333: { // :irc.server.com 333 yournick #chan setternick 1112320796 CChan* pChan = m_pNetwork->FindChan(sLine.Token(3)); if (pChan) { sNick = sLine.Token(4); unsigned long ulDate = sLine.Token(5).ToULong(); pChan->SetTopicOwner(sNick); pChan->SetTopicDate(ulDate); if (pChan->IsDetached()) { return; } } break; } case 352: { // WHO // :irc.yourserver.com 352 yournick #chan ident theirhost.com irc.theirserver.com theirnick H :0 Real Name sServer = sLine.Token(0); sNick = sLine.Token(7); CString sChan = sLine.Token(3); CString sIdent = sLine.Token(4); CString sHost = sLine.Token(5); sServer.LeftChomp(); if (sNick.Equals(GetNick())) { m_Nick.SetIdent(sIdent); m_Nick.SetHost(sHost); } m_pNetwork->SetIRCNick(m_Nick); m_pNetwork->SetIRCServer(sServer); const vector& vChans = m_pNetwork->GetChans(); for (unsigned int a = 0; a < vChans.size(); a++) { vChans[a]->OnWho(sNick, sIdent, sHost); } if (m_bNamesx && (sNick.size() > 1) && IsPermChar(sNick[1])) { // sLine uses multi-prefix vector& vClients = m_pNetwork->GetClients(); vector::iterator it; for (it = vClients.begin(); it != vClients.end(); ++it) { CClient *pClient = *it; if (pClient->HasNamesx()) { m_pNetwork->PutUser(sLine, pClient); } else { // The client doesn't support multi-prefix so we need to remove // the other prefixes. CString sNewNick = sNick; size_t pos = sNick.find_first_not_of(GetPerms()); if (pos >= 2 && pos != CString::npos) { sNewNick = sNick[0] + sNick.substr(pos); } CString sNewLine = sServer + " 352 " + sLine.Token(2) + " " + sChan + " " + sIdent + " " + sHost + " " + sLine.Token(6) + " " + sNewNick + " " + sLine.Token(8, true); m_pNetwork->PutUser(sNewLine, pClient); } } return; } CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan && pChan->IsDetached()) { return; } break; } case 353: { // NAMES sRest.Trim(); // Todo: allow for non @+= server msgs CChan* pChan = m_pNetwork->FindChan(sRest.Token(1)); // If we don't know that channel, some client might have // requested a /names for it and we really should forward this. if (pChan) { CString sNicks = sRest.Token(2, true).TrimPrefix_n(); pChan->AddNicks(sNicks); if (pChan->IsDetached()) { return; } } ForwardRaw353(sLine); // We forwarded it already, so return return; } case 366: { // end of names list // :irc.server.com 366 nick #chan :End of /NAMES list. CChan* pChan = m_pNetwork->FindChan(sRest.Token(0)); if (pChan) { if (pChan->IsOn()) { // If we are the only one in the chan, set our default modes if (pChan->GetNickCount() == 1) { CString sModes = pChan->GetDefaultModes(); if (sModes.empty()) { sModes = m_pNetwork->GetUser()->GetDefaultChanModes(); } if (!sModes.empty()) { PutIRC("MODE " + pChan->GetName() + " " + sModes); } } } if (pChan->IsDetached()) { // don't put it to clients return; } } break; } case 381: // You are now an IRC Operator m_pNetwork->AddRawBuffer(":" + _NAMEDFMT(sServer) + " " + sCmd + " {target} " + _NAMEDFMT(sRest)); break; case 375: // begin motd case 422: // MOTD File is missing if (m_pNetwork->GetIRCServer().Equals(sServer)) { m_pNetwork->ClearMotdBuffer(); } case 372: // motd case 376: // end motd if (m_pNetwork->GetIRCServer().Equals(sServer)) { m_pNetwork->AddMotdBuffer(":" + _NAMEDFMT(sServer) + " " + sCmd + " {target} " + _NAMEDFMT(sRest)); } break; case 437: // :irc.server.net 437 * badnick :Nick/channel is temporarily unavailable // :irc.server.net 437 mynick badnick :Nick/channel is temporarily unavailable // :irc.server.net 437 mynick badnick :Cannot change nickname while banned on channel if (m_pNetwork->IsChan(sRest.Token(0)) || sNick != "*") break; case 432: // :irc.server.com 432 * nick :Erroneous Nickname: Illegal characters case 433: { CString sBadNick = sRest.Token(0); if (!m_bAuthed) { SendAltNick(sBadNick); return; } break; } case 451: // :irc.server.com 451 CAP :You have not registered // Servers that dont support CAP will give us this error, dont send it to the client if (sNick.Equals("CAP")) return; case 470: { // :irc.unreal.net 470 mynick [Link] #chan1 has become full, so you are automatically being transferred to the linked channel #chan2 // :mccaffrey.freenode.net 470 mynick #electronics ##electronics :Forwarding to another channel // freenode style numeric CChan* pChan = m_pNetwork->FindChan(sRest.Token(0)); if (!pChan) { // unreal style numeric pChan = m_pNetwork->FindChan(sRest.Token(1)); } if (pChan) { pChan->Disable(); m_pNetwork->PutStatus("Channel [" + pChan->GetName() + "] is linked to " "another channel and was thus disabled."); } break; } case 670: // :hydra.sector5d.org 670 kylef :STARTTLS successful, go ahead with TLS handshake // 670 is a response to `STARTTLS` telling the client to switch to TLS if (!GetSSL()) { StartTLS(); m_pNetwork->PutStatus("Switched to SSL (STARTTLS)"); } return; } } else { CNick Nick(sLine.Token(0).TrimPrefix_n()); sCmd = sLine.Token(1); CString sRest = sLine.Token(2, true); if (sCmd.Equals("NICK")) { CString sNewNick = sRest.TrimPrefix_n(); bool bIsVisible = false; vector vFoundChans; const vector& vChans = m_pNetwork->GetChans(); for (unsigned int a = 0; a < vChans.size(); a++) { CChan* pChan = vChans[a]; if (pChan->ChangeNick(Nick.GetNick(), sNewNick)) { vFoundChans.push_back(pChan); if (!pChan->IsDetached()) { bIsVisible = true; } } } // Todo: use nick compare function here if (Nick.GetNick().Equals(GetNick())) { // We are changing our own nick, the clients always must see this! bIsVisible = false; SetNick(sNewNick); m_pNetwork->PutUser(sLine); } IRCSOCKMODULECALL(OnNick(Nick, sNewNick, vFoundChans), NOTHING); if (!bIsVisible) { return; } } else if (sCmd.Equals("QUIT")) { CString sMessage = sRest.TrimPrefix_n(); bool bIsVisible = false; // :nick!ident@host.com QUIT :message if (Nick.GetNick().Equals(GetNick())) { m_pNetwork->PutStatus("You quit [" + sMessage + "]"); // We don't call module hooks and we don't // forward this quit to clients (Some clients // disconnect if they receive such a QUIT) return; } vector vFoundChans; const vector& vChans = m_pNetwork->GetChans(); for (unsigned int a = 0; a < vChans.size(); a++) { CChan* pChan = vChans[a]; if (pChan->RemNick(Nick.GetNick())) { vFoundChans.push_back(pChan); if (!pChan->IsDetached()) { bIsVisible = true; } } } IRCSOCKMODULECALL(OnQuit(Nick, sMessage, vFoundChans), NOTHING); if (!bIsVisible) { return; } } else if (sCmd.Equals("JOIN")) { CString sChan = sRest.Token(0).TrimPrefix_n(); CChan* pChan; // Todo: use nick compare function if (Nick.GetNick().Equals(GetNick())) { m_pNetwork->AddChan(sChan, false); pChan = m_pNetwork->FindChan(sChan); if (pChan) { pChan->Enable(); pChan->SetIsOn(true); PutIRC("MODE " + sChan); } } else { pChan = m_pNetwork->FindChan(sChan); } if (pChan) { pChan->AddNick(Nick.GetNickMask()); IRCSOCKMODULECALL(OnJoin(Nick.GetNickMask(), *pChan), NOTHING); if (pChan->IsDetached()) { return; } } } else if (sCmd.Equals("PART")) { CString sChan = sRest.Token(0).TrimPrefix_n(); CString sMsg = sRest.Token(1, true).TrimPrefix_n(); CChan* pChan = m_pNetwork->FindChan(sChan); bool bDetached = false; if (pChan) { pChan->RemNick(Nick.GetNick()); IRCSOCKMODULECALL(OnPart(Nick.GetNickMask(), *pChan, sMsg), NOTHING); if (pChan->IsDetached()) bDetached = true; } // Todo: use nick compare function if (Nick.GetNick().Equals(GetNick())) { m_pNetwork->DelChan(sChan); } /* * We use this boolean because * m_pNetwork->DelChan() will delete this channel * and thus we would dereference an * already-freed pointer! */ if (bDetached) { return; } } else if (sCmd.Equals("MODE")) { CString sTarget = sRest.Token(0); CString sModes = sRest.Token(1, true); if (sModes.Left(1) == ":") sModes = sModes.substr(1); CChan* pChan = m_pNetwork->FindChan(sTarget); if (pChan) { pChan->ModeChange(sModes, &Nick); if (pChan->IsDetached()) { return; } } else if (sTarget == m_Nick.GetNick()) { CString sModeArg = sModes.Token(0); bool bAdd = true; /* no module call defined (yet?) MODULECALL(OnRawUserMode(*pOpNick, *this, sModeArg, sArgs), m_pNetwork->GetUser(), NULL, ); */ for (unsigned int a = 0; a < sModeArg.size(); a++) { const unsigned char& uMode = sModeArg[a]; if (uMode == '+') { bAdd = true; } else if (uMode == '-') { bAdd = false; } else { if (bAdd) { m_scUserModes.insert(uMode); } else { m_scUserModes.erase(uMode); } } } } } else if (sCmd.Equals("KICK")) { // :opnick!ident@host.com KICK #chan nick :msg CString sChan = sRest.Token(0); CString sKickedNick = sRest.Token(1); CString sMsg = sRest.Token(2, true); sMsg.LeftChomp(); CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan) { IRCSOCKMODULECALL(OnKick(Nick, sKickedNick, *pChan, sMsg), NOTHING); // do not remove the nick till after the OnKick call, so modules // can do Chan.FindNick or something to get more info. pChan->RemNick(sKickedNick); } if (GetNick().Equals(sKickedNick) && pChan) { pChan->SetIsOn(false); // Don't try to rejoin! pChan->Disable(); } if ((pChan) && (pChan->IsDetached())) { return; } } else if (sCmd.Equals("NOTICE")) { // :nick!ident@host.com NOTICE #chan :Message CString sTarget = sRest.Token(0); CString sMsg = sRest.Token(1, true); sMsg.LeftChomp(); if (sMsg.WildCmp("\001*\001")) { sMsg.LeftChomp(); sMsg.RightChomp(); if (sTarget.Equals(GetNick())) { if (OnCTCPReply(Nick, sMsg)) { return; } } m_pNetwork->PutUser(":" + Nick.GetNickMask() + " NOTICE " + sTarget + " :\001" + sMsg + "\001"); return; } else { if (sTarget.Equals(GetNick())) { if (OnPrivNotice(Nick, sMsg)) { return; } } else { if (OnChanNotice(Nick, sTarget, sMsg)) { return; } } } if (Nick.GetNick().Equals(m_pNetwork->GetIRCServer())) { m_pNetwork->PutUser(":" + Nick.GetNick() + " NOTICE " + sTarget + " :" + sMsg); } else { m_pNetwork->PutUser(":" + Nick.GetNickMask() + " NOTICE " + sTarget + " :" + sMsg); } return; } else if (sCmd.Equals("TOPIC")) { // :nick!ident@host.com TOPIC #chan :This is a topic CChan* pChan = m_pNetwork->FindChan(sLine.Token(2)); if (pChan) { CString sTopic = sLine.Token(3, true); sTopic.LeftChomp(); IRCSOCKMODULECALL(OnTopic(Nick, *pChan, sTopic), &bReturn); if (bReturn) return; pChan->SetTopicOwner(Nick.GetNick()); pChan->SetTopicDate((unsigned long) time(NULL)); pChan->SetTopic(sTopic); if (pChan->IsDetached()) { return; // Don't forward this } sLine = ":" + Nick.GetNickMask() + " TOPIC " + pChan->GetName() + " :" + sTopic; } } else if (sCmd.Equals("PRIVMSG")) { // :nick!ident@host.com PRIVMSG #chan :Message CString sTarget = sRest.Token(0); CString sMsg = sRest.Token(1, true).TrimPrefix_n(); if (sMsg.WildCmp("\001*\001")) { sMsg.LeftChomp(); sMsg.RightChomp(); if (sTarget.Equals(GetNick())) { if (OnPrivCTCP(Nick, sMsg)) { return; } } else { if (OnChanCTCP(Nick, sTarget, sMsg)) { return; } } m_pNetwork->PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + sTarget + " :\001" + sMsg + "\001"); return; } else { if (sTarget.Equals(GetNick())) { if (OnPrivMsg(Nick, sMsg)) { return; } } else { if (OnChanMsg(Nick, sTarget, sMsg)) { return; } } m_pNetwork->PutUser(":" + Nick.GetNickMask() + " PRIVMSG " + sTarget + " :" + sMsg); return; } } else if (sCmd.Equals("WALLOPS")) { // :blub!dummy@rox-8DBEFE92 WALLOPS :this is a test CString sMsg = sRest.Token(0, true).TrimPrefix_n(); if (!m_pNetwork->IsUserOnline()) { m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " WALLOPS :{text}", sMsg); } } else if (sCmd.Equals("CAP")) { // CAPs are supported only before authorization. if (!m_bAuthed) { // sRest.Token(0) is most likely "*". No idea why, the // CAP spec don't mention this, but all implementations // I've seen add this extra asterisk CString sSubCmd = sRest.Token(1); // If the caplist of a reply is too long, it's split // into multiple replies. A "*" is prepended to show // that the list was split into multiple replies. // This is useful mainly for LS. For ACK and NAK // replies, there's no real need for this, because // we request only 1 capability per line. // If we will need to support broken servers or will // send several requests per line, need to delay ACK // actions until all ACK lines are received and // to recognize past request of NAK by 100 chars // of this reply. CString sArgs; if (sRest.Token(2) == "*") { sArgs = sRest.Token(3, true).TrimPrefix_n(); } else { sArgs = sRest.Token(2, true).TrimPrefix_n(); } if (sSubCmd == "LS") { VCString vsTokens; VCString::iterator it; sArgs.Split(" ", vsTokens, false); for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { if (OnServerCapAvailable(*it) || *it == "multi-prefix" || *it == "userhost-in-names") { m_ssPendingCaps.insert(*it); } } } else if (sSubCmd == "ACK") { sArgs.Trim(); IRCSOCKMODULECALL(OnServerCapResult(sArgs, true), NOTHING); if ("multi-prefix" == sArgs) { m_bNamesx = true; } else if ("userhost-in-names" == sArgs) { m_bUHNames = true; } m_ssAcceptedCaps.insert(sArgs); } else if (sSubCmd == "NAK") { // This should work because there's no [known] // capability with length of name more than 100 characters. sArgs.Trim(); IRCSOCKMODULECALL(OnServerCapResult(sArgs, false), NOTHING); } SendNextCap(); } // Don't forward any CAP stuff to the client return; } else if (sCmd.Equals("INVITE")) { IRCSOCKMODULECALL(OnInvite(Nick, sLine.Token(3).TrimPrefix_n(":")), &bReturn); if (bReturn) return; } } m_pNetwork->PutUser(sLine); } void CIRCSock::SendNextCap() { if (!m_uCapPaused) { if (m_ssPendingCaps.empty()) { // We already got all needed ACK/NAK replies. PutIRC("CAP END"); } else { CString sCap = *m_ssPendingCaps.begin(); m_ssPendingCaps.erase(m_ssPendingCaps.begin()); PutIRC("CAP REQ :" + sCap); } } } void CIRCSock::PauseCap() { ++m_uCapPaused; } void CIRCSock::ResumeCap() { --m_uCapPaused; SendNextCap(); } bool CIRCSock::OnServerCapAvailable(const CString& sCap) { bool bResult = false; IRCSOCKMODULECALL(OnServerCapAvailable(sCap), &bResult); return bResult; } bool CIRCSock::OnCTCPReply(CNick& Nick, CString& sMessage) { bool bResult = false; IRCSOCKMODULECALL(OnCTCPReply(Nick, sMessage), &bResult); return bResult; } bool CIRCSock::OnPrivCTCP(CNick& Nick, CString& sMessage) { bool bResult = false; IRCSOCKMODULECALL(OnPrivCTCP(Nick, sMessage), &bResult); if (bResult) return true; if (sMessage.TrimPrefix("ACTION ")) { bResult = false; IRCSOCKMODULECALL(OnPrivAction(Nick, sMessage), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline()) { // If the user is detached, add to the buffer m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :\001ACTION {text}\001", sMessage); } sMessage = "ACTION " + sMessage; } // This handles everything which wasn't handled yet return OnGeneralCTCP(Nick, sMessage); } bool CIRCSock::OnGeneralCTCP(CNick& Nick, CString& sMessage) { const MCString& mssCTCPReplies = m_pNetwork->GetUser()->GetCTCPReplies(); CString sQuery = sMessage.Token(0).AsUpper(); MCString::const_iterator it = mssCTCPReplies.find(sQuery); bool bHaveReply = false; CString sReply; if (it != mssCTCPReplies.end()) { sReply = m_pNetwork->ExpandString(it->second); bHaveReply = true; if (sReply.empty()) { return true; } } if (!bHaveReply && !m_pNetwork->IsUserAttached()) { if (sQuery == "VERSION") { sReply = CZNC::GetTag(); } else if (sQuery == "PING") { sReply = sMessage.Token(1, true); } } if (!sReply.empty()) { time_t now = time(NULL); // If the last CTCP is older than m_uCTCPFloodTime, reset the counter if (m_lastCTCP + m_uCTCPFloodTime < now) m_uNumCTCP = 0; m_lastCTCP = now; // If we are over the limit, don't reply to this CTCP if (m_uNumCTCP >= m_uCTCPFloodCount) { DEBUG("CTCP flood detected - not replying to query"); return false; } m_uNumCTCP++; PutIRC("NOTICE " + Nick.GetNick() + " :\001" + sQuery + " " + sReply + "\001"); return true; } return false; } bool CIRCSock::OnPrivNotice(CNick& Nick, CString& sMessage) { bool bResult = false; IRCSOCKMODULECALL(OnPrivNotice(Nick, sMessage), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline()) { // If the user is detached, add to the buffer m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE {target} :{text}", sMessage); } return false; } bool CIRCSock::OnPrivMsg(CNick& Nick, CString& sMessage) { bool bResult = false; IRCSOCKMODULECALL(OnPrivMsg(Nick, sMessage), &bResult); if (bResult) return true; if (!m_pNetwork->IsUserOnline()) { // If the user is detached, add to the buffer m_pNetwork->AddQueryBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG {target} :{text}", sMessage); } return false; } bool CIRCSock::OnChanCTCP(CNick& Nick, const CString& sChan, CString& sMessage) { CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan) { bool bResult = false; IRCSOCKMODULECALL(OnChanCTCP(Nick, *pChan, sMessage), &bResult); if (bResult) return true; // Record a /me if (sMessage.TrimPrefix("ACTION ")) { bResult = false; IRCSOCKMODULECALL(OnChanAction(Nick, *pChan, sMessage), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sChan) + " :\001ACTION {text}\001", sMessage); } sMessage = "ACTION " + sMessage; } } if (OnGeneralCTCP(Nick, sMessage)) return true; return (pChan && pChan->IsDetached()); } bool CIRCSock::OnChanNotice(CNick& Nick, const CString& sChan, CString& sMessage) { CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan) { bool bResult = false; IRCSOCKMODULECALL(OnChanNotice(Nick, *pChan, sMessage), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " NOTICE " + _NAMEDFMT(sChan) + " :{text}", sMessage); } } return ((pChan) && (pChan->IsDetached())); } bool CIRCSock::OnChanMsg(CNick& Nick, const CString& sChan, CString& sMessage) { CChan* pChan = m_pNetwork->FindChan(sChan); if (pChan) { bool bResult = false; IRCSOCKMODULECALL(OnChanMsg(Nick, *pChan, sMessage), &bResult); if (bResult) return true; if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline() || pChan->IsDetached()) { pChan->AddBuffer(":" + _NAMEDFMT(Nick.GetNickMask()) + " PRIVMSG " + _NAMEDFMT(sChan) + " :{text}", sMessage); } } return ((pChan) && (pChan->IsDetached())); } void CIRCSock::PutIRC(const CString& sLine) { DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" << m_pNetwork->GetName() << ") ZNC -> IRC [" << sLine << "] (queued)"); m_vsSendQueue.push_back(sLine); TrySend(); } void CIRCSock::PutIRCQuick(const CString& sLine) { DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" << m_pNetwork->GetName() << ") ZNC -> IRC [" << sLine << "] (queued to front)"); m_vsSendQueue.push_front(sLine); TrySend(); } void CIRCSock::TrySend() { while (!m_vsSendQueue.empty() && (m_iSendsAllowed > 0 || !m_bFloodProtection)) { m_iSendsAllowed--; DEBUG("(" << m_pNetwork->GetUser()->GetUserName() << "/" << m_pNetwork->GetName() << ") ZNC -> IRC [" << m_vsSendQueue.front() << "]"); Write(m_vsSendQueue.front() + "\r\n"); m_vsSendQueue.pop_front(); } } void CIRCSock::SetNick(const CString& sNick) { m_Nick.SetNick(sNick); m_pNetwork->SetIRCNick(m_Nick); } void CIRCSock::Connected() { DEBUG(GetSockName() << " == Connected()"); CString sPass = m_sPass; CString sNick = m_pNetwork->GetNick(); CString sIdent = m_pNetwork->GetIdent(); CString sRealName = m_pNetwork->GetRealName(); bool bReturn = false; IRCSOCKMODULECALL(OnIRCRegistration(sPass, sNick, sIdent, sRealName), &bReturn); if (bReturn) return; PutIRC("CAP LS"); if (!sPass.empty()) { PutIRC("PASS " + sPass); } PutIRC("NICK " + sNick); PutIRC("USER " + sIdent + " \"" + sIdent + "\" \"" + sIdent + "\" :" + sRealName); // SendAltNick() needs this m_Nick.SetNick(sNick); } void CIRCSock::Disconnected() { IRCSOCKMODULECALL(OnIRCDisconnected(), NOTHING); DEBUG(GetSockName() << " == Disconnected()"); if (!m_pNetwork->GetUser()->IsBeingDeleted() && m_pNetwork->GetIRCConnectEnabled() && m_pNetwork->GetServers().size() != 0) { m_pNetwork->PutStatus("Disconnected from IRC. Reconnecting..."); } m_pNetwork->ClearRawBuffer(); m_pNetwork->ClearMotdBuffer(); ResetChans(); // send a "reset user modes" cmd to the client. // otherwise, on reconnect, it might think it still // had user modes that it actually doesn't have. CString sUserMode; for (set::const_iterator it = m_scUserModes.begin(); it != m_scUserModes.end(); ++it) { sUserMode += *it; } if (!sUserMode.empty()) { m_pNetwork->PutUser(":" + m_pNetwork->GetIRCNick().GetNickMask() + " MODE " + m_pNetwork->GetIRCNick().GetNick() + " :-" + sUserMode); } // also clear the user modes in our space: m_scUserModes.clear(); } void CIRCSock::SockError(int iErrno, const CString& sDescription) { CString sError = sDescription; DEBUG(GetSockName() << " == SockError(" << iErrno << " " << sError << ")"); if (!m_pNetwork->GetUser()->IsBeingDeleted()) { if (GetConState() != CST_OK) { m_pNetwork->PutStatus("Cannot connect to IRC (" + sError + "). Retrying..."); } else { m_pNetwork->PutStatus("Disconnected from IRC (" + sError + "). Reconnecting..."); } } m_pNetwork->ClearRawBuffer(); m_pNetwork->ClearMotdBuffer(); ResetChans(); m_scUserModes.clear(); } void CIRCSock::Timeout() { DEBUG(GetSockName() << " == Timeout()"); if (!m_pNetwork->GetUser()->IsBeingDeleted()) { m_pNetwork->PutStatus("IRC connection timed out. Reconnecting..."); } m_pNetwork->ClearRawBuffer(); m_pNetwork->ClearMotdBuffer(); ResetChans(); m_scUserModes.clear(); } void CIRCSock::ConnectionRefused() { DEBUG(GetSockName() << " == ConnectionRefused()"); if (!m_pNetwork->GetUser()->IsBeingDeleted()) { m_pNetwork->PutStatus("Connection Refused. Reconnecting..."); } m_pNetwork->ClearRawBuffer(); m_pNetwork->ClearMotdBuffer(); } void CIRCSock::ReachedMaxBuffer() { DEBUG(GetSockName() << " == ReachedMaxBuffer()"); m_pNetwork->PutStatus("Received a too long line from the IRC server!"); Quit(); } void CIRCSock::ParseISupport(const CString& sLine) { VCString vsTokens; VCString::iterator it; sLine.Split(" ", vsTokens, false); for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { CString sName = it->Token(0, false, "="); CString sValue = it->Token(1, true, "="); if (0 < sName.length() && ':' == sName[0]) { break; } m_mISupport[sName] = sValue; if (sName.Equals("PREFIX")) { CString sPrefixes = sValue.Token(1, false, ")"); CString sPermModes = sValue.Token(0, false, ")"); sPermModes.TrimLeft("("); if (!sPrefixes.empty() && sPermModes.size() == sPrefixes.size()) { m_sPerms = sPrefixes; m_sPermModes = sPermModes; } } else if (sName.Equals("CHANTYPES")) { m_pNetwork->SetChanPrefixes(sValue); } else if (sName.Equals("NICKLEN")) { unsigned int uMax = sValue.ToUInt(); if (uMax) { m_uMaxNickLen = uMax; } } else if (sName.Equals("CHANMODES")) { if (!sValue.empty()) { m_mueChanModes.clear(); for (unsigned int a = 0; a < 4; a++) { CString sModes = sValue.Token(a, false, ","); for (unsigned int b = 0; b < sModes.size(); b++) { m_mueChanModes[sModes[b]] = (EChanModeArgs) a; } } } } else if (sName.Equals("NAMESX")) { if (m_bNamesx) continue; m_bNamesx = true; PutIRC("PROTOCTL NAMESX"); } else if (sName.Equals("UHNAMES")) { if (m_bUHNames) continue; m_bUHNames = true; PutIRC("PROTOCTL UHNAMES"); } } } void CIRCSock::ForwardRaw353(const CString& sLine) const { vector& vClients = m_pNetwork->GetClients(); vector::iterator it; for (it = vClients.begin(); it != vClients.end(); ++it) { ForwardRaw353(sLine, *it); } } void CIRCSock::ForwardRaw353(const CString& sLine, CClient* pClient) const { CString sNicks = sLine.Token(5, true).TrimPrefix_n(); if ((!m_bNamesx || pClient->HasNamesx()) && (!m_bUHNames || pClient->HasUHNames())) { // Client and server have both the same UHNames and Namesx stuff enabled m_pNetwork->PutUser(sLine, pClient); } else { // Get everything except the actual user list CString sTmp = sLine.Token(0, false, " :") + " :"; VCString vsNicks; VCString::const_iterator it; // This loop runs once for every nick on the channel sNicks.Split(" ", vsNicks, false); for (it = vsNicks.begin(); it != vsNicks.end(); ++it) { CString sNick = *it; if (sNick.empty()) break; if (m_bNamesx && !pClient->HasNamesx() && IsPermChar(sNick[0])) { // Server has, client doesn't have NAMESX, so we just use the first perm char size_t pos = sNick.find_first_not_of(GetPerms()); if (pos >= 2 && pos != CString::npos) { sNick = sNick[0] + sNick.substr(pos); } } if (m_bUHNames && !pClient->HasUHNames()) { // Server has, client hasnt UHNAMES, // so we strip away ident and host. sNick = sNick.Token(0, false, "!"); } sTmp += sNick + " "; } // Strip away the spaces we inserted at the end sTmp.TrimRight(" "); m_pNetwork->PutUser(sTmp, pClient); } } void CIRCSock::SendAltNick(const CString& sBadNick) { const CString& sLastNick = m_Nick.GetNick(); // We don't know the maximum allowed nick length yet, but we know which // nick we sent last. If sBadNick is shorter than that, we assume the // server truncated our nick. if (sBadNick.length() < sLastNick.length()) m_uMaxNickLen = (unsigned int)sBadNick.length(); unsigned int uMax = m_uMaxNickLen; const CString& sConfNick = m_pNetwork->GetNick(); const CString& sAltNick = m_pNetwork->GetAltNick(); CString sNewNick; if (sLastNick.Equals(sConfNick)) { if ((!sAltNick.empty()) && (!sConfNick.Equals(sAltNick))) { sNewNick = sAltNick; } else { sNewNick = sConfNick.Left(uMax - 1) + "-"; } } else if (sLastNick.Equals(sAltNick)) { sNewNick = sConfNick.Left(uMax -1) + "-"; } else if (sLastNick.Equals(CString(sConfNick.Left(uMax -1) + "-"))) { sNewNick = sConfNick.Left(uMax -1) + "|"; } else if (sLastNick.Equals(CString(sConfNick.Left(uMax -1) + "|"))) { sNewNick = sConfNick.Left(uMax -1) + "^"; } else if (sLastNick.Equals(CString(sConfNick.Left(uMax -1) + "^"))) { sNewNick = sConfNick.Left(uMax -1) + "a"; } else { char cLetter = 0; if (sBadNick.empty()) { m_pNetwork->PutUser("No free nick available"); Quit(); return; } cLetter = sBadNick.Right(1)[0]; if (cLetter == 'z') { m_pNetwork->PutUser("No free nick found"); Quit(); return; } sNewNick = sConfNick.Left(uMax -1) + ++cLetter; } PutIRC("NICK " + sNewNick); m_Nick.SetNick(sNewNick); } unsigned char CIRCSock::GetPermFromMode(unsigned char uMode) const { if (m_sPermModes.size() == m_sPerms.size()) { for (unsigned int a = 0; a < m_sPermModes.size(); a++) { if (m_sPermModes[a] == uMode) { return m_sPerms[a]; } } } return 0; } CIRCSock::EChanModeArgs CIRCSock::GetModeType(unsigned char uMode) const { map::const_iterator it = m_mueChanModes.find(uMode); if (it == m_mueChanModes.end()) { return NoArg; } return it->second; } void CIRCSock::ResetChans() { for (map::iterator a = m_msChans.begin(); a != m_msChans.end(); ++a) { a->second->Reset(); } }