about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/engine/server/econ.cpp0
-rw-r--r--src/engine/server/econ.h0
-rw-r--r--src/engine/server/server.cpp116
-rw-r--r--src/engine/server/server.h25
-rw-r--r--src/engine/shared/config_variables.h4
-rw-r--r--src/engine/shared/network.h78
-rw-r--r--src/engine/shared/network_console.cpp144
-rw-r--r--src/engine/shared/network_console_conn.cpp176
8 files changed, 539 insertions, 4 deletions
diff --git a/src/engine/server/econ.cpp b/src/engine/server/econ.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/engine/server/econ.cpp
diff --git a/src/engine/server/econ.h b/src/engine/server/econ.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/engine/server/econ.h
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 2b174610..8e151035 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -317,8 +317,15 @@ int CServer::Init()
 		m_aClients[i].m_Snapshots.Init();
 	}
 
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		m_aEconClients[i].m_State = CEconClient::STATE_EMPTY;
+	}
+
 	m_CurrentGameTick = 0;
 
+	m_UseEcon = 0;
+
 	return 0;
 }
 
@@ -611,6 +618,30 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
 	return 0;
 }
 
+int CServer::NewConsoleClientCallback(int EconID, void *pUser)
+{
+	CServer *pThis = (CServer *)pUser;
+	pThis->m_aEconClients[EconID].m_State = CEconClient::STATE_CONNECTED;
+	pThis->m_NetConsole.SetTimeout(EconID, g_Config.m_SvEconAuthTimeout);
+	return 0;
+}
+
+int CServer::DelConsoleClientCallback(int EconID, const char *pReason, void *pUser)
+{
+	CServer *pThis = (CServer *)pUser;
+
+	NETADDR Addr = pThis->m_NetConsole.ClientAddr(EconID);
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	char aBuf[256];
+	str_format(aBuf, sizeof(aBuf), "econ client dropped. eid=%d addr=%s reason='%s'", EconID, aAddrStr, pReason);
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf);
+
+	pThis->m_aEconClients[EconID].m_State = CEconClient::STATE_EMPTY;
+	return 0;
+}
+
+
 void CServer::SendMap(int ClientID)
 {
 	CMsgPacker Msg(NETMSG_MAP_CHANGE);
@@ -633,7 +664,12 @@ void CServer::SendRconLine(int ClientID, const char *pLine)
 	SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
 }
 
-void CServer::SendRconLineAuthed(const char *pLine, void *pUser)
+void CServer::SendEconLine(int EconID, const char *pLine)
+{
+	m_NetConsole.Send(EconID, pLine);
+}
+
+void CServer::SendConsoleLineAuthed(const char *pLine, void *pUser)
 {
 	CServer *pThis = (CServer *)pUser;
 	static volatile int ReentryGuard = 0;
@@ -648,6 +684,15 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser)
 			pThis->SendRconLine(i, pLine);
 	}
 
+	if(pThis->m_UseEcon)
+	{
+		for(i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+		{
+			if(pThis->m_aEconClients[i].m_State == CEconClient::STATE_AUTHED)
+				pThis->SendEconLine(i, pLine);
+		}
+	}
+
 	ReentryGuard--;
 }
 
@@ -1084,6 +1129,47 @@ void CServer::PumpNetwork()
 		else
 			ProcessClientPacket(&Packet);
 	}
+
+	if(m_UseEcon)
+		EconPumpNetwork();
+}
+
+void CServer::EconPumpNetwork()
+{
+	m_NetConsole.Update();
+
+	char aBuf[NET_MAX_PACKETSIZE];
+	int EconID;
+
+	while(m_NetConsole.Recv(aBuf, sizeof(aBuf) - 1, &EconID))
+	{
+		dbg_assert(m_aEconClients[EconID].m_State != CEconClient::STATE_EMPTY, "got message from empty slot");
+		if(m_aEconClients[EconID].m_State == CEconClient::STATE_CONNECTED)
+		{
+			if(str_comp(aBuf, g_Config.m_SvRconPassword) == 0)
+			{
+				m_aEconClients[EconID].m_State = CEconClient::STATE_AUTHED;
+				m_NetConsole.Send(EconID, "Authentication successful. Remote console access granted.");
+				m_NetConsole.SetTimeout(EconID, g_Config.m_SvEconTimeout);
+
+				str_format(aBuf, sizeof(aBuf), "EconID=%d authed", EconID);
+				Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
+			}
+			else
+			{
+				m_NetConsole.Send(EconID, "Wrong password");
+			}
+		}
+		else if(m_aEconClients[EconID].m_State == CEconClient::STATE_AUTHED)
+		{
+			char aFormatted[256];
+			str_format(aFormatted, sizeof(aBuf), "eid=%d cmd='%s'", EconID, aBuf);
+			Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted);
+			m_RconClientID = EconID;
+			Console()->ExecuteLine(aBuf);
+			m_RconClientID = -1;
+		}
+	}
 }
 
 char *CServer::GetMapName()
@@ -1158,7 +1244,7 @@ int CServer::Run()
 	m_pStorage = Kernel()->RequestInterface<IStorage>();
 
 	//
-	Console()->RegisterPrintCallback(SendRconLineAuthed, this);
+	Console()->RegisterPrintCallback(SendConsoleLineAuthed, this);
 
 	// load map
 	if(!LoadMap(g_Config.m_SvMap))
@@ -1181,7 +1267,6 @@ int CServer::Run()
 		BindAddr.port = g_Config.m_SvPort;
 	}
 
-
 	if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0))
 	{
 		dbg_msg("server", "couldn't open socket. port might already be in use");
@@ -1190,6 +1275,31 @@ int CServer::Run()
 
 	m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this);
 
+	if(g_Config.m_SvEconPort && g_Config.m_SvRconPassword[0])
+	{
+		dbg_msg("econ", "binding econ to %s:%d", g_Config.m_SvEconBindaddr, g_Config.m_SvEconPort);
+		if(g_Config.m_SvEconBindaddr[0] && net_host_lookup(g_Config.m_SvEconBindaddr, &BindAddr, NETTYPE_ALL) == 0)
+		{
+			BindAddr.port = g_Config.m_SvEconPort;
+		}
+		else
+		{
+			mem_zero(&BindAddr, sizeof(BindAddr));
+			BindAddr.type = NETTYPE_ALL;
+			BindAddr.port = g_Config.m_SvEconPort;
+		}
+
+		if(m_NetConsole.Open(BindAddr, 0))
+		{
+			m_NetConsole.SetCallbacks(NewConsoleClientCallback, DelConsoleClientCallback, this);
+			m_UseEcon = 1;
+		}
+		else
+		{
+			dbg_msg("econ", "couldn't open econ socket. port might already be in use");
+		}
+	}
+
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName);
 	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index 5b6e038d..d744b9ff 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -109,10 +109,26 @@ public:
 
 	CClient m_aClients[MAX_CLIENTS];
 
+	class CEconClient
+	{
+	public:
+		enum
+		{
+			STATE_EMPTY=0,
+			STATE_CONNECTED,
+			STATE_AUTHED
+		};
+
+		int m_State;
+	};
+
+	CEconClient m_aEconClients[NET_MAX_CONSOLE_CLIENTS];
+
 	CSnapshotDelta m_SnapshotDelta;
 	CSnapshotBuilder m_SnapshotBuilder;
 	CSnapIDPool m_IDPool;
 	CNetServer m_NetServer;
+	CNetConsole m_NetConsole;
 
 	IEngineMap *m_pMap;
 
@@ -123,6 +139,8 @@ public:
 	int m_RconClientID;
 	int m_RconAuthLevel;
 
+	int m_UseEcon;
+
 	int64 m_Lastheartbeat;
 	//static NETADDR4 master_server;
 
@@ -168,10 +186,14 @@ public:
 	static int NewClientCallback(int ClientID, void *pUser);
 	static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
 
+	static int NewConsoleClientCallback(int EconID, void *pUser);
+	static int DelConsoleClientCallback(int EconID, const char *pReason, void *pUser);
+
 	void SendMap(int ClientID);
 	void SendConnectionReady(int ClientID);
 	void SendRconLine(int ClientID, const char *pLine);
-	static void SendRconLineAuthed(const char *pLine, void *pUser);
+	void SendEconLine(int EconID, const char *pLine);
+	static void SendConsoleLineAuthed(const char *pLine, void *pUser);
 
 	void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID);
 	void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID);
@@ -185,6 +207,7 @@ public:
 	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
 	int BanRemove(NETADDR Addr);
 
+	void EconPumpNetwork();
 
 	void PumpNetwork();
 
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index 213ebf26..fd318a7d 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -86,6 +86,10 @@ MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remo
 MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER, "Remote console password for moderators (limited access)")
 MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 3, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication")
 MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick")
+MACRO_CONFIG_STR(SvEconBindaddr, sv_econ_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous")
+MACRO_CONFIG_INT(SvEconPort, sv_econ_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console")
+MACRO_CONFIG_INT(SvEconAuthTimeout, sv_econ_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out")
+MACRO_CONFIG_INT(SvEconTimeout, sv_econ_timeout, 300, 1, 3600, CFGFLAG_SERVER, "Time in seconds before the econ connection times out")
 
 MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode")
 MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems")
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index 228ba6dd..d0b78d05 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -49,6 +49,7 @@ enum
 	NET_MAX_CHUNKHEADERSIZE = 5,
 	NET_PACKETHEADERSIZE = 3,
 	NET_MAX_CLIENTS = 16,
+	NET_MAX_CONSOLE_CLIENTS = 16,
 	NET_MAX_SEQUENCE = 1<<10,
 	NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1,
 
@@ -192,6 +193,41 @@ public:
 	int AckSequence() const { return m_Ack; }
 };
 
+class CConsoleNetConnection
+{
+private:
+	unsigned m_State;
+
+	NETADDR m_PeerAddr;
+	NETSOCKET m_Socket;
+
+	char m_aBuffer[NET_MAX_PACKETSIZE];
+	char *m_pBufferPos;
+
+	char m_aErrorString[256];
+
+	int m_Timeout;
+	int64 m_LastRecvTime;
+
+public:
+	void Init(NETSOCKET Socket);
+	void Init(NETSOCKET Socket, const NETADDR *pAddr);
+	int Connect(const NETADDR *pAddr);
+	void Disconnect(const char *pReason);
+
+	int State() const { return m_State; }
+	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const char *ErrorString() const { return m_aErrorString; }
+
+	void SetTimeout(int Timeout) { m_Timeout = Timeout; }
+	int Timeout() const { return m_Timeout; }
+
+	void Reset();
+	int Update();
+	int Send(const char *pLine);
+	int Recv(char *pLine, int MaxLength);
+};
+
 class CNetRecvUnpacker
 {
 public:
@@ -292,6 +328,48 @@ public:
 	void SetMaxClientsPerIP(int Max);
 };
 
+class CNetConsole
+{
+private:
+	struct CSlot
+	{
+		CConsoleNetConnection m_Connection;
+	};
+
+	NETSOCKET m_Socket;
+	CSlot m_aSlots[NET_MAX_CLIENTS];
+
+	NETFUNC_NEWCLIENT m_pfnNewClient;
+	NETFUNC_DELCLIENT m_pfnDelClient;
+	void *m_UserPtr;
+
+	CNetRecvUnpacker m_RecvUnpacker;
+
+public:
+	int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+
+	//
+	bool Open(NETADDR BindAddr, int Flags);
+	int Close();
+
+	//
+	int Broadcast(const char *pLine);
+	int Recv(char *pLine, int MaxLength, int *pClientID = 0);
+	int Send(int ClientID, const char *pLine);
+	int Update();
+
+	//
+	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
+	int Drop(int ClientID, const char *pReason);
+
+	//
+	void SetTimeout(int ClientID, int Timeout) { m_aSlots[ClientID].m_Connection.SetTimeout(Timeout); }
+
+	// status requests
+	int Timeout(int ClientID) { return m_aSlots[ClientID].m_Connection.Timeout(); }
+	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+};
+
 
 
 // client side
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
new file mode 100644
index 00000000..3e50a1ac
--- /dev/null
+++ b/src/engine/shared/network_console.cpp
@@ -0,0 +1,144 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <base/system.h>
+#include "network.h"
+
+bool CNetConsole::Open(NETADDR BindAddr, int Flags)
+{
+	// zero out the whole structure
+	mem_zero(this, sizeof(*this));
+
+	// open socket
+	m_Socket = net_tcp_create(&BindAddr);
+	if(!m_Socket.type)
+		return false;
+	if(net_tcp_listen(m_Socket, NET_MAX_CONSOLE_CLIENTS))
+		return false;
+	net_tcp_set_non_blocking(m_Socket);
+
+	for(int i = 0; i < NET_MAX_CLIENTS; i++)
+		m_aSlots[i].m_Connection.Init(m_Socket);
+
+	return true;
+}
+
+int CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+{
+	m_pfnNewClient = pfnNewClient;
+	m_pfnDelClient = pfnDelClient;
+	m_UserPtr = pUser;
+	return 0;
+}
+
+int CNetConsole::Close()
+{
+	// TODO: implement me
+	return 0;
+}
+
+int CNetConsole::Drop(int ClientID, const char *pReason)
+{
+	NETADDR Addr = ClientAddr(ClientID);
+
+	char aAddrStr[NETADDR_MAXSTRSIZE];
+	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+
+	if(m_pfnDelClient)
+		m_pfnDelClient(ClientID, pReason, m_UserPtr);
+
+	m_aSlots[ClientID].m_Connection.Disconnect(pReason);
+
+	return 0;
+}
+
+int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	char aError[256] = { 0 };
+	int FreeSlot = -1;
+	
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
+			FreeSlot = i;
+		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
+		{
+			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();;
+			if(net_addr_comp(pAddr, &PeerAddr) == 0)
+			{
+				str_copy(aError, "Only one client per IP allowed", sizeof(aError));
+				break;
+			}
+		}
+	}
+
+	if(!aError[0] && FreeSlot != -1)
+	{
+		m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
+		if(m_pfnNewClient)
+			m_pfnNewClient(FreeSlot, m_UserPtr);
+		return 1;
+	}
+
+	if(!aError[0])
+	{
+		str_copy(aError, "No free slot available", sizeof(aError));
+	}
+	
+	dbg_msg("netconsole", "refused client, reason=\"%s\"", aError);
+
+	net_tcp_send(Socket, aError, str_length(aError));
+	net_tcp_close(Socket);
+
+	return 0;
+}
+
+int CNetConsole::Update()
+{
+	NETSOCKET Socket;
+	NETADDR Addr;
+
+	while(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
+	{
+		AcceptClient(Socket, &Addr);
+	}
+
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
+			m_aSlots[i].m_Connection.Update();
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
+			Drop(i, m_aSlots[i].m_Connection.ErrorString());
+	}
+
+	return 0;
+}
+
+int CNetConsole::Recv(char *pLine, int MaxLength, int *pClientID)
+{
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength))
+		{
+			if(pClientID)
+				*pClientID = i;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CNetConsole::Broadcast(const char *pLine)
+{
+	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
+	{
+		if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
+			Send(i, pLine);
+	}
+	return 0;
+}
+
+int CNetConsole::Send(int ClientID, const char *pLine)
+{
+	return m_aSlots[ClientID].m_Connection.Send(pLine);
+}
+
diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp
new file mode 100644
index 00000000..4dd971a6
--- /dev/null
+++ b/src/engine/shared/network_console_conn.cpp
@@ -0,0 +1,176 @@
+/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
+/* If you are missing that file, acquire a complete release at teeworlds.com.                */
+#include <base/system.h>
+#include "config.h"
+#include "network.h"
+
+void CConsoleNetConnection::Reset()
+{
+	m_State = NET_CONNSTATE_OFFLINE;
+	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+	m_aErrorString[0] = 0;
+
+	m_aBuffer[0] = 0;
+	m_pBufferPos = 0;
+	m_Timeout = 0;
+}
+
+void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr)
+{
+	Reset();
+
+	m_Socket = Socket;
+	net_tcp_set_non_blocking(m_Socket);
+
+	m_LastRecvTime = time_get();
+
+	m_PeerAddr = *pAddr;
+	m_State = NET_CONNSTATE_ONLINE;
+}
+
+void CConsoleNetConnection::Init(NETSOCKET Socket)
+{
+	Reset();
+
+	m_Socket = Socket;
+	net_tcp_set_non_blocking(m_Socket);
+
+	m_LastRecvTime = time_get();
+}
+
+int CConsoleNetConnection::Connect(const NETADDR *pAddr)
+{
+	if(State() != NET_CONNSTATE_OFFLINE)
+		return -1;
+
+	// init connection
+	Reset();
+	m_PeerAddr = *pAddr;
+	net_tcp_connect(m_Socket, pAddr);
+	m_State = NET_CONNSTATE_ONLINE;
+	return 0;
+}
+
+void CConsoleNetConnection::Disconnect(const char *pReason)
+{
+	if(State() == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(pReason)
+	{
+		char aBuf[sizeof(pReason) + 4];
+		str_format(aBuf, sizeof(aBuf), "%s", pReason);
+		Send(aBuf);
+	}
+
+	net_tcp_close(m_Socket);
+
+	Reset();
+}
+
+int CConsoleNetConnection::Update()
+{
+	if(m_Timeout && time_get() > m_LastRecvTime + m_Timeout * time_freq())
+	{
+		m_State = NET_CONNSTATE_ERROR;
+		str_copy(m_aErrorString, "timeout", sizeof(m_aErrorString));
+		return -1;
+	}
+
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		char aBuf[NET_MAX_PACKETSIZE];
+
+		int Bytes = net_tcp_recv(m_Socket, aBuf, sizeof(aBuf) - 1);
+
+		if(Bytes > 0)
+		{
+			aBuf[Bytes - 1] = 0;
+
+			if(!m_pBufferPos)
+				m_aBuffer[0] = 0;
+			else if(m_pBufferPos != m_aBuffer)
+				mem_move(m_pBufferPos, m_aBuffer, str_length(m_pBufferPos) + 1); // +1 for the \0
+			m_pBufferPos = m_aBuffer;
+
+			str_append(m_aBuffer, aBuf, sizeof(m_aBuffer));
+
+			m_LastRecvTime = time_get();
+		}
+		else if(Bytes < 0)
+		{
+			if(net_would_block()) // no data received
+				return 0;
+
+			m_State = NET_CONNSTATE_ERROR; // error
+			str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString));
+			return -1;
+		}
+		else
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString));
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int CConsoleNetConnection::Recv(char *pLine, int MaxLength)
+{
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if(m_pBufferPos && *m_pBufferPos)
+		{
+			char *pResult = m_pBufferPos;
+
+			while(*m_pBufferPos && *m_pBufferPos != '\r' && *m_pBufferPos != '\n')
+				m_pBufferPos++;
+
+			if(*m_pBufferPos) // haven't reached the end of the buffer?
+			{
+				if(*m_pBufferPos == '\r' && *(m_pBufferPos + 1) == '\n')
+				{
+					*m_pBufferPos = 0;
+					m_pBufferPos += 2;
+				}
+				else
+				{
+					*m_pBufferPos = 0;
+					m_pBufferPos++;
+				}
+			}
+			else
+			{
+				m_pBufferPos = 0;
+			}
+
+			str_copy(pLine, pResult, MaxLength);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int CConsoleNetConnection::Send(const char *pLine)
+{
+	if(State() != NET_CONNSTATE_ONLINE)
+		return -1;
+
+	int Length = str_length(pLine);
+	char aBuf[1024];
+	str_copy(aBuf, pLine, sizeof(aBuf) - 2);
+	aBuf[Length + 1] = '\n';
+	aBuf[Length + 2] = '\0';
+
+	if(net_tcp_send(m_Socket, aBuf, Length + 2) < 0)
+	{
+		m_State = NET_CONNSTATE_ERROR;
+		str_copy(m_aErrorString, "Failed to send packet", sizeof(m_aErrorString));
+		return -1;
+	}
+	
+	return 0;
+}
+