Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

604 lines
12 KiB

// irc.cpp
// Made by Adi Degani - http://www.codeguru.com/network/irc.html
#include <stdio.h>
#include <stdlib.h>
#include <Tchar.h>
#include "agglobal.h"
#include "irc.h"
using namespace irc;
////////////////////////////////////////////////////////////////////
CIrcMessage::CIrcMessage(const char* lpszCmdLine, bool bIncoming)
: m_bIncoming(bIncoming)
{
ParseIrcCommand(lpszCmdLine);
}
CIrcMessage::CIrcMessage(const CIrcMessage& m)
: sCommand(m.sCommand),
parameters(m.parameters),
m_bIncoming(m.m_bIncoming)
{
prefix.sNick = m.prefix.sNick;
prefix.sUser = m.prefix.sUser;
prefix.sHost = m.prefix.sHost;
}
void CIrcMessage::Reset()
{
prefix.sNick = prefix.sUser = prefix.sHost = sCommand = "";
m_bIncoming = false;
parameters.clear();
}
CIrcMessage& CIrcMessage::operator = (const CIrcMessage& m)
{
if( &m != this )
{
sCommand = m.sCommand;
parameters = m.parameters;
prefix.sNick = m.prefix.sNick;
prefix.sUser = m.prefix.sUser;
prefix.sHost = m.prefix.sHost;
m_bIncoming = m.m_bIncoming;
}
return *this;
}
CIrcMessage& CIrcMessage::operator = (const char* lpszCmdLine)
{
Reset();
ParseIrcCommand(lpszCmdLine);
return *this;
}
void CIrcMessage::ParseIrcCommand(const char* lpszCmdLine)
{
const char* p1 = lpszCmdLine;
const char* p2 = lpszCmdLine;
assert(lpszCmdLine != NULL);
assert(*lpszCmdLine);
// prefix exists ?
if( *p1 == ':' )
{ // break prefix into its components (nick!user@host)
p2 = ++p1;
while( *p2 && !strchr(" !", *p2) )
++p2;
prefix.sNick.assign(p1, p2 - p1);
if( *p2 != '!' )
goto end_of_prefix;
p1 = ++p2;
while( *p2 && !strchr(" @", *p2) )
++p2;
prefix.sUser.assign(p1, p2 - p1);
if( *p2 != '@' )
goto end_of_prefix;
p1 = ++p2;
while( *p2 && !isspace(*p2) )
++p2;
prefix.sHost.assign(p1, p2 - p1);
end_of_prefix :
while( *p2 && isspace(*p2) )
++p2;
p1 = p2;
}
// get command
assert(*p1 != '\0');
p2 = p1;
while( *p2 && !isspace(*p2) )
++p2;
sCommand.assign(p1, p2 - p1);
_strupr((char*)sCommand.c_str());
while( *p2 && isspace(*p2) )
++p2;
p1 = p2;
// get parameters
while( *p1 )
{
if( *p1 == ':' )
{
++p1;
// seek end-of-message
while( *p2 )
++p2;
parameters.push_back(AgString(p1, p2 - p1));
break;
}
else
{
// seek end of parameter
while( *p2 && !isspace(*p2) )
++p2;
parameters.push_back(AgString(p1, p2 - p1));
// see next parameter
while( *p2 && isspace(*p2) )
++p2;
p1 = p2;
}
} // end parameters loop
}
AgString CIrcMessage::AsString() const
{
AgString s;
if( prefix.sNick.length() )
{
s += ":" + prefix.sNick;
if( prefix.sUser.length() && prefix.sHost.length() )
s += "!" + prefix.sUser + "@" + prefix.sHost;
s += " ";
}
s += sCommand;
for(unsigned int i=0; i < parameters.size(); i++)
{
s += " ";
if( i == parameters.size() - 1 ) // is last parameter ?
s += ":";
s += parameters[i];
}
s += endl;
return s;
}
////////////////////////////////////////////////////////////////////
CIrcSession::CIrcSession(IIrcSessionMonitor* pMonitor)
: m_hThread(NULL)
{
InitializeCriticalSection(&m_cs);
}
CIrcSession::~CIrcSession()
{
Disconnect();
DeleteCriticalSection(&m_cs);
}
bool CIrcSession::Connect(const CIrcSessionInfo& info)
{
assert(m_hThread==NULL && !(bool)m_socket);
try
{
m_identServer.Start(info.sUserID.c_str());
if( !m_socket.Create() )
throw "Failed to create socket!";
InetAddr addr(info.sServer.c_str(), (WORD)info.iPort);
if( !m_socket.Connect(addr) )
{
m_socket.Close();
throw "Failed to connect to host!";
}
m_info = info;
// start receiving messages from host
DWORD dwThreadId = 0;
m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, &dwThreadId);
Sleep(100);
if( info.sPassword.length() )
m_socket.Send("PASS %s\r\n", info.sPassword.c_str());
m_socket.Send("NICK %s\r\n", info.sNick.c_str());
TCHAR szHostName[MAX_PATH];
DWORD cbHostName = sizeof(szHostName);
GetComputerName(szHostName, &cbHostName);
m_socket.Send("USER %s %s %s :%s\r\n",
info.sUserID.c_str(), szHostName, "server", info.sFullName.c_str());
}
catch( const char* )
{
Disconnect();
}
catch( ... )
{
Disconnect();
}
return (bool)m_socket;
}
void CIrcSession::Disconnect(const char* lpszMessage)
{
static const DWORD dwServerTimeout = 3 * 1000;
if( !m_hThread )
return;
m_socket.Send("QUIT :%s\r\n", lpszMessage ? lpszMessage : "bye!");
if( m_hThread && WaitForSingleObject(m_hThread, dwServerTimeout) != WAIT_OBJECT_0 )
{
m_socket.Close();
Sleep(100);
if( m_hThread && WaitForSingleObject(m_hThread, dwServerTimeout) != WAIT_OBJECT_0 )
{
TerminateThread(m_hThread, 1);
CloseHandle(m_hThread);
m_hThread = NULL;
m_info.Reset();
}
}
}
void CIrcSession::Notify(const CIrcMessage* pmsg)
{
// forward message to monitor objects
EnterCriticalSection(&m_cs);
for(CIrcSessionMonitors::iterator it = m_monitors.begin();
it != m_monitors.end();
it++
)
{
(*it)->OnIrcMessage(pmsg);
}
LeaveCriticalSection(&m_cs);
}
void CIrcSession::DoReceive()
{
// CIrcIdentServer m_identServer;
char chBuf[1024*4+1];
int cbInBuf = 0;
// if( m_info.bIdentServer )
//m_identServer.Start(m_info.sUserID.c_str());
//Sleep(100);
while( m_socket )
{
int cbRead;
int nLinesProcessed = 0;
cbRead = m_socket.Receive((unsigned char*)chBuf+cbInBuf, sizeof(chBuf)-cbInBuf-1);
if( cbRead <= 0 )
break;
cbInBuf += cbRead;
chBuf[cbInBuf] = '\0';
char* pStart = chBuf;
while( *pStart )
{
char* pEnd;
// seek end-of-line
for(pEnd=pStart; *pEnd && *pEnd != '\r' && *pEnd != '\n'; ++pEnd)
;
if( *pEnd == '\0' )
break; // uncomplete message. stop parsing.
++nLinesProcessed;
// replace end-of-line with NULLs and skip
while( *pEnd == '\r' || *pEnd == '\n' )
*pEnd++ = '\0';
if( *pStart )
{
// process single message by monitor objects
CIrcMessage msg(pStart, true);
Notify(&msg);
}
cbInBuf -= pEnd - pStart;
assert(cbInBuf >= 0);
pStart = pEnd;
}
// discard processed messages
if( nLinesProcessed != 0 )
memmove(chBuf, pStart, cbInBuf+1);
}
if( m_socket )
m_socket.Close();
if( m_info.bIdentServer )
m_identServer.Stop();
// notify monitor objects that the connection has been closed
Notify(NULL);
}
DWORD WINAPI CIrcSession::ThreadProc(LPVOID pparam)
{
CIrcSession* pThis = (CIrcSession*)pparam;
try { pThis->DoReceive(); } catch( ... ) {}
pThis->m_info.Reset();
CloseHandle(pThis->m_hThread);
pThis->m_hThread = NULL;
return 0;
}
void CIrcSession::AddMonitor(IIrcSessionMonitor* pMonitor)
{
assert(pMonitor != NULL);
EnterCriticalSection(&m_cs);
m_monitors.insert(pMonitor);
LeaveCriticalSection(&m_cs);
}
void CIrcSession::RemoveMonitor(IIrcSessionMonitor* pMonitor)
{
assert(pMonitor != NULL);
EnterCriticalSection(&m_cs);
m_monitors.erase(pMonitor);
LeaveCriticalSection(&m_cs);
}
////////////////////////////////////////////////////////////////////
CIrcSessionInfo::CIrcSessionInfo()
: iPort(0), bIdentServer(false), iIdentServerPort(0)
{
}
CIrcSessionInfo::CIrcSessionInfo(const CIrcSessionInfo& si)
: sServer(si.sServer),
sServerName(si.sServerName),
iPort(si.iPort),
sNick(si.sNick),
sUserID(si.sUserID),
sFullName(si.sFullName),
sPassword(si.sPassword),
bIdentServer(si.bIdentServer),
sIdentServerType(si.sIdentServerType),
iIdentServerPort(si.iIdentServerPort)
{
}
void CIrcSessionInfo::Reset()
{
sServer = "";
sServerName = "";
iPort = 0;
sNick = "";
sUserID = "";
sFullName = "";
sPassword = "";
bIdentServer = false;
sIdentServerType = "";
iIdentServerPort = 0;
}
////////////////////////////////////////////////////////////////////
CIrcIdentServer::CIrcIdentServer()
: m_uiPort(0), m_hThread(NULL)
{
m_hReady = CreateEvent(NULL,TRUE,FALSE,"IDENTEVENT");
}
CIrcIdentServer::~CIrcIdentServer()
{
CloseHandle(m_hReady);
Stop();
}
bool CIrcIdentServer::Start(
const char* lpszUserID,
unsigned int uiPort,
const char* lpszResponseType
)
{
if( m_socket )
return false;
if( !m_socket.Create() )
return false;
if( !m_socket.Bind(InetAddr((WORD)uiPort)) )
{
m_socket.Close();
return false;
}
m_sResponseType = lpszResponseType;
m_sUserID = lpszUserID;
m_uiPort = uiPort;
DWORD dwThreadId = 0;
m_hThread = CreateThread(NULL, 0, ListenProc, this, 0, &dwThreadId);
WaitForSingleObject(m_hReady,INFINITE);
return true;
}
void CIrcIdentServer::Stop()
{
if( m_hThread )
{
m_socket.Close();
Sleep(100);
if( WaitForSingleObject(m_hThread, 2000) != WAIT_OBJECT_0 && m_hThread )
{
TerminateThread(m_hThread, 1);
CloseHandle(m_hThread);
m_hThread = NULL;
}
}
}
void CIrcIdentServer::DoThread()
{
m_socket.Listen();
while( (bool)m_socket )
{
InetAddr addr;
SetEvent(m_hReady);
Socket s = m_socket.Accept(addr);
//int iErr = WSAGetLastError();
if( !(bool)s )
break;
char szBuf[1024];
int cbRead = s.Receive((unsigned char*)szBuf, sizeof(szBuf)-1);
if( cbRead <= 0 )
continue;
szBuf[cbRead] = '\0';
// strip CRLF from query
for(char* p = szBuf; *p && *p != '\r' && *p != '\n'; ++p)
;
*p = '\0';
s.Send("%s : USERID : %s : %s\r\n",
szBuf, m_sResponseType.c_str(), m_sUserID.c_str());
Sleep(500);
s.Close();
}
SetEvent(m_hReady);
m_socket.Close();
}
DWORD WINAPI CIrcIdentServer::ListenProc(LPVOID pparam)
{
CIrcIdentServer* pThis = (CIrcIdentServer*)pparam;
try { pThis->DoThread(); } catch( ... ) {}
pThis->m_sResponseType = "";
pThis->m_sUserID = "";
pThis->m_uiPort = 0;
CloseHandle(pThis->m_hThread);
pThis->m_hThread = NULL;
return 0;
}
////////////////////////////////////////////////////////////////////
CIrcMonitor::HandlersMap CIrcMonitor::m_handlers;
CIrcMonitor::IrcCommandsMapsListEntry CIrcMonitor::m_handlersMapsListEntry
= { &CIrcMonitor::m_handlers, NULL };
CIrcMonitor::CIrcMonitor(CIrcSession& session)
: m_session(session)
{
m_xPost.SetMonitor(this);
}
CIrcMonitor::~CIrcMonitor()
{
}
void CIrcMonitor::OnIrcMessage(const CIrcMessage* pmsg)
{
CIrcMessage* pMsgCopy = NULL;
if( pmsg )
pMsgCopy = new CIrcMessage(*pmsg);
m_xPost.Post(0, (LPARAM)pMsgCopy);
}
void CIrcMonitor::OnCrossThreadsMessage(WPARAM wParam, LPARAM lParam)
{
CIrcMessage* pmsg = (CIrcMessage*)lParam;
OnIrcAll(pmsg);
if( pmsg )
{
PfnIrcMessageHandler pfn = FindMethod(pmsg->sCommand.c_str());
if( pfn )
{
// call member function. if it returns 'false',
// call the default handling
if( !(this->*pfn)(pmsg) )
OnIrcDefault(pmsg);
}
else // handler not found. call default handler
OnIrcDefault(pmsg);
delete pmsg;
}
else
OnIrcDisconnected();
}
CIrcMonitor::PfnIrcMessageHandler CIrcMonitor::FindMethod(const char* lpszName)
{
// call the recursive version with the most derived map
return FindMethod(GetIrcCommandsMap(), lpszName);
}
CIrcMonitor::PfnIrcMessageHandler CIrcMonitor::FindMethod(IrcCommandsMapsListEntry* pMapsList, const char* lpszName)
{
HandlersMap::iterator it = pMapsList->pHandlersMap->find(lpszName);
if( it != pMapsList->pHandlersMap->end() )
return (*it).second; // found !
else if( pMapsList->pBaseHandlersMap )
return FindMethod(pMapsList->pBaseHandlersMap, lpszName); // try at base class
return NULL; // not found in any map
}
////////////////////////////////////////////////////////////////////
DECLARE_IRC_MAP(CIrcDefaultMonitor, CIrcMonitor)
CIrcDefaultMonitor::CIrcDefaultMonitor(CIrcSession& session)
: CIrcMonitor(session)
{
IRC_MAP_ENTRY(CIrcDefaultMonitor, "NICK", OnIrc_NICK)
IRC_MAP_ENTRY(CIrcDefaultMonitor, "PING", OnIrc_PING)
IRC_MAP_ENTRY(CIrcDefaultMonitor, "002", OnIrc_YOURHOST)
IRC_MAP_ENTRY(CIrcDefaultMonitor, "005", OnIrc_BOUNCE)
}
bool CIrcDefaultMonitor::OnIrc_NICK(const CIrcMessage* pmsg)
{
if( (m_session.GetInfo().sNick == pmsg->prefix.sNick) && (pmsg->parameters.size() > 0) )
m_session.m_info.sNick = pmsg->parameters[0];
return false;
}
bool CIrcDefaultMonitor::OnIrc_PING(const CIrcMessage* pmsg)
{
char szResponse[100];
sprintf(szResponse, "PONG %s", pmsg->parameters[0].c_str());
m_session << CIrcMessage(szResponse);
return false;
}
bool CIrcDefaultMonitor::OnIrc_YOURHOST(const CIrcMessage* pmsg)
{
static const char* lpszFmt = "Your host is %[^ \x5b,], running version %s";
char szHostName[100], szVersion[100];
if( sscanf(pmsg->parameters[1].c_str(), lpszFmt, &szHostName, &szVersion) > 0 )
m_session.m_info.sServerName = szHostName;
return false;
}
bool CIrcDefaultMonitor::OnIrc_BOUNCE(const CIrcMessage* pmsg)
{
static const char* lpszFmt = "Try server %[^ ,], port %d";
char szAltServer[100];
int iAltPort = 0;
if( sscanf(pmsg->parameters[1].c_str(), lpszFmt, &szAltServer, &iAltPort) == 2 )
{
}
return false;
}