Merge pull request #294 from calzoneman/svsingle

Server refactoring
This commit is contained in:
Calvin Montgomery 2013-10-11 20:04:11 -07:00
commit 141516bf36
13 changed files with 1103 additions and 1104 deletions

28
index.js Normal file
View file

@ -0,0 +1,28 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
*/
var Server = require("./lib/server");
var Config = require("./lib/config");
var Logger = require("./lib/logger");
Config.load("cfg.json", function (cfg) {
var sv = Server.init(cfg);
if(!cfg["debug"]) {
process.on("uncaughtException", function (err) {
Logger.errlog.log("[SEVERE] Uncaught Exception: " + err);
Logger.errlog.log(err.stack);
});
process.on("SIGINT", function () {
sv.shutdown();
});
}
});

View file

@ -10,170 +10,170 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
*/
var Logger = require("./logger");
var Server = require("./server");
var ActionLog = require("./actionlog");
module.exports = function (Server) {
var db = Server.db;
var ActionLog = Server.actionlog;
return {
init: function(user) {
ActionLog.record(user.ip, user.name, "acp-init");
user.socket.on("acp-announce", function(data) {
ActionLog.record(user.ip, user.name, "acp-announce", data);
Server.announcement = data;
Server.io.sockets.emit("announcement", data);
if (Server.cfg["enable-ssl"])
Server.sslio.sockets.emit("announcement", data);
});
module.exports = {
init: function (user) {
var sv = Server.getServer();
var db = sv.db;
ActionLog.record(user.ip, user.name, "acp-init");
user.socket.on("acp-announce", function(data) {
ActionLog.record(user.ip, user.name, "acp-announce", data);
sv.announcement = data;
sv.io.sockets.emit("announcement", data);
if (sv.cfg["enable-ssl"])
sv.ioSecure.sockets.emit("announcement", data);
});
user.socket.on("acp-announce-clear", function() {
ActionLog.record(user.ip, user.name, "acp-announce-clear");
Server.announcement = null;
});
user.socket.on("acp-announce-clear", function() {
ActionLog.record(user.ip, user.name, "acp-announce-clear");
sv.announcement = null;
});
user.socket.on("acp-global-ban", function(data) {
ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip);
db.setGlobalIPBan(data.ip, data.note, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
user.socket.on("acp-global-ban", function(data) {
ActionLog.record(user.ip, user.name, "acp-global-ban", data.ip);
db.setGlobalIPBan(data.ip, data.note, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
});
});
user.socket.on("acp-global-unban", function(ip) {
ActionLog.record(user.ip, user.name, "acp-global-unban", ip);
db.clearGlobalIPBan(ip, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
user.socket.on("acp-global-unban", function(ip) {
ActionLog.record(user.ip, user.name, "acp-global-unban", ip);
db.clearGlobalIPBan(ip, function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
});
});
db.listGlobalIPBans(function (err, res) {
db.listGlobalIPBans(function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
});
user.socket.on("acp-lookup-user", function(name) {
db.searchUser(name, function (err, res) {
res = res || [];
user.socket.emit("acp-global-banlist", res);
user.socket.emit("acp-userdata", res);
});
});
user.socket.on("acp-lookup-user", function(name) {
db.searchUser(name, function (err, res) {
res = res || [];
user.socket.emit("acp-userdata", res);
});
user.socket.on("acp-lookup-channel", function (data) {
db.searchChannel(data.field, data.value, function (e, res) {
res = res || [];
user.socket.emit("acp-channeldata", res);
});
});
user.socket.on("acp-lookup-channel", function (data) {
db.searchChannel(data.field, data.value, function (e, res) {
res = res || [];
user.socket.emit("acp-channeldata", res);
});
});
user.socket.on("acp-reset-password", function(data) {
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
db.genPasswordReset(user.ip, data.name, data.email,
function (err, hash) {
var pkt = {
success: !err
};
if(err) {
pkt.error = err;
} else {
pkt.hash = hash;
}
user.socket.emit("acp-reset-password", pkt);
ActionLog.record(user.ip, user.name,
"acp-reset-password", data.name);
});
});
});
user.socket.on("acp-set-rank", function(data) {
if(data.rank < 1 || data.rank >= user.global_rank)
user.socket.on("acp-reset-password", function(data) {
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
db.genPasswordReset(user.ip, data.name, data.email,
function (err, hash) {
var pkt = {
success: !err
};
db.setGlobalRank(data.name, data.rank,
function (err, res) {
ActionLog.record(user.ip, user.name, "acp-set-rank",
data);
if(!err)
user.socket.emit("acp-set-rank", data);
});
if(err) {
pkt.error = err;
} else {
pkt.hash = hash;
}
user.socket.emit("acp-reset-password", pkt);
ActionLog.record(user.ip, user.name,
"acp-reset-password", data.name);
});
});
});
user.socket.on("acp-list-loaded", function() {
var chans = [];
var all = Server.channels;
for(var c in all) {
var chan = all[c];
user.socket.on("acp-set-rank", function(data) {
if(data.rank < 1 || data.rank >= user.global_rank)
return;
chans.push({
name: chan.name,
title: chan.opts.pagetitle,
usercount: chan.users.length,
mediatitle: chan.playlist.current ? chan.playlist.current.media.title : "-",
is_public: chan.opts.show_public,
registered: chan.registered
});
}
db.getGlobalRank(data.name, function (err, rank) {
if(err || rank >= user.global_rank)
return;
user.socket.emit("acp-list-loaded", chans);
});
user.socket.on("acp-channel-unload", function(data) {
if(Server.channelLoaded(data.name)) {
var c = Server.getChannel(data.name);
if(!c)
return;
ActionLog.record(user.ip, user.name, "acp-channel-unload");
c.initialized = data.save;
// copy the list of users to prevent concurrent
// modification
var users = Array.prototype.slice.call(c.users);
users.forEach(function (u) {
c.kick(u, "Channel shutting down");
});
// At this point c should be unloaded
// if it's still loaded, kill it
if(Server.channelLoaded(data.name))
Server.unloadChannel(Server.getChannel(data.name));
}
});
user.socket.on("acp-actionlog-list", function () {
ActionLog.listActionTypes(function (err, types) {
db.setGlobalRank(data.name, data.rank,
function (err, res) {
ActionLog.record(user.ip, user.name, "acp-set-rank",
data);
if(!err)
user.socket.emit("acp-actionlog-list", types);
user.socket.emit("acp-set-rank", data);
});
});
});
user.socket.on("acp-actionlog-clear", function(data) {
ActionLog.clear(data);
ActionLog.record(user.ip, user.name, "acp-actionlog-clear", data);
});
user.socket.on("acp-list-loaded", function() {
var chans = [];
var all = sv.channels;
for(var c in all) {
var chan = all[c];
user.socket.on("acp-actionlog-clear-one", function(data) {
ActionLog.clearOne(data);
ActionLog.record(user.ip, user.name, "acp-actionlog-clear-one", data);
});
user.socket.on("acp-view-stats", function () {
db.listStats(function (err, res) {
if(!err)
user.socket.emit("acp-view-stats", res);
chans.push({
name: chan.name,
title: chan.opts.pagetitle,
usercount: chan.users.length,
mediatitle: chan.playlist.current ? chan.playlist.current.media.title : "-",
is_public: chan.opts.show_public,
registered: chan.registered
});
}
user.socket.emit("acp-list-loaded", chans);
});
user.socket.on("acp-channel-unload", function(data) {
if(sv.isChannelLoaded(data.name)) {
var c = sv.getChannel(data.name);
if(!c)
return;
ActionLog.record(user.ip, user.name, "acp-channel-unload");
c.initialized = data.save;
// copy the list of users to prevent concurrent
// modification
var users = Array.prototype.slice.call(c.users);
users.forEach(function (u) {
c.kick(u, "Channel shutting down");
});
// At this point c should be unloaded
// if it's still loaded, kill it
if(sv.isChannelLoaded(data.name))
sv.unloadChannel(sv.getChannel(data.name));
}
});
user.socket.on("acp-actionlog-list", function () {
ActionLog.listActionTypes(function (err, types) {
if(!err)
user.socket.emit("acp-actionlog-list", types);
});
}
});
user.socket.on("acp-actionlog-clear", function(data) {
ActionLog.clear(data);
ActionLog.record(user.ip, user.name, "acp-actionlog-clear", data);
});
user.socket.on("acp-actionlog-clear-one", function(data) {
ActionLog.clearOne(data);
ActionLog.record(user.ip, user.name, "acp-actionlog-clear-one", data);
});
user.socket.on("acp-view-stats", function () {
db.listStats(function (err, res) {
if(!err)
user.socket.emit("acp-view-stats", res);
});
});
}
}

View file

@ -10,49 +10,53 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
*/
var Logger = require("./logger");
var Server = require("./server");
module.exports = function (Server) {
var db = Server.db;
return {
record: function (ip, name, action, args) {
if(!args)
module.exports = {
record: function (ip, name, action, args) {
var db = Server.getServer().db;
if(!args)
args = "";
else {
try {
args = JSON.stringify(args);
} catch(e) {
args = "";
else {
try {
args = JSON.stringify(args);
} catch(e) {
args = "";
}
}
db.recordAction(ip, name, action, args);
},
clear: function (actions) {
db.clearActions(actions);
},
clearOne: function (item) {
db.clearSingleAction(item);
},
throttleRegistrations: function (ip, callback) {
db.recentRegistrationCount(ip, function (err, count) {
if(err) {
callback(err, null);
return;
}
callback(null, count > 4);
});
},
listActionTypes: function (callback) {
db.listActionTypes(callback);
},
listActions: function (types, callback) {
db.listActions(types, callback);
}
};
db.recordAction(ip, name, action, args);
},
clear: function (actions) {
var db = Server.getServer().db;
db.clearActions(actions);
},
clearOne: function (item) {
var db = Server.getServer().db;
db.clearSingleAction(item);
},
throttleRegistrations: function (ip, callback) {
var db = Server.getServer().db;
db.recentRegistrationCount(ip, function (err, count) {
if(err) {
callback(err, null);
return;
}
callback(null, count > 4);
});
},
listActionTypes: function (callback) {
var db = Server.getServer().db;
db.listActionTypes(callback);
},
listActions: function (types, callback) {
var db = Server.getServer().db;
db.listActions(types, callback);
}
};

View file

@ -13,9 +13,9 @@ var Logger = require("./logger");
var fs = require("fs");
var path = require("path");
var $util = require("./utilities");
var ActionLog = require("./actionlog");
module.exports = function (Server) {
var ActionLog = Server.actionlog;
function getIP(req) {
var raw = req.connection.remoteAddress;
var forward = req.header("x-forwarded-for");
@ -51,7 +51,7 @@ module.exports = function (Server) {
return data;
}
var app = Server.app;
var app = Server.express;
var db = Server.db;
/* <https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol> */

View file

@ -12,24 +12,26 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
var fs = require("fs");
var path = require("path");
var url = require("url");
var Server = require("./server");
var Poll = require("./poll.js").Poll;
var Media = require("./media.js").Media;
var Logger = require("./logger.js");
var Rank = require("./rank.js");
var ChatCommand = require("./chatcommand.js");
var Filter = require("./filter.js").Filter;
var Playlist = require("./playlist");
var sanitize = require("validator").sanitize;
var $util = require("./utilities");
var url = require("url");
var AsyncQueue = require("./asyncqueue");
var ActionLog = require("./actionlog");
var InfoGetter = require("./get-info");
var Channel = function(name, Server) {
var Channel = function(name) {
var self = this;
Logger.syslog.log("Opening channel " + name);
self.initialized = false;
self.dbloaded = false;
self.server = Server;
self.server = Server.getServer();
self.name = name;
self.canonical_name = name.toLowerCase();
@ -123,7 +125,7 @@ var Channel = function(name, Server) {
self.ipkey += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[parseInt(Math.random() * 65)]
}
Server.db.loadChannelData(self, function (err) {
Server.getServer().db.loadChannelData(self, function (err) {
if (err && err === "channel_dead")
return;
else if (!err || err === "channel_unregistered")
@ -384,7 +386,7 @@ Channel.prototype.tryReadLog = function (user) {
Channel.prototype.tryRegister = function (user) {
var self = this;
if(self.registered) {
self.server.actionlog.record(user.ip, user.name, "channel-register-failure",
ActionLog.record(user.ip, user.name, "channel-register-failure",
[self.name, "Channel already registered"]);
user.socket.emit("registerChannel", {
success: false,
@ -392,7 +394,7 @@ Channel.prototype.tryRegister = function (user) {
});
}
else if(!user.loggedIn) {
self.server.actionlog.record(user.ip, user.name, "channel-register-failure",
ActionLog.record(user.ip, user.name, "channel-register-failure",
[self.name, "Not logged in"]);
user.socket.emit("registerChannel", {
success: false,
@ -400,8 +402,8 @@ Channel.prototype.tryRegister = function (user) {
});
}
else if(!Rank.hasPermission(user, "registerChannel")) {
self.server.actionlog.record(user.ip, user.name, "channel-register-failure",
else if(user.rank < 10) {
ActionLog.record(user.ip, user.name, "channel-register-failure",
[self.name, "Insufficient permissions"]);
user.socket.emit("registerChannel", {
success: false,
@ -419,7 +421,7 @@ Channel.prototype.tryRegister = function (user) {
return;
}
self.server.actionlog.record(user.ip, user.name,
ActionLog.record(user.ip, user.name,
"channel-register-success", self.name);
if (self.dead)
return;
@ -927,7 +929,7 @@ Channel.prototype.sendBanlist = function(user) {
var name = this.ipbans[ip][0];
var ip_hidden = this.hideIP(ip);
var disp = ip;
if(user.rank < Rank.Siteadmin) {
if(user.rank < 255) {
disp = $util.maskIP(ip);
}
ents.push({
@ -971,7 +973,7 @@ Channel.prototype.sendRankStuff = function(user) {
}
Channel.prototype.sendChannelRanks = function(user) {
if(Rank.hasPermission(user, "acl") && this.registered) {
if(user.rank >= 3 && this.registered) {
this.server.db.listChannelRanks(this.name, function (err, res) {
if(err) {
user.socket.emit("errorMsg", {
@ -1028,12 +1030,12 @@ Channel.prototype.sendAll = function(message, data) {
return;
this.server.io.sockets.in(this.name).emit(message, data);
if (this.server.cfg["enable-ssl"])
this.server.sslio.sockets.in(this.name).emit(message, data);
this.server.ioSecure.sockets.in(this.name).emit(message, data);
}
Channel.prototype.sendAllWithPermission = function(perm, msg, data) {
Channel.prototype.sendAllWithRank = function(rank, msg, data) {
for(var i = 0; i < this.users.length; i++) {
if(Rank.hasPermission(this.users[i], perm)) {
if(this.users[i].rank >= rank) {
this.users[i].socket.emit(msg, data);
}
}
@ -1101,7 +1103,7 @@ Channel.prototype.broadcastNewUser = function(user) {
profile: user.profile
});
if(user.rank > Rank.Guest) {
if(user.rank > 0) {
self.saveRank(user);
}
@ -1209,7 +1211,7 @@ Channel.prototype.broadcastBanlist = function() {
}
for(var i = 0; i < this.users.length; i++) {
if(this.hasPermission(this.users[i], "ban")) {
if(this.users[i].rank >= Rank.Siteadmin) {
if(this.users[i].rank >= 255) {
this.users[i].socket.emit("banlist", adminents);
}
else {
@ -1248,8 +1250,7 @@ Channel.prototype.broadcastVoteskipUpdate = function() {
var count = this.calcVoteskipMax();
var need = this.voteskip ? Math.ceil(count * this.opts.voteskip_ratio) : 0;
for(var i = 0; i < this.users.length; i++) {
if(Rank.hasPermission(this.users[i], "seeVoteskip") ||
this.leader == this.users[i]) {
if(this.users[i].rank >= 1.5) {
this.users[i].socket.emit("voteskip", {
count: amt,
need: need
@ -1332,7 +1333,7 @@ Channel.prototype.tryQueue = function(user, data) {
sustained: 1
};
if (user.rank >= Rank.Moderator || this.leader == user) {
if (user.rank >= 2 || this.leader == user) {
limit = {
burst: 10,
sustained: 2
@ -1471,8 +1472,8 @@ Channel.prototype.addMedia = function(data, user) {
self.plqueue.queue(function (q) {
if (self.dead)
return;
self.server.infogetter.getMedia(data.id, data.type,
function (e, vids) {
InfoGetter.getMedia(data.id, data.type,
function (e, vids) {
if (e) {
user.socket.emit("queueFail", {
msg: e,
@ -1506,8 +1507,8 @@ Channel.prototype.addMedia = function(data, user) {
if (self.dead)
return;
var cb = afterData.bind(self, q, false);
self.server.infogetter.getMedia(data.id, data.type,
function (e, m) {
InfoGetter.getMedia(data.id, data.type,
function (e, m) {
if (self.dead)
return;
if (e) {
@ -1550,8 +1551,8 @@ Channel.prototype.addMedia = function(data, user) {
afterData.bind(self, q, true)(item);
} else {
self.server.infogetter.getMedia(data.id, data.type,
function (e, m) {
InfoGetter.getMedia(data.id, data.type,
function (e, m) {
if (self.dead)
return;
if (e) {
@ -1671,7 +1672,7 @@ Channel.prototype.tryDequeue = function(user, data) {
Channel.prototype.tryUncache = function(user, data) {
var self = this;
if(!Rank.hasPermission(user, "uncache")) {
if(user.rank < 2) {
return;
}
if(typeof data.id != "string") {
@ -1923,7 +1924,7 @@ Channel.prototype.setLock = function(locked) {
}
Channel.prototype.trySetLock = function(user, data) {
if(!Rank.hasPermission(user, "qlock")) {
if(user.rank < 2) {
return;
}
@ -1936,7 +1937,7 @@ Channel.prototype.trySetLock = function(user, data) {
}
Channel.prototype.tryToggleLock = function(user) {
if(!Rank.hasPermission(user, "qlock")) {
if(user.rank < 2) {
return;
}
@ -2028,7 +2029,7 @@ Channel.prototype.tryMoveFilter = function(user, data) {
}
Channel.prototype.tryUpdatePermissions = function(user, perms) {
if(!Rank.hasPermission(user, "channelperms")) {
if(user.rank < 3) {
return;
}
for(var key in perms) {
@ -2039,7 +2040,7 @@ Channel.prototype.tryUpdatePermissions = function(user, perms) {
}
Channel.prototype.tryUpdateOptions = function(user, data) {
if(!Rank.hasPermission(user, "channelOpts")) {
if(user.rank < 2) {
return;
}
@ -2058,7 +2059,7 @@ Channel.prototype.tryUpdateOptions = function(user, data) {
for(var key in this.opts) {
if(key in data) {
if(key in adminonly && user.rank < Rank.Owner) {
if(key in adminonly && user.rank < 3) {
continue;
}
if(key === "afk_timeout" && this.opts[key] != data[key]) {
@ -2075,7 +2076,7 @@ Channel.prototype.tryUpdateOptions = function(user, data) {
}
Channel.prototype.trySetCSS = function(user, data) {
if(!Rank.hasPermission(user, "setcss")) {
if(user.rank < 3) {
return;
}
@ -2092,7 +2093,7 @@ Channel.prototype.trySetCSS = function(user, data) {
}
Channel.prototype.trySetJS = function(user, data) {
if(!Rank.hasPermission(user, "setjs")) {
if(user.rank < 3) {
return;
}
@ -2234,7 +2235,7 @@ Channel.prototype.sendMessage = function(username, msg, msgclass, data) {
Channel.prototype.trySetRank = function(user, data) {
var self = this;
if(!Rank.hasPermission(user, "promote"))
if(user.rank < 2)
return;
if(typeof data.user !== "string" || typeof data.rank !== "number")
@ -2265,7 +2266,7 @@ Channel.prototype.trySetRank = function(user, data) {
self.logger.log("*** " + user.name + " set " +
data.user + "'s rank to " + data.rank);
self.sendAllWithPermission("acl", "setChannelRank", data);
self.sendAllWithRank(3, "setChannelRank", data);
});
}
self.broadcastUserUpdate(receiver);
@ -2286,7 +2287,7 @@ Channel.prototype.trySetRank = function(user, data) {
self.logger.log("*** " + user.name + " set " +
data.user + "'s rank to " + data.rank);
self.sendAllWithPermission("acl", "setChannelRank", data);
self.sendAllWithRank(3, "setChannelRank", data);
});
});
}
@ -2329,7 +2330,7 @@ Channel.prototype.changeLeader = function(name) {
}
Channel.prototype.tryChangeLeader = function(user, data) {
if(!Rank.hasPermission(user, "assignLeader")) {
if(user.rank < 2) {
return;
}

View file

@ -9,8 +9,6 @@ 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.
*/
var Rank = require("./rank.js");
var Poll = require("./poll.js").Poll;
var Logger = require("./logger.js");
function handle(chan, user, msg, data) {
@ -19,7 +17,7 @@ function handle(chan, user, msg, data) {
else if(msg.indexOf("/sp ") == 0)
chan.sendMessage(user.name, msg.substring(4), "spoiler", data);
else if(msg.indexOf("/say ") == 0) {
if(Rank.hasPermission(user, "shout") || chan.leader == user) {
if(user.rank >= 1.5) {
chan.sendMessage(user.name, msg.substring(5), "shout", data);
}
}
@ -27,12 +25,12 @@ function handle(chan, user, msg, data) {
user.setAFK(!user.meta.afk);
}
else if(msg.indexOf("/m ") == 0) {
if(user.rank >= Rank.Moderator) {
if(user.rank >= 2) {
chan.chainMessage(user, msg.substring(3), {modflair: user.rank})
}
}
else if(msg.indexOf("/a ") == 0) {
if(user.rank >= Rank.Siteadmin) {
if(user.rank >= 255) {
var flair = {
superadminflair: {
labelclass: "label-important",
@ -241,7 +239,7 @@ function handleDrink(chan, user, msg, data) {
}
function handleClear(chan, user) {
if(user.rank < Rank.Moderator) {
if(user.rank < 2) {
return;
}

View file

@ -62,7 +62,7 @@ function save(cfg, file) {
fs.writeFileSync(file, JSON.stringify(x, null, 4));
}
exports.load = function (Server, file, callback) {
exports.load = function (file, callback) {
var cfg = {};
for(var k in defaults)
cfg[k] = defaults[k];
@ -102,7 +102,6 @@ exports.load = function (Server, file, callback) {
cfg["loaded"] = true;
save(cfg, file);
Server.cfg = cfg;
callback();
callback(cfg);
});
}

View file

@ -5,8 +5,9 @@ var $util = require("./utilities");
var Logger = require("./logger");
var Database = function (cfg) {
this.cfg = cfg;
this.pool = mysql.createPool({
var self = this;
self.cfg = cfg;
self.pool = mysql.createPool({
host: cfg["mysql-server"],
user: cfg["mysql-user"],
password: cfg["mysql-pw"],
@ -15,16 +16,16 @@ var Database = function (cfg) {
});
// Test the connection
this.pool.getConnection(function (err, conn) {
self.pool.getConnection(function (err, conn) {
if(err) {
Logger.errlog.log("! DB connection failed");
return;
} else {
self.init();
}
});
this.global_ipbans = {};
self.global_ipbans = {};
};
Database.prototype.query = function (query, sub, callback) {

File diff suppressed because it is too large Load diff

View file

@ -1,55 +0,0 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
*/
exports.Anonymous = -1;
exports.Guest = 0;
exports.Member = 1;
exports.Moderator = 2;
exports.Owner = 3;
exports.Siteadmin = 255;
var permissions = {
acp : exports.Siteadmin,
announce : exports.Siteadmin,
registerChannel : exports.Owner,
acl : exports.Owner,
chatFilter : exports.Owner,
setcss : exports.Owner,
setjs : exports.Owner,
channelperms : exports.Owner,
queue : exports.Moderator,
assignLeader : exports.Moderator,
kick : exports.Moderator,
ipban : exports.Moderator,
ban : exports.Moderator,
promote : exports.Moderator,
qlock : exports.Moderator,
poll : exports.Moderator,
shout : exports.Moderator,
channelOpts : exports.Moderator,
jump : exports.Moderator,
updateMotd : exports.Moderator,
drink : exports.Moderator,
seeVoteskip : exports.Moderator,
uncache : exports.Moderator,
seenlogins : exports.Moderator,
settemp : exports.Moderator,
search : exports.Guest,
chat : exports.Guest,
};
// Check if someone has permission to do shit
exports.hasPermission = function(user, what) {
if(what in permissions) {
return user.rank >= permissions[what];
}
else return false;
}

View file

@ -1,3 +1,38 @@
/*
The MIT License (MIT)
Copyright (c) 2013 Calvin Montgomery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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 = "2.4.3";
var singleton = null;
module.exports = {
init: function (cfg) {
Logger.syslog.log("Starting CyTube v" + VERSION);
var chanlogpath = path.join(__dirname, "../chanlogs");
fs.exists(chanlogpath, function (exists) {
exists || fs.mkdir(chanlogpath);
});
var chandumppath = path.join(__dirname, "../chandump");
fs.exists(chandumppath, function (exists) {
exists || fs.mkdir(chandumppath);
});
singleton = new Server(cfg);
return singleton;
},
getServer: function () {
return singleton;
}
};
var path = require("path");
var fs = require("fs");
var http = require("http");
@ -7,23 +42,148 @@ var Config = require("./config");
var Logger = require("./logger");
var Channel = require("./channel");
var User = require("./user");
var $util = require("./utilities");
const VERSION = "2.4.3";
var Server = function (cfg) {
var self = this;
function getIP(req) {
self.cfg = cfg;
self.channels = [],
self.express = null;
self.http = null;
self.https = null;
self.io = null;
self.ioWeb = null;
self.ioSecure = null;
self.ipCount = {};
self.db = null;
self.api = null;
self.announcement = null;
self.httplog = null;
self.actionlog = null;
self.infogetter = null;
// database init ------------------------------------------------------
var Database = require("./database");
self.db = new Database(self.cfg);
// webserver init -----------------------------------------------------
self.httplog = new Logger.Logger(path.join(__dirname,
"../httpaccess.log"));
self.express = express();
self.express.use(express.bodyParser());
// channel route
self.express.get("/r/:channel(*)", function (req, res, next) {
var c = req.params.channel;
if (!$util.isValidChannelName(c)) {
res.redirect("/" + c);
return;
}
self.logHTTP(req);
res.sendfile("channel.html", {
root: path.join(__dirname, "../www")
});
});
// api route
self.api = require("./api")(self);
// index
self.express.get("/", function (req, res, next) {
self.logHTTP(req);
res.sendfile("index.html", {
root: path.join(__dirname, "../www")
});
});
// default route
self.express.get("/:thing(*)", function (req, res, next) {
var opts = {
root: path.join(__dirname, "../www"),
maxAge: self.cfg["asset-cache-ttl"]
};
res.sendfile(req.params.thing, opts, function (e) {
if (e) {
self.logHTTP(req, e.status);
if (req.params.thing.match(/\.\.|(%25)?%2e(%25)?%2e/)) {
res.send("Don't try that again.");
Logger.syslog.log("WARNING: Attempted path traversal "+
"from IP " + self.getHTTPIP(req));
Logger.syslog.log("Path was: " + req.url);
self.actionlog.record(self.getHTTPIP(req), "",
"path-traversal",
req.url);
} else if (e.status >= 500) {
Logger.errlog.log(err);
}
res.send(e.status);
} else {
self.logHTTP(req);
}
});
});
// fallback route
self.express.use(function (err, req, res, next) {
self.logHTTP(req, err.status);
if (err.status === 404) {
res.send(404);
} else {
next(err);
}
});
// http/https/sio server init -----------------------------------------
if (self.cfg["enable-ssl"]) {
var key = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-keyfile"]));
var cert = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-certfile"]));
var opts = {
key: key,
cert: cert,
passphrase: self.cfg["ssl-passphrase"]
};
self.https = https.createServer(opts, self.express)
.listen(self.cfg["ssl-port"]);
self.ioSecure = require("socket.io").listen(self.https);
self.ioSecure.set("log level", 1);
self.ioSecure.on("connection", function (sock) {
self.handleSocketConnection(sock);
});
}
self.http = self.express.listen(self.cfg["web-port"],
self.cfg["express-host"]);
self.ioWeb = express().listen(self.cfg["io-port"], self.cfg["io-host"]);
self.io = require("socket.io").listen(self.ioWeb);
self.io.set("log level", 1);
self.io.sockets.on("connection", function (sock) {
self.handleSocketConnection(sock);
});
// background tasks init ----------------------------------------------
require("./bgtask")(self);
};
Server.prototype.getHTTPIP = function (req) {
var raw = req.connection.remoteAddress;
var forward = req.header("x-forwarded-for");
if((Server.cfg["trust-x-forward"] || raw === "127.0.0.1") && forward) {
if((this.cfg["trust-x-forward"] || raw === "127.0.0.1") && forward) {
var ip = forward.split(",")[0];
Logger.syslog.log("REVPROXY " + raw + " => " + ip);
return ip;
}
return raw;
}
};
function getSocketIP(socket) {
Server.prototype.getSocketIP = function (socket) {
var raw = socket.handshake.address.address;
if(Server.cfg["trust-x-forward"] || raw === "127.0.0.1") {
if(this.cfg["trust-x-forward"] || raw === "127.0.0.1") {
if(typeof socket.handshake.headers["x-forwarded-for"] == "string") {
var ip = socket.handshake.headers["x-forwarded-for"]
.split(",")[0];
@ -32,257 +192,114 @@ function getSocketIP(socket) {
}
}
return raw;
}
var Server = {
channels: [],
channelLoaded: function (name) {
for(var i in this.channels) {
if(this.channels[i].canonical_name == name.toLowerCase())
return true;
}
return false;
},
getChannel: function (name) {
for(var i in this.channels) {
if(this.channels[i].canonical_name == name.toLowerCase())
return this.channels[i];
}
var c = new Channel(name, this);
this.channels.push(c);
return c;
},
unloadChannel: function(chan) {
if(chan.registered)
chan.saveDump();
chan.playlist.die();
chan.logger.close();
for(var i in this.channels) {
if(this.channels[i].canonical_name == chan.canonical_name) {
this.channels.splice(i, 1);
break;
}
}
var keys = Object.keys(chan);
for (var i in keys) {
delete chan[keys[i]];
}
chan.dead = true;
},
app: null,
io: null,
httpserv: null,
sslserv: null,
sslio: null,
ioserv: null,
db: null,
ips: {},
acp: null,
announcement: null,
httpaccess: null,
actionlog: null,
logHTTP: function (req, status) {
if(status === undefined)
status = 200;
var ip = req.connection.remoteAddress;
var ip2 = false;
if(this.cfg["trust-x-forward"])
ip2 = req.header("x-forwarded-for") || req.header("cf-connecting-ip");
var ipstr = !ip2 ? ip : ip + " (X-Forwarded-For " + ip2 + ")";
var url = req.url;
// Remove query
if(url.indexOf("?") != -1)
url = url.substring(0, url.lastIndexOf("?"));
this.httpaccess.log([ipstr, req.method, url, status, req.headers["user-agent"]].join(" "));
},
handleIOConnection: function (socket) {
var self = this;
var ip = getSocketIP(socket);
socket._ip = ip;
self.db.isGlobalIPBanned(ip, function (err, bant) {
if(bant) {
Logger.syslog.log("Disconnecting " + ip + " - gbanned");
socket.emit("kick", {
reason: "You're globally banned."
});
socket.disconnect(true);
}
});
socket.on("disconnect", function () {
self.ips[ip]--;
}.bind(self));
if(!(ip in self.ips))
self.ips[ip] = 0;
self.ips[ip]++;
if(self.ips[ip] > Server.cfg["ip-connection-limit"]) {
socket.emit("kick", {
reason: "Too many connections from your IP address"
});
socket.disconnect(true);
return;
}
// finally a valid user
Logger.syslog.log("Accepted socket from /" + socket._ip);
new User(socket, self);
},
init: function () {
var self = this;
// init database
var Database = require("./database");
this.db = new Database(self.cfg);
this.db.init();
this.actionlog = require("./actionlog")(self);
this.httpaccess = new Logger.Logger(path.join(__dirname,
"../httpaccess.log"));
this.app = express();
this.app.use(express.bodyParser());
// channel path
self.app.get("/r/:channel(*)", function (req, res, next) {
var c = req.params.channel;
if(!c.match(/^[\w-_]+$/)) {
res.redirect("/" + c);
}
else {
self.logHTTP(req);
res.sendfile("channel.html", {
root: path.join(__dirname, "../www")
});
}
});
// api path
self.api = require("./api")(self);
self.app.get("/", function (req, res, next) {
self.logHTTP(req);
res.sendfile("index.html", {
root: path.join(__dirname, "../www")
});
});
// default path
self.app.get("/:thing(*)", function (req, res, next) {
var opts = {
root: path.join(__dirname, "../www"),
maxAge: self.cfg["asset-cache-ttl"]
}
res.sendfile(req.params.thing, opts, function (err) {
if(err) {
self.logHTTP(req, err.status);
// Damn path traversal attacks
if(req.params.thing.indexOf("%2e") != -1) {
res.send("Don't try that again, I'll ban you");
Logger.syslog.log("WARNING: Attempted path "+
"traversal from /" + getIP(req));
Logger.syslog.log("URL: " + req.url);
}
// Something actually went wrong
else {
// Status codes over 500 are server errors
if(err.status >= 500)
Logger.errlog.log(err);
res.send(err.status);
}
}
else {
self.logHTTP(req);
}
});
});
// fallback
self.app.use(function (err, req, res, next) {
self.logHTTP(req, err.status);
if(err.status == 404) {
res.send(404);
} else {
next(err);
}
});
// bind servers
if (self.cfg["enable-ssl"]) {
var key = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-keyfile"]));
var cert = fs.readFileSync(path.resolve(__dirname, "..",
self.cfg["ssl-certfile"]));
var options = {
key: key,
passphrase: self.cfg["ssl-passphrase"],
cert: cert
};
self.sslserv = https.createServer(options, self.app)
.listen(self.cfg["ssl-port"]);
self.sslio = require("socket.io").listen(self.sslserv);
self.sslio.set("log level", 1);
self.sslio.sockets.on("connection", function (socket) {
self.handleIOConnection(socket);
});
}
self.httpserv = self.app.listen(Server.cfg["web-port"],
Server.cfg["express-host"]);
self.ioserv = express().listen(Server.cfg["io-port"],
Server.cfg["io-host"]);
// init socket.io
self.io = require("socket.io").listen(self.ioserv);
self.io.set("log level", 1);
self.io.sockets.on("connection", function (socket) {
self.handleIOConnection(socket);
});
// init ACP
self.acp = require("./acp")(self);
// init background tasks
require("./bgtask")(self);
// init media retriever
self.infogetter = require("./get-info")(self);
},
shutdown: function () {
Logger.syslog.log("Unloading channels");
for(var i in this.channels) {
if(this.channels[i].registered) {
Logger.syslog.log("Saving /r/" + this.channels[i].name);
this.channels[i].saveDump();
}
}
Logger.syslog.log("Goodbye");
process.exit(0);
}
};
Logger.syslog.log("Starting CyTube v" + VERSION);
var chanlogpath = path.join(__dirname, "../chanlogs");
fs.exists(chanlogpath, function (exists) {
exists || fs.mkdir(chanlogpath);
});
var chandumppath = path.join(__dirname, "../chandump");
fs.exists(chandumppath, function (exists) {
exists || fs.mkdir(chandumppath);
});
Config.load(Server, path.join(__dirname, "../cfg.json"), function () {
Server.init();
if(!Server.cfg["debug"]) {
process.on("uncaughtException", function (err) {
Logger.errlog.log("[SEVERE] Uncaught Exception: " + err);
Logger.errlog.log(err.stack);
});
process.on("SIGINT", function () {
Server.shutdown();
});
Server.prototype.isChannelLoaded = function (name) {
name = name.toLowerCase();
for (var i = 0; i < this.channels.length; i++) {
if (this.channels[i].canonical_name == name)
return true;
}
});
return false;
};
Server.prototype.getChannel = function (name) {
var cname = name.toLowerCase();
for (var i = 0; i < this.channels.length; i++) {
if (this.channels[i].canonical_name == name)
return this.channels[i];
}
var c = new Channel(name);
this.channels.push(c);
return c;
};
Server.prototype.unloadChannel = function (chan) {
if (chan.registered)
chan.saveDump();
chan.playlist.die();
chan.logger.close();
for (var i = 0; i < this.channels.length; i++) {
if (this.channels[i].canonical_name === chan.canonical_name) {
this.channels.splice(i, 1);
i--;
}
}
// Empty all outward references from the channel
var keys = Object.keys(chan);
for (var i in keys) {
delete chan[keys[i]];
}
chan.dead = true;
};
Server.prototype.logHTTP = function (req, status) {
if (status === undefined)
status = 200;
var ip = req.connection.remoteAddress;
var ip2 = req.header("x-forwarded-for") ||
req.header("cf-connecting-ip");
var ipstr = !ip2 ? ip : ip + " (X-Forwarded-For " + ip2 + ")";
var url = req.url;
// Remove query
if(url.indexOf("?") != -1)
url = url.substring(0, url.lastIndexOf("?"));
this.httplog.log([
ipstr,
req.method,
url,
status,
req.header("user-agent")
].join(" "));
};
Server.prototype.handleSocketConnection = function (socket) {
var self = this;
var ip = self.getSocketIP(socket);
socket._ip = ip;
// Check for global ban on the IP
self.db.isGlobalIPBanned(ip, function (err, banned) {
if (banned) {
Logger.syslog.log("Disconnecting " + ip + " - global banned");
socket.emit("kick", { reason: "Your IP is globally banned." });
socket.disconnect(true);
}
});
socket.on("disconnect", function () {
self.ipCount[ip]--;
});
if (!(ip in self.ipCount))
self.ipCount[ip] = 0;
self.ipCount[ip]++;
if (self.ipCount[ip] > self.cfg["ip-connection-limit"]) {
socket.emit("kick", {
reason: "Too many connections from your IP address"
});
socket.disconnect(true);
return;
}
Logger.syslog.log("Accepted socket from " + ip);
new User(socket);
};
Server.prototype.shutdown = function () {
Logger.syslog.log("Unloading channels");
for (var i = 0; i < this.channels.length; i++) {
if (this.channels[i].registered) {
Logger.syslog.log("Saving /r/" + this.channels[i].name);
this.channels[i].saveDump();
}
}
Logger.syslog.log("Goodbye");
process.exit(0);
};

View file

@ -9,21 +9,24 @@ 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.
*/
var Rank = require("./rank.js");
var Channel = require("./channel.js").Channel;
var Logger = require("./logger.js");
var $util = require("./utilities");
var ActionLog = require("./actionlog");
var Server = require("./server");
var ACP = require("./acp");
var InfoGetter = require("./get-info");
// Represents a client connected via socket.io
var User = function (socket, Server) {
var User = function (socket) {
this.ip = socket._ip;
this.server = Server;
this.server = Server.getServer();
this.socket = socket;
this.loggedIn = false;
this.loggingIn = false;
this.saverank = false;
this.rank = Rank.Anonymous;
this.global_rank = Rank.Anonymous;
this.rank = -1
this.global_rank = -1;
this.channel = null;
this.name = "";
this.meta = {
@ -42,8 +45,8 @@ var User = function (socket, Server) {
this.autoAFK();
this.initCallbacks();
if (Server.announcement !== null) {
this.socket.emit("announcement", Server.announcement);
if (this.server.announcement !== null) {
this.socket.emit("announcement", this.server.announcement);
}
};
@ -321,7 +324,7 @@ User.prototype.initCallbacks = function () {
self.socket.on("searchMedia", function (data) {
if (self.inChannel()) {
if (data.source == "yt") {
var searchfn = self.server.infogetter.Getters.ytSearch;
var searchfn = InfoGetter.Getters.ytSearch;
searchfn(data.query.split(" "), function (e, vids) {
if (!e) {
self.socket.emit("searchResults", {
@ -556,8 +559,8 @@ User.prototype.initCallbacks = function () {
});
self.socket.on("acp-init", function () {
if (self.global_rank >= Rank.Siteadmin)
self.server.acp.init(self);
if (self.global_rank >= 255)
ACP.init(self);
});
self.socket.on("borrow-rank", function (rank) {
@ -662,7 +665,7 @@ User.prototype.login = function (name, pw, session) {
self.server.db.userLogin(name, pw, session, function (err, row) {
if (err) {
self.loggingIn = false;
self.server.actionlog.record(self.ip, name, "login-failure",
ActionLog.record(self.ip, name, "login-failure",
err);
self.socket.emit("login", {
success: false,
@ -686,7 +689,7 @@ User.prototype.login = function (name, pw, session) {
}
// Record logins for administrator accounts
if (self.global_rank >= 255)
self.server.actionlog.record(self.ip, name, "login-success");
ActionLog.record(self.ip, name, "login-success");
self.loggedIn = true;
self.loggingIn = false;
self.socket.emit("login", {

2
run.sh
View file

@ -2,7 +2,7 @@
while :
do
node lib/server.js
node index.js
sleep 2
done