Do some server refactoring

This commit is contained in:
calzoneman 2013-10-09 18:10:26 -05:00
parent a3153246ce
commit 128367bfc4
5 changed files with 260 additions and 235 deletions

View file

@ -131,7 +131,7 @@ module.exports = function (Server) {
}); });
user.socket.on("acp-channel-unload", function(data) { user.socket.on("acp-channel-unload", function(data) {
if(Server.channelLoaded(data.name)) { if(Server.isChannelLoaded(data.name)) {
var c = Server.getChannel(data.name); var c = Server.getChannel(data.name);
if(!c) if(!c)
return; return;
@ -146,7 +146,7 @@ module.exports = function (Server) {
// At this point c should be unloaded // At this point c should be unloaded
// if it's still loaded, kill it // if it's still loaded, kill it
if(Server.channelLoaded(data.name)) if(Server.isChannelLoaded(data.name))
Server.unloadChannel(Server.getChannel(data.name)); Server.unloadChannel(Server.getChannel(data.name));
} }
}); });

View file

@ -51,7 +51,7 @@ module.exports = function (Server) {
return data; return data;
} }
var app = Server.app; var app = Server.express;
var db = Server.db; var db = Server.db;
/* <https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol> */ /* <https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol> */

View file

@ -1028,7 +1028,7 @@ Channel.prototype.sendAll = function(message, data) {
return; return;
this.server.io.sockets.in(this.name).emit(message, data); this.server.io.sockets.in(this.name).emit(message, data);
if (this.server.cfg["enable-ssl"]) 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.sendAllWithPermission = function(perm, msg, data) {

View file

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

View file

@ -7,23 +7,152 @@ var Config = require("./config");
var Logger = require("./logger"); var Logger = require("./logger");
var Channel = require("./channel"); var Channel = require("./channel");
var User = require("./user"); var User = require("./user");
var $util = require("./utilities");
const VERSION = "2.4.3"; const VERSION = "2.4.3";
function getIP(req) {
var Server = function (cfg) {
var self = this;
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.acp = 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);
// TODO self.db.init();
self.actionlog = require("./actionlog")(self);
self.httplog = new Logger.Logger(path.join(__dirname,
"../httpaccess.log"));
// webserver init -----------------------------------------------------
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);
// 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);
});
// acp init -----------------------------------------------------------
self.acp = require("./acp")(self);
// background tasks init ----------------------------------------------
require("./bgtask")(self);
// media metadata retriever init --------------------------------------
// TODO make the constructor just accept cfg?
self.infogetter = require("./get-info")(self);
};
Server.prototype.getHTTPIP = function (req) {
var raw = req.connection.remoteAddress; var raw = req.connection.remoteAddress;
var forward = req.header("x-forwarded-for"); 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]; var ip = forward.split(",")[0];
Logger.syslog.log("REVPROXY " + raw + " => " + ip); Logger.syslog.log("REVPROXY " + raw + " => " + ip);
return ip; return ip;
} }
return raw; return raw;
} };
function getSocketIP(socket) { Server.prototype.getSocketIP = function (socket) {
var raw = socket.handshake.address.address; 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") { if(typeof socket.handshake.headers["x-forwarded-for"] == "string") {
var ip = socket.handshake.headers["x-forwarded-for"] var ip = socket.handshake.headers["x-forwarded-for"]
.split(",")[0]; .split(",")[0];
@ -32,237 +161,131 @@ function getSocketIP(socket) {
} }
} }
return raw; return raw;
} };
var Server = { Server.prototype.isChannelLoaded = function (name) {
channels: [], name = name.toLowerCase();
channelLoaded: function (name) { for (var i = 0; i < this.channels.length; i++) {
for(var i in this.channels) { if (this.channels[i].canonical_name == name)
if(this.channels[i].canonical_name == name.toLowerCase()) return true;
return true; }
} return false;
return false; };
},
getChannel: function (name) { Server.prototype.getChannel = function (name) {
for(var i in this.channels) { var cname = name.toLowerCase();
if(this.channels[i].canonical_name == name.toLowerCase()) for (var i = 0; i < this.channels.length; i++) {
return this.channels[i]; if (this.channels[i].canonical_name == name)
return this.channels[i];
}
var c = new Channel(name, this);
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--;
} }
}
var c = new Channel(name, this); // Empty all outward references from the channel
this.channels.push(c); var keys = Object.keys(chan);
return c; for (var i in keys) {
}, delete chan[keys[i]];
unloadChannel: function(chan) { }
if(chan.registered) chan.dead = true;
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 () { Server.prototype.logHTTP = function (req, status) {
self.ips[ip]--; if (status === undefined)
}.bind(self)); status = 200;
if(!(ip in self.ips)) var ip = req.connection.remoteAddress;
self.ips[ip] = 0; var ip2 = req.header("x-forwarded-for") ||
self.ips[ip]++; 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(" "));
};
if(self.ips[ip] > Server.cfg["ip-connection-limit"]) { Server.prototype.handleSocketConnection = function (socket) {
socket.emit("kick", { var self = this;
reason: "Too many connections from your IP address" 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.disconnect(true);
return;
} }
});
// finally a valid user socket.on("disconnect", function () {
Logger.syslog.log("Accepted socket from /" + socket._ip); self.ipCount[ip]--;
new User(socket, self); });
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, self);
};
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);
};
var singleton = null;
module.exports = {
init: function (cfg) {
singleton = new Server(cfg);
return singleton;
}, },
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 getServer: function () {
self.api = require("./api")(self); return singleton;
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); Logger.syslog.log("Starting CyTube v" + VERSION);
var chanlogpath = path.join(__dirname, "../chanlogs"); var chanlogpath = path.join(__dirname, "../chanlogs");
fs.exists(chanlogpath, function (exists) { fs.exists(chanlogpath, function (exists) {
exists || fs.mkdir(chanlogpath); exists || fs.mkdir(chanlogpath);
@ -273,16 +296,17 @@ fs.exists(chandumppath, function (exists) {
exists || fs.mkdir(chandumppath); exists || fs.mkdir(chandumppath);
}); });
Config.load(Server, path.join(__dirname, "../cfg.json"), function () { var x = {};
Server.init(); Config.load(x, path.join(__dirname, "../cfg.json"), function () {
if(!Server.cfg["debug"]) { module.exports.init(x.cfg);
if(!singleton.cfg["debug"]) {
process.on("uncaughtException", function (err) { process.on("uncaughtException", function (err) {
Logger.errlog.log("[SEVERE] Uncaught Exception: " + err); Logger.errlog.log("[SEVERE] Uncaught Exception: " + err);
Logger.errlog.log(err.stack); Logger.errlog.log(err.stack);
}); });
process.on("SIGINT", function () { process.on("SIGINT", function () {
Server.shutdown(); singleton.shutdown();
}); });
} }
}); });