1#include <base/math.h>
2
3#include <engine/console.h>
4#include <engine/shared/config.h>
5#include <engine/storage.h>
6
7#include "netban.h"
8
9CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
10{
11 if(pAddr->type == NETTYPE_IPV4)
12 m_Hash = (pAddr->ip[0] + pAddr->ip[1] + pAddr->ip[2] + pAddr->ip[3]) & 0xFF;
13 else
14 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] +
15 pAddr->ip[8] + pAddr->ip[9] + pAddr->ip[10] + pAddr->ip[11] + pAddr->ip[12] + pAddr->ip[13] + pAddr->ip[14] + pAddr->ip[15]) &
16 0xFF;
17 m_HashIndex = 0;
18}
19
20CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
21{
22 m_Hash = 0;
23 m_HashIndex = 0;
24 for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
25 {
26 m_Hash += pRange->m_LB.ip[i];
27 ++m_HashIndex;
28 }
29 m_Hash &= 0xFF;
30}
31
32int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
33{
34 int Length = pAddr->type == NETTYPE_IPV4 ? 4 : 16;
35 aHash[0].m_Hash = 0;
36 aHash[0].m_HashIndex = 0;
37 for(int i = 1, Sum = 0; i <= Length; ++i)
38 {
39 Sum += pAddr->ip[i - 1];
40 aHash[i].m_Hash = Sum & 0xFF;
41 aHash[i].m_HashIndex = i % Length;
42 }
43 return Length;
44}
45
46template<class T, int HashCount>
47void CNetBan::CBanPool<T, HashCount>::InsertUsed(CBan<T> *pBan)
48{
49 if(m_pFirstUsed)
50 {
51 for(CBan<T> *p = m_pFirstUsed;; p = p->m_pNext)
52 {
53 if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && pBan->m_Info.m_Expires <= p->m_Info.m_Expires))
54 {
55 // insert before
56 pBan->m_pNext = p;
57 pBan->m_pPrev = p->m_pPrev;
58 if(p->m_pPrev)
59 p->m_pPrev->m_pNext = pBan;
60 else
61 m_pFirstUsed = pBan;
62 p->m_pPrev = pBan;
63 break;
64 }
65
66 if(!p->m_pNext)
67 {
68 // last entry
69 p->m_pNext = pBan;
70 pBan->m_pPrev = p;
71 pBan->m_pNext = 0;
72 break;
73 }
74 }
75 }
76 else
77 {
78 m_pFirstUsed = pBan;
79 pBan->m_pNext = pBan->m_pPrev = 0;
80 }
81}
82
83template<class T, int HashCount>
84typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash)
85{
86 if(!m_pFirstFree)
87 return 0;
88
89 // create new ban
90 CBan<T> *pBan = m_pFirstFree;
91 pBan->m_Data = *pData;
92 pBan->m_Info = *pInfo;
93 pBan->m_NetHash = *pNetHash;
94 if(pBan->m_pNext)
95 pBan->m_pNext->m_pPrev = pBan->m_pPrev;
96 if(pBan->m_pPrev)
97 pBan->m_pPrev->m_pNext = pBan->m_pNext;
98 else
99 m_pFirstFree = pBan->m_pNext;
100
101 // add it to the hash list
102 if(m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
103 m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
104 pBan->m_pHashPrev = 0;
105 pBan->m_pHashNext = m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
106 m_aapHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
107
108 // insert it into the used list
109 InsertUsed(pBan);
110
111 // update ban count
112 ++m_CountUsed;
113
114 return pBan;
115}
116
117template<class T, int HashCount>
118int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan)
119{
120 if(pBan == 0)
121 return -1;
122
123 // remove from hash list
124 if(pBan->m_pHashNext)
125 pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
126 if(pBan->m_pHashPrev)
127 pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
128 else
129 m_aapHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
130 pBan->m_pHashNext = pBan->m_pHashPrev = 0;
131
132 // remove from used list
133 if(pBan->m_pNext)
134 pBan->m_pNext->m_pPrev = pBan->m_pPrev;
135 if(pBan->m_pPrev)
136 pBan->m_pPrev->m_pNext = pBan->m_pNext;
137 else
138 m_pFirstUsed = pBan->m_pNext;
139
140 // add to recycle list
141 if(m_pFirstFree)
142 m_pFirstFree->m_pPrev = pBan;
143 pBan->m_pPrev = 0;
144 pBan->m_pNext = m_pFirstFree;
145 m_pFirstFree = pBan;
146
147 // update ban count
148 --m_CountUsed;
149
150 return 0;
151}
152
153template<class T, int HashCount>
154void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo)
155{
156 pBan->m_Info = *pInfo;
157
158 // remove from used list
159 if(pBan->m_pNext)
160 pBan->m_pNext->m_pPrev = pBan->m_pPrev;
161 if(pBan->m_pPrev)
162 pBan->m_pPrev->m_pNext = pBan->m_pNext;
163 else
164 m_pFirstUsed = pBan->m_pNext;
165
166 // insert it into the used list
167 InsertUsed(pBan);
168}
169
170void CNetBan::UnbanAll()
171{
172 m_BanAddrPool.Reset();
173 m_BanRangePool.Reset();
174}
175
176template<class T, int HashCount>
177void CNetBan::CBanPool<T, HashCount>::Reset()
178{
179 mem_zero(m_aapHashList, sizeof(m_aapHashList));
180 mem_zero(m_aBans, sizeof(m_aBans));
181 m_pFirstUsed = 0;
182 m_CountUsed = 0;
183
184 for(int i = 1; i < MAX_BANS - 1; ++i)
185 {
186 m_aBans[i].m_pNext = &m_aBans[i + 1];
187 m_aBans[i].m_pPrev = &m_aBans[i - 1];
188 }
189
190 m_aBans[0].m_pNext = &m_aBans[1];
191 m_aBans[MAX_BANS - 1].m_pPrev = &m_aBans[MAX_BANS - 2];
192 m_pFirstFree = &m_aBans[0];
193}
194
195template<class T, int HashCount>
196typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const
197{
198 if(Index < 0 || Index >= Num())
199 return 0;
200
201 for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
202 {
203 if(Index == 0)
204 return pBan;
205 }
206
207 return 0;
208}
209
210template<class T>
211int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
212{
213 // do not ban localhost
214 if(NetMatch(pData, &m_LocalhostIpV4) || NetMatch(pData, &m_LocalhostIpV6))
215 {
216 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban failed (localhost)");
217 return -1;
218 }
219
220 int Stamp = Seconds > 0 ? time_timestamp() + Seconds : CBanInfo::EXPIRES_NEVER;
221
222 // set up info
223 CBanInfo Info = {.m_Expires: 0};
224 Info.m_Expires = Stamp;
225 str_copy(dst&: Info.m_aReason, src: pReason);
226
227 // check if it already exists
228 CNetHash NetHash(pData);
229 CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
230 if(pBan)
231 {
232 // adjust the ban
233 pBanPool->Update(pBan, &Info);
234 char aBuf[128];
235 MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
236 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
237 return 1;
238 }
239
240 // add ban and print result
241 pBan = pBanPool->Add(pData, &Info, &NetHash);
242 if(pBan)
243 {
244 char aBuf[128];
245 MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
246 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
247 return 0;
248 }
249 else
250 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban failed (full banlist)");
251 return -1;
252}
253
254template<class T>
255int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
256{
257 CNetHash NetHash(pData);
258 CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
259 if(pBan)
260 {
261 char aBuf[256];
262 MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
263 pBanPool->Remove(pBan);
264 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
265 return 0;
266 }
267 else
268 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "unban failed (invalid entry)");
269 return -1;
270}
271
272void CNetBan::Init(IConsole *pConsole, IStorage *pStorage)
273{
274 m_pConsole = pConsole;
275 m_pStorage = pStorage;
276 m_BanAddrPool.Reset();
277 m_BanRangePool.Reset();
278
279 net_host_lookup(hostname: "localhost", addr: &m_LocalhostIpV4, types: NETTYPE_IPV4);
280 net_host_lookup(hostname: "localhost", addr: &m_LocalhostIpV6, types: NETTYPE_IPV6);
281
282 Console()->Register(pName: "ban", pParams: "s[ip|id] ?i[minutes] r[reason]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConBan, pUser: this, pHelp: "Ban ip for x minutes for any reason");
283 Console()->Register(pName: "ban_range", pParams: "s[first ip] s[last ip] ?i[minutes] r[reason]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConBanRange, pUser: this, pHelp: "Ban ip range for x minutes for any reason");
284 Console()->Register(pName: "unban", pParams: "s[ip|entry]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConUnban, pUser: this, pHelp: "Unban ip/banlist entry");
285 Console()->Register(pName: "unban_range", pParams: "s[first ip] s[last ip]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConUnbanRange, pUser: this, pHelp: "Unban ip range");
286 Console()->Register(pName: "unban_all", pParams: "", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConUnbanAll, pUser: this, pHelp: "Unban all entries");
287 Console()->Register(pName: "bans", pParams: "?i[page]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER, pfnFunc: ConBans, pUser: this, pHelp: "Show banlist (page 0 by default, 20 entries per page)");
288 Console()->Register(pName: "bans_save", pParams: "s[file]", Flags: CFGFLAG_SERVER | CFGFLAG_MASTER | CFGFLAG_STORE, pfnFunc: ConBansSave, pUser: this, pHelp: "Save banlist in a file");
289}
290
291void CNetBan::Update()
292{
293 int Now = time_timestamp();
294
295 // remove expired bans
296 char aBuf[256], aNetStr[256];
297 while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
298 {
299 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban %s expired", NetToString(pData: &m_BanAddrPool.First()->m_Data, pBuffer: aNetStr, BufferSize: sizeof(aNetStr)));
300 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
301 m_BanAddrPool.Remove(pBan: m_BanAddrPool.First());
302 }
303 while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
304 {
305 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban %s expired", NetToString(pData: &m_BanRangePool.First()->m_Data, pBuffer: aNetStr, BufferSize: sizeof(aNetStr)));
306 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
307 m_BanRangePool.Remove(pBan: m_BanRangePool.First());
308 }
309}
310
311int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
312{
313 return Ban(pBanPool: &m_BanAddrPool, pData: pAddr, Seconds, pReason);
314}
315
316int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
317{
318 if(pRange->IsValid())
319 return Ban(pBanPool: &m_BanRangePool, pData: pRange, Seconds, pReason);
320
321 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban failed (invalid range)");
322 return -1;
323}
324
325int CNetBan::UnbanByAddr(const NETADDR *pAddr)
326{
327 return Unban(pBanPool: &m_BanAddrPool, pData: pAddr);
328}
329
330int CNetBan::UnbanByRange(const CNetRange *pRange)
331{
332 if(pRange->IsValid())
333 return Unban(pBanPool: &m_BanRangePool, pData: pRange);
334
335 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban failed (invalid range)");
336 return -1;
337}
338
339int CNetBan::UnbanByIndex(int Index)
340{
341 int Result;
342 char aBuf[256];
343 CBanAddr *pBan = m_BanAddrPool.Get(Index);
344 if(pBan)
345 {
346 NetToString(pData: &pBan->m_Data, pBuffer: aBuf, BufferSize: sizeof(aBuf));
347 Result = m_BanAddrPool.Remove(pBan);
348 }
349 else
350 {
351 CBanRange *pBanRange = m_BanRangePool.Get(Index: Index - m_BanAddrPool.Num());
352 if(pBanRange)
353 {
354 NetToString(pData: &pBanRange->m_Data, pBuffer: aBuf, BufferSize: sizeof(aBuf));
355 Result = m_BanRangePool.Remove(pBan: pBanRange);
356 }
357 else
358 {
359 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "unban failed (invalid index)");
360 return -1;
361 }
362 }
363
364 char aMsg[256];
365 str_format(buffer: aMsg, buffer_size: sizeof(aMsg), format: "unbanned index %i (%s)", Index, aBuf);
366 Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aMsg);
367 return Result;
368}
369
370bool CNetBan::IsBanned(const NETADDR *pOrigAddr, char *pBuf, unsigned BufferSize) const
371{
372 NETADDR Addr;
373 const NETADDR *pAddr = pOrigAddr;
374 if(pOrigAddr->type == NETTYPE_WEBSOCKET_IPV4)
375 {
376 mem_copy(dest: &Addr, source: pOrigAddr, size: sizeof(NETADDR));
377 pAddr = &Addr;
378 Addr.type = NETTYPE_IPV4;
379 }
380 CNetHash aHash[17];
381 int Length = CNetHash::MakeHashArray(pAddr, aHash);
382
383 // check ban addresses
384 CBanAddr *pBan = m_BanAddrPool.Find(pData: pAddr, pNetHash: &aHash[Length]);
385 if(pBan)
386 {
387 MakeBanInfo(pBan, pBuf, BuffSize: BufferSize, Type: MSGTYPE_PLAYER);
388 return true;
389 }
390
391 // check ban ranges
392 for(int i = Length - 1; i >= 0; --i)
393 {
394 for(CBanRange *pBanRange = m_BanRangePool.First(pNetHash: &aHash[i]); pBanRange; pBanRange = pBanRange->m_pHashNext)
395 {
396 if(NetMatch(pRange: &pBanRange->m_Data, pAddr, Start: i, Length))
397 {
398 MakeBanInfo(pBan: pBanRange, pBuf, BuffSize: BufferSize, Type: MSGTYPE_PLAYER);
399 return true;
400 }
401 }
402 }
403
404 return false;
405}
406
407void CNetBan::ConBan(IConsole::IResult *pResult, void *pUser)
408{
409 CNetBan *pThis = static_cast<CNetBan *>(pUser);
410
411 const char *pStr = pResult->GetString(Index: 0);
412 int Minutes = pResult->NumArguments() > 1 ? clamp(val: pResult->GetInteger(Index: 1), lo: 0, hi: 525600) : 30;
413 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "No reason given";
414
415 NETADDR Addr;
416 if(net_addr_from_str(addr: &Addr, string: pStr) == 0)
417 pThis->BanAddr(pAddr: &Addr, Seconds: Minutes * 60, pReason);
418 else
419 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (invalid network address)");
420}
421
422void CNetBan::ConBanRange(IConsole::IResult *pResult, void *pUser)
423{
424 CNetBan *pThis = static_cast<CNetBan *>(pUser);
425
426 const char *pStr1 = pResult->GetString(Index: 0);
427 const char *pStr2 = pResult->GetString(Index: 1);
428 int Minutes = pResult->NumArguments() > 2 ? clamp(val: pResult->GetInteger(Index: 2), lo: 0, hi: 525600) : 30;
429 const char *pReason = pResult->NumArguments() > 3 ? pResult->GetString(Index: 3) : "No reason given";
430
431 CNetRange Range;
432 if(net_addr_from_str(addr: &Range.m_LB, string: pStr1) == 0 && net_addr_from_str(addr: &Range.m_UB, string: pStr2) == 0)
433 pThis->BanRange(pRange: &Range, Seconds: Minutes * 60, pReason);
434 else
435 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "ban error (invalid range)");
436}
437
438void CNetBan::ConUnban(IConsole::IResult *pResult, void *pUser)
439{
440 CNetBan *pThis = static_cast<CNetBan *>(pUser);
441
442 const char *pStr = pResult->GetString(Index: 0);
443 if(str_isallnum(str: pStr))
444 pThis->UnbanByIndex(Index: str_toint(str: pStr));
445 else
446 {
447 NETADDR Addr;
448 if(net_addr_from_str(addr: &Addr, string: pStr) == 0)
449 pThis->UnbanByAddr(pAddr: &Addr);
450 else
451 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "unban error (invalid network address)");
452 }
453}
454
455void CNetBan::ConUnbanRange(IConsole::IResult *pResult, void *pUser)
456{
457 CNetBan *pThis = static_cast<CNetBan *>(pUser);
458
459 const char *pStr1 = pResult->GetString(Index: 0);
460 const char *pStr2 = pResult->GetString(Index: 1);
461
462 CNetRange Range;
463 if(net_addr_from_str(addr: &Range.m_LB, string: pStr1) == 0 && net_addr_from_str(addr: &Range.m_UB, string: pStr2) == 0)
464 pThis->UnbanByRange(pRange: &Range);
465 else
466 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "unban error (invalid range)");
467}
468
469void CNetBan::ConUnbanAll(IConsole::IResult *pResult, void *pUser)
470{
471 CNetBan *pThis = static_cast<CNetBan *>(pUser);
472
473 pThis->UnbanAll();
474 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: "unbanned all entries");
475}
476
477void CNetBan::ConBans(IConsole::IResult *pResult, void *pUser)
478{
479 CNetBan *pThis = static_cast<CNetBan *>(pUser);
480
481 int Page = pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 0;
482 static const int s_EntriesPerPage = 20;
483 const int Start = Page * s_EntriesPerPage;
484 const int End = (Page + 1) * s_EntriesPerPage;
485
486 int Count = 0;
487 char aBuf[256], aMsg[256];
488 for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext, Count++)
489 {
490 if(Count < Start || Count >= End)
491 {
492 continue;
493 }
494 pThis->MakeBanInfo(pBan, pBuf: aBuf, BuffSize: sizeof(aBuf), Type: MSGTYPE_LIST);
495 str_format(buffer: aMsg, buffer_size: sizeof(aMsg), format: "#%i %s", Count, aBuf);
496 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aMsg);
497 }
498 for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext, Count++)
499 {
500 if(Count < Start || Count >= End)
501 {
502 continue;
503 }
504 pThis->MakeBanInfo(pBan, pBuf: aBuf, BuffSize: sizeof(aBuf), Type: MSGTYPE_LIST);
505 str_format(buffer: aMsg, buffer_size: sizeof(aMsg), format: "#%i %s", Count, aBuf);
506 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aMsg);
507 }
508 str_format(buffer: aMsg, buffer_size: sizeof(aMsg), format: "%d %s, showing entries %d - %d", Count, Count == 1 ? "ban" : "bans", Start, End - 1);
509 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aMsg);
510}
511
512void CNetBan::ConBansSave(IConsole::IResult *pResult, void *pUser)
513{
514 CNetBan *pThis = static_cast<CNetBan *>(pUser);
515
516 char aBuf[256];
517 IOHANDLE File = pThis->Storage()->OpenFile(pFilename: pResult->GetString(Index: 0), Flags: IOFLAG_WRITE, Type: IStorage::TYPE_SAVE);
518 if(!File)
519 {
520 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to save banlist to '%s'", pResult->GetString(Index: 0));
521 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
522 return;
523 }
524
525 int Now = time_timestamp();
526 char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
527 for(CBanAddr *pBan = pThis->m_BanAddrPool.First(); pBan; pBan = pBan->m_pNext)
528 {
529 int Min = pBan->m_Info.m_Expires > -1 ? (pBan->m_Info.m_Expires - Now + 59) / 60 : -1;
530 net_addr_str(addr: &pBan->m_Data, string: aAddrStr1, max_length: sizeof(aAddrStr1), add_port: false);
531 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban %s %i %s", aAddrStr1, Min, pBan->m_Info.m_aReason);
532 io_write(io: File, buffer: aBuf, size: str_length(str: aBuf));
533 io_write_newline(io: File);
534 }
535 for(CBanRange *pBan = pThis->m_BanRangePool.First(); pBan; pBan = pBan->m_pNext)
536 {
537 int Min = pBan->m_Info.m_Expires > -1 ? (pBan->m_Info.m_Expires - Now + 59) / 60 : -1;
538 net_addr_str(addr: &pBan->m_Data.m_LB, string: aAddrStr1, max_length: sizeof(aAddrStr1), add_port: false);
539 net_addr_str(addr: &pBan->m_Data.m_UB, string: aAddrStr2, max_length: sizeof(aAddrStr2), add_port: false);
540 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "ban_range %s %s %i %s", aAddrStr1, aAddrStr2, Min, pBan->m_Info.m_aReason);
541 io_write(io: File, buffer: aBuf, size: str_length(str: aBuf));
542 io_write_newline(io: File);
543 }
544
545 io_close(io: File);
546 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "saved banlist to '%s'", pResult->GetString(Index: 0));
547 pThis->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "net_ban", pStr: aBuf);
548}
549