Work on banlist

This commit is contained in:
calzoneman 2014-01-11 23:55:52 -06:00
parent 501a22556a
commit 637ece4044
4 changed files with 273 additions and 184 deletions

View File

@ -97,8 +97,6 @@ function Channel(name) {
html: "" // Filtered MOTD text (XSS removed; \n replaced by <br>) html: "" // Filtered MOTD text (XSS removed; \n replaced by <br>)
}; };
self.filters = DEFAULT_FILTERS; self.filters = DEFAULT_FILTERS;
self.ipbans = {};
self.namebans = {};
self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs", self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs",
self.uniqueName + ".log")); self.uniqueName + ".log"));
self.css = ""; // Up to 20KB of inline CSS self.css = ""; // Up to 20KB of inline CSS
@ -442,19 +440,9 @@ Channel.prototype.getIPRank = function (ip, callback) {
*/ */
Channel.prototype.join = function (user, password) { Channel.prototype.join = function (user, password) {
var self = this; 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 afterIPBanCheck = function () {
var range = user.ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); if (self.dead) {
if (user.ip in self.ipbans || range in self.ipbans ||
user.name.toLowerCase() in self.namebans) {
user.kick("You're banned!");
return; return;
} }
@ -467,27 +455,12 @@ Channel.prototype.join = function (user, password) {
self.sendUsercount(self.users); self.sendUsercount(self.users);
user.whenLoggedIn(function () { user.whenLoggedIn(function () {
var lname = user.name.toLowerCase(); db.channels.isNameBanned(self.name, user.name, function (err, banned) {
for (var i = 0; i < self.users.length; i++) { if (!err && banned) {
if (self.users[i].name.toLowerCase() === lname && self.users[i] !== user) { user.kick("You're banned!");
self.users[i].kick("Duplicate login");
}
}
self.getRank(user.name, function (err, rank) {
if (self.dead) {
return;
}
if (err) {
user.rank = user.global_rank;
} else { } 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"); self.logger.log("+++ " + user.ip + " joined");
Logger.syslog.log(user.ip + " joined channel " + self.name); 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; return false;
} }
if (!self.registered) {
actor.socket.emit("errorMsg", {
msg: "Banning is only supported in registered channels"
});
return;
}
name = name.toLowerCase(); name = name.toLowerCase();
if (name == actor.name.toLowerCase()) { if (name == actor.name.toLowerCase()) {
actor.socket.emit("costanza", { actor.socket.emit("costanza", {
@ -655,12 +679,6 @@ Channel.prototype.handleNameBan = function (actor, name, reason) {
} }
reason = reason.substring(0, 255); 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 // If in the channel already, kick the banned user
for (var i = 0; i < self.users.length; i++) { for (var i = 0; i < self.users.length; i++) {
@ -672,7 +690,15 @@ Channel.prototype.handleNameBan = function (actor, name, reason) {
self.logger.log("*** " + actor.name + " namebanned " + name); self.logger.log("*** " + actor.name + " namebanned " + name);
self.sendModMessage(actor.name + " banned " + name, self.permissions.ban); self.sendModMessage(actor.name + " banned " + name, self.permissions.ban);
if (!self.registered) { db.channels.isNameBanned(self.name, name, function (err, banned) {
if (!err && banned) {
actor.socket.emit("errorMsg", {
msg: name + " is already banned"
});
return;
}
if (self.dead) {
return; return;
} }
@ -680,6 +706,7 @@ Channel.prototype.handleNameBan = function (actor, name, reason) {
db.channels.ban(self.name, "*", name, reason, actor.name); db.channels.ban(self.name, "*", name, reason, actor.name);
// TODO send banlist? // TODO send banlist?
}); });
});
}; };
/** /**
@ -690,18 +717,65 @@ Channel.prototype.handleUnbanName = function (actor, name) {
return; 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) { if (!this.registered) {
return; return;
} }
this.logger.log("*** " + actor.name + " un-namebanned " + name);
this.sendModMessage(actor.name + " unbanned " + name, this.permissions.ban);
db.channels.unbanName(this.name, name); db.channels.unbanName(this.name, name);
// TODO send banlist? // 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 * Bans all IP addresses associated with a username
*/ */
@ -715,6 +789,13 @@ Channel.prototype.handleBanAllIP = function (actor, name, reason, range) {
return; return;
} }
if (!self.registered) {
actor.socket.emit("errorMsg", {
msg: "Banning is not supported for unregistered rooms"
});
return;
}
name = name.toLowerCase(); name = name.toLowerCase();
if (name === actor.name.toLowerCase()) { if (name === actor.name.toLowerCase()) {
actor.socket.emit("costanza", { actor.socket.emit("costanza", {
@ -774,13 +855,6 @@ Channel.prototype.banIP = function (actor, ip, name, reason, range) {
return; return;
} }
self.ipbans[ip] = {
ip: ip,
name: name,
bannedby: actor.name,
reason: reason
};
self.logger.log("*** " + actor.name + " banned " + ip + " (" + name + ")"); self.logger.log("*** " + actor.name + " banned " + ip + " (" + name + ")");
self.sendModMessage(actor.name + " banned " + ip + " (" + name + ")", self.permissions.ban); self.sendModMessage(actor.name + " banned " + ip + " (" + name + ")", self.permissions.ban);
// If in the channel already, kick the banned user // If in the channel already, kick the banned user
@ -795,30 +869,25 @@ Channel.prototype.banIP = function (actor, ip, name, reason, range) {
return; return;
} }
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 // channel, ip, name, reason, ban actor
db.channels.ban(self.name, ip, name, reason, actor.name); 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 * Sends the banlist
@ -828,33 +897,27 @@ Channel.prototype.sendBanlist = function (users) {
var bans = []; var bans = [];
var unmaskedbans = []; var unmaskedbans = [];
for (var ip in self.ipbans) { db.channels.listBans(self.name, function (err, banlist) {
bans.push({ if (err) {
ip: util.maskIP(ip), return;
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) { console.log(banlist);
for (var i = 0; i < banlist.length; i++) {
bans.push({ bans.push({
ip: "*", id: banlist[i].id,
name: name, ip: banlist[i].ip === "*" ? "*" : util.maskIP(banlist[i].ip),
reason: self.namebans[name].reason, name: banlist[i].name,
bannedby: self.namebans[name].bannedby reason: banlist[i].reason,
bannedby: banlist[i].bannedby
}); });
unmaskedbans.push({ unmaskedbans.push({
ip: "*", id: banlist[i].id,
name: name, ip: banlist[i].ip,
reason: self.namebans[name].reason, name: banlist[i].name,
bannedby: self.namebans[name].bannedby reason: banlist[i].reason,
bannedby: banlist[i].bannedby
}); });
} }
@ -869,6 +932,7 @@ Channel.prototype.sendBanlist = function (users) {
u.socket.emit("banlist", bans); u.socket.emit("banlist", bans);
} }
}); });
});
}; };
/** /**
@ -1122,11 +1186,6 @@ Channel.prototype.sendUserJoin = function (users, user) {
user.meta.aliases = aliases; user.meta.aliases = aliases;
if (user.name.toLowerCase() in self.namebans) {
user.kick("You're banned!");
return;
}
if (self.isShadowMuted(user.name)) { if (self.isShadowMuted(user.name)) {
user.meta.muted = true; user.meta.muted = true;
user.meta.shadowmuted = true; user.meta.shadowmuted = true;

View File

@ -27,11 +27,12 @@ function createLibraryTable(name, callback) {
function createBansTable(name, callback) { function createBansTable(name, callback) {
db.query("CREATE TABLE `chan_" + name + "_bans` (" + db.query("CREATE TABLE `chan_" + name + "_bans` (" +
"`id` INT NOT NULL AUTO_INCREMENT," +
"`ip` VARCHAR(39) NOT NULL," + "`ip` VARCHAR(39) NOT NULL," +
"`name` VARCHAR(20) NOT NULL," + "`name` VARCHAR(20) NOT NULL," +
"`bannedby` VARCHAR(20) NOT NULL," + "`bannedby` VARCHAR(20) NOT NULL," +
"`reason` VARCHAR(255) NOT NULL," + "`reason` VARCHAR(255) NOT NULL," +
"PRIMARY KEY (`ip`, `name`))" + "PRIMARY KEY (`id`), UNIQUE (`name`, `ip`))" +
"CHARACTER SET utf8", callback); "CHARACTER SET utf8", callback);
} }
@ -311,32 +312,9 @@ module.exports = {
chan.name = res[0].name; chan.name = res[0].name;
chan.canonical_name = chan.name.toLowerCase(); chan.canonical_name = chan.name.toLowerCase();
chan.registered = true; 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"); chan.logger.log("*** Loaded channel from database");
callback(null, true); callback(null, true);
}); });
});
}, },
/** /**
@ -557,6 +535,60 @@ module.exports = {
"VALUES (?, ?, ?, ?)", [ip, name, note, bannedby], callback); "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 * Removes a ban from the banlist
*/ */
@ -589,5 +621,22 @@ module.exports = {
db.query("DELETE FROM `chan_" + chan + "_bans` WHERE ip=?", db.query("DELETE FROM `chan_" + chan + "_bans` WHERE ip=?",
[ip], callback); [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);
} }
}; };

View File

@ -227,6 +227,10 @@ User.prototype.initChannelCallbacks = function () {
self.channel.handleSetRank(self, data); self.channel.handleSetRank(self, data);
}); });
wrapTypecheck("unban", function (data) {
self.channel.handleUnban(self, data);
});
wrapTypecheck("chatMsg", function (data) { wrapTypecheck("chatMsg", function (data) {
if (typeof data.msg !== "string") { if (typeof data.msg !== "string") {
return; return;

View File

@ -169,40 +169,11 @@ function addUserDropdown(entry) {
$("<strong/>").text(name).appendTo(menu); $("<strong/>").text(name).appendTo(menu);
$("<br/>").appendTo(menu); $("<br/>").appendTo(menu);
/* rank selector (admin+ only) var btngroup = $("<div/>").addClass("btn-group-vertical").appendTo(menu);
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 = $("<select/>")
.addClass("form-control")
.appendTo(menu);
$("<option/>").attr("value", "1").text("Regular User")
.appendTo(sel);
$("<option/>").attr("value", "2").text("Moderator")
.appendTo(sel);
if(CLIENT.rank > 3) {
$("<option/>").attr("value", "3").text("Channel Admin")
.appendTo(sel);
if(rank > 3) {
$("<option/>").attr("value", ""+rank)
.text("Current Rank (" + rank + ")")
.appendTo(sel);
}
}
sel.change(function () {
socket.emit("setChannelRank", {
user: name,
rank: parseInt(sel.val())
});
});
sel.val(""+rank);
}
/* ignore button */ /* ignore button */
var ignore = $("<button/>").addClass("btn btn-xs btn-default btn-block") var ignore = $("<button/>").addClass("btn btn-xs btn-default")
.appendTo(menu) .appendTo(btngroup)
.click(function () { .click(function () {
if(IGNORED.indexOf(name) == -1) { if(IGNORED.indexOf(name) == -1) {
ignore.text("Unignore User"); ignore.text("Unignore User");
@ -220,8 +191,8 @@ function addUserDropdown(entry) {
/* gib/remove leader (moderator+ only) */ /* gib/remove leader (moderator+ only) */
if(CLIENT.rank >= 2) { if(CLIENT.rank >= 2) {
var ldr = $("<button/>").addClass("btn btn-xs btn-default btn-block") var ldr = $("<button/>").addClass("btn btn-xs btn-default")
.appendTo(menu); .appendTo(btngroup);
if(leader) { if(leader) {
ldr.text("Remove Leader"); ldr.text("Remove Leader");
ldr.click(function () { ldr.click(function () {
@ -241,34 +212,34 @@ function addUserDropdown(entry) {
/* kick button */ /* kick button */
if(hasPermission("kick")) { if(hasPermission("kick")) {
$("<button/>").addClass("btn btn-xs btn-default btn-block") $("<button/>").addClass("btn btn-xs btn-default")
.text("Kick") .text("Kick")
.click(function () { .click(function () {
socket.emit("chatMsg", { socket.emit("chatMsg", {
msg: "/kick " + name msg: "/kick " + name
}); });
}) })
.appendTo(menu); .appendTo(btngroup);
} }
/* ban buttons */ /* ban buttons */
if(hasPermission("ban")) { if(hasPermission("ban")) {
$("<button/>").addClass("btn btn-xs btn-default btn-block") $("<button/>").addClass("btn btn-xs btn-default")
.text("Name Ban") .text("Name Ban")
.click(function () { .click(function () {
socket.emit("chatMsg", { socket.emit("chatMsg", {
msg: "/ban " + name msg: "/ban " + name
}); });
}) })
.appendTo(menu); .appendTo(btngroup);
$("<button/>").addClass("btn btn-xs btn-default btn-block") $("<button/>").addClass("btn btn-xs btn-default")
.text("IP Ban") .text("IP Ban")
.click(function () { .click(function () {
socket.emit("chatMsg", { socket.emit("chatMsg", {
msg: "/ipban " + name msg: "/ipban " + name
}); });
}) })
.appendTo(menu); .appendTo(btngroup);
} }
entry.contextmenu(function(ev) { entry.contextmenu(function(ev) {
@ -1798,6 +1769,12 @@ function formatCSBanlist() {
} }
var unban = $("<button/>").addClass("btn btn-xs btn-danger") var unban = $("<button/>").addClass("btn btn-xs btn-danger")
.appendTo($("<td/>").appendTo(tr)); .appendTo($("<td/>").appendTo(tr));
unban.click(function () {
socket.emit("unban", {
id: entry.id,
name: entry.name
});
});
$("<span/>").addClass("glyphicon glyphicon-remove-circle").appendTo(unban); $("<span/>").addClass("glyphicon glyphicon-remove-circle").appendTo(unban);
$("<td/>").text(entry.ip).appendTo(tr); $("<td/>").text(entry.ip).appendTo(tr);
$("<td/>").text(entry.name).appendTo(tr); $("<td/>").text(entry.name).appendTo(tr);