From 449d01180a19c8c2d57b96d01ed2e8ea0b9747fa Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Mon, 17 Jun 2013 23:57:29 -0400 Subject: [PATCH] Banlist and recent login history --- channel.js | 184 +++++++++++++++---------------- chatcommand.js | 27 +---- database.js | 54 ++++++++- user.js | 6 + www/assets/js/callbacks.js | 46 +++++++- www/assets/js/channelsettings.js | 5 + www/channeloptions.html | 26 ++++- 7 files changed, 222 insertions(+), 126 deletions(-) diff --git a/channel.js b/channel.js index 65c25fb8..6741b536 100644 --- a/channel.js +++ b/channel.js @@ -93,7 +93,9 @@ var Channel = function(name) { }; this.ipbans = {}; this.namebans = {}; - this.logins = {}; + this.ip_alias = {}; + this.name_alias = {}; + this.login_hist = []; this.logger = new Logger.Logger("chanlogs/" + this.name + ".log"); this.i = 0; this.time = new Date().getTime(); @@ -205,10 +207,6 @@ Channel.prototype.loadDump = function() { this.motd = data.motd; this.broadcastMotd(); } - data.logins = data.logins || {}; - for(var ip in data.logins) { - this.logins[ip] = data.logins[ip]; - } this.setLock(!(data.openqueue || false)); this.chatbuffer = data.chatbuffer || []; for(var i = 0; i < this.chatbuffer.length; i++) { @@ -242,7 +240,6 @@ Channel.prototype.saveDump = function() { permissions: this.permissions, filters: filts, motd: this.motd, - logins: this.logins, openqueue: this.openqueue, chatbuffer: this.chatbuffer, css: this.css, @@ -323,11 +320,9 @@ Channel.prototype.saveRank = function(user) { Channel.prototype.getIPRank = function(ip) { var names = []; - if(this.logins[ip] === undefined || this.logins[ip].length == 0) { - return 0; - } - - this.logins[ip].forEach(function(name) { + if(!(ip in this.ip_alias)) + this.ip_alias = Database.getAliases(ip); + this.ip_alias[ip].forEach(function(name) { names.push(name); }); @@ -339,16 +334,6 @@ Channel.prototype.getIPRank = function(ip) { return rank; } -Channel.prototype.seen = function(ip, name) { - name = name.toLowerCase(); - for(var i = 0; i < this.logins[ip].length; i++) { - if(this.logins[ip][i].toLowerCase() == name) { - return true; - } - } - return false; -} - Channel.prototype.cacheMedia = function(media) { // Prevent the copy in the playlist from messing with this one media = media.dup(); @@ -362,7 +347,7 @@ Channel.prototype.cacheMedia = function(media) { return false; } -Channel.prototype.banName = function(actor, name) { +Channel.prototype.tryNameBan = function(actor, name) { if(!this.hasPermission(actor, "ban")) { return false; } @@ -370,11 +355,6 @@ Channel.prototype.banName = function(actor, name) { name = name.toLowerCase(); var rank = this.getRank(name); - if(rank < 1) { - actor.socket.emit("errorMsg", {msg: "You can't ban guest names. Use a kick or IP ban."}); - return false; - } - if(rank >= actor.rank) { actor.socket.emit("errorMsg", {msg: "You don't have permission to ban this person."}); return false; @@ -409,47 +389,47 @@ Channel.prototype.unbanName = function(actor, name) { return Database.channelUnbanName(this.name, name); } -Channel.prototype.tryIPBan = function(actor, data) { +Channel.prototype.tryIPBan = function(actor, name, range) { if(!this.hasPermission(actor, "ban")) { return false; } - if(typeof data.id != "string") { - return false; - } - if(typeof data.name != "string") { - return false; - } - var ip = this.hideIP(data.id); - if(this.getIPRank(ip) >= actor.rank) { - actor.socket.emit("errorMsg", {msg: "You don't have permission to ban this IP"}); + if(typeof name != "string") { return false; } + var ips = Database.ipForName(name); + var chan = this; + ips.forEach(function(ip) { + if(chan.getIPRank(ip) >= actor.rank) { + actor.socket.emit("errorMsg", {msg: "You don't have permission to ban IP: x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1")}); + return false; + } - if(data.range) { - ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); - for(var ip2 in this.logins) { - if(ip2.indexOf(ip) == 0 && this.getIPRank(ip2) >= actor.rank) { - actor.socket.emit("errorMsg", {msg: "You don't have permission to ban this IP"}); - return false; + if(range) { + ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); + for(var ip2 in chan.ip_alias) { + if(ip2.indexOf(ip) == 0 && chan.getIPRank(ip2) >= actor.rank) { + actor.socket.emit("errorMsg", {msg: "You don't have permission to ban IP: x.x." + ip2.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1")}); + return false; + } } } - } - this.ipbans[ip] = [data.name, actor.name]; - this.broadcastBanlist(); - this.logger.log(ip + " (" + data.name + ") was banned by " + actor.name); + chan.ipbans[ip] = [name, actor.name]; + chan.broadcastBanlist(); + chan.logger.log(ip + " (" + name + ") was banned by " + actor.name); - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].ip.indexOf(ip) == 0) { - this.kick(this.users[i], "Your IP is banned!"); - i--; + for(var i = 0; i < chan.users.length; i++) { + if(chan.users[i].ip.indexOf(ip) == 0) { + chan.kick(chan.users[i], "Your IP is banned!"); + i--; + } } - } - if(!this.registered) - return false; + if(!chan.registered) + return false; - // Update database ban table - return Database.channelBan(this.name, ip, data.name, actor.name); + // Update database ban table + return Database.channelBan(chan.name, ip, name, actor.name); + }); } Channel.prototype.banIP = function(actor, receiver) { @@ -488,8 +468,8 @@ Channel.prototype.unbanIP = function(actor, ip) { } Channel.prototype.tryUnban = function(actor, data) { - if(data.id) { - var ip = this.hideIP(data.id); + if(data.ip_hidden) { + var ip = this.hideIP(data.ip_hidden); this.unbanIP(actor, ip); } else if(data.name) { @@ -531,9 +511,6 @@ Channel.prototype.search = function(query, callback) { /* REGION User interaction */ Channel.prototype.userJoin = function(user) { - if(!(user.ip in this.logins)) { - this.logins[user.ip] = []; - } var parts = user.ip.split("."); var slash24 = parts[0] + "." + parts[1] + "." + parts[2]; // GTFO @@ -653,27 +630,29 @@ Channel.prototype.hideIP = function(ip) { return chars.join(""); } +Channel.prototype.sendLoginHistory = function(user) { + if(user.rank < 2) + return; + + user.socket.emit("recentLogins", this.login_hist); +} + Channel.prototype.sendRankStuff = function(user) { if(this.hasPermission(user, "ban")) { var ents = []; for(var ip in this.ipbans) { if(this.ipbans[ip] != null) { - var name; - if(ip in this.logins) { - name = this.logins[ip].join(", "); - } - else { - name = this.ipbans[ip][0]; - } - var id = this.hideIP(ip); + var name = this.ipbans[ip][0]; + var ip_hidden = this.hideIP(ip); var disp = ip; if(user.rank < Rank.Siteadmin) { - disp = "(Hidden)"; + disp = "x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1"); } ents.push({ - ip: disp, - id: id, + ip_displayed: disp, + ip_hidden: ip_hidden, name: name, + aliases: this.ip_alias[ip] || [], banner: this.ipbans[ip][1] }); } @@ -681,17 +660,20 @@ Channel.prototype.sendRankStuff = function(user) { for(var name in this.namebans) { if(this.namebans[name] != null) { ents.push({ - ip: "*", + ip_displayed: "*", + ip_hidden: false, name: name, + aliases: this.name_alias[name] || [], banner: this.namebans[name] }); } } - user.socket.emit("banlist", {entries: ents}); + user.socket.emit("banlist", ents); } + // TODO get rid of this if(Rank.hasPermission(user, "seenlogins")) { var ents = []; - for(var ip in this.logins) { + for(var ip in this.ip_alias) { var disp = ip; if(user.rank < Rank.Siteadmin) { disp = "(Hidden)"; @@ -702,7 +684,7 @@ Channel.prototype.sendRankStuff = function(user) { ents.push({ ip: disp, id: this.hideIP(ip), - names: this.logins[ip], + names: this.ip_alias[ip], banned: banned }); } @@ -797,9 +779,21 @@ Channel.prototype.broadcastUsercount = function() { } Channel.prototype.broadcastNewUser = function(user) { - if(!this.seen(user.ip, user.name)) { - this.logins[user.ip].push(user.name); - } + var aliases = Database.getAliases(user.ip); + var chan = this; + this.ip_alias[user.ip] = aliases; + aliases.forEach(function(alias) { + chan.name_alias[alias] = aliases; + }); + + this.login_hist.unshift({ + name: user.name, + aliases: this.ip_alias[user.ip], + time: Date.now() + }); + if(this.login_hist.length > 20) + this.login_hist.pop(); + if(user.name.toLowerCase() in this.namebans && this.namebans[user.name.toLowerCase()] != null) { this.kick(user, "You're banned!"); @@ -850,24 +844,20 @@ Channel.prototype.broadcastBanlist = function() { var adminents = []; for(var ip in this.ipbans) { if(this.ipbans[ip] != null) { - var name; - if(ip in this.logins) { - name = this.logins[ip].join(", "); - } - else { - name = this.ipbans[ip][0]; - } - var id = this.hideIP(ip); + var name = this.ipbans[ip][0]; + var ip_hidden = this.hideIP(ip); ents.push({ - ip: "(Hidden)", - id: id, + ip_displayed: "x.x." + ip.replace(/\d+\.\d+\.(\d+\.\d+)/, "$1"), + ip_hidden: ip_hidden, name: name, + aliases: this.ip_alias[ip] || [], banner: this.ipbans[ip][1] }); adminents.push({ - ip: ip, - id: id, + ip_displayed: ip, + ip_hidden: ip_hidden, name: name, + aliases: this.ip_alias[ip] || [], banner: this.ipbans[ip][1] }); } @@ -875,13 +865,17 @@ Channel.prototype.broadcastBanlist = function() { for(var name in this.namebans) { if(this.namebans[name] != null) { ents.push({ - ip: "*", + ip_displayed: "*", + ip_hidden: false, name: name, + aliases: this.name_alias[name] || [], banner: this.namebans[name] }); adminents.push({ - ip: "*", + ip_displayed: "*", + ip_hidden: false, name: name, + aliases: this.name_alias[name] || [], banner: this.namebans[name] }); } @@ -889,10 +883,10 @@ Channel.prototype.broadcastBanlist = function() { for(var i = 0; i < this.users.length; i++) { if(this.hasPermission(this.users[i], "ban")) { if(this.users[i].rank >= Rank.Siteadmin) { - this.users[i].socket.emit("banlist", {entries: adminents}); + this.users[i].socket.emit("banlist", adminents); } else { - this.users[i].socket.emit("banlist", {entries: ents}); + this.users[i].socket.emit("banlist", ents); } } } diff --git a/chatcommand.js b/chatcommand.js index a134574a..89d77f73 100644 --- a/chatcommand.js +++ b/chatcommand.js @@ -39,7 +39,7 @@ function handle(chan, user, msg, data) { handleBan(chan, user, msg.substring(5).split(" ")); } else if(msg.indexOf("/ipban ") == 0) { - handleIPBan(chan, user, msg.substring(5).split(" ")); + handleIPBan(chan, user, msg.substring(7).split(" ")); } else if(msg.indexOf("/unban ") == 0) { handleUnban(chan, user, msg.substring(7).split(" ")); @@ -77,30 +77,13 @@ function handleKick(chan, user, args) { } function handleIPBan(chan, user, args) { - if(chan.hasPermission(user, "ban") && args.length > 0) { - args[0] = args[0].toLowerCase(); - var kickee; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == args[0]) { - kickee = chan.users[i]; - break; - } - } - if(kickee && kickee.rank < user.rank) { - chan.logger.log("*** " + user.name + " banned " + args[0]); - args[0] = ""; - var reason = args.join(" "); - chan.kick(kickee, "(banned) " + reason); - chan.banIP(user, kickee); - } - } + chan.tryIPBan(user, args[0], args[1]); + // Ban the name too for good measure + chan.tryNameBan(user, args[0]); } function handleBan(chan, user, args) { - if(chan.hasPermission(user, "ban") && args.length > 0) { - args[0] = args[0].toLowerCase(); - chan.banName(user, args[0]); - } + chan.tryNameBan(user, args[0]); } function handleUnban(chan, user, args) { diff --git a/database.js b/database.js index 13f5196f..d6a4d095 100644 --- a/database.js +++ b/database.js @@ -918,7 +918,6 @@ function recordVisit(ip, name) { var results = db.querySync(query); if(!results) { - console.log(results); Logger.errlog.log("! Failed to record visit"); } @@ -931,11 +930,60 @@ function recordVisit(ip, name) { ");"].join(""), [ip] )); - console.log(results); return results; } +function getAliases(ip) { + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT name FROM aliases WHERE ip=?", + [ip] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve aliases"); + return []; + } + + var names = []; + results.fetchAllSync().forEach(function(row) { + names.push(row.name); + }); + + return names; +} + +function ipForName(name) { + var db = getConnection(); + if(!db) { + return []; + } + + var query = createQuery( + "SELECT ip FROM aliases WHERE name=?", + [name] + ); + + var results = db.querySync(query); + if(!results) { + Logger.errlog.log("! Failed to retrieve IP for name"); + return []; + } + + var ips = []; + results.fetchAllSync().forEach(function(row) { + ips.push(row.ip); + }); + + return ips; +} + exports.setup = setup; exports.getConnection = getConnection; exports.createQuery = createQuery; @@ -966,3 +1014,5 @@ exports.loadUserPlaylist = loadUserPlaylist; exports.saveUserPlaylist = saveUserPlaylist; exports.deleteUserPlaylist = deleteUserPlaylist; exports.recordVisit = recordVisit; +exports.getAliases = getAliases; +exports.ipForName = ipForName; diff --git a/user.js b/user.js index 1b461751..22372bf9 100644 --- a/user.js +++ b/user.js @@ -379,6 +379,12 @@ User.prototype.initCallbacks = function() { } }.bind(this)); + this.socket.on("requestLoginHistory", function() { + if(this.channel != null) { + this.channel.sendLoginHistory(this); + } + }.bind(this)); + this.socket.on("requestAcl", function() { if(this.channel != null) { this.channel.sendACL(this); diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 4369933a..aeec406e 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -250,26 +250,60 @@ Callbacks = { banlist: function(entries) { var tbl = $("#banlist table"); + // dumb hack because of jquery UI + // sortable turns tables and lists into a mess of race conditions + if(!tbl.hasClass("table")) { + setTimeout(function() { + Callbacks.banlist(entries); + }, 100); + return; + } if(tbl.children().length > 1) { $(tbl.children()[1]).remove(); } for(var i = 0; i < entries.length; i++) { - var tr = $("").appendTo(tbl); + var tr = document.createElement("tr"); var remove = $(" +
+ + + + + + + + + + +
UnbanIPNameAliasesBanned By
+
+
+ + + + + + + + +
NameAliasesTime
+