1#ifndef ENGINE_SHARED_NETBAN_H
2#define ENGINE_SHARED_NETBAN_H
3
4#include <base/mem.h>
5#include <base/net.h>
6#include <base/str.h>
7#include <base/time.h>
8
9#include <engine/console.h>
10
11inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
12{
13 return mem_comp(a: pAddr1, b: pAddr2, size: pAddr1->type == NETTYPE_IPV4 ? 8 : 20);
14}
15
16class CNetRange
17{
18public:
19 NETADDR m_LB;
20 NETADDR m_UB;
21
22 bool IsValid() const { return m_LB.type == m_UB.type && NetComp(pAddr1: &m_LB, pAddr2: &m_UB) < 0; }
23};
24
25inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
26{
27 return NetComp(pAddr1: &pRange1->m_LB, pAddr2: &pRange2->m_LB) || NetComp(pAddr1: &pRange1->m_UB, pAddr2: &pRange2->m_UB);
28}
29
30class CNetBan
31{
32protected:
33 bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
34 {
35 return NetComp(pAddr1, pAddr2) == 0;
36 }
37
38 bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
39 {
40 return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(a: &pRange->m_LB.ip[0], b: &pAddr->ip[0], size: Start) == 0) &&
41 mem_comp(a: &pRange->m_LB.ip[Start], b: &pAddr->ip[Start], size: Length - Start) <= 0 && mem_comp(a: &pRange->m_UB.ip[Start], b: &pAddr->ip[Start], size: Length - Start) >= 0;
42 }
43
44 bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
45 {
46 return NetMatch(pRange, pAddr, Start: 0, Length: pRange->m_LB.type == NETTYPE_IPV4 ? 4 : 16);
47 }
48
49 const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
50 {
51 char aAddrStr[NETADDR_MAXSTRSIZE];
52 net_addr_str(addr: pData, string: aAddrStr, max_length: sizeof(aAddrStr), add_port: false);
53 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "<{'%s'}>", aAddrStr);
54 return pBuffer;
55 }
56
57 const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
58 {
59 char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
60 net_addr_str(addr: &pData->m_LB, string: aAddrStr1, max_length: sizeof(aAddrStr1), add_port: false);
61 net_addr_str(addr: &pData->m_UB, string: aAddrStr2, max_length: sizeof(aAddrStr2), add_port: false);
62 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "<{'%s' - '%s'}>", aAddrStr1, aAddrStr2);
63 return pBuffer;
64 }
65
66 class CNetHash
67 {
68 public:
69 int m_Hash;
70 int m_HashIndex; // matching parts for ranges, 0 for addr
71
72 CNetHash() = default;
73 CNetHash(const NETADDR *pAddr);
74 CNetHash(const CNetRange *pRange);
75
76 static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
77 };
78
79 struct CBanInfo
80 {
81 enum
82 {
83 EXPIRES_NEVER = -1,
84 REASON_LENGTH = 128,
85 };
86 int64_t m_Expires;
87 char m_aReason[REASON_LENGTH];
88 bool m_VerbatimReason;
89 };
90
91 template<class T>
92 struct CBan
93 {
94 T m_Data;
95 CBanInfo m_Info;
96 CNetHash m_NetHash;
97
98 // hash list
99 CBan *m_pHashNext;
100 CBan *m_pHashPrev;
101
102 // used or free list
103 CBan *m_pNext;
104 CBan *m_pPrev;
105 };
106
107 template<class T, int HashCount>
108 class CBanPool
109 {
110 public:
111 typedef T CDataType;
112
113 CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
114 int Remove(CBan<CDataType> *pBan);
115 void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo);
116 void Reset();
117
118 int Num() const { return m_CountUsed; }
119 bool IsFull() const { return m_CountUsed == MAX_BANS; }
120
121 CBan<CDataType> *First() const { return m_pFirstUsed; }
122 CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
123 CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const
124 {
125 for(CBan<CDataType> *pBan = m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
126 {
127 if(NetComp(&pBan->m_Data, pData) == 0)
128 return pBan;
129 }
130
131 return nullptr;
132 }
133 CBan<CDataType> *Get(int Index) const;
134
135 private:
136 enum
137 {
138 MAX_BANS = 2048,
139 };
140
141 CBan<CDataType> *m_aapHashList[HashCount][256];
142 CBan<CDataType> m_aBans[MAX_BANS];
143 CBan<CDataType> *m_pFirstFree;
144 CBan<CDataType> *m_pFirstUsed;
145 int m_CountUsed;
146
147 void InsertUsed(CBan<CDataType> *pBan);
148 };
149
150 typedef CBanPool<NETADDR, 1> CBanAddrPool;
151 typedef CBanPool<CNetRange, 16> CBanRangePool;
152 typedef CBan<NETADDR> CBanAddr;
153 typedef CBan<CNetRange> CBanRange;
154
155 template<class T>
156 void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const;
157 template<class T>
158 int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason, bool VerbatimReason);
159 template<class T>
160 int Unban(T *pBanPool, const typename T::CDataType *pData);
161
162 class IConsole *m_pConsole;
163 class IStorage *m_pStorage;
164 CBanAddrPool m_BanAddrPool;
165 CBanRangePool m_BanRangePool;
166 NETADDR m_LocalhostIpV4, m_LocalhostIpV6;
167
168public:
169 enum
170 {
171 MSGTYPE_PLAYER = 0,
172 MSGTYPE_LIST,
173 MSGTYPE_BANADD,
174 MSGTYPE_BANREM,
175 };
176
177 class IConsole *Console() const { return m_pConsole; }
178 class IStorage *Storage() const { return m_pStorage; }
179
180 virtual ~CNetBan() = default;
181 void Init(class IConsole *pConsole, class IStorage *pStorage);
182 void Update();
183
184 virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason, bool VerbatimReason);
185 virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
186 int UnbanByAddr(const NETADDR *pAddr);
187 int UnbanByRange(const CNetRange *pRange);
188 int UnbanByIndex(int Index);
189 void UnbanAll();
190 bool IsBanned(const NETADDR *pOrigAddr, char *pBuf, unsigned BufferSize) const;
191
192 static void ConBan(class IConsole::IResult *pResult, void *pUser);
193 static void ConBanRange(class IConsole::IResult *pResult, void *pUser);
194 static void ConUnban(class IConsole::IResult *pResult, void *pUser);
195 static void ConUnbanRange(class IConsole::IResult *pResult, void *pUser);
196 static void ConUnbanAll(class IConsole::IResult *pResult, void *pUser);
197 static void ConBans(class IConsole::IResult *pResult, void *pUser);
198 static void ConBansFind(class IConsole::IResult *pResult, void *pUser);
199 static void ConBansSave(class IConsole::IResult *pResult, void *pUser);
200};
201
202template<class T>
203void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const
204{
205 if(pBan == nullptr || pBuf == nullptr)
206 {
207 if(BuffSize > 0)
208 pBuf[0] = 0;
209 return;
210 }
211
212 // build type based part
213 char aBuf[256];
214 if(Type == MSGTYPE_PLAYER)
215 str_copy(dst&: aBuf, src: "You have been banned");
216 else
217 {
218 char aTemp[256];
219 switch(Type)
220 {
221 case MSGTYPE_LIST:
222 str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp)));
223 break;
224 case MSGTYPE_BANADD:
225 str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp)));
226 break;
227 case MSGTYPE_BANREM:
228 str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp)));
229 break;
230 default:
231 aBuf[0] = 0;
232 }
233 }
234
235 // add info part
236 if(!pBan->m_Info.m_VerbatimReason && pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
237 {
238 int Mins = ((pBan->m_Info.m_Expires - time_timestamp()) + 59) / 60;
239 if(Mins <= 1)
240 str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
241 else
242 str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
243 }
244 else
245 str_format(pBuf, BuffSize, "%s (%s)", aBuf, pBan->m_Info.m_aReason);
246}
247
248#endif
249