Files
znc/modules/schat.cpp
psychon db21f88584 Rename OnUserAttached and OnUserDetached and add OnClientConnect
OnUserAttached is renamed to OnClientLogin and
OnUserDetached to OnClientDisconnect.
This adds some new function with different arguments for the old names to cause
warnings from -Woverloaded-virtual while compiling.

This patch also adds OnClientConnect() which is called when the low-level
raw connection is established. (No SSL-handshake was done at this point yet!)


git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@1266 726aef4b-f618-498e-8847-2d620e286838
2008-10-29 17:26:30 +00:00

523 lines
13 KiB
C++

/*
* Copyright (C) 2004-2008 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.
*
* Secure chat system
* Author: imaginos <imaginos@imaginos.net>
*/
#define REQUIRESSL
#include "User.h"
#include "znc.h"
#include <sstream>
using std::pair;
using std::stringstream;
class CSChat;
class CRemMarkerJob : public CTimer
{
public:
CRemMarkerJob(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel,
const CString& sDescription)
: CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {}
virtual ~CRemMarkerJob() {}
void SetNick(const CString & sNick)
{
m_sNick = sNick;
}
protected:
virtual void RunJob();
CString m_sNick;
};
class CSChatSock : public Csock
{
public:
CSChatSock(CSChat *pMod) : Csock()
{
m_pModule = pMod;
}
CSChatSock(int itimeout = 60) : Csock(itimeout)
{
m_pModule = NULL;
EnableReadLine();
}
CSChatSock(const CS_STRING & sHost, u_short iPort, int iTimeout = 60)
: Csock(sHost, iPort, iTimeout)
{
m_pModule = NULL;
EnableReadLine();
}
~CSChatSock();
virtual Csock *GetSockObj(const CS_STRING & sHostname, u_short iPort)
{
CSChatSock *p = new CSChatSock(sHostname, iPort);
p->SetModule(m_pModule);
p->SetChatNick(m_sChatNick);
p->SetSockName(GetSockName() + "::" + m_sChatNick);
return(p);
}
virtual bool ConnectionFrom(const CS_STRING & sHost, u_short iPort)
{
Close(); // close the listener after the first connection
return(true);
}
virtual void Connected();
virtual void Timeout();
void SetModule(CSChat *p)
{
m_pModule = p;
}
void SetChatNick(const CString & sNick)
{
m_sChatNick = sNick;
}
const CString & GetChatNick() const { return(m_sChatNick); }
virtual void ReadLine(const CS_STRING & sLine);
virtual void Disconnected();
virtual void AddLine(const CString & sLine)
{
m_vBuffer.insert(m_vBuffer.begin(), sLine);
if (m_vBuffer.size() > 200)
m_vBuffer.pop_back();
}
virtual void DumpBuffer()
{
for (vector<CS_STRING>::reverse_iterator it = m_vBuffer.rbegin(); it != m_vBuffer.rend(); it++)
ReadLine(*it);
m_vBuffer.clear();
}
private:
CSChat *m_pModule;
CString m_sChatNick;
vector<CS_STRING> m_vBuffer;
};
class CSChat : public CModule
{
public:
MODCONSTRUCTOR(CSChat) {}
virtual ~CSChat() { CleanSocks(); }
virtual bool OnLoad(const CString & sArgs, CString & sMessage)
{
m_sPemFile = sArgs;
if (m_sPemFile.empty())
{
m_sPemFile = CZNC::Get().GetPemLocation();
}
if (!CFile::Exists(m_sPemFile)) {
sMessage = "Unable to load pem file [" + m_sPemFile + "]";
return false;
}
return true;
}
virtual void OnClientLogin()
{
CString sName = "SCHAT::" + m_pUser->GetUserName();
for (u_int a = 0; a < m_pManager->size(); a++)
{
if ((*m_pManager)[a]->GetSockName() != sName.c_str() ||
((*m_pManager)[a]->GetType() == CSChatSock::LISTENER))
continue;
CSChatSock *p = (CSChatSock *)(*m_pManager)[a];
p->DumpBuffer();
}
}
void CleanSocks()
{
CString sName = "SCHAT::" + m_pUser->GetUserName();
for (u_int a= 0; a < m_pManager->size(); a++)
{
if ((*m_pManager)[a]->GetSockName() == sName)
m_pManager->DelSock(a--);
}
}
virtual EModRet OnUserRaw(CString & sLine)
{
if (sLine.Equals("schat ", false, 6))
{
OnModCommand("chat " + sLine.substr(6));
return(HALT);
} else if (sLine.Equals("schat"))
{
PutModule("SChat User Area ...");
OnModCommand("help");
return(HALT);
}
return(CONTINUE);
}
virtual void OnModCommand(const CString& sCommand)
{
CString sCom, sArgs;
sCom = sCommand.Token(0);
sArgs = sCommand.Token(1, true);
if (sCom.Equals("chat") && !sArgs.empty()) {
CString sSockName = "SCHAT::" + m_pUser->GetUserName();
CString sNick = "(s)" + sArgs;
for (u_int a= 0; a < m_pManager->size(); a++)
{
if ((*m_pManager)[a]->GetSockName() != sSockName)
continue;
CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a];
if (pSock->GetChatNick().Equals(sNick))
{
PutModule("Already Connected to [" + sArgs + "]");
return;
}
}
CSChatSock *pSock = new CSChatSock;
pSock->SetCipher("HIGH");
pSock->SetPemLocation(m_sPemFile);
pSock->SetModule(this);
pSock->SetChatNick(sNick);
u_short iPort = m_pManager->ListenRand(sSockName, m_pUser->GetLocalIP(), true,
SOMAXCONN, pSock, 60);
if (iPort == 0)
{
PutModule("Failed to start chat!");
return;
}
stringstream s;
s << "PRIVMSG " << sArgs << " :\001";
s << "DCC SCHAT chat ";
s << CUtils::GetLongIP(m_pUser->GetLocalIP());
s << " " << iPort << "\001";
PutIRC(s.str());
} else if (sCom.Equals("list"))
{
CString sName = "SCHAT::" + m_pUser->GetUserName();
CTable Table;
Table.AddColumn("Nick");
Table.AddColumn("Created");
Table.AddColumn("Host");
Table.AddColumn("Port");
Table.AddColumn("Status");
Table.AddColumn("Cipher");
for (u_int a= 0; a < m_pManager->size(); a++)
{
if ((*m_pManager)[a]->GetSockName() != sName)
continue;
Table.AddRow();
CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a];
Table.SetCell("Nick", pSock->GetChatNick());
unsigned long long iStartTime = pSock->GetStartTime();
time_t iTime = iStartTime / 1000;
char *pTime = ctime(&iTime);
if (pTime)
{
CString sTime = pTime;
sTime.Trim();
Table.SetCell("Created", sTime);
}
if (pSock->GetType() != CSChatSock::LISTENER)
{
Table.SetCell("Status", "Established");
Table.SetCell("Host", pSock->GetRemoteIP());
Table.SetCell("Port", CString(pSock->GetRemotePort()));
SSL_SESSION *pSession = pSock->GetSSLSession();
if ((pSession) && (pSession->cipher) && (pSession->cipher->name))
Table.SetCell("Cipher", pSession->cipher->name);
} else
{
Table.SetCell("Status", "Waiting");
Table.SetCell("Port", CString(pSock->GetLocalPort()));
}
}
if (Table.size()) {
PutModule(Table);
} else
PutModule("No SDCCs currently in session");
} else if (sCom.Equals("close"))
{
CString sName = "SCHAT::" + m_pUser->GetUserName();
for (u_int a = 0; a < m_pManager->size(); a++)
{
if ((*m_pManager)[a]->GetSockName() != sName)
continue;
CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a];
if (!sArgs.Equals("(s)", false, 3))
sArgs = "(s)" + sArgs;
if (sArgs.Equals(pSock->GetChatNick()))
{
pSock->Close();
return;
}
}
PutModule("No Such Chat [" + sArgs + "]");
} else if (sCom.Equals("showsocks") && m_pUser->IsAdmin())
{
CTable Table;
Table.AddColumn("SockName");
Table.AddColumn("Created");
Table.AddColumn("LocalIP:Port");
Table.AddColumn("RemoteIP:Port");
Table.AddColumn("Type");
Table.AddColumn("Cipher");
for (u_int a = 0; a < m_pManager->size(); a++)
{
Table.AddRow();
Csock *pSock = (*m_pManager)[a];
Table.SetCell("SockName", pSock->GetSockName());
unsigned long long iStartTime = pSock->GetStartTime();
time_t iTime = iStartTime / 1000;
char *pTime = ctime(&iTime);
if (pTime)
{
CString sTime = pTime;
sTime.Trim();
Table.SetCell("Created", sTime);
}
if (pSock->GetType() != Csock::LISTENER)
{
if (pSock->GetType() == Csock::OUTBOUND)
Table.SetCell("Type", "Outbound");
else
Table.SetCell("Type", "Inbound");
Table.SetCell("LocalIP:Port", pSock->GetLocalIP() + ":" +
CString(pSock->GetLocalPort()));
Table.SetCell("RemoteIP:Port", pSock->GetRemoteIP() + ":" +
CString(pSock->GetRemotePort()));
SSL_SESSION *pSession = pSock->GetSSLSession();
if ((pSession) && (pSession->cipher) && (pSession->cipher->name))
Table.SetCell("Cipher", pSession->cipher->name);
else
Table.SetCell("Cipher", "None");
} else
{
Table.SetCell("Type", "Listener");
Table.SetCell("LocalIP:Port", pSock->GetLocalIP() +
":" + CString(pSock->GetLocalPort()));
Table.SetCell("RemoteIP:Port", "0.0.0.0:0");
}
}
if (Table.size())
PutModule(Table);
else
PutModule("Error Finding Sockets");
} else if (sCom.Equals("help"))
{
PutModule("Commands are: ");
PutModule(" help - This text.");
PutModule(" chat <nick> - Chat a nick.");
PutModule(" list - List current chats.");
PutModule(" close <nick> - Close a chat to a nick.");
PutModule(" timers - Shows related timers.");
if (m_pUser->IsAdmin()) {
PutModule(" showsocks - Shows all socket connections.");
}
} else if (sCom.Equals("timers"))
ListTimers();
else
PutModule("Unknown command [" + sCom + "] [" + sArgs + "]");
}
virtual EModRet OnPrivCTCP(CNick& Nick, CString& sMessage)
{
if (sMessage.Equals("DCC SCHAT ", false, 10))
{
// chat ip port
unsigned long iIP = sMessage.Token(3).ToULong();
unsigned short iPort = sMessage.Token(4).ToUShort();
if ((iIP > 0) && (iPort > 0))
{
pair<u_long, u_short> pTmp;
CString sMask;
pTmp.first = iIP;
pTmp.second = iPort;
sMask = "(s)" + Nick.GetNick() + "!" + "(s)" +
Nick.GetNick() + "@" + CUtils::GetIP(iIP);
m_siiWaitingChats["(s)" + Nick.GetNick()] = pTmp;
SendToUser(sMask, "*** Incoming DCC SCHAT, Accept ? (yes/no)");
CRemMarkerJob *p = new CRemMarkerJob(this, 60, 1,
"Remove (s)" + Nick.GetNick(),
"Removes this nicks entry for waiting DCC.");
p->SetNick("(s)" + Nick.GetNick());
AddTimer(p);
return(HALT);
}
}
return(CONTINUE);
}
void AcceptSDCC(const CString & sNick, u_long iIP, u_short iPort)
{
CSChatSock *p = new CSChatSock(CUtils::GetIP(iIP), iPort, 60);
p->SetModule(this);
p->SetChatNick(sNick);
CString sSockName = "SCHAT::" + m_pUser->GetUserName() + "::" + sNick;
m_pManager->Connect(CUtils::GetIP(iIP), iPort, sSockName, 60, true, m_pUser->GetLocalIP(), p);
RemTimer("Remove " + sNick); // delete any associated timer to this nick
}
virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage)
{
if (sTarget.Left(3) == "(s)")
{
CString sSockName = "SCHAT::" + m_pUser->GetUserName() + "::" + sTarget;
CSChatSock *p = (CSChatSock *)m_pManager->FindSockByName(sSockName);
if (!p)
{
map< CString,pair< u_long,u_short > >::iterator it;
it = m_siiWaitingChats.find(sTarget);
if (it != m_siiWaitingChats.end())
{
if (!sMessage.Equals("yes"))
SendToUser(sTarget + "!" + sTarget + "@" +
CUtils::GetIP(it->second.first),
"Refusing to accept DCC SCHAT!");
else
AcceptSDCC(sTarget, it->second.first, it->second.second);
m_siiWaitingChats.erase(it);
return(HALT);
}
PutModule("No such SCHAT to [" + sTarget + "]");
} else
p->Write(sMessage + "\n");
return(HALT);
}
return(CONTINUE);
}
virtual void RemoveMarker(const CString & sNick)
{
map< CString,pair< u_long,u_short > >::iterator it = m_siiWaitingChats.find(sNick);
if (it != m_siiWaitingChats.end())
m_siiWaitingChats.erase(it);
}
void SendToUser(const CString & sFrom, const CString & sText)
{
//:*schat!znc@znc.in PRIVMSG Jim :
CString sSend = ":" + sFrom + " PRIVMSG " + m_pUser->GetCurNick() + " :" + sText;
PutUser(sSend);
}
bool IsAttached()
{
return(m_pUser->IsUserAttached());
}
private:
map< CString,pair< u_long,u_short > > m_siiWaitingChats;
CString m_sPemFile;
};
//////////////////// methods ////////////////
CSChatSock::~CSChatSock()
{
m_pModule->GetUser()->AddBytesRead(GetBytesRead());
m_pModule->GetUser()->AddBytesWritten(GetBytesWritten());
}
void CSChatSock::ReadLine(const CS_STRING & sLine)
{
if (m_pModule)
{
CString sText = sLine;
if (sText[sText.length()-1] == '\n')
sText.erase(sText.length()-1, 1);
if (sText[sText.length()-1] == '\r')
sText.erase(sText.length()-1, 1);
if (m_pModule->IsAttached())
m_pModule->SendToUser(m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), sText);
else
AddLine(sText);
}
}
void CSChatSock::Disconnected()
{
if (m_pModule)
m_pModule->SendToUser(m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(),
"*** Disconnected.");
}
void CSChatSock::Connected()
{
SetTimeout(0);
if (m_pModule)
m_pModule->SendToUser(m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), "*** Connected.");
}
void CSChatSock::Timeout()
{
if (m_pModule)
{
if (GetType() == LISTENER)
m_pModule->PutModule("Timeout while waiting for [" + m_sChatNick + "]");
else
m_pModule->SendToUser(m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(),
"*** Connection Timed out.");
}
}
void CRemMarkerJob::RunJob()
{
CSChat *p = (CSChat *)m_pModule;
p->RemoveMarker(m_sNick);
// store buffer
}
MODULEDEFS(CSChat, "Secure cross platform (:P) chat system")