diff --git a/lib/channel-new.js b/lib/channel-new.js index 04dcc757..e0b1851c 100644 --- a/lib/channel-new.js +++ b/lib/channel-new.js @@ -97,8 +97,6 @@ function Channel(name) { html: "" // Filtered MOTD text (XSS removed; \n replaced by
) }; self.filters = DEFAULT_FILTERS; - self.ipbans = {}; - self.namebans = {}; self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs", self.uniqueName + ".log")); self.css = ""; // Up to 20KB of inline CSS @@ -442,19 +440,9 @@ Channel.prototype.getIPRank = function (ip, callback) { */ Channel.prototype.join = function (user, password) { var self = this; - self.whenReady(function () { - if (self.opts.password !== false && user.rank < 2) { - if (password !== self.opts.password) { - user.socket.emit("needPassword", typeof password === "undefined"); - return; - } - } - user.socket.emit("cancelNeedPassword"); - var range = user.ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); - if (user.ip in self.ipbans || range in self.ipbans || - user.name.toLowerCase() in self.namebans) { - user.kick("You're banned!"); + var afterIPBanCheck = function () { + if (self.dead) { return; } @@ -467,27 +455,12 @@ Channel.prototype.join = function (user, password) { self.sendUsercount(self.users); user.whenLoggedIn(function () { - var lname = user.name.toLowerCase(); - for (var i = 0; i < self.users.length; i++) { - if (self.users[i].name.toLowerCase() === lname && self.users[i] !== user) { - self.users[i].kick("Duplicate login"); - } - } - - self.getRank(user.name, function (err, rank) { - if (self.dead) { - return; - } - - if (err) { - user.rank = user.global_rank; + db.channels.isNameBanned(self.name, user.name, function (err, banned) { + if (!err && banned) { + user.kick("You're banned!"); } else { - user.rank = Math.max(rank, user.global_rank); + afterLogin(); } - - user.socket.emit("rank", user.rank); - self.sendUserJoin(self.users, user); - self.sendUserlist([user]); }); }); @@ -505,6 +478,50 @@ Channel.prototype.join = function (user, password) { self.logger.log("+++ " + user.ip + " joined"); Logger.syslog.log(user.ip + " joined channel " + self.name); + }; + + var afterLogin = function () { + if (self.dead) { + return; + } + + var lname = user.name.toLowerCase(); + for (var i = 0; i < self.users.length; i++) { + if (self.users[i].name.toLowerCase() === lname && self.users[i] !== user) { + self.users[i].kick("Duplicate login"); + } + } + + self.getRank(user.name, function (err, rank) { + if (err) { + user.rank = user.global_rank; + } else { + user.rank = Math.max(rank, user.global_rank); + } + + user.socket.emit("rank", user.rank); + self.sendUserJoin(self.users, user); + self.sendUserlist([user]); + }); + }; + + self.whenReady(function () { + if (self.opts.password !== false && user.rank < 2) { + if (password !== self.opts.password) { + user.socket.emit("needPassword", typeof password === "undefined"); + return; + } + } + + user.socket.emit("cancelNeedPassword"); + db.channels.isIPBanned(self.name, user.ip, function (err, banned) { + if (!err && banned) { + user.kick("You're banned!"); + return; + } else { + afterIPBanCheck(); + } + }); }); }; @@ -622,6 +639,13 @@ Channel.prototype.handleNameBan = function (actor, name, reason) { return false; } + if (!self.registered) { + actor.socket.emit("errorMsg", { + msg: "Banning is only supported in registered channels" + }); + return; + } + name = name.toLowerCase(); if (name == actor.name.toLowerCase()) { actor.socket.emit("costanza", { @@ -655,12 +679,6 @@ Channel.prototype.handleNameBan = function (actor, name, reason) { } reason = reason.substring(0, 255); - self.namebans[name] = { - ip: "*", - name: name, - bannedby: actor.name, - reason: reason - }; // If in the channel already, kick the banned user for (var i = 0; i < self.users.length; i++) { @@ -672,13 +690,22 @@ Channel.prototype.handleNameBan = function (actor, name, reason) { self.logger.log("*** " + actor.name + " namebanned " + name); self.sendModMessage(actor.name + " banned " + name, self.permissions.ban); - if (!self.registered) { - return; - } + db.channels.isNameBanned(self.name, name, function (err, banned) { + if (!err && banned) { + actor.socket.emit("errorMsg", { + msg: name + " is already banned" + }); + return; + } - // channel, ip, name, reason, actor - db.channels.ban(self.name, "*", name, reason, actor.name); - // TODO send banlist? + if (self.dead) { + return; + } + + // channel, ip, name, reason, actor + db.channels.ban(self.name, "*", name, reason, actor.name); + // TODO send banlist? + }); }); }; @@ -690,18 +717,65 @@ Channel.prototype.handleUnbanName = function (actor, name) { return; } - delete this.namebans[name]; - this.logger.log("*** " + actor.name + " un-namebanned " + name); - this.sendModMessage(actor.name + " unbanned " + name, this.permissions.ban); - if (!this.registered) { return; } + this.logger.log("*** " + actor.name + " un-namebanned " + name); + this.sendModMessage(actor.name + " unbanned " + name, this.permissions.ban); + db.channels.unbanName(this.name, name); // TODO send banlist? }; +/** + * Removes a ban by ID + */ +Channel.prototype.handleUnban = function (actor, data) { + var self = this; + if (!this.hasPermission(actor, "ban")) { + return; + } + + if (typeof data.id !== "number") { + data.id = parseInt(data.id); + if (isNaN(data.id)) { + return; + } + } + + data.actor = actor.name; + + if (!self.registered) { + return; + } + + db.channels.unbanId(self.name, data.id, function (err, res) { + if (err) { + actor.socket.emit("errorMsg", { + msg: err + }); + return; + } + + self.sendUnban(self.users, data); + }); +}; + +/** + * Sends an unban packet + */ +Channel.prototype.sendUnban = function (users, data) { + var self = this; + users.forEach(function (u) { + if (self.hasPermission(u, "ban")) { + u.socket.emit("banlistRemove", data); + } + }); + self.logger.log("*** " + data.actor + " unbanned " + data.name); + self.sendModMessage(data.actor + " unbanned " + data.name, self.permissions.ban); +}; + /** * Bans all IP addresses associated with a username */ @@ -715,6 +789,13 @@ Channel.prototype.handleBanAllIP = function (actor, name, reason, range) { return; } + if (!self.registered) { + actor.socket.emit("errorMsg", { + msg: "Banning is not supported for unregistered rooms" + }); + return; + } + name = name.toLowerCase(); if (name === actor.name.toLowerCase()) { actor.socket.emit("costanza", { @@ -774,13 +855,6 @@ Channel.prototype.banIP = function (actor, ip, name, reason, range) { return; } - self.ipbans[ip] = { - ip: ip, - name: name, - bannedby: actor.name, - reason: reason - }; - self.logger.log("*** " + actor.name + " banned " + ip + " (" + name + ")"); self.sendModMessage(actor.name + " banned " + ip + " (" + name + ")", self.permissions.ban); // If in the channel already, kick the banned user @@ -795,30 +869,25 @@ Channel.prototype.banIP = function (actor, ip, name, reason, range) { return; } - // channel, ip, name, reason, ban actor - db.channels.ban(self.name, ip, name, reason, actor.name); + db.channels.isIPBanned(self.name, ip, function (err, banned) { + if (!err && banned) { + var disp = actor.global_rank >= 255 ? ip : util.maskIP(ip); + actor.socket.emit("errorMsg", { + msg: disp + " is alraedy banned" + }); + return; + } + + if (self.dead) { + return; + } + + // channel, ip, name, reason, ban actor + db.channels.ban(self.name, ip, name, reason, actor.name); + }); }); }; -/** - * Removes an IP ban - */ -Channel.prototype.handleUnbanIP = function (actor, ip) { - if (!this.hasPermission(actor, "ban")) { - return; - } - - var record = this.ipbans[ip]; - delete this.ipbans[ip]; - this.logger.log("*** " + actor.name + " unbanned " + ip + " (" + record.name + ")"); - this.sendModMessage(actor.name + " unbanned " + util.maskIP(ip) + " (" + record.name + ")", this.permissions.ban); - - if (!this.registered) { - return; - } - - db.channels.unbanIP(this.name, ip); -}; /** * Sends the banlist @@ -828,46 +897,41 @@ Channel.prototype.sendBanlist = function (users) { var bans = []; var unmaskedbans = []; - for (var ip in self.ipbans) { - bans.push({ - ip: util.maskIP(ip), - name: self.ipbans[ip].name, - reason: self.ipbans[ip].reason, - bannedby: self.ipbans[ip].bannedby - }); - unmaskedbans.push({ - ip: ip, - name: self.ipbans[ip].name, - reason: self.ipbans[ip].reason, - bannedby: self.ipbans[ip].bannedby - }); - } - - for (var name in self.namebans) { - bans.push({ - ip: "*", - name: name, - reason: self.namebans[name].reason, - bannedby: self.namebans[name].bannedby - }); - unmaskedbans.push({ - ip: "*", - name: name, - reason: self.namebans[name].reason, - bannedby: self.namebans[name].bannedby - }); - } - - users.forEach(function (u) { - if (!self.hasPermission(u, "ban")) { + db.channels.listBans(self.name, function (err, banlist) { + if (err) { return; } - if (u.rank >= 255) { - u.socket.emit("banlist", unmaskedbans); - } else { - u.socket.emit("banlist", bans); + console.log(banlist); + + for (var i = 0; i < banlist.length; i++) { + bans.push({ + id: banlist[i].id, + ip: banlist[i].ip === "*" ? "*" : util.maskIP(banlist[i].ip), + name: banlist[i].name, + reason: banlist[i].reason, + bannedby: banlist[i].bannedby + }); + unmaskedbans.push({ + id: banlist[i].id, + ip: banlist[i].ip, + name: banlist[i].name, + reason: banlist[i].reason, + bannedby: banlist[i].bannedby + }); } + + users.forEach(function (u) { + if (!self.hasPermission(u, "ban")) { + return; + } + + if (u.rank >= 255) { + u.socket.emit("banlist", unmaskedbans); + } else { + u.socket.emit("banlist", bans); + } + }); }); }; @@ -1122,11 +1186,6 @@ Channel.prototype.sendUserJoin = function (users, user) { user.meta.aliases = aliases; - if (user.name.toLowerCase() in self.namebans) { - user.kick("You're banned!"); - return; - } - if (self.isShadowMuted(user.name)) { user.meta.muted = true; user.meta.shadowmuted = true; diff --git a/lib/database/channels.js b/lib/database/channels.js index 5f72da51..1b602944 100644 --- a/lib/database/channels.js +++ b/lib/database/channels.js @@ -27,11 +27,12 @@ function createLibraryTable(name, callback) { function createBansTable(name, callback) { db.query("CREATE TABLE `chan_" + name + "_bans` (" + + "`id` INT NOT NULL AUTO_INCREMENT," + "`ip` VARCHAR(39) NOT NULL," + "`name` VARCHAR(20) NOT NULL," + "`bannedby` VARCHAR(20) NOT NULL," + "`reason` VARCHAR(255) NOT NULL," + - "PRIMARY KEY (`ip`, `name`))" + + "PRIMARY KEY (`id`), UNIQUE (`name`, `ip`))" + "CHARACTER SET utf8", callback); } @@ -311,31 +312,8 @@ module.exports = { chan.name = res[0].name; chan.canonical_name = chan.name.toLowerCase(); chan.registered = true; - - // Load bans - db.query("SELECT * FROM `chan_" + chan.name + "_bans`", function (err, rows) { - if (chan.dead) { - callback("Channel is dead", null); - return; - } - - if (err) { - callback(err, null); - return; - } - - for (var i = 0; i < rows.length; i++) { - var r = rows[i]; - if (r.ip === "*") { - chan.namebans[r.name] = r; - } else { - chan.ipbans[r.ip] = r; - } - } - - chan.logger.log("*** Loaded channel from database"); - callback(null, true); - }); + chan.logger.log("*** Loaded channel from database"); + callback(null, true); }); }, @@ -557,6 +535,60 @@ module.exports = { "VALUES (?, ?, ?, ?)", [ip, name, note, bannedby], callback); }, + /** + * Check if an IP address or range is banned + */ + isIPBanned: function (chan, ip, callback) { + if (typeof callback !== "function") { + return; + } + + if (!valid(chan)) { + callback("Invalid channel name", null); + return; + } + + db.query("SELECT * FROM `chan_" + chan + "_bans` WHERE ip LIKE ?", [ip+"%"], + function (err, rows) { + callback(err, err ? false : rows.length > 0); + }); + }, + + /** + * Check if a username is banned + */ + isNameBanned: function (chan, name, callback) { + if (typeof callback !== "function") { + return; + } + + if (!valid(chan)) { + callback("Invalid channel name", null); + return; + } + + db.query("SELECT * FROM `chan_" + chan + "_bans` WHERE name=?", [name], + function (err, rows) { + callback(err, err ? false : rows.length > 0); + }); + }, + + /** + * Lists all bans + */ + listBans: function (chan, callback) { + if (typeof callback !== "function") { + return; + } + + if (!valid(chan)) { + callback("Invalid channel name", null); + return; + } + + db.query("SELECT * FROM `chan_" + chan + "_bans` WHERE 1", callback); + }, + /** * Removes a ban from the banlist */ @@ -589,5 +621,22 @@ module.exports = { db.query("DELETE FROM `chan_" + chan + "_bans` WHERE ip=?", [ip], callback); + }, + + /** + * Removes a ban from the banlist + */ + unbanId: function (chan, id, callback) { + if (typeof callback !== "function") { + callback = blackHole; + } + + if (!valid(chan)) { + callback("Invalid channel name", null); + return; + } + + db.query("DELETE FROM `chan_" + chan + "_bans` WHERE id=?", + [id], callback); } }; diff --git a/lib/user.js b/lib/user.js index c85a4462..7d2d4cda 100644 --- a/lib/user.js +++ b/lib/user.js @@ -227,6 +227,10 @@ User.prototype.initChannelCallbacks = function () { self.channel.handleSetRank(self, data); }); + wrapTypecheck("unban", function (data) { + self.channel.handleUnban(self, data); + }); + wrapTypecheck("chatMsg", function (data) { if (typeof data.msg !== "string") { return; diff --git a/www/assets/js/util.js b/www/assets/js/util.js index 09234501..3c088522 100644 --- a/www/assets/js/util.js +++ b/www/assets/js/util.js @@ -169,40 +169,11 @@ function addUserDropdown(entry) { $("").text(name).appendTo(menu); $("
").appendTo(menu); - /* rank selector (admin+ only) - to prevent odd behaviour, this selector is only visible - when the selected user has a normal rank (e.g. not a guest - or a non-moderator leader - */ - if(CLIENT.rank >= 3 && CLIENT.rank > rank && rank > 0 && rank != 1.5) { - var sel = $("