about summary refs log tree commit diff
path: root/src/engine/shared
diff options
context:
space:
mode:
authoroy <Tom_Adams@web.de>2011-12-29 23:36:53 +0100
committeroy <Tom_Adams@web.de>2011-12-29 23:36:53 +0100
commitb44ee3d9755ff35a6df1358dcfe85ce681bbe081 (patch)
treea168de3012907094d550d22771b103c1f35b9fa8 /src/engine/shared
parent72d5ef329b1a0ec07f8fb6c5e23011b7e1a45b61 (diff)
downloadzcatch-b44ee3d9755ff35a6df1358dcfe85ce681bbe081.tar.gz
zcatch-b44ee3d9755ff35a6df1358dcfe85ce681bbe081.zip
reworked ban system
Diffstat (limited to 'src/engine/shared')
-rw-r--r--src/engine/shared/config.cpp7
-rw-r--r--src/engine/shared/console.cpp11
-rw-r--r--src/engine/shared/econ.cpp17
-rw-r--r--src/engine/shared/econ.h3
-rw-r--r--src/engine/shared/masterserver.cpp6
-rw-r--r--src/engine/shared/netban.cpp615
-rw-r--r--src/engine/shared/netban.h184
-rw-r--r--src/engine/shared/network.h73
-rw-r--r--src/engine/shared/network_console.cpp97
-rw-r--r--src/engine/shared/network_server.cpp256
10 files changed, 860 insertions, 409 deletions
diff --git a/src/engine/shared/config.cpp b/src/engine/shared/config.cpp
index b9aa1320..d0cb7a6b 100644
--- a/src/engine/shared/config.cpp
+++ b/src/engine/shared/config.cpp
@@ -111,13 +111,8 @@ public:
 	{
 		if(!m_ConfigFile)
 			return;
-#if defined(CONF_FAMILY_WINDOWS)
-		static const char Newline[] = "\r\n";
-#else
-		static const char Newline[] = "\n";
-#endif
 		io_write(m_ConfigFile, pLine, str_length(pLine));
-		io_write(m_ConfigFile, Newline, sizeof(Newline)-1);
+		io_write_newline(m_ConfigFile);
 	}
 };
 
diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp
index de5116c1..1b98a158 100644
--- a/src/engine/shared/console.cpp
+++ b/src/engine/shared/console.cpp
@@ -658,7 +658,13 @@ void CConsole::AddCommandSorted(CCommand *pCommand)
 void CConsole::Register(const char *pName, const char *pParams,
 	int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp)
 {
-	CCommand *pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand;
+	CCommand *pCommand = FindCommand(pName, Flags);
+	bool DoAdd = false;
+	if(pCommand == 0)
+	{
+		pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand;
+		DoAdd = true;
+	}
 	pCommand->m_pfnCallback = pfnFunc;
 	pCommand->m_pUserData = pUser;
 
@@ -669,7 +675,8 @@ void CConsole::Register(const char *pName, const char *pParams,
 	pCommand->m_Flags = Flags;
 	pCommand->m_Temp = false;
 
-	AddCommandSorted(pCommand);
+	if(DoAdd)
+		AddCommandSorted(pCommand);
 }
 
 void CConsole::RegisterTemp(const char *pName, const char *pParams,	int Flags, const char *pHelp)
diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp
index 617cdbd6..3eaf7aac 100644
--- a/src/engine/shared/econ.cpp
+++ b/src/engine/shared/econ.cpp
@@ -2,14 +2,15 @@
 #include <engine/shared/config.h>
 
 #include "econ.h"
+#include "netban.h"
+
 
 int CEcon::NewClientCallback(int ClientID, void *pUser)
 {
 	CEcon *pThis = (CEcon *)pUser;
 
-	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	net_addr_str(pThis->m_NetConsole.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 	char aBuf[128];
 	str_format(aBuf, sizeof(aBuf), "client accepted. cid=%d addr=%s'", ClientID, aAddrStr);
 	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
@@ -26,9 +27,8 @@ int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser)
 {
 	CEcon *pThis = (CEcon *)pUser;
 
-	NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID);
 	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr));
+	net_addr_str(pThis->m_NetConsole.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
 	char aBuf[256];
 	str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
 	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf);
@@ -52,7 +52,7 @@ void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUse
 	}
 }
 
-void CEcon::Init(IConsole *pConsole)
+void CEcon::Init(IConsole *pConsole, CNetBan *pNetBan)
 {
 	m_pConsole = pConsole;
 
@@ -74,7 +74,7 @@ void CEcon::Init(IConsole *pConsole)
 		BindAddr.port = g_Config.m_EcPort;
 	}
 
-	if(m_NetConsole.Open(BindAddr, 0))
+	if(m_NetConsole.Open(BindAddr, pNetBan, 0))
 	{
 		m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this);
 		m_Ready = true;
@@ -123,10 +123,7 @@ void CEcon::Update()
 					if(!g_Config.m_EcBantime)
 						m_NetConsole.Drop(ClientID, "Too many authentication tries");
 					else
-					{
-						NETADDR Addr = m_NetConsole.ClientAddr(ClientID);
-						m_NetConsole.AddBan(Addr, g_Config.m_EcBantime*60);
-					}
+						m_NetConsole.NetBan()->BanAddr(m_NetConsole.ClientAddr(ClientID), g_Config.m_EcBantime*60, "Too many authentication tries");
 				}
 			}
 		}
diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h
index daec34c4..ed7d929b 100644
--- a/src/engine/shared/econ.h
+++ b/src/engine/shared/econ.h
@@ -3,6 +3,7 @@
 
 #include "network.h"
 
+
 class CEcon
 {
 	enum
@@ -41,7 +42,7 @@ class CEcon
 public:
 	IConsole *Console() { return m_pConsole; }
 
-	void Init(IConsole *pConsole);
+	void Init(IConsole *pConsole, class CNetBan *pNetBan);
 	void Update();
 	void Send(int ClientID, const char *pLine);
 	void Shutdown();
diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp
index eb63bab5..95482639 100644
--- a/src/engine/shared/masterserver.cpp
+++ b/src/engine/shared/masterserver.cpp
@@ -192,13 +192,13 @@ public:
 		{
 			char aAddrStr[NETADDR_MAXSTRSIZE];
 			if(m_aMasterServers[i].m_Addr.type != NETTYPE_INVALID)
-				net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr));
+				net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr), true);
 			else
 				aAddrStr[0] = 0;
 			char aBuf[256];
-			str_format(aBuf, sizeof(aBuf), "%s %s\n", m_aMasterServers[i].m_aHostname, aAddrStr);
-
+			str_format(aBuf, sizeof(aBuf), "%s %s", m_aMasterServers[i].m_aHostname, aAddrStr);
 			io_write(File, aBuf, str_length(aBuf));
+			io_write_newline(File);
 		}
 
 		io_close(File);
diff --git a/src/engine/shared/netban.cpp b/src/engine/shared/netban.cpp
new file mode 100644
index 00000000..eebe7c84
--- /dev/null
+++ b/src/engine/shared/netban.cpp
@@ -0,0 +1,615 @@
+#include <base/math.h>
+
+#include <engine/console.h>
+#include <engine/storage.h>
+#include <engine/shared/config.h>
+
+#include "netban.h"
+
+
+bool CNetBan::StrAllnum(const char *pStr)
+{
+	while(*pStr)
+	{
+		if(!(*pStr >= '0' && *pStr <= '9'))
+			return false;
+		pStr++;
+	}
+	return true;
+}
+
+
+CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
+{
+	if(pAddr->type==NETTYPE_IPV4)
+		m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
+	else
+		m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+
+			pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF;
+	m_HashIndex = 0;
+}
+
+CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
+{
+	m_Hash = 0;
+	m_HashIndex = 0;
+	for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
+	{
+		m_Hash += pRange->m_LB.ip[i];
+		++m_HashIndex;
+	}
+	m_Hash &= 0xFF;
+}
+
+int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
+{
+	int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16;
+	aHash[0].m_Hash = 0;
+	aHash[0].m_HashIndex = 0;
+	for(int i = 1, Sum = 0; i <= Length; ++i)
+	{
+		Sum += pAddr->ip[i-1];
+		aHash[i].m_Hash = Sum&0xFF;
+		aHash[i].m_HashIndex = i%Length;
+	}
+	return Length;
+}
+
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo,  const CNetHash *pNetHash)
+{
+	if(!m_pFirstFree)
+		return 0;
+
+	// create new ban
+	CBan<T> *pBan = m_pFirstFree;
+	pBan->m_Data = *pData;
+	pBan->m_Info = *pInfo;
+	pBan->m_NetHash = *pNetHash;
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstFree = pBan->m_pNext;
+
+	// add it to the hash list
+	if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
+		m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
+	pBan->m_pHashPrev = 0;
+	pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
+	m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+
+	// update ban count
+	++m_CountUsed;
+
+	return pBan;
+}
+
+template<class T, int HashCount>
+int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan)
+{
+	if(pBan == 0)
+		return -1;
+
+	// remove from hash list
+	if(pBan->m_pHashNext)
+		pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
+	if(pBan->m_pHashPrev)
+		pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
+	else
+		m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
+	pBan->m_pHashNext = pBan->m_pHashPrev = 0;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// add to recycle list
+	if(m_pFirstFree)
+		m_pFirstFree->m_pPrev = pBan;
+	pBan->m_pPrev = 0;
+	pBan->m_pNext = m_pFirstFree;
+	m_pFirstFree = pBan;
+
+	// update ban count
+	--m_CountUsed;
+
+	return 0;
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo)
+{
+	pBan->m_Info = *pInfo;
+
+	// remove from used list
+	if(pBan->m_pNext)
+		pBan->m_pNext->m_pPrev = pBan->m_pPrev;
+	if(pBan->m_pPrev)
+		pBan->m_pPrev->m_pNext = pBan->m_pNext;
+	else
+		m_pFirstUsed = pBan->m_pNext;
+
+	// insert it into the used list
+	if(m_pFirstUsed)
+	{
+		for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
+		{
+			if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
+			{
+				// insert before
+				pBan->m_pNext = p;
+				pBan->m_pPrev = p->m_pPrev;
+				if(p->m_pPrev)
+					p->m_pPrev->m_pNext = pBan;
+				else
+					m_pFirstUsed = pBan;
+				p->m_pPrev = pBan;
+				break;
+			}
+
+			if(!p->m_pNext)
+			{
+				// last entry
+				p->m_pNext = pBan;
+				pBan->m_pPrev = p;
+				pBan->m_pNext = 0;
+				break;
+			}
+		}
+	}
+	else
+	{
+		m_pFirstUsed = pBan;
+		pBan->m_pNext = pBan->m_pPrev = 0;
+	}
+}
+
+template<class T, int HashCount>
+void CNetBan::CBanPool<T, HashCount>::Reset()
+{
+	mem_zero(m_paaHashList, sizeof(m_paaHashList));
+	mem_zero(m_aBans, sizeof(m_aBans));
+	m_pFirstUsed = 0;
+	m_CountUsed = 0;
+
+	for(int i = 1; i < MAX_BANS-1; ++i)
+	{
+		m_aBans[i].m_pNext = &m_aBans[i+1];
+		m_aBans[i].m_pPrev = &m_aBans[i-1];
+	}
+
+	m_aBans[0].m_pNext = &m_aBans[1];
+	m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2];
+	m_pFirstFree = &m_aBans[0];
+}
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Find(const T *pData, const CNetHash *pNetHash) const
+{
+	for(CBan<T> *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
+	{
+		if(NetComp(&pBan->m_Data, pData) == 0)
+			return pBan;
+	}
+
+	return 0;
+}
+
+template<class T, int HashCount>
+typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const
+{
+	if(Index < 0 || Index >= Num())
+		return 0;
+
+	for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
+	{
+		if(Index == 0)
+			return pBan;
+	}
+
+	return 0;
+}
+
+
+template<class T>
+void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const
+{
+	if(pBan == 0)
+	{
+		if(BuffSize > 0)
+			pBuf[0] = 0;
+		return;
+	}
+	
+	// build type based part
+	char aBuf[256];
+	if(Type == MSGTYPE_PLAYER)
+		str_copy(aBuf, "You have been banned", sizeof(aBuf));
+	else
+	{
+		char aTemp[256];
+		switch(Type)
+		{
+		case MSGTYPE_LIST:
+			str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANADD:
+			str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		case MSGTYPE_BANREM:
+			str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
+		default:
+			aBuf[0] = 0;
+		}
+	}
+
+	// add info part
+	if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
+	{
+		int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60;
+		if(Mins <= 1)
+			str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
+		else
+			str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
+	}
+	else
+		str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason);
+}
+
+template<class T>
+int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
+{
+	// do not ban localhost
+	if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6))
+	{
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (localhost)");
+		return -1;
+	}
+
+	int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER;
+
+	// set up info
+	CBanInfo Info = {0};
+	Info.m_Expires = Stamp;
+	str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason));
+
+	// check if it already exists
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		// adjust the ban
+		pBanPool->Update(pBan, &Info);
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 1;
+	}
+
+	// add ban and print result
+	pBan = pBanPool->Add(pData, &Info, &NetHash);
+	if(pBan)
+	{
+		char aBuf[128];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 0;
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (full banlist)");
+	return -1;
+}
+
+template<class T>
+int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
+{
+	CNetHash NetHash(pData);
+	CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
+	if(pBan)
+	{
+		char aBuf[256];
+		MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
+		pBanPool->Remove(pBan);
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return 0;
+	}
+	else
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban failed (invalid entry)");
+	return -1;
+}
+
+void CNetBan::Init(IConsole *pConsole, IStorage *pStorage)
+{
+	m_pConsole = pConsole;
+	m_pStorage = pStorage;
+	m_BanAddrPool.Reset();
+	m_BanRangePool.Reset();
+
+	net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4);
+	net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6);
+
+	Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBan, this, "Ban ip for x minutes for any reason");
+	Console()->Register("ban_range", "ss?ir", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBanRange, this, "Ban ip range for x minutes for any reason");
+	Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnban, this, "Unban ip/banlist entry");
+	Console()->Register("unban_range", "ss", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnbanRange, this, "Unban ip range");
+	Console()->Register("unban_all", "", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConUnbanAll, this, "Unban all entries");
+	Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBans, this, "Show banlist");
+	Console()->Register("bans_save", "s", CFGFLAG_SERVER|CFGFLAG_MASTER|CFGFLAG_STORE, ConBansSave, this, "Save banlist in a file");
+}
+
+void CNetBan::Update()
+{
+	int Now = time_timestamp();
+
+	// remove expired bans
+	char aBuf[256], aNetStr[256];
+	while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		m_BanAddrPool.Remove(m_BanAddrPool.First());
+	}
+	while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
+	{
+		str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr)));
+		Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		m_BanRangePool.Remove(m_BanRangePool.First());
+	}
+}
+
+int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
+{
+	return Ban(&m_BanAddrPool, pAddr, Seconds, pReason);
+}
+
+int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
+{
+	if(pRange->IsValid())
+		return Ban(&m_BanRangePool, pRange, Seconds, pReason);
+
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByAddr(const NETADDR *pAddr)
+{
+	return Unban(&m_BanAddrPool, pAddr);
+}
+
+int CNetBan::UnbanByRange(const CNetRange *pRange)
+{
+	if(pRange->IsValid())
+		return Unban(&m_BanRangePool, pRange);
+	
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban failed (invalid range)");
+	return -1;
+}
+
+int CNetBan::UnbanByIndex(int Index)
+{
+	int Result;
+	char aBuf[256];
+	CBanAddr *pBan = m_BanAddrPool.Get(Index);
+	if(pBan)
+	{
+		NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+		Result = m_BanAddrPool.Remove(pBan);
+	}
+	else
+	{
+		CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num());
+		if(pBan)
+		{
+			NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
+			Result = m_BanRangePool.Remove(pBan);
+		}
+		else
+		{
+			Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban failed (invalid index)");
+			return -1;
+		}
+	}
+
+	char aMsg[256];
+	str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf);
+	Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	return Result;
+}
+
+bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const
+{
+	CNetHash aHash[17];
+	int Length = CNetHash::MakeHashArray(pAddr, aHash);
+
+	// check ban adresses
+	CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]);
+	if(pBan)
+	{
+		MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+		return true;
+	}
+
+	// check ban ranges
+	for(int i = Length-1; i >= 0; --i)
+	{
+		for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext)
+		{
+			if(NetMatch(&pBan->m_Data, pAddr, i, Length))
+			{
+				MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}
+
+void CNetBan::ConBan(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr = pResult->GetString(0);
+	int Minutes = pResult->NumArguments()>1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30;
+	const char *pReason = pResult->NumArguments()>2 ? pResult->GetString(2) : "No reason given";
+
+	NETADDR Addr;
+	if(net_addr_from_str(&Addr, pStr) == 0)
+		pThis->BanAddr(&Addr, Minutes*60, pReason);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid network address)");
+}
+
+void CNetBan::ConBanRange(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr1 = pResult->GetString(0);
+	const char *pStr2 = pResult->GetString(1);
+	int Minutes = pResult->NumArguments()>2 ? clamp(pResult->GetInteger(2), 0, 44640) : 30;
+	const char *pReason = pResult->NumArguments()>3 ? pResult->GetString(3) : "No reason given";
+
+	CNetRange Range;
+	if(net_addr_from_str(&Range.m_LB, pStr1) == 0 && net_addr_from_str(&Range.m_UB, pStr2) == 0)
+		pThis->BanRange(&Range, Minutes*60, pReason);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "ban error (invalid range)");
+}
+
+void CNetBan::ConUnban(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr = pResult->GetString(0);
+	if(StrAllnum(pStr))
+		pThis->UnbanByIndex(str_toint(pStr));
+	else
+	{
+		NETADDR Addr;
+		if(net_addr_from_str(&Addr, pStr) == 0)
+			pThis->UnbanByAddr(&Addr);
+		else
+			pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban error (invalid network address)");
+	}
+}
+
+void CNetBan::ConUnbanRange(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	const char *pStr1 = pResult->GetString(0);
+	const char *pStr2 = pResult->GetString(1);
+
+	CNetRange Range;
+	if(net_addr_from_str(&Range.m_LB, pStr1) == 0 && net_addr_from_str(&Range.m_UB, pStr2) == 0)
+		pThis->UnbanByRange(&Range);
+	else
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unban error (invalid range)");
+}
+
+void CNetBan::ConUnbanAll(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	pThis->UnbanAll();
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", "unbanned all entries");
+}
+
+void CNetBan::ConBans(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	int Count = 0;
+	char aBuf[256], aMsg[256];
+	for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		pThis->MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		str_format(aMsg, sizeof(aMsg), "#%i %s", Count++, aBuf);
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	}
+	for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		pThis->MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
+		str_format(aMsg, sizeof(aMsg), "#%i %s", Count++, aBuf);
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+	}
+	str_format(aMsg, sizeof(aMsg), "%d %s", Count, Count==1?"ban":"bans");
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aMsg);
+}
+
+void CNetBan::ConBansSave(IConsole::IResult *pResult, void *pUser)
+{
+	CNetBan *pThis = static_cast<CNetBan *>(pUser);
+
+	char aBuf[256];
+	IOHANDLE File = pThis->Storage()->OpenFile(pResult->GetString(0), IOFLAG_WRITE, IStorage::TYPE_SAVE);
+	if(!File)
+	{
+		str_format(aBuf, sizeof(aBuf), "failed to save banlist to '%s'", pResult->GetString(0));
+		pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+		return;
+	}
+
+	int Now = time_timestamp();
+	char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
+	for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		int Min = pBan->m_Info.m_Expires>-1 ? (pBan->m_Info.m_Expires-Now+59)/60 : -1;
+		net_addr_str(&pBan->m_Data, aAddrStr1, sizeof(aAddrStr1), false);
+		str_format(aBuf, sizeof(aBuf), "ban_ip %s %i %s", aAddrStr1, Min, pBan->m_Info.m_aReason);
+		io_write(File, aBuf, str_length(aBuf));
+		io_write_newline(File);
+	}
+	for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext)
+	{
+		int Min = pBan->m_Info.m_Expires>-1 ? (pBan->m_Info.m_Expires-Now+59)/60 : -1;
+		net_addr_str(&pBan->m_Data.m_LB, aAddrStr1, sizeof(aAddrStr1), false);
+		net_addr_str(&pBan->m_Data.m_UB, aAddrStr2, sizeof(aAddrStr2), false);
+		str_format(aBuf, sizeof(aBuf), "ban_range %s %i %s", aAddrStr1, aAddrStr2, Min, pBan->m_Info.m_aReason);
+		io_write(File, aBuf, str_length(aBuf));
+		io_write_newline(File);
+	}
+
+	io_close(File);
+	str_format(aBuf, sizeof(aBuf), "saved banlist to '%s'", pResult->GetString(0));
+	pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "net_ban", aBuf);
+}
diff --git a/src/engine/shared/netban.h b/src/engine/shared/netban.h
new file mode 100644
index 00000000..a93cc797
--- /dev/null
+++ b/src/engine/shared/netban.h
@@ -0,0 +1,184 @@
+#ifndef ENGINE_SHARED_NETBAN_H
+#define ENGINE_SHARED_NETBAN_H
+
+#include <base/system.h>
+
+
+inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
+{
+	return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20);
+}
+
+class CNetRange
+{
+public:
+	NETADDR m_LB;
+	NETADDR m_UB;
+
+	bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; }
+};
+
+inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
+{
+	return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB);
+}
+
+
+class CNetBan
+{
+protected:
+	bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
+	{
+		return NetComp(pAddr1, pAddr2) == 0;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
+	{
+		return pRange->m_LB.type == pAddr->type &&
+			mem_comp(&pRange->m_LB.ip[Start], &pAddr->ip[Start], Length-Start) <= 0 && mem_comp(&pRange->m_UB.ip[Start], &pAddr->ip[Start], Length-Start) >= 0;
+	}
+
+	bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
+	{
+		return NetMatch(pRange, pAddr, 0,  pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16);
+	}
+
+	const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr[NETADDR_MAXSTRSIZE];
+		net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false);
+		str_format(pBuffer, BufferSize, "'%s'", aAddrStr);
+		return pBuffer;
+	}
+
+	const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
+	{
+		char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
+		net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false);
+		net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false);
+		str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2);
+		return pBuffer;
+	}
+
+	// todo: move?
+	static bool StrAllnum(const char *pStr);
+
+	class CNetHash
+	{
+	public:
+		int m_Hash;
+		int m_HashIndex;	// matching parts for ranges, 0 for addr
+
+		CNetHash() {}	
+		CNetHash(const NETADDR *pAddr);
+		CNetHash(const CNetRange *pRange);
+
+		static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
+	};
+
+	struct CBanInfo
+	{
+		enum
+		{
+			EXPIRES_NEVER=-1,
+			REASON_LENGTH=64,
+		};
+		int m_Expires;
+		char m_aReason[REASON_LENGTH];
+	};
+
+	template<class T> struct CBan
+	{
+		T m_Data;
+		CBanInfo m_Info;
+		CNetHash m_NetHash;
+
+		// hash list
+		CBan *m_pHashNext;
+		CBan *m_pHashPrev;
+
+		// used or free list
+		CBan *m_pNext;
+		CBan *m_pPrev;
+	};
+
+	template<class T, int HashCount> class CBanPool
+	{
+	public:
+		typedef T CDataType;
+
+		CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
+		int Remove(CBan<CDataType> *pBan);
+		void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo);
+		void Reset();
+	
+		int Num() const { return m_CountUsed; }
+		bool IsFull() const { return m_CountUsed == MAX_BANS; }
+
+		CBan<CDataType> *First() const { return m_pFirstUsed; }
+		CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
+		CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const;
+		CBan<CDataType> *Get(int Index) const;
+
+	private:
+		enum
+		{
+			MAX_BANS=1024,
+		};
+
+		CBan<CDataType> *m_paaHashList[HashCount][256];
+		CBan<CDataType> m_aBans[MAX_BANS];
+		CBan<CDataType> *m_pFirstFree;
+		CBan<CDataType> *m_pFirstUsed;
+		int m_CountUsed;
+	};
+
+	typedef CBanPool<NETADDR, 1> CBanAddrPool;
+	typedef CBanPool<CNetRange, 16> CBanRangePool;
+	typedef CBan<NETADDR> CBanAddr;
+	typedef CBan<CNetRange> CBanRange;
+	
+	template<class T> void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const;
+	template<class T> int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
+	template<class T> int Unban(T *pBanPool, const typename T::CDataType *pData);
+
+	class IConsole *m_pConsole;
+	class IStorage *m_pStorage;
+	CBanAddrPool m_BanAddrPool;
+	CBanRangePool m_BanRangePool;
+	NETADDR m_LocalhostIPV4, m_LocalhostIPV6;
+
+public:
+	enum
+	{
+		MSGTYPE_PLAYER=0,
+		MSGTYPE_LIST,
+		MSGTYPE_BANADD,
+		MSGTYPE_BANREM,
+	};
+
+	class IConsole *Console() const { return m_pConsole; }
+	class IStorage *Storage() const { return m_pStorage; }
+
+	virtual ~CNetBan() {}
+	virtual void Init(class IConsole *pConsole, class IStorage *pStorage);
+	void Update();
+
+	virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
+	virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
+	int UnbanByAddr(const NETADDR *pAddr);
+	int UnbanByRange(const CNetRange *pRange);
+	int UnbanByIndex(int Index);
+	void UnbanAll() { m_BanAddrPool.Reset(); m_BanRangePool.Reset(); }
+	bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const;
+
+	static void ConBan(class IConsole::IResult *pResult, void *pUser);
+	static void ConBanRange(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnban(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnbanRange(class IConsole::IResult *pResult, void *pUser);
+	static void ConUnbanAll(class IConsole::IResult *pResult, void *pUser);
+	static void ConBans(class IConsole::IResult *pResult, void *pUser);
+	static void ConBansSave(class IConsole::IResult *pResult, void *pUser);
+};
+
+#endif
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index ca460d67..dd43389e 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -73,8 +73,6 @@ enum
 	NET_CTRLMSG_ACCEPT=3,
 	NET_CTRLMSG_CLOSE=4,
 
-	NET_SERVER_MAXBANS=1024,
-
 	NET_CONN_BUFFERSIZE=1024*32,
 
 	NET_ENUM_TERMINATOR
@@ -182,7 +180,7 @@ public:
 	const char *ErrorString();
 	void SignalResend();
 	int State() const { return m_State; }
-	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const NETADDR *PeerAddress() const { return &m_PeerAddr; }
 
 	void ResetErrorString() { m_ErrorString[0] = 0; }
 	const char *ErrorString() const { return m_ErrorString; }
@@ -214,7 +212,7 @@ public:
 	void Disconnect(const char *pReason);
 
 	int State() const { return m_State; }
-	NETADDR PeerAddress() const { return m_PeerAddr; }
+	const NETADDR *PeerAddress() const { return &m_PeerAddr; }
 	const char *ErrorString() const { return m_aErrorString; }
 
 	void Reset();
@@ -244,59 +242,29 @@ public:
 // server side
 class CNetServer
 {
-public:
-	struct CBanInfo
-	{
-		NETADDR m_Addr;
-		int m_Expires;
-		char m_Reason[128];
-	};
-
-private:
 	struct CSlot
 	{
 	public:
 		CNetConnection m_Connection;
 	};
 
-	struct CBan
-	{
-	public:
-		CBanInfo m_Info;
-
-		// hash list
-		CBan *m_pHashNext;
-		CBan *m_pHashPrev;
-
-		// used or free list
-		CBan *m_pNext;
-		CBan *m_pPrev;
-	};
-
-
 	NETSOCKET m_Socket;
+	class CNetBan *m_pNetBan;
 	CSlot m_aSlots[NET_MAX_CLIENTS];
 	int m_MaxClients;
 	int m_MaxClientsPerIP;
 
-	CBan *m_aBans[256];
-	CBan m_BanPool[NET_SERVER_MAXBANS];
-	CBan *m_BanPool_FirstFree;
-	CBan *m_BanPool_FirstUsed;
-
 	NETFUNC_NEWCLIENT m_pfnNewClient;
 	NETFUNC_DELCLIENT m_pfnDelClient;
 	void *m_UserPtr;
 
 	CNetRecvUnpacker m_RecvUnpacker;
 
-	void BanRemoveByObject(CBan *pBan);
-
 public:
 	int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
 
 	//
-	bool Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int Flags);
+	bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags);
 	int Close();
 
 	//
@@ -307,16 +275,10 @@ public:
 	//
 	int Drop(int ClientID, const char *pReason);
 
-	// banning
-	int BanAdd(NETADDR Addr, int Seconds, const char *pReason);
-	int BanRemove(NETADDR Addr);
-	int BanRemoveAll();
-	int BanNum(); // caution, slow
-	int BanGet(int Index, CBanInfo *pInfo); // caution, slow
-
 	// status requests
-	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
 	NETSOCKET Socket() const { return m_Socket; }
+	class CNetBan *NetBan() const { return m_pNetBan; }
 	int NetType() const { return m_Socket.type; }
 	int MaxClients() const { return m_MaxClients; }
 
@@ -326,27 +288,13 @@ public:
 
 class CNetConsole
 {
-	enum
-	{
-		MAX_BANS=128,
-	};
-
-	int FindBan(NETADDR Addr);
-	void UpdateBans();
-
-	struct CBanEntry
-	{
-		NETADDR m_Addr;
-		int m_Expires;
-	} m_aBans[MAX_BANS];
-	int m_NumBans;
-
 	struct CSlot
 	{
 		CConsoleNetConnection m_Connection;
 	};
 
 	NETSOCKET m_Socket;
+	class CNetBan *m_pNetBan;
 	CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
 
 	NETFUNC_NEWCLIENT m_pfnNewClient;
@@ -359,7 +307,7 @@ public:
 	void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
 
 	//
-	bool Open(NETADDR BindAddr, int Flags);
+	bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int Flags);
 	int Close();
 
 	//
@@ -371,10 +319,9 @@ public:
 	int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
 	int Drop(int ClientID, const char *pReason);
 
-	bool AddBan(NETADDR Addr, int Seconds);
-
 	// status requests
-	NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
+	class CNetBan *NetBan() const { return m_pNetBan; }
 };
 
 
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
index cfa081a2..ded83f68 100644
--- a/src/engine/shared/network_console.cpp
+++ b/src/engine/shared/network_console.cpp
@@ -1,15 +1,21 @@
 /* (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 <engine/console.h>
+
+#include "netban.h"
 #include "network.h"
 
-bool CNetConsole::Open(NETADDR BindAddr, int Flags)
+
+bool CNetConsole::Open(NETADDR BindAddr, CNetBan *pNetBan, int Flags)
 {
 	// zero out the whole structure
 	mem_zero(this, sizeof(*this));
 	m_Socket.type = NETTYPE_INVALID;
 	m_Socket.ipv4sock = -1;
 	m_Socket.ipv6sock = -1;
+	m_pNetBan = pNetBan;
 
 	// open socket
 	m_Socket = net_tcp_create(BindAddr);
@@ -64,8 +70,7 @@ int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
 			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)
+			if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0)
 			{
 				str_copy(aError, "only one client per IP allowed", sizeof(aError));
 				break;
@@ -99,26 +104,16 @@ int CNetConsole::Update()
 
 	if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
 	{
-		int Index = FindBan(Addr);
-		if(Index == -1)
-			AcceptClient(Socket, &Addr);
-		else
+		// check if we just should drop the packet
+		char aBuf[128];
+		if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
 		{
-			char aBuf[128];
-			if(m_aBans[Index].m_Expires > -1)
-			{
-				int Mins = (m_aBans[Index].m_Expires-time_timestamp()+ 59) / 60;
-				if(Mins <= 1)
-					str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
-				else
-					str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
-			}
-			else
-				str_format(aBuf, sizeof(aBuf), "You have been banned for life");
-
+			// banned, reply with a message and drop
 			net_tcp_send(Socket, aBuf, str_length(aBuf));
 			net_tcp_close(Socket);
 		}
+		else
+			AcceptClient(Socket, &Addr);
 	}
 
 	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
@@ -129,8 +124,6 @@ int CNetConsole::Update()
 			Drop(i, m_aSlots[i].m_Connection.ErrorString());
 	}
 
-	UpdateBans();
-
 	return 0;
 }
 
@@ -155,65 +148,3 @@ int CNetConsole::Send(int ClientID, const char *pLine)
 	else
 		return -1;
 }
-
-int CNetConsole::FindBan(NETADDR Addr)
-{
-	Addr.port = 0;
-	for(int i = 0; i < m_NumBans; i++)
-		if(net_addr_comp(&m_aBans[i].m_Addr, &Addr) == 0)
-			return i;
-
-	return -1;
-}
-
-bool CNetConsole::AddBan(NETADDR Addr, int Seconds)
-{
-	if(m_NumBans == MAX_BANS)
-		return false;
-
-	Addr.port = 0;
-	int Index = FindBan(Addr);
-	if(Index == -1)
-	{
-		Index = m_NumBans++;
-		m_aBans[Index].m_Addr = Addr;
-	}
-	m_aBans[Index].m_Expires = Seconds>0 ? time_timestamp()+Seconds : -1;
-
-	for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++)
-	{
-		if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
-		{
-			NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
-			PeerAddr.port = 0;
-			if(net_addr_comp(&Addr, &PeerAddr) == 0)
-			{
-				char aBuf[128];
-				if(Seconds>0)
-				{
-					int Mins = (Seconds + 59) / 60;
-					if(Mins <= 1)
-						str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute");
-					else
-						str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins);
-				}
-				else
-					str_format(aBuf, sizeof(aBuf), "You have been banned for life");
-				Drop(i, aBuf);
-			}
-		}
-	}
-	return true;
-}
-
-void CNetConsole::UpdateBans()
-{
-	int Now = time_timestamp();
-	for(int i = 0; i < m_NumBans; ++i)
-		if(m_aBans[i].m_Expires > 0 && m_aBans[i].m_Expires < Now)
-		{
-			m_aBans[i] = m_aBans[m_NumBans-1];
-			--m_NumBans;
-			break;
-		}
-}
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index b4986bf0..1264a4a5 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -1,32 +1,14 @@
 /* (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"
 
-#define MACRO_LIST_LINK_FIRST(Object, First, Prev, Next) \
-	{ if(First) First->Prev = Object; \
-	Object->Prev = (struct CBan *)0; \
-	Object->Next = First; \
-	First = Object; }
-
-#define MACRO_LIST_LINK_AFTER(Object, After, Prev, Next) \
-	{ Object->Prev = After; \
-	Object->Next = After->Next; \
-	After->Next = Object; \
-	if(Object->Next) \
-		Object->Next->Prev = Object; \
-	}
+#include <engine/console.h>
 
-#define MACRO_LIST_UNLINK(Object, First, Prev, Next) \
-	{ if(Object->Next) Object->Next->Prev = Object->Prev; \
-	if(Object->Prev) Object->Prev->Next = Object->Next; \
-	else First = Object->Next; \
-	Object->Next = 0; Object->Prev = 0; }
+#include "netban.h"
+#include "network.h"
 
-#define MACRO_LIST_FIND(Start, Next, Expression) \
-	{ while(Start && !(Expression)) Start = Start->Next; }
 
-bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int Flags)
+bool CNetServer::Open(NETADDR BindAddr, CNetBan *pNetBan, int MaxClients, int MaxClientsPerIP, int Flags)
 {
 	// zero out the whole structure
 	mem_zero(this, sizeof(*this));
@@ -36,6 +18,8 @@ bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int
 	if(!m_Socket.type)
 		return false;
 
+	m_pNetBan = pNetBan;
+
 	// clamp clients
 	m_MaxClients = MaxClients;
 	if(m_MaxClients > NET_MAX_CLIENTS)
@@ -48,8 +32,6 @@ bool CNetServer::Open(NETADDR BindAddr, int MaxClients, int MaxClientsPerIP, int
 	for(int i = 0; i < NET_MAX_CLIENTS; i++)
 		m_aSlots[i].m_Connection.Init(m_Socket);
 
-	BanRemoveAll();
-
 	return true;
 }
 
@@ -85,181 +67,8 @@ int CNetServer::Drop(int ClientID, const char *pReason)
 	return 0;
 }
 
-int CNetServer::BanGet(int Index, CBanInfo *pInfo)
-{
-	CBan *pBan;
-	for(pBan = m_BanPool_FirstUsed; pBan && Index; pBan = pBan->m_pNext, Index--)
-		{}
-
-	if(!pBan)
-		return 0;
-	*pInfo = pBan->m_Info;
-	return 1;
-}
-
-int CNetServer::BanNum()
-{
-	int Count = 0;
-	CBan *pBan;
-	for(pBan = m_BanPool_FirstUsed; pBan; pBan = pBan->m_pNext)
-		Count++;
-	return Count;
-}
-
-void CNetServer::BanRemoveByObject(CBan *pBan)
-{
-	int IpHash = (pBan->m_Info.m_Addr.ip[0]+pBan->m_Info.m_Addr.ip[1]+pBan->m_Info.m_Addr.ip[2]+pBan->m_Info.m_Addr.ip[3]+
-					pBan->m_Info.m_Addr.ip[4]+pBan->m_Info.m_Addr.ip[5]+pBan->m_Info.m_Addr.ip[6]+pBan->m_Info.m_Addr.ip[7]+
-					pBan->m_Info.m_Addr.ip[8]+pBan->m_Info.m_Addr.ip[9]+pBan->m_Info.m_Addr.ip[10]+pBan->m_Info.m_Addr.ip[11]+
-					pBan->m_Info.m_Addr.ip[12]+pBan->m_Info.m_Addr.ip[13]+pBan->m_Info.m_Addr.ip[14]+pBan->m_Info.m_Addr.ip[15])&0xff;
-	char aAddrStr[NETADDR_MAXSTRSIZE];
-	net_addr_str(&pBan->m_Info.m_Addr, aAddrStr, sizeof(aAddrStr));
-	dbg_msg("netserver", "removing ban on %s", aAddrStr);
-	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-	MACRO_LIST_UNLINK(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
-	MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
-}
-
-int CNetServer::BanRemove(NETADDR Addr)
-{
-	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3]+Addr.ip[4]+Addr.ip[5]+Addr.ip[6]+Addr.ip[7]+
-					Addr.ip[8]+Addr.ip[9]+Addr.ip[10]+Addr.ip[11]+Addr.ip[12]+Addr.ip[13]+Addr.ip[14]+Addr.ip[15])&0xff;
-	CBan *pBan = m_aBans[IpHash];
-
-	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
-
-	if(pBan)
-	{
-		BanRemoveByObject(pBan);
-		return 0;
-	}
-
-	return -1;
-}
-
-int CNetServer::BanRemoveAll()
-{
-	// clear bans memory
-	mem_zero(m_aBans, sizeof(m_aBans));
-	mem_zero(m_BanPool, sizeof(m_BanPool));
-	m_BanPool_FirstFree = 0;
-	m_BanPool_FirstUsed = 0;
-
-	// setup all pointers for bans
-	for(int i = 1; i < NET_SERVER_MAXBANS-1; i++)
-	{
-		m_BanPool[i].m_pNext = &m_BanPool[i+1];
-		m_BanPool[i].m_pPrev = &m_BanPool[i-1];
-	}
-
-	m_BanPool[0].m_pNext = &m_BanPool[1];
-	m_BanPool[NET_SERVER_MAXBANS-1].m_pPrev = &m_BanPool[NET_SERVER_MAXBANS-2];
-	m_BanPool_FirstFree = &m_BanPool[0];
-
-	return 0;
-}
-
-int CNetServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason)
-{
-	int IpHash = (Addr.ip[0]+Addr.ip[1]+Addr.ip[2]+Addr.ip[3]+Addr.ip[4]+Addr.ip[5]+Addr.ip[6]+Addr.ip[7]+
-					Addr.ip[8]+Addr.ip[9]+Addr.ip[10]+Addr.ip[11]+Addr.ip[12]+Addr.ip[13]+Addr.ip[14]+Addr.ip[15])&0xff;
-	int Stamp = -1;
-	CBan *pBan;
-
-	// remove the port
-	Addr.port = 0;
-
-	if(Seconds)
-		Stamp = time_timestamp() + Seconds;
-
-	// search to see if it already exists
-	pBan = m_aBans[IpHash];
-	MACRO_LIST_FIND(pBan, m_pHashNext, net_addr_comp(&pBan->m_Info.m_Addr, &Addr) == 0);
-	if(pBan)
-	{
-		// adjust the ban
-		pBan->m_Info.m_Expires = Stamp;
-		return 0;
-	}
-
-	if(!m_BanPool_FirstFree)
-		return -1;
-
-	// fetch and clear the new ban
-	pBan = m_BanPool_FirstFree;
-	MACRO_LIST_UNLINK(pBan, m_BanPool_FirstFree, m_pPrev, m_pNext);
-
-	// setup the ban info
-	pBan->m_Info.m_Expires = Stamp;
-	pBan->m_Info.m_Addr = Addr;
-	str_copy(pBan->m_Info.m_Reason, pReason, sizeof(pBan->m_Info.m_Reason));
-
-	// add it to the ban hash
-	MACRO_LIST_LINK_FIRST(pBan, m_aBans[IpHash], m_pHashPrev, m_pHashNext);
-
-	// insert it into the used list
-	{
-		if(m_BanPool_FirstUsed)
-		{
-			CBan *pInsertAfter = m_BanPool_FirstUsed;
-			MACRO_LIST_FIND(pInsertAfter, m_pNext, Stamp < pInsertAfter->m_Info.m_Expires);
-
-			if(pInsertAfter)
-				pInsertAfter = pInsertAfter->m_pPrev;
-			else
-			{
-				// add to last
-				pInsertAfter = m_BanPool_FirstUsed;
-				while(pInsertAfter->m_pNext)
-					pInsertAfter = pInsertAfter->m_pNext;
-			}
-
-			if(pInsertAfter)
-			{
-				MACRO_LIST_LINK_AFTER(pBan, pInsertAfter, m_pPrev, m_pNext);
-			}
-			else
-			{
-				MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-			}
-		}
-		else
-		{
-			MACRO_LIST_LINK_FIRST(pBan, m_BanPool_FirstUsed, m_pPrev, m_pNext);
-		}
-	}
-
-	// drop banned clients
-	{
-		char Buf[128];
-		NETADDR BanAddr;
-
-		if(Stamp > -1)
-		{
-			int Mins = (Seconds + 59) / 60;
-			if(Mins <= 1)
-				str_format(Buf, sizeof(Buf), "You have been banned for 1 minute (%s)", pReason);
-			else
-				str_format(Buf, sizeof(Buf), "You have been banned for %d minutes (%s)", Mins, pReason);
-		}
-		else
-			str_format(Buf, sizeof(Buf), "You have been banned for life (%s)", pReason);
-
-		for(int i = 0; i < MaxClients(); i++)
-		{
-			BanAddr = m_aSlots[i].m_Connection.PeerAddress();
-			BanAddr.port = 0;
-
-			if(net_addr_comp(&Addr, &BanAddr) == 0)
-				Drop(i, Buf);
-		}
-	}
-	return 0;
-}
-
 int CNetServer::Update()
 {
-	int Now = time_timestamp();
 	for(int i = 0; i < MaxClients(); i++)
 	{
 		m_aSlots[i].m_Connection.Update();
@@ -267,13 +76,6 @@ int CNetServer::Update()
 			Drop(i, m_aSlots[i].m_Connection.ErrorString());
 	}
 
-	// remove expired bans
-	while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires > -1 && m_BanPool_FirstUsed->m_Info.m_Expires < Now)
-	{
-		CBan *pBan = m_BanPool_FirstUsed;
-		BanRemoveByObject(pBan);
-	}
-
 	return 0;
 }
 
@@ -282,8 +84,6 @@ int CNetServer::Update()
 */
 int CNetServer::Recv(CNetChunk *pChunk)
 {
-	unsigned Now = time_timestamp();
-
 	while(1)
 	{
 		NETADDR Addr;
@@ -301,36 +101,12 @@ int CNetServer::Recv(CNetChunk *pChunk)
 
 		if(CNetBase::UnpackPacket(m_RecvUnpacker.m_aBuffer, Bytes, &m_RecvUnpacker.m_Data) == 0)
 		{
-			CBan *pBan = 0;
-			NETADDR BanAddr = Addr;
-			int IpHash = (BanAddr.ip[0]+BanAddr.ip[1]+BanAddr.ip[2]+BanAddr.ip[3]+BanAddr.ip[4]+BanAddr.ip[5]+BanAddr.ip[6]+BanAddr.ip[7]+
-							BanAddr.ip[8]+BanAddr.ip[9]+BanAddr.ip[10]+BanAddr.ip[11]+BanAddr.ip[12]+BanAddr.ip[13]+BanAddr.ip[14]+BanAddr.ip[15])&0xff;
-			int Found = 0;
-			BanAddr.port = 0;
-
-			// search a ban
-			for(pBan = m_aBans[IpHash]; pBan; pBan = pBan->m_pHashNext)
-			{
-				if(net_addr_comp(&pBan->m_Info.m_Addr, &BanAddr) == 0)
-					break;
-			}
-
 			// check if we just should drop the packet
-			if(pBan)
+			char aBuf[128];
+			if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
 			{
 				// banned, reply with a message
-				char BanStr[128];
-				if(pBan->m_Info.m_Expires > -1)
-				{
-					int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60;
-					if(Mins <= 1)
-						str_format(BanStr, sizeof(BanStr), "Banned for 1 minute (%s)", pBan->m_Info.m_Reason);
-					else
-						str_format(BanStr, sizeof(BanStr), "Banned for %d minutes (%s)", Mins, pBan->m_Info.m_Reason);
-				}
-				else
-					str_format(BanStr, sizeof(BanStr), "Banned for life (%s)", pBan->m_Info.m_Reason);
-				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, BanStr, str_length(BanStr)+1);
+				CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf)+1);
 				continue;
 			}
 
@@ -348,16 +124,15 @@ int CNetServer::Recv(CNetChunk *pChunk)
 				// TODO: check size here
 				if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && m_RecvUnpacker.m_Data.m_aChunkData[0] == NET_CTRLMSG_CONNECT)
 				{
-					Found = 0;
+					bool Found = false;
 
 					// check if we already got this client
 					for(int i = 0; i < MaxClients(); i++)
 					{
-						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
 						if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE &&
-							net_addr_comp(&PeerAddr, &Addr) == 0)
+							net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
 						{
-							Found = 1; // silent ignore.. we got this client already
+							Found = true; // silent ignore.. we got this client already
 							break;
 						}
 					}
@@ -374,7 +149,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
 								continue;
 
-							OtherAddr = m_aSlots[i].m_Connection.PeerAddress();
+							OtherAddr = *m_aSlots[i].m_Connection.PeerAddress();
 							OtherAddr.port = 0;
 							if(!net_addr_comp(&ThisAddr, &OtherAddr))
 							{
@@ -392,7 +167,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 						{
 							if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
 							{
-								Found = 1;
+								Found = true;
 								m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
 								if(m_pfnNewClient)
 									m_pfnNewClient(i, m_UserPtr);
@@ -412,8 +187,7 @@ int CNetServer::Recv(CNetChunk *pChunk)
 					// normal packet, find matching slot
 					for(int i = 0; i < MaxClients(); i++)
 					{
-						NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress();
-						if(net_addr_comp(&PeerAddr, &Addr) == 0)
+						if(net_addr_comp(m_aSlots[i].m_Connection.PeerAddress(), &Addr) == 0)
 						{
 							if(m_aSlots[i].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
 							{