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 = $("")
- .addClass("form-control")
- .appendTo(menu);
- $("").attr("value", "1").text("Regular User")
- .appendTo(sel);
- $("").attr("value", "2").text("Moderator")
- .appendTo(sel);
- if(CLIENT.rank > 3) {
- $("").attr("value", "3").text("Channel Admin")
- .appendTo(sel);
- if(rank > 3) {
- $("").attr("value", ""+rank)
- .text("Current Rank (" + rank + ")")
- .appendTo(sel);
- }
- }
- sel.change(function () {
- socket.emit("setChannelRank", {
- user: name,
- rank: parseInt(sel.val())
- });
- });
- sel.val(""+rank);
- }
+ var btngroup = $("