mirror of https://github.com/calzoneman/sync.git
Add IP cloaking; make tor bans channel specific
This commit is contained in:
parent
ecca806a58
commit
8fddbc3e6e
|
@ -115,8 +115,6 @@ max-channels-per-user: 5
|
|||
max-accounts-per-ip: 5
|
||||
# Minimum number of seconds between guest logins from the same IP
|
||||
guest-login-delay: 60
|
||||
# Block known Tor IP addresses
|
||||
enable-tor-blocker: true
|
||||
|
||||
# Configure statistics tracking
|
||||
stats:
|
||||
|
|
|
@ -17,7 +17,7 @@ var Config = require("./config");
|
|||
var Server = require("./server");
|
||||
|
||||
function eventUsername(user) {
|
||||
return user.getName() + "@" + user.ip;
|
||||
return user.getName() + "@" + user.realip;
|
||||
}
|
||||
|
||||
function handleAnnounce(user, data) {
|
||||
|
|
|
@ -330,10 +330,24 @@ Channel.prototype.acceptUser = function (user) {
|
|||
user.autoAFK();
|
||||
user.socket.on("readChanLog", this.handleReadLog.bind(this, user));
|
||||
|
||||
Logger.syslog.log(user.ip + " joined " + this.name);
|
||||
this.logger.log("[login] Accepted connection from " + user.longip);
|
||||
Logger.syslog.log(user.realip + " joined " + this.name);
|
||||
if (user.socket._isUsingTor) {
|
||||
if (this.modules.options && this.modules.options.get("torbanned")) {
|
||||
user.kick("This channel has banned connections from Tor.");
|
||||
user.socket.disconnect(true);
|
||||
this.logger.log("[login] Blocked connection from Tor exit at " +
|
||||
user.displayip);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log("[login] Accepted connection from Tor exit at " +
|
||||
user.displayip);
|
||||
} else {
|
||||
this.logger.log("[login] Accepted connection from " + user.displayip);
|
||||
}
|
||||
|
||||
if (user.is(Flags.U_LOGGED_IN)) {
|
||||
this.logger.log("[login] " + user.longip + " authenticated as " + user.getName());
|
||||
this.logger.log("[login] " + user.displayip + " authenticated as " + user.getName());
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
@ -367,7 +381,7 @@ Channel.prototype.partUser = function (user) {
|
|||
return;
|
||||
}
|
||||
|
||||
this.logger.log("[login] " + user.longip + " (" + user.getName() + ") " +
|
||||
this.logger.log("[login] " + user.displayip + " (" + user.getName() + ") " +
|
||||
"disconnected.");
|
||||
user.channel = null;
|
||||
/* Should be unnecessary because partUser only occurs if the socket dies */
|
||||
|
@ -412,7 +426,7 @@ Channel.prototype.packUserData = function (user) {
|
|||
muted: user.is(Flags.U_MUTED),
|
||||
smuted: user.is(Flags.U_SMUTED),
|
||||
aliases: user.account.aliases,
|
||||
ip: util.maskIP(user.longip)
|
||||
ip: user.displayip
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -425,7 +439,7 @@ Channel.prototype.packUserData = function (user) {
|
|||
muted: user.is(Flags.U_MUTED),
|
||||
smuted: user.is(Flags.U_SMUTED),
|
||||
aliases: user.account.aliases,
|
||||
ip: user.ip
|
||||
ip: user.realip
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -534,7 +548,7 @@ Channel.prototype.sendUserJoin = function (users, user) {
|
|||
user.account.aliases.join(",") + ")", 2);
|
||||
};
|
||||
|
||||
Channel.prototype.readLog = function (shouldMaskIP, cb) {
|
||||
Channel.prototype.readLog = function (cb) {
|
||||
var maxLen = 102400;
|
||||
var file = this.logger.filename;
|
||||
this.activeLock.lock();
|
||||
|
@ -558,16 +572,6 @@ Channel.prototype.readLog = function (shouldMaskIP, cb) {
|
|||
buffer += data;
|
||||
});
|
||||
read.on("end", function () {
|
||||
if (shouldMaskIP) {
|
||||
buffer = buffer.replace(
|
||||
/(?:^|\s)(\d+\.\d+\.\d+)\.\d+/g,
|
||||
"$1.x"
|
||||
).replace(
|
||||
/(?:^|\s)((?:[0-9a-f]+:){3}[0-9a-f]+):(?:[0-9a-f]+:){3}[0-9a-f]+/g,
|
||||
"$1:x:x:x:x"
|
||||
);
|
||||
}
|
||||
|
||||
cb(null, buffer);
|
||||
self.activeLock.release();
|
||||
});
|
||||
|
@ -589,7 +593,7 @@ Channel.prototype.handleReadLog = function (user) {
|
|||
}
|
||||
|
||||
var shouldMaskIP = user.account.globalRank < 255;
|
||||
this.readLog(shouldMaskIP, function (err, data) {
|
||||
this.readLog(function (err, data) {
|
||||
if (err) {
|
||||
user.socket.emit("readChanLog", {
|
||||
success: false,
|
||||
|
|
|
@ -24,36 +24,48 @@ function KickBanModule(channel) {
|
|||
|
||||
KickBanModule.prototype = Object.create(ChannelModule.prototype);
|
||||
|
||||
function checkIPBan(cname, ip, cb) {
|
||||
db.channels.isIPBanned(cname, ip, function (err, banned) {
|
||||
if (err) {
|
||||
cb(false);
|
||||
} else {
|
||||
cb(banned);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkNameBan(cname, name, cb) {
|
||||
db.channels.isNameBanned(cname, name, function (err, banned) {
|
||||
if (err) {
|
||||
cb(false);
|
||||
} else {
|
||||
cb(banned);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
KickBanModule.prototype.onUserPreJoin = function (user, data, cb) {
|
||||
if (!this.channel.is(Flags.C_REGISTERED)) {
|
||||
return cb(null, ChannelModule.PASSTHROUGH);
|
||||
}
|
||||
|
||||
var cname = this.channel.name;
|
||||
db.channels.isIPBanned(cname, user.longip, function (err, banned) {
|
||||
if (err) {
|
||||
cb(null, ChannelModule.PASSTHROUGH);
|
||||
} else if (!banned) {
|
||||
if (user.is(Flags.U_LOGGED_IN)) {
|
||||
checkNameBan();
|
||||
} else {
|
||||
cb(null, ChannelModule.PASSTHROUGH);
|
||||
}
|
||||
} else {
|
||||
checkIPBan(cname, user.realip, function (banned) {
|
||||
if (banned) {
|
||||
cb(null, ChannelModule.DENY);
|
||||
user.kick("Your IP address is banned from this channel.");
|
||||
} else {
|
||||
checkNameBan(cname, user.getName(), function (banned) {
|
||||
if (banned) {
|
||||
cb(null, ChannelModule.DENY);
|
||||
user.kick("Your username is banned from this channel.");
|
||||
} else {
|
||||
cb(null, ChannelModule.PASSTHROUGH);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function checkNameBan() {
|
||||
db.channels.isNameBanned(cname, user.getName(), function (err, banned) {
|
||||
if (err) {
|
||||
cb(null, ChannelModule.PASSTHROUGH);
|
||||
} else {
|
||||
cb(null, banned ? ChannelModule.DENY : ChannelModule.PASSTHROUGH);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
KickBanModule.prototype.onUserPostJoin = function (user) {
|
||||
|
@ -98,7 +110,7 @@ KickBanModule.prototype.sendBanlist = function (users) {
|
|||
for (var i = 0; i < banlist.length; i++) {
|
||||
bans.push({
|
||||
id: banlist[i].id,
|
||||
ip: banlist[i].ip === "*" ? "*" : util.maskIP(banlist[i].ip),
|
||||
ip: banlist[i].ip === "*" ? "*" : util.cloakIP(banlist[i].ip),
|
||||
name: banlist[i].name,
|
||||
reason: banlist[i].reason,
|
||||
bannedby: banlist[i].bannedby
|
||||
|
@ -381,7 +393,7 @@ KickBanModule.prototype.kickBanTarget = function (name, ip) {
|
|||
name = name.toLowerCase();
|
||||
for (var i = 0; i < this.channel.users.length; i++) {
|
||||
if (this.channel.users[i].getLowerName() === name ||
|
||||
this.channel.users[i].longip === ip) {
|
||||
this.channel.users[i].realip === ip) {
|
||||
this.channel.users[i].kick("You're banned!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ function OptionsModule(channel) {
|
|||
show_public: false, // List the channel on the index page
|
||||
enable_link_regex: true, // Use the built-in link filter
|
||||
password: false, // Channel password (false -> no password required for entry)
|
||||
allow_dupes: false // Allow duplicate videos on the playlist
|
||||
allow_dupes: false, // Allow duplicate videos on the playlist
|
||||
torbanned: false // Block connections from Tor exit nodes
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -245,6 +246,10 @@ OptionsModule.prototype.handleSetOptions = function (user, data) {
|
|||
this.opts.allow_dupes = Boolean(data.allow_dupes);
|
||||
}
|
||||
|
||||
if ("torbanned" in data && user.account.effectiveRank >= 3) {
|
||||
this.opts.torbanned = Boolean(data.torbanned);
|
||||
}
|
||||
|
||||
this.channel.logger.log("[mod] " + user.getName() + " updated channel options");
|
||||
this.sendOpts(this.channel.users);
|
||||
};
|
||||
|
|
|
@ -67,7 +67,7 @@ PollModule.prototype.onUserPostJoin = function (user) {
|
|||
|
||||
PollModule.prototype.onUserPart = function(user) {
|
||||
if (this.poll) {
|
||||
this.poll.unvote(user.ip);
|
||||
this.poll.unvote(user.realip);
|
||||
this.sendPollUpdate(this.channel.users);
|
||||
}
|
||||
};
|
||||
|
@ -142,7 +142,7 @@ PollModule.prototype.handleVote = function (user, data) {
|
|||
}
|
||||
|
||||
if (this.poll) {
|
||||
this.poll.vote(user.ip, data.option);
|
||||
this.poll.vote(user.realip, data.option);
|
||||
this.sendPollUpdate(this.channel.users);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ VoteskipModule.prototype.onUserPart = function(user) {
|
|||
return;
|
||||
}
|
||||
|
||||
this.unvote(user.ip);
|
||||
this.unvote(user.realip);
|
||||
this.update();
|
||||
};
|
||||
|
||||
|
@ -40,7 +40,7 @@ VoteskipModule.prototype.handleVoteskip = function (user) {
|
|||
this.poll = new Poll("[server]", "voteskip", ["skip"], false);
|
||||
}
|
||||
|
||||
this.poll.vote(user.ip, 0);
|
||||
this.poll.vote(user.realip, 0);
|
||||
|
||||
var title = "";
|
||||
if (this.channel.modules.playlist.current) {
|
||||
|
|
|
@ -69,7 +69,6 @@ var defaults = {
|
|||
"max-channels-per-user": 5,
|
||||
"max-accounts-per-ip": 5,
|
||||
"guest-login-delay": 60,
|
||||
"enable-tor-blocker": true,
|
||||
stats: {
|
||||
interval: 3600000,
|
||||
"max-age": 86400000
|
||||
|
|
|
@ -11,6 +11,8 @@ var Account = require("../account");
|
|||
var typecheck = require("json-typecheck");
|
||||
var net = require("net");
|
||||
var util = require("../utilities");
|
||||
var crypto = require("crypto");
|
||||
var isTorExit = require("../tor").isTorExit;
|
||||
|
||||
var CONNECT_RATE = {
|
||||
burst: 5,
|
||||
|
@ -43,27 +45,8 @@ function handleAuth(data, accept) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a connection is accepted
|
||||
*/
|
||||
function handleConnection(sock) {
|
||||
var ip = sock.handshake.address.address;
|
||||
var longip = ip;
|
||||
sock._ip = ip;
|
||||
if (net.isIPv6(ip)) {
|
||||
longip = util.expandIPv6(ip);
|
||||
}
|
||||
sock._longip = longip;
|
||||
var srv = Server.getServer();
|
||||
if (srv.torblocker && srv.torblocker.shouldBlockIP(ip)) {
|
||||
sock.emit("kick", {
|
||||
reason: "This server does not allow connections from Tor. "+
|
||||
"Please log in with your regular internet connection."
|
||||
});
|
||||
Logger.syslog.log("Blocked Tor IP: " + ip);
|
||||
sock.disconnect(true);
|
||||
return;
|
||||
}
|
||||
function throttleIP(sock) {
|
||||
var ip = sock._realip;
|
||||
|
||||
if (!(ip in ipThrottle)) {
|
||||
ipThrottle[ip] = $util.newRateLimiter();
|
||||
|
@ -75,16 +58,14 @@ function handleConnection(sock) {
|
|||
reason: "Your IP address is connecting too quickly. Please "+
|
||||
"wait 10 seconds before joining again."
|
||||
});
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for global ban on the IP
|
||||
if (db.isGlobalIPBanned(ip)) {
|
||||
Logger.syslog.log("Rejecting " + ip + " - global banned");
|
||||
sock.emit("kick", { reason: "Your IP is globally banned." });
|
||||
sock.disconnect(true);
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function ipLimitReached(sock) {
|
||||
var ip = sock._realip;
|
||||
|
||||
sock.on("disconnect", function () {
|
||||
ipCount[ip]--;
|
||||
|
@ -106,9 +87,9 @@ function handleConnection(sock) {
|
|||
sock.disconnect(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.syslog.log("Accepted socket from " + ip);
|
||||
|
||||
function addTypecheckedFunctions(sock) {
|
||||
sock.typecheckedOn = function (msg, template, cb) {
|
||||
sock.on(msg, function (data) {
|
||||
typecheck(data, template, function (err, data) {
|
||||
|
@ -136,6 +117,44 @@ function handleConnection(sock) {
|
|||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a connection is accepted
|
||||
*/
|
||||
function handleConnection(sock) {
|
||||
var ip = sock.handshake.address.address;
|
||||
if (net.isIPv6(ip)) {
|
||||
ip = util.expandIPv6(ip);
|
||||
}
|
||||
sock._realip = ip;
|
||||
sock._displayip = $util.cloakIP(ip);
|
||||
|
||||
if (isTorExit(ip)) {
|
||||
sock._isUsingTor = true;
|
||||
}
|
||||
|
||||
var srv = Server.getServer();
|
||||
|
||||
if (throttleIP(ip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for global ban on the IP
|
||||
if (db.isGlobalIPBanned(ip)) {
|
||||
Logger.syslog.log("Rejecting " + ip + " - global banned");
|
||||
sock.emit("kick", { reason: "Your IP is globally banned." });
|
||||
sock.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ipLimitReached(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.syslog.log("Accepted socket from " + ip);
|
||||
|
||||
addTypecheckedFunctions(sock);
|
||||
|
||||
var user = new User(sock);
|
||||
if (sock.handshake.user) {
|
||||
|
@ -148,6 +167,7 @@ function handleConnection(sock) {
|
|||
user.setFlag(Flags.U_READY);
|
||||
return;
|
||||
}
|
||||
|
||||
user.socket.emit("login", {
|
||||
success: true,
|
||||
name: user.getName(),
|
||||
|
|
|
@ -24,8 +24,8 @@ function retrieveIPs(cb) {
|
|||
|
||||
var d = domain.create();
|
||||
d.on("error", function (err) {
|
||||
if (err.trace)
|
||||
Logger.errlog.log(err.trace());
|
||||
if (err.stack)
|
||||
Logger.errlog.log(err.stack);
|
||||
else
|
||||
Logger.errlog.log(err);
|
||||
});
|
||||
|
@ -63,26 +63,17 @@ function getTorIPs(cb) {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
var x = {
|
||||
ipList: [],
|
||||
shouldBlockIP: function (ip) {
|
||||
return this.ipList.indexOf(ip) >= 0;
|
||||
}
|
||||
};
|
||||
var _ipList = [];
|
||||
getTorIPs(function (err, ips) {
|
||||
if (err) {
|
||||
Logger.errlog.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var init = function () {
|
||||
getTorIPs(function (err, ips) {
|
||||
if (err) {
|
||||
Logger.errlog.log(err);
|
||||
return;
|
||||
}
|
||||
Logger.syslog.log("Loaded Tor IP list");
|
||||
_ipList = ips;
|
||||
});
|
||||
|
||||
Logger.syslog.log("Loaded Tor IP list");
|
||||
x.ipList = ips;
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
return x;
|
||||
exports.isTorExit = function (ip) {
|
||||
return this._ipList.indexOf(ip) >= 0;
|
||||
};
|
33
lib/user.js
33
lib/user.js
|
@ -14,9 +14,10 @@ function User(socket) {
|
|||
MakeEmitter(self);
|
||||
self.flags = 0;
|
||||
self.socket = socket;
|
||||
self.ip = socket._ip;
|
||||
self.longip = socket._longip;
|
||||
self.account = Account.default(self.longip);
|
||||
self.realip = socket._realip;
|
||||
self.displayip = socket._displayip;
|
||||
self.hostmask = socket._hostmask;
|
||||
self.account = Account.default(self.realip);
|
||||
self.channel = null;
|
||||
self.queueLimiter = util.newRateLimiter();
|
||||
self.chatLimiter = util.newRateLimiter();
|
||||
|
@ -65,7 +66,7 @@ function User(socket) {
|
|||
self.kick("Attempted initACP from non privileged user. This incident " +
|
||||
"will be reported.");
|
||||
Logger.eventlog.log("[acp] Attempted initACP from socket client " +
|
||||
self.getName() + "@" + self.ip);
|
||||
self.getName() + "@" + self.realip);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -176,7 +177,7 @@ User.prototype.setAFK = function (afk) {
|
|||
if (afk) {
|
||||
this.setFlag(Flags.U_AFK);
|
||||
if (this.channel.modules.voteskip) {
|
||||
this.channel.modules.voteskip.unvote(this.ip);
|
||||
this.channel.modules.voteskip.unvote(this.realip);
|
||||
}
|
||||
} else {
|
||||
this.clearFlag(Flags.U_AFK);
|
||||
|
@ -255,7 +256,7 @@ User.prototype.login = function (name, pw) {
|
|||
if (err) {
|
||||
if (err === "Invalid username/password combination") {
|
||||
Logger.eventlog.log("[loginfail] Login failed (bad password): " + name
|
||||
+ "@" + self.ip);
|
||||
+ "@" + self.realip);
|
||||
}
|
||||
|
||||
self.socket.emit("login", {
|
||||
|
@ -283,11 +284,11 @@ User.prototype.login = function (name, pw) {
|
|||
success: true,
|
||||
name: user.name
|
||||
});
|
||||
db.recordVisit(self.longip, self.getName());
|
||||
db.recordVisit(self.realip, self.getName());
|
||||
self.socket.emit("rank", self.account.effectiveRank);
|
||||
Logger.syslog.log(self.ip + " logged in as " + user.name);
|
||||
Logger.syslog.log(self.realip + " logged in as " + user.name);
|
||||
if (self.inChannel()) {
|
||||
self.channel.logger.log(self.longip + " logged in as " + user.name);
|
||||
self.channel.logger.log(self.displayip + " logged in as " + user.name);
|
||||
}
|
||||
self.setFlag(Flags.U_LOGGED_IN);
|
||||
self.clearFlag(Flags.U_LOGGING_IN);
|
||||
|
@ -300,8 +301,8 @@ var lastguestlogin = {};
|
|||
User.prototype.guestLogin = function (name) {
|
||||
var self = this;
|
||||
|
||||
if (self.ip in lastguestlogin) {
|
||||
var diff = (Date.now() - lastguestlogin[self.ip]) / 1000;
|
||||
if (self.realip in lastguestlogin) {
|
||||
var diff = (Date.now() - lastguestlogin[self.realip]) / 1000;
|
||||
if (diff < Config.get("guest-login-delay")) {
|
||||
self.socket.emit("login", {
|
||||
success: false,
|
||||
|
@ -355,7 +356,7 @@ User.prototype.guestLogin = function (name) {
|
|||
}
|
||||
|
||||
// Login succeeded
|
||||
lastguestlogin[self.ip] = Date.now();
|
||||
lastguestlogin[self.realip] = Date.now();
|
||||
|
||||
var opts = { name: name };
|
||||
if (self.inChannel()) {
|
||||
|
@ -373,11 +374,11 @@ User.prototype.guestLogin = function (name) {
|
|||
name: name,
|
||||
guest: true
|
||||
});
|
||||
db.recordVisit(self.longip, self.getName());
|
||||
db.recordVisit(self.realip, self.getName());
|
||||
self.socket.emit("rank", 0);
|
||||
Logger.syslog.log(self.ip + " signed in as " + name);
|
||||
Logger.syslog.log(self.realip + " signed in as " + name);
|
||||
if (self.inChannel()) {
|
||||
self.channel.logger.log(self.longip + " signed in as " + name);
|
||||
self.channel.logger.log(self.displayip + " signed in as " + name);
|
||||
}
|
||||
self.setFlag(Flags.U_LOGGED_IN);
|
||||
self.emit("login", self.account);
|
||||
|
@ -422,7 +423,7 @@ User.prototype.refreshAccount = function (opts, cb) {
|
|||
opts.registered = this.is(Flags.U_REGISTERED);
|
||||
var self = this;
|
||||
var old = this.account;
|
||||
Account.getAccount(name, this.longip, opts, function (err, account) {
|
||||
Account.getAccount(name, this.realip, opts, function (err, account) {
|
||||
if (!err) {
|
||||
/* Update account if anything changed in the meantime */
|
||||
for (var key in old) {
|
||||
|
|
|
@ -78,31 +78,6 @@
|
|||
return salt.join('');
|
||||
},
|
||||
|
||||
root.maskIP = function (ip) {
|
||||
if (net.isIPv4(ip)) {
|
||||
/* Full /32 IPv4 address */
|
||||
return ip.replace(/^(\d+\.\d+\.\d+)\.\d+/, "$1.x");
|
||||
} else if (net.isIPv4(ip + ".0")) {
|
||||
/* /24 IPv4 range */
|
||||
return ip + ".0/24";
|
||||
} else if (net.isIPv4(ip + ".0.0")) {
|
||||
/* /16 IPv4 widerange */
|
||||
return ip + ".0.0/16";
|
||||
} else if (net.isIPv6(ip)) {
|
||||
/* /128 IPv6 address */
|
||||
return ip.replace(/^((?:[0-9a-f]+:){3}[0-9a-f]+):(?:[0-9a-f]+:){3}[0-9a-f]+$/,
|
||||
"$1:x:x:x:x");
|
||||
} else if (net.isIPv6(ip + ":0:0:0:0")) {
|
||||
/* /64 IPv6 range */
|
||||
return ip + "::0/64";
|
||||
} else if (net.isIPv6(ip + ":0:0:0:0:0")) {
|
||||
/* /48 IPv6 widerange */
|
||||
return ip + "::0/48";
|
||||
} else {
|
||||
return ip;
|
||||
}
|
||||
},
|
||||
|
||||
root.getIPRange = function (ip) {
|
||||
if (net.isIPv6(ip)) {
|
||||
return root.expandIPv6(ip)
|
||||
|
@ -291,4 +266,48 @@
|
|||
shasum.update(data);
|
||||
return shasum.digest("hex");
|
||||
}
|
||||
|
||||
root.cloakIP = function (ip) {
|
||||
if (ip.match(/\d+\.\d+(\.\d+)?(\.\d+)?/)) {
|
||||
return cloakIPv4(ip);
|
||||
} else if (ip.match(/([0-9a-f]{1,4}\:){1,7}[0-9a-f]{1,4}/)) {
|
||||
return cloakIPv6(ip);
|
||||
} else {
|
||||
return ip;
|
||||
}
|
||||
|
||||
function iphash(ip, segment, len) {
|
||||
var md5 = crypto.createHash("md5");
|
||||
md5.update(ip);
|
||||
md5.update(segment);
|
||||
return md5.digest("base64").substring(0, len);
|
||||
}
|
||||
|
||||
function cloakIPv4(ip) {
|
||||
var parts = ip.split(".");
|
||||
|
||||
parts = parts.map(function (segment, i) {
|
||||
if (i < 2) return segment;
|
||||
|
||||
return iphash(ip, segment + i, 3);
|
||||
});
|
||||
|
||||
while (parts.length < 4) parts.push("*");
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
function cloakIPv6(ip) {
|
||||
var parts = ip.split(":");
|
||||
parts.splice(4, 4);
|
||||
|
||||
parts = parts.map(function (segment, i) {
|
||||
if (i < 2) return segment;
|
||||
|
||||
return iphash(ip, segment + i, 4);
|
||||
});
|
||||
|
||||
while (parts.length < 4) parts.push("*");
|
||||
return parts.join(":");
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -75,6 +75,7 @@ mixin adminoptions
|
|||
mixin textbox-auto("cs-externalcss", "External CSS", "Stylesheet URL")
|
||||
mixin textbox-auto("cs-externaljs", "External Javascript", "Script URL")
|
||||
mixin rcheckbox-auto("cs-show_public", "List channel publicly")
|
||||
mixin rcheckbox-auto("cs-torbanned", "Block connections from Tor")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
span.text-info Changes are automatically saved.
|
||||
|
|
|
@ -864,6 +864,7 @@ function handleModPermissions() {
|
|||
$("#cs-allow_voteskip").prop("checked", CHANNEL.opts.allow_voteskip);
|
||||
$("#cs-voteskip_ratio").val(CHANNEL.opts.voteskip_ratio);
|
||||
$("#cs-allow_dupes").val(CHANNEL.opts.allow_dupes);
|
||||
$("#cs-torbanned").val(CHANNEL.opts.torbanned);
|
||||
(function() {
|
||||
if(typeof CHANNEL.opts.maxlength != "number") {
|
||||
$("#cs-maxlength").val("");
|
||||
|
|
Loading…
Reference in New Issue