]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/ipban.qc
added a simple way to sync ban lists online via a http server. CGI scripts for that...
[divverent/nexuiz.git] / data / qcsrc / server / ipban.qc
1 float Ban_Insert(string ip, float bantime, string reason, float dosync);
2
3 void OnlineBanList_SendBan(string ip, float bantime, string reason)
4 {
5         string uri;
6
7         uri = cvar_string("g_ban_sync_uri");
8
9         if(uri == "")
10                 return;
11
12         uri = strcat(uri, "?action=ban");
13         uri = strcat(uri, "&ip=", uri_escape(ip));
14         uri = strcat(uri, "&duration=", ftos(bantime));
15         uri = strcat(uri, "&why=", uri_escape(reason));
16
17         uri_get(uri, 0); // 0 = "discard" callback target
18 }
19
20 void OnlineBanList_SendUnban(string ip)
21 {
22         string uri;
23
24         uri = cvar_string("g_ban_sync_uri");
25
26         if(uri == "")
27                 return;
28
29         uri = strcat(uri, "?action=unban");
30         uri = strcat(uri, "&ip=", uri_escape(ip));
31
32         uri_get(uri, 0); // 0 = "discard" callback target
33 }
34
35 string OnlineBanList_Servers;
36 float OnlineBanList_Timeout;
37
38 void OnlineBanList_URI_Get_Callback(float status, string data)
39 {
40         float n, i, j, l;
41         string ip;
42         float timeleft;
43         string reason;
44         string serverip;
45         float syncinterval;
46
47         if(time > OnlineBanList_Timeout)
48                 return;
49         OnlineBanList_Timeout = 0;
50
51         syncinterval = cvar("g_ban_sync_interval");
52         if(syncinterval == 0)
53                 return;
54         if(syncinterval > 0)
55                 syncinterval *= 60;
56
57         if(status != 0)
58         {
59                 print("Error receiving the online ban list.\nStatus is ", ftos(status), "\n");
60                 return;
61         }
62
63         if(substring(data, 0, 1) == "<")
64         {
65                 print("Error receiving the online ban list.\nReceived HTML instead of a ban list.\n");
66                 return;
67         }
68
69         if(strstrofs(data, "\r", 0) != -1)
70         {
71                 print("Error receiving the online ban list.\nReceived carriage returns.\n");
72                 return;
73         }
74
75         n = tokenizebyseparator(data, "\n");
76         if(mod(n, 4) != 0)
77         {
78                 print("Error receiving the online ban list.\nReceived invalid item count.\n");
79                 return;
80         }
81
82         for(i = 0; i < n; i += 4)
83         {
84                 ip = argv(i);
85                 timeleft = stof(argv(i + 1));
86                 reason = argv(i + 2);
87                 serverip = argv(i + 3);
88
89                 timeleft -= 15;
90                 if(timeleft < 0)
91                         continue;
92
93                 l = strlen(ip);
94                 for(j = 0; j < l; ++j)
95                         if(strstrofs("0123456789.", substring(ip, j, 1), 0) == -1)
96                         {
97                                 print("Invalid character ", substring(ip, j, 1), " in IP address ", ip, ". Skipping this ban.\n");
98                                 goto skip;
99                         }
100
101                 if(strstrofs(strcat(":", OnlineBanList_Servers, ":"), strcat(":", serverip, ":"), 0) != -1)
102                 {
103                         if(syncinterval > 0)
104                                 timeleft = min(syncinterval + 15, timeleft);
105                                 // 15 seconds for safety
106                                 // the ban will be prolonged on the next sync
107                         Ban_Insert(ip, timeleft, strcat("ban synced from ", serverip), 0);
108                         print("Ban list syncing: accepted ban of ", ip, " by ", serverip, ": ", reason, "\n");
109                 }
110
111                 continue;
112 :skip
113         }
114 }
115
116 void OnlineBanList_Think()
117 {
118         float argc;
119         string uri;
120         float i;
121         
122         uri = cvar_string("g_ban_sync_uri");
123
124         if(uri == "")
125                 return;
126         if(cvar("g_ban_sync_interval") == 0) // < 0 is okay, it means "sync on level start only"
127                 return;
128         argc = tokenize_sane(cvar_string("g_ban_sync_trusted_servers"));
129         if(argc == 0)
130                 return;
131
132         if(OnlineBanList_Timeout == 0) // only if there is no ongoing request!
133         {
134                 if(OnlineBanList_Servers)
135                         strunzone(OnlineBanList_Servers);
136                 OnlineBanList_Servers = argv(0);
137                 for(i = 1; i < argc; ++i)
138                         OnlineBanList_Servers = strcat(OnlineBanList_Servers, ":", argv(i));
139                 OnlineBanList_Servers = strzone(OnlineBanList_Servers);
140                 
141                 uri = strcat(uri, "?action=list");
142                 uri = strcat(uri, "&servers=", uri_escape(OnlineBanList_Servers));
143
144                 OnlineBanList_Timeout = time + 10;
145                 uri_get(uri, 1); // 1 = "banlist" callback target
146         }
147         
148         if(cvar("g_ban_sync_interval") > 0)
149                 self.nextthink = time + max(60, cvar("g_ban_sync_interval") * 60);
150 }
151
152 #define BAN_MAX 64
153 float ban_loaded;
154 string ban_ip[BAN_MAX];
155 float ban_expire[BAN_MAX];
156 float ban_count;
157
158 string ban_ip1;
159 string ban_ip2;
160 string ban_ip3;
161 string ban_ip4;
162
163 void Ban_SaveBans()
164 {
165         string out;
166         float i;
167
168         if(!ban_loaded)
169                 return;
170
171         // version of list
172         out = "1";
173         for(i = 0; i < ban_count; ++i)
174         {
175                 if(time > ban_expire[i])
176                         continue;
177                 out = strcat(out, " ", ban_ip[i]);
178                 out = strcat(out, " ", ftos(ban_expire[i] - time));
179         }
180         if(strlen(out) <= 1) // no real entries
181                 cvar_set("g_banned_list", "");
182         else
183                 cvar_set("g_banned_list", out);
184 }
185
186 float Ban_Delete(float i)
187 {
188         if(i < 0)
189                 return FALSE;
190         if(i >= ban_count)
191                 return FALSE;
192         if(ban_expire[i] == 0)
193                 return FALSE;
194         if(ban_expire[i] > 0)
195         {
196                 OnlineBanList_SendUnban(ban_ip[i]);
197                 strunzone(ban_ip[i]);
198         }
199         ban_expire[i] = 0;
200         ban_ip[i] = "";
201         Ban_SaveBans();
202         return TRUE;
203 }
204
205 void Ban_LoadBans()
206 {
207         float i, n;
208         for(i = 0; i < ban_count; ++i)
209                 Ban_Delete(i);
210         ban_count = 0;
211         ban_loaded = TRUE;
212         n = tokenize_sane(cvar_string("g_banned_list"));
213         if(stof(argv(0)) == 1)
214         {
215                 ban_count = (n - 1) / 2;
216                 for(i = 0; i < ban_count; ++i)
217                 {
218                         ban_ip[i] = strzone(argv(2*i+1));
219                         ban_expire[i] = time + stof(argv(2*i+2));
220                 }
221         }
222
223         entity e;
224         e = spawn();
225         e.classname = "bansyncer";
226         e.think = OnlineBanList_Think;
227         e.nextthink = time + 1;
228 }
229
230 void Ban_View()
231 {
232         float i;
233         string msg;
234         for(i = 0; i < ban_count; ++i)
235         {
236                 if(time > ban_expire[i])
237                         continue;
238                 msg = strcat("#", ftos(i), ": ");
239                 msg = strcat(msg, ban_ip[i], " is still banned for ");
240                 msg = strcat(msg, ftos(ban_expire[i] - time), " seconds");
241                 print(msg, "\n");
242         }
243 }
244
245 float Ban_GetClientIP(entity client)
246 {
247         float n;
248         n = tokenizebyseparator(client.netaddress, ".");
249         if(n != 4)
250                 return FALSE;
251         ban_ip1 = strcat1(argv(0));
252         ban_ip2 = strcat(ban_ip1, ".", argv(1));
253         ban_ip3 = strcat(ban_ip2, ".", argv(2));
254         ban_ip4 = strcat(ban_ip3, ".", argv(3));
255         return TRUE;
256 }
257
258 float Ban_IsClientBanned(entity client, float idx)
259 {
260         float i, b, e;
261         if(!ban_loaded)
262                 Ban_LoadBans();
263         if(!Ban_GetClientIP(client))
264                 return FALSE;
265         if(idx < 0)
266         {
267                 b = 0;
268                 e = ban_count;
269         }
270         else
271         {
272                 b = idx;
273                 e = idx + 1;
274         }
275         for(i = b; i < e; ++i)
276         {
277                 string s;
278                 if(time > ban_expire[i])
279                         continue;
280                 s = ban_ip[i];
281                 if(ban_ip1 == s) return TRUE;
282                 if(ban_ip2 == s) return TRUE;
283                 if(ban_ip3 == s) return TRUE;
284                 if(ban_ip4 == s) return TRUE;
285         }
286         return FALSE;
287 }
288
289 float Ban_MaybeEnforceBan(entity client)
290 {
291         if(Ban_IsClientBanned(client, -1))
292         {
293                 string s;
294                 s = strcat("^1NOTE:^7 banned client ", client.netaddress, " just tried to enter\n");
295                 dropclient(client);
296                 bprint(s);
297                 return TRUE;
298         }
299         return FALSE;
300 }
301
302 float Ban_Insert(string ip, float bantime, string reason, float dosync)
303 {
304         float i;
305         float j;
306         float bestscore;
307         entity e;
308         string s;
309
310         if(dosync)
311                 OnlineBanList_SendBan(ip, bantime, reason);
312
313         // already banned?
314         for(i = 0; i < ban_count; ++i)
315                 if(ban_ip[i] == ip)
316                 {
317                         // prolong the ban
318                         if(time + bantime > ban_expire[i])
319                         {
320                                 ban_expire[i] = time + bantime;
321                                 print(ip, "'s ban has been prolonged to ", ftos(bantime), " seconds from now\n");
322                         }
323                         // and abort
324                         return FALSE;
325                 }
326         // do we have a free slot?
327         for(i = 0; i < ban_count; ++i)
328                 if(time > ban_expire[i])
329                         break;
330         // no free slot? Then look for the one who would get unbanned next
331         if(i >= BAN_MAX)
332         {
333                 i = 0;
334                 bestscore = ban_expire[i];
335                 for(j = 1; j < ban_count; ++j)
336                 {
337                         if(ban_expire[j] < bestscore)
338                         {
339                                 i = j;
340                                 bestscore = ban_expire[i];
341                         }
342                 }
343         }
344         // if we replace someone, will we be banned longer than him (so long-term
345         // bans never get overridden by short-term bans)
346         if(ban_expire[i] > time + bantime)
347                 return FALSE;
348         // okay, insert our new victim as i
349         Ban_Delete(i);
350         print(ip, " has been banned for ", ftos(bantime), " seconds\n");
351         ban_expire[i] = time + bantime;
352         ban_ip[i] = strzone(ip);
353         ban_count = max(ban_count, i + 1);
354
355         Ban_SaveBans();
356
357         // Enforce our new ban
358         s = "";
359         FOR_EACH_REALCLIENT(e)
360                 if(Ban_IsClientBanned(e, i))
361                 {
362                         s = strcat(s, "^1NOTE:^7 banned client ", e.netname, "^7 has to go\n");
363                         dropclient(e);
364                 }
365         bprint(s);
366
367         return TRUE;
368 }
369
370 void Ban_KickBanClient(entity client, float bantime, float masksize, string reason)
371 {
372         if(!Ban_GetClientIP(client))
373         {
374                 sprint(client, strcat("Kickbanned: ", reason, "\n"));
375                 dropclient(client);
376                 return;
377         }
378         // now ban him
379         switch(masksize)
380         {
381                 case 1:
382                         Ban_Insert(ban_ip1, bantime, reason, 1);
383                         break;
384                 case 2:
385                         Ban_Insert(ban_ip2, bantime, reason, 1);
386                         break;
387                 case 3:
388                         Ban_Insert(ban_ip3, bantime, reason, 1);
389                         break;
390                 default:
391                         Ban_Insert(ban_ip4, bantime, reason, 1);
392                         break;
393         }
394         // and kick him
395         sprint(client, strcat("Kickbanned: ", reason, "\n"));
396         dropclient(client);
397 }
398
399 float GameCommand_Ban(string command)
400 {
401         float argc;
402         float bantime;
403         entity client;
404         float entno;
405         float masksize;
406         string reason;
407
408         argc = tokenize_sane(command);
409         if(argv(0) == "help")
410         {
411                 print("  kickban # n m p - kickban player n for m seconds, using mask size p (1 to 4)\n");
412                 print("  ban ip m - ban an IP or range (incomplete IP, like 1.2.3) for m seconds\n");
413                 print("  bans - list all existing bans\n");
414                 print("  unban n - delete the entry #n from the bans list\n");
415                 return TRUE;
416         }
417         if(argv(0) == "kickban")
418         {
419                 if(argc >= 3)
420                 {
421                         entno = stof(argv(2));
422                         if(entno > maxclients || entno < 1)
423                                 return TRUE;
424                         client = edict_num(entno);
425                         if(argc >= 4)
426                                 bantime = stof(argv(3));
427                         else
428                                 bantime = cvar("g_ban_default_bantime");
429                         if(argc >= 5)
430                                 masksize = stof(argv(4));
431                         else
432                                 masksize = cvar("g_ban_default_masksize");
433                         if(argc >= 6)
434                                 reason = substring(command, argv_start_index(5), strlen(command) - argv_start_index(5));
435                         else
436                                 reason = "";
437                         Ban_KickBanClient(client, bantime, masksize, reason);
438                         return TRUE;
439                 }
440         }
441         else if(argv(0) == "ban")
442         {
443                 if(argc >= 2)
444                 {
445                         string ip;
446                         ip = argv(1);
447                         if(argc >= 3)
448                                 bantime = stof(argv(2));
449                         else
450                                 bantime = cvar("g_ban_default_bantime");
451                         if(argc >= 4)
452                                 reason = substring(command, argv_start_index(3), strlen(command) - argv_start_index(3));
453                         else
454                                 reason = "";
455                         Ban_Insert(ip, bantime, reason, 1);
456                         return TRUE;
457                 }
458         }
459         else if(argv(0) == "bans")
460         {
461                 Ban_View();
462                 return TRUE;
463         }
464         else if(argv(0) == "unban")
465         {
466                 if(argc >= 2)
467                 {
468                         float who;
469                         who = stof(argv(1));
470                         Ban_Delete(who);
471                         return TRUE;
472                 }
473         }
474         return FALSE;
475 }