diff --git a/channel.js b/channel.js index 0ec9991c..9f79b125 100644 --- a/channel.js +++ b/channel.js @@ -68,6 +68,7 @@ var Channel = function(name) { html: "" }; this.ipbans = {}; + this.namebans = {}; this.logins = {}; this.logger = new Logger.Logger("chanlogs/" + this.name + ".log"); this.i = 0; @@ -80,6 +81,11 @@ var Channel = function(name) { this.css = ""; this.js = ""; + this.ipkey = ""; + for(var i = 0; i < 15; i++) { + this.ipkey += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[parseInt(Math.random() * 65)] + } + Database.loadChannel(this); if(this.registered) { this.loadDump(); @@ -290,6 +296,102 @@ Channel.prototype.cacheMedia = function(media) { return false; } +Channel.prototype.banName = function(actor, name) { + if(!Rank.hasPermission(actor, "ban")) { + return false; + } + + 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; + } + + this.namebans[name] = actor.name; + for(var i = 0; i < this.users.length; i++) { + if(this.users[i].name.toLowerCase() == name) { + this.kick(this.users[i], "You're banned!"); + break; + } + } + this.broadcastBanlist(); + this.logger.log(name + " was banned by " + actor.name); + if(!this.registered) { + return false; + } + + return Database.addChannelBan(this.name, actor, { + ip: "*", + name: name + }); +} + +Channel.prototype.unbanName = function(actor, name) { + if(!Rank.hasPermission(actor, "ban")) { + return false; + } + + this.namebans[name] = null; + delete this.namebans[name]; + this.broadcastBanlist(); + this.logger.log(name + " was unbanned by " + actor.name); + + return Database.removeNameBan(this.name, name); +} + +Channel.prototype.tryIPBan = function(actor, data) { + if(!Rank.hasPermission(actor, "ipban")) { + return false; + } + if(typeof data.id != "string" || data.id.length != 15) { + 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"}); + 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; + } + } + } + this.ipbans[ip] = [data.name, actor.name]; + this.broadcastBanlist(); + this.logger.log(ip + " (" + data.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--; + } + } + + if(!this.registered) + return false; + + // Update database ban table + return Database.addChannelBan(this.name, actor, { + ip: ip, + name: data.name + }); +} + Channel.prototype.banIP = function(actor, receiver) { if(!Rank.hasPermission(actor, "ipban")) return false; @@ -315,6 +417,10 @@ Channel.prototype.unbanIP = function(actor, ip) { if(!Rank.hasPermission(actor, "ipban")) return false; + if(this.getIPRank(ip) >= actor.rank) { + return false; + } + this.ipbans[ip] = null; if(!this.registered) @@ -325,6 +431,16 @@ Channel.prototype.unbanIP = function(actor, ip) { return Database.removeChannelBan(this.name, ip); } +Channel.prototype.tryUnban = function(actor, data) { + if(data.id) { + var ip = this.hideIP(data.id); + this.unbanIP(actor, ip); + } + else if(data.name) { + this.unbanName(actor, data.name); + } +} + Channel.prototype.search = function(query, callback) { // Search youtube if(callback) { @@ -369,6 +485,11 @@ Channel.prototype.userJoin = function(user) { this.kick(user, "You're banned!"); return; } + if(user.name && user.name.toLowerCase() in this.namebans && + this.namebans[user.name.toLowerCase()] != null) { + this.kick(user, "You're banned!"); + return; + } // Join the socket pool for this channel user.socket.join(this.name); @@ -470,6 +591,21 @@ Channel.prototype.kick = function(user, reason) { user.socket.disconnect(true); } +Channel.prototype.hideIP = function(ip) { + while(ip.length < 15) { + ip += "X"; + } + var chars = new Array(15); + for(var i = 0; i < ip.length; i++) { + chars[i] = String.fromCharCode(ip.charCodeAt(i) ^ this.ipkey.charCodeAt(i)); + if(chars[i] == "X") { + chars[i] = ""; + break; + } + } + return chars.join(""); +} + Channel.prototype.sendRankStuff = function(user) { if(Rank.hasPermission(user, "ipban")) { var ents = []; @@ -482,13 +618,28 @@ Channel.prototype.sendRankStuff = function(user) { else { name = this.ipbans[ip][0]; } + var id = this.hideIP(ip); + var disp = ip; + if(user.rank < Rank.Siteadmin) { + disp = "(Hidden)"; + } ents.push({ - ip: ip, + ip: disp, + id: id, name: name, banner: this.ipbans[ip][1] }); } } + for(var name in this.namebans) { + if(this.namebans[name] != null) { + ents.push({ + ip: "*", + name: name, + banner: this.namebans[name] + }); + } + } user.socket.emit("banlist", {entries: ents}); } if(Rank.hasPermission(user, "seenlogins")) { @@ -496,11 +647,13 @@ Channel.prototype.sendRankStuff = function(user) { for(var ip in this.logins) { var disp = ip; if(user.rank < Rank.Siteadmin) { - disp = "(Masked)"; + disp = "(Hidden)"; } ents.push({ ip: disp, - name: this.logins[ip].join(",") + id: this.hideIP(ip), + names: this.logins[ip], + banned: (ip in this.ipbans && this.ipbans[ip] != null) }); } user.socket.emit("seenlogins", {entries: ents}); @@ -591,6 +744,11 @@ Channel.prototype.broadcastNewUser = function(user) { if(!this.seen(user.ip, user.name)) { this.logins[user.ip].push(user.name); } + if(user.name.toLowerCase() in this.namebans && + this.namebans[user.name.toLowerCase()] != null) { + this.kick(user, "You're banned!"); + return; + } this.sendAll("addUser", { name: user.name, rank: user.rank, @@ -633,6 +791,7 @@ Channel.prototype.broadcastOpts = function() { Channel.prototype.broadcastBanlist = function() { var ents = []; + var adminents = []; for(var ip in this.ipbans) { if(this.ipbans[ip] != null) { var name; @@ -642,16 +801,43 @@ Channel.prototype.broadcastBanlist = function() { else { name = this.ipbans[ip][0]; } + var id = this.hideIP(ip); ents.push({ + ip: "(Hidden)", + id: id, + name: name, + banner: this.ipbans[ip][1] + }); + adminents.push({ ip: ip, + id: id, name: name, banner: this.ipbans[ip][1] }); } } + for(var name in this.namebans) { + if(this.namebans[name] != null) { + ents.push({ + ip: "*", + name: name, + banner: this.namebans[name] + }); + adminents.push({ + ip: "*", + name: name, + banner: this.namebans[name] + }); + } + } for(var i = 0; i < this.users.length; i++) { if(Rank.hasPermission(this.users[i], "ipban")) { - this.users[i].socket.emit("banlist", {entries: ents}); + if(this.users[i].rank >= Rank.Siteadmin) { + this.users[i].socket.emit("banlist", {entries: adminents}); + } + else { + this.users[i].socket.emit("banlist", {entries: ents}); + } } } } diff --git a/chatcommand.js b/chatcommand.js index 9e97893c..9d8c5b27 100644 --- a/chatcommand.js +++ b/chatcommand.js @@ -38,9 +38,6 @@ function handle(chan, user, msg, data) { else if(msg.indexOf("/ban ") == 0) { handleBan(chan, user, msg.substring(5).split(" ")); } - else if(msg.indexOf("/ipban ") == 0) { - handleIpban(chan, user, msg.substring(7).split(" ")); - } else if(msg.indexOf("/unban ") == 0) { handleUnban(chan, user, msg.substring(7).split(" ")); } @@ -89,45 +86,6 @@ function handleBan(chan, user, args) { chan.kick(kickee, "(banned) " + reason); chan.banIP(user, kickee); } - else if(!kickee && chan.getRank(args[0]) < user.rank) { - for(var ip in chan.logins) { - if(chan.getIPRank(ip) >= user.rank) { - return; - } - if(!chan.seen(ip, args[0])) { - continue; - } - if(args.length >= 2 && args[1] == "range") { - var parts = ip.split("."); - ip = parts[0] + "." + parts[1] + "." + parts[2]; - } - chan.logger.log("*** " + user.name + " banned " + ip); - chan.banIP(user, { - ip: ip, - name: args[0] - }); - } - } - } -} - -function handleIpban(chan, user, args) { - if(Rank.hasPermission(user, "ipban") && args.length > 0) { - var name = ""; - for(var ip in chan.logins) { - if(ip.indexOf(args[0]) == 0) { - if(chan.getIPRank(ip) >= user.rank) { - return; - } - var names = chan.logins[ip]; - name = names[names.length - 1]; - } - } - chan.logger.log("*** " + user.name + " banned " + args[0]); - chan.banIP(user, { - ip: args[0], - name: name - }); } } diff --git a/database.js b/database.js index ae881cf7..860b1065 100644 --- a/database.js +++ b/database.js @@ -202,7 +202,13 @@ exports.loadChannel = function(chan) { } var rows = results.fetchAllSync(); for(var i = 0; i < rows.length; i++) { - chan.ipbans[rows[i].ip] = [rows[i].name, rows[i].banner]; + // Name ban + if(rows[i].ip == "*") { + chan.namebans[rows[i].name] = rows[i].banner; + } + else { + chan.ipbans[rows[i].ip] = [rows[i].name, rows[i].banner]; + } } chan.logger.log("*** Loaded channel from database"); @@ -388,6 +394,19 @@ exports.removeChannelBan = function(channame, ip) { return results; } +exports.removeNameBan = function(channame, name) { + var db = exports.getConnection(); + if(!db) { + return false; + } + var query = "DELETE FROM `chan_{1}_bans` WHERE `ip` = '*' AND `name`='{2}'" + .replace("{1}", sqlEscape(channame)) + .replace("{2}", sqlEscape(name)); + results = db.querySync(query); + db.closeSync(); + return results; +} + exports.getChannelRanks = function(channame) { var db = exports.getConnection(); if(!db) { diff --git a/package.json b/package.json index ac15f600..4f71bf6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "1.8.1", + "version": "1.8.2", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/rank.js b/rank.js index 62ecfd3d..a0bf0e1a 100644 --- a/rank.js +++ b/rank.js @@ -27,6 +27,7 @@ var permissions = { assignLeader : exports.Moderator, kick : exports.Moderator, ipban : exports.Moderator, + ban : exports.Moderator, promote : exports.Moderator, qlock : exports.Moderator, poll : exports.Moderator, diff --git a/server.js b/server.js index 35c633ec..9c7ec3c0 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const VERSION = "1.8.1"; +const VERSION = "1.8.2"; var fs = require("fs"); var Logger = require("./logger.js"); diff --git a/user.js b/user.js index 6cab90a8..6636222c 100644 --- a/user.js +++ b/user.js @@ -143,6 +143,24 @@ User.prototype.initCallbacks = function() { } }.bind(this)); + this.socket.on("banName", function(data) { + if(this.channel != null) { + this.channel.banName(this, data.name || ""); + } + }.bind(this)); + + this.socket.on("banIP", function(data) { + if(this.channel != null) { + this.channel.tryIPBan(this, data); + } + }.bind(this)); + + this.socket.on("unban", function(data) { + if(this.channel != null) { + this.channel.tryUnban(this, data); + } + }.bind(this)); + this.socket.on("chatMsg", function(data) { if(this.channel != null) { this.channel.tryChat(this, data); diff --git a/www/assets/js/callbacks.js b/www/assets/js/callbacks.js index 475529e2..ca68cc62 100644 --- a/www/assets/js/callbacks.js +++ b/www/assets/js/callbacks.js @@ -26,6 +26,10 @@ Callbacks = { $("#messagebuffer").scrollTop($("#messagebuffer").prop("scrollHeight")); }, + errorMsg: function(data) { + alert(data.msg); + }, + announcement: function(data) { $("#announcerow").html(""); $("#announcerow").css("display", ""); @@ -269,11 +273,12 @@ Callbacks = { var name = $("").text(entries[i].name).appendTo(tr); var banner = $("").text(entries[i].banner).appendTo(tr); - var callback = (function(ip) { return function() { - socket.emit("chatMsg", { - msg: "/unban " + ip + var callback = (function(id, name) { return function() { + socket.emit("unban", { + id: id, + name: name }); - } })(entries[i].ip); + } })(entries[i].id, entries[i].name); remove.click(callback); } }, @@ -299,27 +304,69 @@ Callbacks = { for(var i = 0; i < entries.length; i++) { var tr = $("").appendTo(tbl); var bantd = $("").appendTo(tr); - var ban = $("