Xonotic
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>
6 #include <common/weapons/_all.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 
33 void 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)
45  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 
55 void OnlineBanList_SendUnban(string ip)
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)
65  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 
79 void 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 
102  if(OnlineBanList_RequestWaiting[id] == 0)
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 
193 LABEL(skip)
194  }
195 }
196 
198 {
199  int argc;
200  string uri;
201  float i, n;
202 
203  if(autocvar_g_ban_sync_uri == "")
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 
248  this.nextthink = time + max(60, autocvar_g_ban_sync_interval * 60);
249 }
250 
251 const float BAN_MAX = 256;
253 string ban_ip[BAN_MAX];
255 float ban_count;
256 
257 string ban_ip1;
258 string ban_ip2;
259 string ban_ip3;
260 string ban_ip4;
261 string 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 
286 float 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 
328 void Ban_View()
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 
353 float Ban_GetClientIP(entity client)
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 = strcat1(s); // 32
383  return true;
384 
385 LABEL(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 
411 float 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 
478 string 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 
505 float 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 him (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 
582 void 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 = strcat1(ban_ip1);
597  break;
598  case 2:
599  ip = strcat1(ban_ip2);
600  break;
601  case 3:
602  ip = strcat1(ban_ip3);
603  break;
604  case 4:
605  default:
606  ip = strcat1(ban_ip4);
607  break;
608  }
609  if(ban_idfp)
610  id = strcat1(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 string_null
Definition: nil.qh:9
string autocvar_g_banned_list
Definition: banning.qh:11
float Ban_Insert(string ip, float bantime, string reason, float dosync)
Definition: ipban.qc:505
float OnlineBanList_Timeout
Definition: ipban.qc:76
bool autocvar_g_banned_list_idmode
Definition: banning.qh:12
void Ban_View()
Definition: ipban.qc:328
entity() spawn
string ban_ip[BAN_MAX]
Definition: ipban.qc:253
void Ban_LoadBans()
Definition: ipban.qc:305
float autocvar_g_ban_sync_interval
Definition: banning.qh:5
string ban_ip3
Definition: ipban.qc:259
string Ban_Enforce(float j, string reason)
Definition: ipban.qc:478
bool autocvar_g_ban_telluser
Definition: banning.qh:10
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
const int URI_GET_IPBAN
Definition: urllib.qh:5
#define strcpy(this, s)
Definition: string.qh:49
float ban_expire[BAN_MAX]
Definition: ipban.qc:254
string ban_idfp
Definition: ipban.qc:261
const int URI_GET_DISCARD
Definition: urllib.qh:4
void OnlineBanList_URI_Get_Callback(float id, float status, string data)
Definition: ipban.qc:79
#define FOREACH_CLIENTSLOT(cond, body)
Definition: utils.qh:39
bool Ban_MaybeEnforceBan(entity client)
Definition: ipban.qc:451
float Ban_IsClientBanned(entity client, float idx)
Definition: ipban.qc:411
string autocvar_hostname
Definition: client.qh:49
void OnlineBanList_SendUnban(string ip)
Definition: ipban.qc:55
#define LOG_INFOF(...)
Definition: log.qh:71
bool autocvar_g_ban_sync_trusted_servers_verify
Definition: banning.qh:8
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
float Ban_GetClientIP(entity client)
Definition: ipban.qc:353
void Ban_SaveBans()
Definition: ipban.qc:263
void OnlineBanList_SendBan(string ip, float bantime, string reason)
Definition: ipban.qc:33
#define LOG_INFO(...)
Definition: log.qh:70
float Ban_Delete(float i)
Definition: ipban.qc:286
bool Ban_MaybeEnforceBanOnce(entity client)
Definition: ipban.qc:471
float OnlineBanList_RequestWaiting[MAX_IPBAN_URIS]
Definition: ipban.qc:77
#define strstrofs
Definition: dpextensions.qh:42
float nextthink
Definition: csprogsdefs.qc:121
#define tokenize_console
Definition: dpextensions.qh:24
string autocvar_g_ban_sync_uri
Definition: banning.qh:9
void Ban_KickBanClient(entity client, float bantime, float masksize, string reason)
Definition: ipban.qc:582
string ban_ip4
Definition: ipban.qc:260
#define LOG_TRACE(...)
Definition: log.qh:81
float ban_count
Definition: ipban.qc:255
string autocvar_g_ban_sync_trusted_servers
Definition: banning.qh:7
#define tokenizebyseparator
Definition: dpextensions.qh:21
void OnlineBanList_Think(entity this)
Definition: ipban.qc:197
float autocvar_g_ban_sync_timeout
Definition: banning.qh:6
string ban_ip1
Definition: ipban.qc:257
#define LABEL(id)
Definition: compiler.qh:36
#define MAX_IPBAN_URIS
Definition: ipban.qc:31
const float BAN_MAX
Definition: ipban.qc:251
#define setthink(e, f)
bool ban_checked
Definition: ipban.qc:470
string ban_ip2
Definition: ipban.qc:258
float time
Definition: csprogsdefs.qc:16
string OnlineBanList_Servers
Definition: ipban.qc:75
float ban_loaded
Definition: ipban.qc:252