· 5 years ago · Aug 11, 2020, 10:32 PM
1/*
2 * Copyright (C) 2018 Luigi Martinelli <luigi@xdefcon.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * @author Luigi Martinelli <luigi@xdefcon.com>
18 *
19 */
20
21
22registerPlugin({
23 name: 'AntiProxy - VPN/Proxy Blocker',
24 version: '2.6',
25 description: 'With this script trolls and spammers will become the last problem for your TeamSpeak server, you ban them, they use a VPN or a proxy to reconnect and they can not!',
26 author: 'Luigi M. - xDefcon (luigi@xdefcon.com)',
27 requiredModules: ['http'],
28 vars: {
29 debugSwitch: {
30 title: 'Enable debug messages?',
31 type: 'select',
32 options: ['no', 'yes']
33 }, punishment: {
34 title: 'Punishment when a proxy is detected',
35 type: 'select',
36 options: ['poke', 'kick', 'tempban', 'chatmessage', 'addgroup' , 'none (notify admins only)']
37 }, tempBanDuration: {
38 title: "Temp ban duration in seconds",
39 type: 'number',
40 conditions: [{field: 'punishment', value: 2}]
41 }, punishmentMessage: {
42 title: "Punishment message (kick, poke, ban, addgroup)",
43 type: 'string',
44 placeholder: "Proxy/VPN detected. Error? Contact: luigi@xdefcon.com or admin"
45 }, notifyOnDetection: {
46 title: 'Notify Admins when a proxy is detected?',
47 type: 'select',
48 options: ['no', 'yes']
49 }, permissionsMessage: {
50 title: "Not enough permissions message",
51 type: 'string',
52 placeholder: "You don't have enough permissions to execute this command."
53 }, apiKey: {
54 title: "API Key (you don't need one if you have less than 100 users, the bot will tell you when " +
55 "you need one. The key gives unlimited proxy checks, contact luigi@xdefcon.com to get one)",
56 type: 'string',
57 placeholder: "Insert the API key here. If you don't have, leave blank."
58 }, admins: {
59 title: "Admin Unique IDs used to send important notifications",
60 type: "array",
61 vars: [{
62 name: 'adminUID',
63 indent: 1,
64 title: 'Admin Client UID',
65 type: 'string'
66 }]
67 }, adminGroups: {
68 title: "Admin Group IDs used to send important notifications",
69 type: "array",
70 vars: [{
71 name: 'groupID',
72 indent: 1,
73 title: 'Admin Group ID',
74 type: 'number'
75 }]
76 }, vpnGroup: {
77 title: "Group ID used to assign VPN Group",
78 type: "number",
79 conditions: [{field: 'punishment', value: 4}]
80 }, whitelist: {
81 title: "Whitelist of IP addresses (Please report to luigi@xdefcon.com if false detection, this is a quick fix.)",
82 type: "array",
83 vars: [{
84 name: "address",
85 indent: 1,
86 title: "Client IP address to whitelist",
87 type: "string"
88 }]
89 }, whitelistGroups: {
90 title: "Group IDs that are automatically whitelisted from detection",
91 type: "array",
92 vars: [{
93 name: 'groupID',
94 indent: 1,
95 title: 'Whitelisted Group ID',
96 type: 'number'
97 }]
98 }, whitelistGroupType: {
99 title: "Group Whitelist type ",
100 type: 'select',
101 options: ['normal (if client has at AT LEAST ONE of the listed group IDs below HE IS NOT checked)',
102 'inverted (ONLY if client has AT LEAST ONE of the listed group IDs below HE IS checked, others are not)']
103 }
104 }
105}, function (sinusbot, config) {
106 if (typeof config.debugSwitch == 'undefined') {
107 config.debugSwitch = 0;
108 }
109 if (typeof config.punishment == 'undefined') {
110 config.punishment = 1;
111 }
112 if (typeof config.punishmentMessage == 'undefined' || config.punishmentMessage == "") {
113 config.punishmentMessage = "Proxy/VPN detected. Error? Contact: luigi@xdefcon.com or admin";
114 }
115 if (typeof config.notifyOnDetection == 'undefined') {
116 config.notifyOnDetection = 1;
117 }
118 if (typeof config.permissionsMessage == 'undefined') {
119 config.permissionsMessage = "You don't have enough permissions to execute this command.";
120 }
121 if (config.punishment == 2 && typeof config.tempBanDuration == 'undefined') {
122 config.tempBanDuration = 10;
123 }
124 if (typeof config.adminGroups == 'undefined') {
125 config.adminGroups = [];
126 }
127 if (typeof config.admins == 'undefined') {
128 config.admins = [];
129 }
130 if (typeof config.vpnGroup == 'undefined') {
131 config.vpnGroup = -1;
132 }
133 if (typeof config.whitelist == 'undefined') {
134 config.whitelist = [];
135 }
136 if (typeof config.whitelistGroups == 'undefined') {
137 config.whitelistGroups = [];
138 }
139 if (typeof config.whitelistGroupType == 'undefined') {
140 config.whitelistGroupType = 0;
141 }
142
143
144 var event = require("event");
145 var engine = require("engine");
146 var backend = require("backend");
147 var http = require("http");
148 var localProxies = {};
149 var rateLimited = false;
150
151 var startedTime = Date.now();
152 var checkedIps = 0;
153 var detectedProxies = 0;
154 var apiRequests = 0;
155 var lastDetection = {
156 client: null,
157 ip: null
158 };
159
160 setInterval(function () {
161 debug("Executing automatic purge of the local IP cache.");
162 localProxies = {};
163 }, 86400000);
164
165 event.on("chat", function (ev) {
166 var message = ev.text;
167 var client = ev.client;
168 if (client.isSelf()) {
169 return;
170 }
171
172 switch (message) {
173 case "!antiproxy info":
174 if (!checkPermissions(client)) {
175 client.chat(config.permissionsMessage);
176 return;
177 }
178
179 //avoiding to print "null null" on the "Last detection" line
180 lastDetection.client = lastDetection.client == null ? "Never" : lastDetection.client;
181 lastDetection.ip = lastDetection.ip == null ? "" : lastDetection.ip;
182
183 client.chat("\n[b]AntiProxy by xDefcon[/b]\n" +
184 "[b]Running time[/b]: " + ((Date.now() - startedTime) / 1000).toString() + "secs\n" +
185 "[b]Proxies detected[/b]: " + detectedProxies + "\n" +
186 "[b]Last detection[/b]: " + lastDetection.client + " " + lastDetection.ip + "\n" +
187 "[b]Checked IPs[/b]: " + checkedIps + "\n" +
188 "[b]IP cached locally[/b]: " + Object.keys(localProxies).length + "\n" +
189 "[b]API requests[/b]: " + apiRequests + "\n");
190 break;
191 case "!antiproxy purgecache":
192 if (!checkPermissions(client)) {
193 client.chat(config.permissionsMessage);
194 return;
195 }
196 localProxies = {};
197 client.chat("Successfully purged the local IP cache.");
198 break;
199 case "!antiproxy checkall":
200 if (!checkPermissions(client)) {
201 client.chat(config.permissionsMessage);
202 return;
203 }
204 client.chat("Checking all clients now connected to the TeamSpeak server.");
205 checkAllClients();
206 client.chat("All clients checked.");
207 break;
208 default:
209 if (message.indexOf("!antiproxy whitelist") !== -1) {
210 if (!checkPermissions(client)) {
211 client.chat(config.permissionsMessage);
212 return;
213 }
214 var ip = message.split(" ")[2]; //todo check if message is a String obj and supports split()
215 if (!isValidIpv4Address(ip)) {
216 client.chat("The address you entered seems not correct, please check it.");
217 return;
218 }
219 addToIpWhitelist(ip);
220 client.chat("Succesfully whitelisted the specified IP address for the current instance. Restarting the bot will cause this change to be lost.");
221 break;
222 }
223 }
224 });
225
226 event.on("clientIPAddress", function (client) {
227 if (client.isSelf()) {
228 return;
229 }
230 debug("Fired clientIPAddress - Client: " + client.name() + " [" + client.getIPAddress() + "].");
231 checkForProxy(client);
232 });
233
234 function addToIpWhitelist(ip) {
235 var addressRow = {};
236 addressRow.address = [ip];
237 config.whitelist.push(addressRow); //todo check if this is the correct way
238 }
239
240 function checkPermissions(client) {
241 var check = false;
242 for (var i = 0; i < config.admins.length; i++) {
243 if (config.admins[i].adminUID == client.uniqueID()) {
244 check = true;
245 break;
246 }
247 }
248 if (!check) {
249 var clientGroups = [];
250 var serverGroups = client.getServerGroups();
251 for (var j = 0; j < serverGroups.length; j++) {
252 clientGroups[j] = "" + serverGroups[j].id();
253 }
254 for (i = 0; i < config.adminGroups.length; i++) {
255 if (clientGroups.indexOf("" + config.adminGroups[i].groupID) !== -1) {
256 check = true;
257 break;
258 }
259 }
260 }
261 return check;
262 }
263
264
265 function sendMessageToStaff(msg) {
266 backend.getClients().forEach(function (client) {
267 if (checkPermissions(client)) {
268 client.chat(msg);
269 }
270 });
271
272 }
273
274
275 function checkAllClients() {
276 debug("Running proxy check on all clients (this may take a while)...");
277 backend.getClients().forEach(function (client) {
278 if (client.isSelf()) {
279 return;
280 }
281 checkForProxy(client);
282 });
283 debug("Finished check.");
284 }
285
286
287 function checkForProxy(client) {
288 var ip = client.getIPAddress();
289 if (ip != null) {
290 ip = ip.replace("[", "").replace("]", "")
291 }
292 var res = checkProxyViaAPI(ip, client);
293 if (res === true) {
294 debug("[PROXY DETECTED] Client: " + client.name() + " (" + client.uniqueID() + ") IP: " + ip);
295 handleDetection(client);
296 } else if (res === false) {
297 debug("Passing proxy check for Client: " + client.name() + " - IP: " + ip);
298 } else {
299 debug("Waiting API response for Client: " + client.name() + " - IP: " + ip);
300 }
301 }
302
303 function isWhitelisted(client) {
304 var ip = client.getIPAddress();
305 var clientGroups = [];
306 var serverGroups = client.getServerGroups();
307 for (var j = 0; j < serverGroups.length; j++) {
308 clientGroups[j] = "" + serverGroups[j].id();
309 }
310
311 var reversed = config.whitelistGroupType == 0 ? false : true;
312 var hasAGroup = false;
313 //check first for group whitelist
314 for (var i = 0; i < config.whitelistGroups.length; i++) {
315 if (typeof config.whitelistGroups[i].groupID != "undefined") { //todo check double strict equals
316 if (clientGroups.indexOf("" + config.whitelistGroups[i].groupID) !== -1) { //if client has A group
317 if (!reversed) {
318 return true;
319 }
320 hasAGroup = true
321 }
322 }
323 }
324
325 //if client does not have any of listed groups
326 if (reversed) {
327 if (!hasAGroup) {
328 return true;
329 }
330 }
331
332 //then for ip address whitelist
333 for (i = 0; i < config.whitelist.length; i++) {
334 if (typeof config.whitelist[i].address != "undefined") { //todo check double strict equals
335 if ("" + ip == config.whitelist[i].address) {
336 return true;
337 }
338 }
339 }
340 return false;
341 }
342
343 function checkProxyViaAPI(ip, client) {
344 ++checkedIps;
345 if (isWhitelisted(client)) {
346 debug("[WHITELIST] Detected Client/IP in whitelist. Skipping check for: " + client.name() + "(" + ip + ").");
347 return false;
348 }
349
350 if (localProxies[ip] != null && localProxies[ip]) {
351 debug("[CACHE] The IP is present in local cache and resulted in a Proxy.");
352 return true;
353 } else if (localProxies[ip] != null && !localProxies[ip]) {
354 debug("[CACHE] The IP is present in local cache and resulted in a clean address.");
355 return false;
356 }
357 var apiUrl = "https://api.xdefcon.com/proxy/check/?ip=" + ip;
358 if (typeof config.apiKey != "undefined" && config.apiKey != " ") {
359 apiUrl = "https://api.xdefcon.com/proxy/check/?ip=" + ip + "&key=" + config.apiKey;
360 }
361 var httpOp = {
362 method: "GET",
363 headers: "Content-type: application/json",
364 timeout: 30000,
365 url: apiUrl
366 };
367 http.simpleRequest(httpOp, function (error, response) {
368 ++apiRequests;
369 if (response == null || response.statusCode !== 200) { //todo debug if api key not valid
370 engine.log("Could not retrieve info for " + ip + " HTTP_ERROR: " + error);
371 return false;
372 }
373 debug("RESPONSE: " + response.data);
374 var result = JSON.parse(response.data);
375 if (result.success && result.proxy != null) {
376 rateLimited = false;
377 if (result.proxy.valueOf()) {
378 localProxies[ip] = true;
379 handleDetection(client);
380 return true;
381 } else {
382 localProxies[ip] = false;
383 return false;
384 }
385 } else if (!result.success && result.message.toLowerCase().indexOf("rate limit") !== -1) {
386 engine.log("[ERROR] API requests limit exceeded, please check www.xdefcon.com/anti-proxy-api-key or " +
387 "contact luigi@xdefcon.com or to remove this limitation.");
388 if (!rateLimited) {
389 rateLimited = true;
390 sendMessageToStaff("[b][AntiProxy][/b] It seems that you have [b]exceeded[/b] the maximum hourly rate of [b]requests to the API[/b]. " +
391 "This means that you will not be able to check [b]new IPs[/b] until the next hour (rate limit reset). If you want to " +
392 "avoid this problem, please read carefully the following page: " +
393 "[b][url=https://www.xdefcon.com/anti-proxy-api-key]www.xdefcon.com/anti-proxy-api-key[/url][/b] - " +
394 "The script [b]will continue working with the local cache[/b], no issues about it.");
395 }
396 return false;
397 }
398 debug("Error in the API response. Is the API offline? Using cached data. URL: " + httpOp.url);
399 return false;
400
401 })
402 }
403
404
405 function handleDetection(client) {
406 ++detectedProxies;
407 lastDetection.client = client.name() + "(" + client.uniqueID() + ")";
408 lastDetection.ip = client.getIPAddress();
409
410 if (config.notifyOnDetection == 1) {
411 sendMessageToStaff("[b][AntiProxy][/b] Detected Proxy on client: [URL=client://" + client.id() + "/" +
412 client.uniqueID() + "]" + client.name() + "[/URL] (" + client.uniqueID() + ") IP: " + client.getIPAddress() +
413 "\n Total client connections: " + client.getTotalConnections() + " - First connection at: " + getDateString(client.getCreationTime()));
414 }
415 debug("Punishment message: " + config.punishmentMessage);
416 if (config.punishment == 0) {
417 client.poke(config.punishmentMessage);
418 debug("Sent poke to Client: " + client.name());
419 }
420 if (config.punishment == 1) {
421 client.kick(config.punishmentMessage);
422 debug("Kicked Client: " + client.name());
423 }
424 if (config.punishment == 2) {
425 client.ban(config.tempBanDuration, config.punishmentMessage.substring(0, 70));
426 debug("Tempbanned Client: " + client.name() + " for " + config.tempBanDuration + " seconds.");
427 }
428 if (config.punishment == 3) {
429 client.chat(config.punishmentMessage);
430 debug("Sent chat message to Client: " + client.name());
431 }
432 if (config.punishment == 4) {
433 if (config.vpnGroup === -1) {
434 debug("The VPN group ID was not set. Please check and set it to the correct value.")
435 } else {
436 client.chat(config.punishmentMessage);
437 client.addToServerGroup(config.vpnGroup);
438 debug("Added VPN Group to Client and informed Client: " + client.name());
439 }
440 }
441 if (config.punishment == 5) {
442 debug("Notify admins only for Client: " + client.name());
443 }
444 }
445
446
447 function debug(msg) {
448 if (config.debugSwitch == 1) {
449 engine.log("[DEBUG] " + msg);
450 }
451 }
452
453
454 function getDateString(timestamp) {
455 var a = new Date(timestamp);
456 var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
457 var year = a.getFullYear();
458 var month = months[a.getMonth()];
459 var date = a.getDate();
460 var hour = a.getHours();
461 if (hour <= 9) hour = "0" + hour;
462 var min = a.getMinutes();
463 if (min <= 9) min = "0" + min;
464 var sec = a.getSeconds();
465 if (sec <= 9) sec = "0" + sec;
466 return date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec;
467 }
468
469 function isValidIpv4Address(ipaddress) {
470 return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress);
471 }
472});
473