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