Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
ipban.qc
Go to the documentation of this file.
1#include "ipban.qh"
2
3#include <common/constants.qh>
4#include <common/stats.qh>
5#include <common/util.qh>
8#include <server/main.qh>
9
10/*
11 * Protocol of online ban list:
12 *
13 * - Reporting a ban:
14 * GET g_ban_sync_uri?action=ban&hostname=...&ip=xxx.xxx.xxx&duration=nnnn&reason=...................
15 * (IP 1, 2, 3, or 4 octets, 3 octets for example is a /24 mask)
16 * - Removing a ban:
17 * GET g_ban_sync_uri?action=unban&hostname=...&ip=xxx.xxx.xxx
18 * - Querying the ban list
19 * GET g_ban_sync_uri?action=list&hostname=...&servers=xxx.xxx.xxx.xxx;xxx.xxx.xxx.xxx;...
20 *
21 * shows the bans from the listed servers, and possibly others.
22 * Format of a ban is ASCII plain text, four lines per ban, delimited by
23 * newline ONLY (no carriage return):
24 *
25 * IP address (also 1, 2, 3, or 4 octets, delimited by dot)
26 * time left in seconds
27 * reason of the ban
28 * server IP that registered the ban
29 */
30
31#define MAX_IPBAN_URIS (URI_GET_IPBAN_END - URI_GET_IPBAN + 1)
32
33void OnlineBanList_SendBan(string ip, float bantime, string reason)
34{
35 string uri;
36 float i, n;
37
38 uri = strcat( "action=ban&hostname=", uri_escape(autocvar_hostname));
39 uri = strcat(uri, "&ip=", uri_escape(ip));
40 uri = strcat(uri, "&duration=", ftos(bantime));
41 uri = strcat(uri, "&reason=", uri_escape(reason));
42
44 if(n >= MAX_IPBAN_URIS)
46 for(i = 0; i < n; ++i)
47 {
48 if(strstrofs(argv(i), "?", 0) >= 0)
49 uri_get(strcat(argv(i), "&", uri), URI_GET_DISCARD); // 0 = "discard" callback target
50 else
51 uri_get(strcat(argv(i), "?", uri), URI_GET_DISCARD); // 0 = "discard" callback target
52 }
53}
54
56{
57 string uri;
58 float i, n;
59
60 uri = strcat( "action=unban&hostname=", uri_escape(autocvar_hostname));
61 uri = strcat(uri, "&ip=", uri_escape(ip));
62
64 if(n >= MAX_IPBAN_URIS)
66 for(i = 0; i < n; ++i)
67 {
68 if(strstrofs(argv(i), "?", 0) >= 0)
69 uri_get(strcat(argv(i), "&", uri), URI_GET_DISCARD); // 0 = "discard" callback target
70 else
71 uri_get(strcat(argv(i), "?", uri), URI_GET_DISCARD); // 0 = "discard" callback target
72 }
73}
74
78
79void OnlineBanList_URI_Get_Callback(float id, float status, string data)
80{
81 float n, i, j, l;
82 string ip;
83 float timeleft;
84 string reason;
85 string serverip;
86 float syncinterval;
87 string uri;
88
89 id -= URI_GET_IPBAN;
90
91 if(id >= MAX_IPBAN_URIS)
92 {
93 LOG_INFO("Received ban list for invalid ID");
94 return;
95 }
96
98 uri = argv(id);
99
100 string prelude = strcat("Received ban list from ", uri, ": ");
101
103 {
104 LOG_INFO(prelude, "rejected (unexpected)");
105 return;
106 }
107
109
111 {
112 LOG_INFO(prelude, "rejected (too late)");
113 return;
114 }
115
116 syncinterval = autocvar_g_ban_sync_interval;
117 if(syncinterval == 0)
118 {
119 LOG_INFO(prelude, "rejected (syncing disabled)");
120 return;
121 }
122 if(syncinterval > 0)
123 syncinterval *= 60;
124
125 if(status != 0)
126 {
127 LOG_INFO(prelude, "error: status is ", ftos(status));
128 return;
129 }
130
131 if(substring(data, 0, 1) == "<")
132 {
133 LOG_INFO(prelude, "error: received HTML instead of a ban list");
134 return;
135 }
136
137 if(strstrofs(data, "\r", 0) != -1)
138 {
139 LOG_INFO(prelude, "error: received carriage returns");
140 return;
141 }
142
143 if(data == "")
144 n = 0;
145 else
146 n = tokenizebyseparator(data, "\n");
147
148 if((n % 4) != 0)
149 {
150 LOG_INFO(prelude, "error: received invalid item count: ", ftos(n));
151 return;
152 }
153
154 LOG_INFO(prelude, "OK, ", ftos(n / 4), " items");
155
156 for(i = 0; i < n; i += 4)
157 {
158 ip = argv(i);
159 timeleft = stof(argv(i + 1));
160 reason = argv(i + 2);
161 serverip = argv(i + 3);
162
163 LOG_TRACE("received ban list item ", ftos(i / 4), ": ip=", ip);
164 LOG_TRACE(" timeleft=", ftos(timeleft), " reason=", reason);
165 LOG_TRACE(" serverip=", serverip);
166
167 timeleft -= 1.5 * autocvar_g_ban_sync_timeout;
168 if(timeleft < 0)
169 continue;
170
171 l = strlen(ip);
172 if(l != 44) // length 44 is a cryptographic ID
173 {
174 for(j = 0; j < l; ++j)
175 if(strstrofs("0123456789.", substring(ip, j, 1), 0) == -1)
176 {
177 LOG_INFO("Invalid character ", substring(ip, j, 1), " in IP address ", ip, ". Skipping this ban.");
178 goto skip;
179 }
180 }
181
183 if((strstrofs(strcat(";", OnlineBanList_Servers, ";"), strcat(";", serverip, ";"), 0) == -1))
184 continue;
185
186 if(syncinterval > 0)
187 timeleft = min(syncinterval + (OnlineBanList_Timeout - time) + 5, timeleft);
188 // the ban will be prolonged on the next sync
189 // or expire 5 seconds after the next timeout
190 Ban_Insert(ip, timeleft, strcat("ban synced from ", serverip, " at ", uri), 0);
191 LOG_INFO("Ban list syncing: accepted ban of ", ip, " by ", serverip, " at ", uri, ": ", reason);
192
193LABEL(skip)
194 }
195}
196
198{
199 int argc;
200 string uri;
201 float i, n;
202
204 {
205 delete(this);
206 return;
207 }
208 if(autocvar_g_ban_sync_interval == 0) // < 0 is okay, it means "sync on level start only"
209 {
210 delete(this);
211 return;
212 }
214 if(argc == 0)
215 {
216 delete(this);
217 return;
218 }
219
220 string s = argv(0); for(i = 1; i < argc; ++i) s = strcat(s, ";", argv(i));
222
223 uri = strcat( "action=list&hostname=", uri_escape(autocvar_hostname));
224 uri = strcat(uri, "&servers=", uri_escape(OnlineBanList_Servers));
225
227
229 if(n >= MAX_IPBAN_URIS)
230 n = MAX_IPBAN_URIS;
231 for(i = 0; i < n; ++i)
232 {
234 continue;
236 if(strstrofs(argv(i), "?", 0) >= 0)
237 uri_get(strcat(argv(i), "&", uri), URI_GET_IPBAN + i); // 1000 = "banlist" callback target
238 else
239 uri_get(strcat(argv(i), "?", uri), URI_GET_IPBAN + i); // 1000 = "banlist" callback target
240 }
241
243 {
244 delete(this);
245 return;
246 }
247
249}
250
251const float BAN_MAX = 256;
256
257string ban_ip1;
258string ban_ip2;
259string ban_ip3;
260string ban_ip4;
261string ban_idfp;
262
264{
265 string out;
266 float i;
267
268 if(!ban_loaded)
269 return;
270
271 // version of list
272 out = "1";
273 for(i = 0; i < ban_count; ++i)
274 {
275 if(time > ban_expire[i])
276 continue;
277 out = strcat(out, " ", ban_ip[i]);
278 out = strcat(out, " ", ftos(ban_expire[i] - time));
279 }
280 if(strlen(out) <= 1) // no real entries
281 cvar_set("g_banned_list", "");
282 else
283 cvar_set("g_banned_list", out);
284}
285
286float Ban_Delete(float i)
287{
288 if(i < 0)
289 return false;
290 if(i >= ban_count)
291 return false;
292 if(ban_expire[i] == 0)
293 return false;
294 if(ban_expire[i] > 0)
295 {
297 strunzone(ban_ip[i]);
298 }
299 ban_expire[i] = 0;
300 ban_ip[i] = "";
301 Ban_SaveBans();
302 return true;
303}
304
306{
307 float i, n;
308 for(i = 0; i < ban_count; ++i)
309 Ban_Delete(i);
310 ban_count = 0;
311 ban_loaded = true;
313 if(stof(argv(0)) == 1)
314 {
315 ban_count = (n - 1) / 2;
316 for(i = 0; i < ban_count; ++i)
317 {
318 ban_ip[i] = strzone(argv(2*i+1));
319 ban_expire[i] = time + stof(argv(2*i+2));
320 }
321 }
322
323 entity e = new(bansyncer);
325 e.nextthink = time + 1;
326}
327
329{
330 float i, n;
331 string msg;
332
333 LOG_INFO("^2Listing all existing active bans:");
334
335 n = 0;
336 for(i = 0; i < ban_count; ++i)
337 {
338 if(time > ban_expire[i])
339 continue;
340
341 ++n; // total number of existing bans
342
343 msg = strcat("#", ftos(i), ": ");
344 msg = strcat(msg, ban_ip[i], " is still banned for ");
345 msg = strcat(msg, ftos(ban_expire[i] - time), " seconds");
346
347 LOG_INFO(" ", msg);
348 }
349
350 LOG_INFO("^2Done listing all active (", ftos(n), ") bans.");
351}
352
354{
355 // we can't use tokenizing here, as this is called during ban list parsing
356 float i1, i2, i3, i4;
357 string s;
358
359 if(client.crypto_idfp_signed)
360 ban_idfp = client.crypto_idfp;
361 else
363
364 s = client.netaddress;
365
366 i1 = strstrofs(s, ".", 0);
367 if(i1 < 0)
368 goto ipv6;
369 i2 = strstrofs(s, ".", i1 + 1);
370 if(i2 < 0)
371 return false;
372 i3 = strstrofs(s, ".", i2 + 1);
373 if(i3 < 0)
374 return false;
375 i4 = strstrofs(s, ".", i3 + 1);
376 if(i4 >= 0)
377 s = substring(s, 0, i4);
378
379 ban_ip1 = substring(s, 0, i1); // 8
380 ban_ip2 = substring(s, 0, i2); // 16
381 ban_ip3 = substring(s, 0, i3); // 24
382 ban_ip4 = strcat(s); // 32
383 return true;
384
385LABEL(ipv6)
386 i1 = strstrofs(s, ":", 0);
387 if(i1 < 0)
388 return false;
389 i1 = strstrofs(s, ":", i1 + 1);
390 if(i1 < 0)
391 return false;
392 i2 = strstrofs(s, ":", i1 + 1);
393 if(i2 < 0)
394 return false;
395 i3 = strstrofs(s, ":", i2 + 1);
396 if(i3 < 0)
397 return false;
398
399 ban_ip1 = strcat(substring(s, 0, i1), "::/32"); // 32
400 ban_ip2 = strcat(substring(s, 0, i2), "::/48"); // 48
401 ban_ip4 = strcat(substring(s, 0, i3), "::/64"); // 64
402
403 if(i3 - i2 > 3) // means there is more than 2 digits and a : in the range
404 ban_ip3 = strcat(substring(s, 0, i2), ":", substring(s, i2 + 1, i3 - i2 - 3), "00::/56");
405 else
406 ban_ip3 = strcat(substring(s, 0, i2), ":0::/56");
407
408 return true;
409}
410
411float Ban_IsClientBanned(entity client, float idx)
412{
413 float i, b, e, ipbanned;
414 if(!ban_loaded)
415 Ban_LoadBans();
416 if(!Ban_GetClientIP(client))
417 return false;
418 if(idx < 0)
419 {
420 b = 0;
421 e = ban_count;
422 }
423 else
424 {
425 b = idx;
426 e = idx + 1;
427 }
428 ipbanned = false;
429 for(i = b; i < e; ++i)
430 {
431 string s;
432 if(time > ban_expire[i])
433 continue;
434 s = ban_ip[i];
435 if(ban_ip1 == s) ipbanned = true;
436 if(ban_ip2 == s) ipbanned = true;
437 if(ban_ip3 == s) ipbanned = true;
438 if(ban_ip4 == s) ipbanned = true;
439 if(ban_idfp == s) return true;
440 }
441 if(ipbanned)
442 {
444 return true;
445 if (!ban_idfp)
446 return true;
447 }
448 return false;
449}
450
452{
453 if (Ban_IsClientBanned(client, -1))
454 {
455 if (!client.crypto_idfp)
456 LOG_INFOF("^1NOTE:^7 banned client %s just tried to enter\n",
457 client.netaddress);
458 else
459 LOG_INFOF("^1NOTE:^7 banned client %s (%s) just tried to enter\n",
460 client.netaddress, client.crypto_idfp);
461
463 sprint(client, "You are banned from this server.\n");
464 dropclient(client);
465 return true;
466 }
467 return false;
468}
469
472{
473 if (client.ban_checked) return false;
474 client.ban_checked = true;
475 return Ban_MaybeEnforceBan(client);
476}
477
478string Ban_Enforce(float j, string reason)
479{
480 string s;
481
482 // Enforce our new ban
483 s = "";
485 {
486 if(Ban_IsClientBanned(it, j))
487 {
488 if(reason != "")
489 {
490 if(s == "")
491 reason = strcat(reason, ": affects ");
492 else
493 reason = strcat(reason, ", ");
494 reason = strcat(reason, it.netname);
495 }
496 s = strcat(s, "^1NOTE:^7 banned client ", it.netname, "^7 has to go\n");
497 dropclient(it);
498 }
499 });
500 bprint(s);
501
502 return reason;
503}
504
505float Ban_Insert(string ip, float bantime, string reason, float dosync)
506{
507 float i;
508 float j;
509 float bestscore;
510
511 // already banned?
512 for(i = 0; i < ban_count; ++i)
513 if(ban_ip[i] == ip)
514 {
515 // prolong the ban
516 if(time + bantime > ban_expire[i])
517 {
518 ban_expire[i] = time + bantime;
519 LOG_TRACE(ip, "'s ban has been prolonged to ", ftos(bantime), " seconds from now");
520 }
521 else
522 LOG_TRACE(ip, "'s ban is still active until ", ftos(ban_expire[i] - time), " seconds from now");
523
524 // and enforce
525 reason = Ban_Enforce(i, reason);
526
527 // and abort
528 if(dosync)
529 if(reason != "")
530 if(substring(reason, 0, 1) != "~") // like IRC: unauthenticated banner
531 OnlineBanList_SendBan(ip, bantime, reason);
532
533 return false;
534 }
535
536 // do we have a free slot?
537 for(i = 0; i < ban_count; ++i)
538 if(time > ban_expire[i])
539 break;
540 // no free slot? Then look for the one who would get unbanned next
541 if(i >= BAN_MAX)
542 {
543 i = 0;
544 bestscore = ban_expire[i];
545 for(j = 1; j < ban_count; ++j)
546 {
547 if(ban_expire[j] < bestscore)
548 {
549 i = j;
550 bestscore = ban_expire[i];
551 }
552 }
553 }
554 // if we replace someone, will we be banned longer than them (so long-term
555 // bans never get overridden by short-term bans)
556 if(i < ban_count)
557 if(ban_expire[i] > time + bantime)
558 {
559 LOG_INFO(ip, " could not get banned due to no free ban slot");
560 return false;
561 }
562 // okay, insert our new victim as i
563 Ban_Delete(i);
564 LOG_TRACE(ip, " has been banned for ", ftos(bantime), " seconds");
565 ban_expire[i] = time + bantime;
566 ban_ip[i] = strzone(ip);
567 ban_count = max(ban_count, i + 1);
568
569 Ban_SaveBans();
570
571 reason = Ban_Enforce(i, reason);
572
573 // and abort
574 if(dosync)
575 if(reason != "")
576 if(substring(reason, 0, 1) != "~") // like IRC: unauthenticated banner
577 OnlineBanList_SendBan(ip, bantime, reason);
578
579 return true;
580}
581
582void Ban_KickBanClient(entity client, float bantime, float masksize, string reason)
583{
584 string ip, id;
585 if(!Ban_GetClientIP(client))
586 {
587 sprint(client, strcat("Kickbanned: ", reason, "\n"));
588 dropclient(client);
589 return;
590 }
591
592 // who to ban?
593 switch(masksize)
594 {
595 case 1:
596 ip = strcat(ban_ip1);
597 break;
598 case 2:
599 ip = strcat(ban_ip2);
600 break;
601 case 3:
602 ip = strcat(ban_ip3);
603 break;
604 case 4:
605 default:
606 ip = strcat(ban_ip4);
607 break;
608 }
609 if(ban_idfp)
610 id = strcat(ban_idfp);
611 else
612 id = string_null;
613
614 Ban_Insert(ip, bantime, reason, 1);
615 if(id)
616 Ban_Insert(id, bantime, reason, 1);
617 /*
618 * not needed, as we enforce the ban in Ban_Insert anyway
619 // and kick him
620 sprint(client, strcat("Kickbanned: ", reason, "\n"));
621 dropclient(client);
622 */
623}
string autocvar_g_ban_sync_trusted_servers
Definition banning.qh:7
bool autocvar_g_ban_sync_trusted_servers_verify
Definition banning.qh:8
bool autocvar_g_ban_telluser
Definition banning.qh:10
float autocvar_g_ban_sync_timeout
Definition banning.qh:6
string autocvar_g_banned_list
Definition banning.qh:11
bool autocvar_g_banned_list_idmode
Definition banning.qh:12
float autocvar_g_ban_sync_interval
Definition banning.qh:5
string autocvar_g_ban_sync_uri
Definition banning.qh:9
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
#define LABEL(id)
Definition compiler.qh:34
float time
float nextthink
#define strstrofs
#define strlen
#define tokenize_console
#define tokenizebyseparator
void OnlineBanList_Think(entity this)
Definition ipban.qc:197
float Ban_IsClientBanned(entity client, float idx)
Definition ipban.qc:411
string ban_ip[BAN_MAX]
Definition ipban.qc:253
bool ban_checked
Definition ipban.qc:470
float OnlineBanList_RequestWaiting[MAX_IPBAN_URIS]
Definition ipban.qc:77
float ban_expire[BAN_MAX]
Definition ipban.qc:254
void Ban_View()
Definition ipban.qc:328
string ban_ip2
Definition ipban.qc:258
float Ban_GetClientIP(entity client)
Definition ipban.qc:353
bool Ban_MaybeEnforceBanOnce(entity client)
Definition ipban.qc:471
float Ban_Insert(string ip, float bantime, string reason, float dosync)
Definition ipban.qc:505
float ban_loaded
Definition ipban.qc:252
#define MAX_IPBAN_URIS
Definition ipban.qc:31
bool Ban_MaybeEnforceBan(entity client)
Definition ipban.qc:451
string ban_ip1
Definition ipban.qc:257
void OnlineBanList_SendUnban(string ip)
Definition ipban.qc:55
void Ban_SaveBans()
Definition ipban.qc:263
float OnlineBanList_Timeout
Definition ipban.qc:76
float Ban_Delete(float i)
Definition ipban.qc:286
string ban_ip4
Definition ipban.qc:260
string Ban_Enforce(float j, string reason)
Definition ipban.qc:478
void Ban_KickBanClient(entity client, float bantime, float masksize, string reason)
Definition ipban.qc:582
string OnlineBanList_Servers
Definition ipban.qc:75
void OnlineBanList_URI_Get_Callback(float id, float status, string data)
Definition ipban.qc:79
float ban_count
Definition ipban.qc:255
string ban_ip3
Definition ipban.qc:259
string ban_idfp
Definition ipban.qc:261
void OnlineBanList_SendBan(string ip, float bantime, string reason)
Definition ipban.qc:33
void Ban_LoadBans()
Definition ipban.qc:305
const float BAN_MAX
Definition ipban.qc:251
#define LOG_INFO(...)
Definition log.qh:65
#define LOG_TRACE(...)
Definition log.qh:76
#define LOG_INFOF(...)
Definition log.qh:66
void cvar_set(string name, string value)
float stof(string val,...)
string substring(string s, float start, float length)
void sprint(float clientnum, string text,...)
void bprint(string text,...)
float min(float f,...)
void strunzone(string s)
string ftos(float f)
string strzone(string s)
string argv(float n)
float max(float f,...)
string string_null
Definition nil.qh:9
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
#define setthink(e, f)
string autocvar_hostname
Definition client.qh:50
#define strcpy(this, s)
Definition string.qh:52
const int URI_GET_IPBAN
Definition urllib.qh:5
const int URI_GET_DISCARD
Definition urllib.qh:4
#define IS_REAL_CLIENT(v)
Definition utils.qh:17
#define FOREACH_CLIENTSLOT(cond, body)
Definition utils.qh:40