diff --git a/lib/web/acp.js b/lib/web/acp.js new file mode 100644 index 00000000..3d672f07 --- /dev/null +++ b/lib/web/acp.js @@ -0,0 +1,112 @@ +var path = require("path"); +var fs = require("fs"); +var webserver = require("./webserver"); +var sendJade = require("./jade").sendJade; +var Logger = require("../logger"); +var db = require("../database"); + +function checkAdmin(cb) { + return function (req, res) { + webserver.logRequest(req); + var auth = req.cookies.auth; + if (!auth) { + res.send(403); + return; + } + db.users.verifyAuth(auth, function (err, user) { + if (err) { + if (err === "Invalid auth string" || + err === "Auth string does not match an existing user") { + res.send(403); + } else { + res.send(500); + } + return; + } + + if (user.global_rank < 255) { + res.send(403); + Logger.eventlog.log("[acp] Attempted GET /acp from non-admin " + + user.name + "@" + webserver.ipForRequest(req)); + return; + } + + cb(req, res, user); + }); + }; +} + +/** + * Handles a request for the ACP + */ +function handleAcp(req, res, user) { + sendJade(res, "acp", { + loginName: user.name, + loggedIn: true + }); +} + +/** + * Streams the last length bytes of file to the given HTTP response + */ +function readLog(res, file, length) { + fs.stat(file, function (err, data) { + if (err) { + res.send(500); + return; + } + + var start = Math.max(0, data.size - length); + if (isNaN(start)) { + res.send(500); + } + var end = Math.max(0, data.size - 1); + if (isNaN(end)) { + res.send(500); + } + fs.createReadStream(file, { start: start, end: end }) + .pipe(res); + }); +} + +/** + * Handles a request to read the syslog + */ +function handleReadSyslog(req, res) { + readLog(res, path.join(__dirname, "..", "..", "sys.log"), 1048576); +} + +/** + * Handles a request to read the error log + */ +function handleReadErrlog(req, res) { + readLog(res, path.join(__dirname, "..", "..", "error.log"), 1048576); +} + +/** + * Handles a request to read the http log + */ +function handleReadHttplog(req, res) { + readLog(res, path.join(__dirname, "..", "..", "http.log"), 1048576); +} + +/** + * Handles a request to read a channel log + */ +function handleReadChanlog(req, res) { + if (!req.params.name.match(/^[\w-]{1,30}$/)) { + res.send(400); + return; + } + readLog(res, path.join(__dirname, "..", "..", "chanlogs", req.params.name + ".log"), 1048576); +} + +module.exports = { + init: function (app) { + app.get("/acp", checkAdmin(handleAcp)); + app.get("/acp/syslog", checkAdmin(handleReadSyslog)); + app.get("/acp/errlog", checkAdmin(handleReadErrlog)); + app.get("/acp/httplog", checkAdmin(handleReadHttplog)); + app.get("/acp/chanlog/:name", checkAdmin(handleReadChanlog)); + } +}; diff --git a/lib/web/webserver.js b/lib/web/webserver.js index d4fc4644..62861893 100644 --- a/lib/web/webserver.js +++ b/lib/web/webserver.js @@ -153,38 +153,6 @@ function handleIndex(req, res) { }); } -/** - * Handles a request for the ACP - */ -function handleAcp(req, res) { - logRequest(req); - - var auth = req.cookies.auth || ""; - db.users.verifyAuth(auth, function (err, user) { - if (err) { - if (err === "Invalid auth string" || - err === "Auth string does not match an existing user") { - res.send(403); - } else { - res.send(500); - } - return; - } - - if (user.global_rank < 255) { - res.send(403); - Logger.eventlog.log("[acp] Attempted GET /acp from non-admin " + user.name + - "@" + ipForRequest(req)); - return; - } - - sendJade(res, "acp", { - loginName: user.name, - loggedIn: true - }); - }); -} - /** * Handles a request for the socket.io information */ @@ -200,8 +168,7 @@ function handleSocketConfig(req, res) { "',ALLOW_SSL="+Config.get("https.enabled")+";" + (Config.get("https.enabled") ? "if(location.protocol=='https:'||USEROPTS.secure_connection){" + - "IO_URL=WEB_URL=SSL_URL;}" : "")); -} + "IO_URL=WEB_URL=SSL_URL;}" : "")); } module.exports = { /** @@ -220,9 +187,12 @@ module.exports = { app.get("/r/:channel", handleChannel); app.get("/", handleIndex); app.get("/sioconfig", handleSocketConfig); - app.get("/acp", handleAcp); + require("./auth").init(app); + require("./account").init(app); + require("./acp").init(app); app.all("*", function (req, res, next) { if (isSuspicious(req)) { + console.log("isSuspicious"); logRequest(req, 403); res.status(403); if (req.header("user-agent").toLowerCase() === "zmeu") { @@ -238,8 +208,6 @@ module.exports = { next(); }); app.use(express.static("www")); - require("./auth").init(app); - require("./account").init(app); }, logRequest: logRequest, diff --git a/templates/acp.jade b/templates/acp.jade index a9f84f3b..232b0439 100644 --- a/templates/acp.jade +++ b/templates/acp.jade @@ -12,6 +12,10 @@ html(lang="en") #nav-collapsible.collapse.navbar-collapse ul.nav.navbar-nav mixin navdefaultlinks("/acp") + li#nav-acp-section.dropdown + a#nav-acp-dd-toggle.dropdown-toggle(data-toggle="dropdown", href="javascript:void(0)") Menu + span.caret + ul.dropdown-menu mixin navloginlogout("/acp") section#mainpage .container @@ -22,6 +26,7 @@ html(lang="en") div.input-group-btn button#acp-syslog-btn.btn.btn-default Syslog button#acp-errlog-btn.btn.btn-default Error log + button#acp-httplog-btn.btn.btn-default HTTP log input#acp-chanlog-name.form-control(type="text", placeholder="Channel name") pre#acp-log #acp-announcements.col-md-12(style="display: none") @@ -40,7 +45,7 @@ html(lang="en") .form-group .col-sm-10.col-sm-offset-2 button#acp-announce-submit.btn.btn-primary Announce - #acp-global-bans.col-md-12 + #acp-global-bans.col-md-12(style="display: none") h3 Global Bans table.table.table-striped.table-bordered thead @@ -62,5 +67,60 @@ html(lang="en") .form-group .col-sm-9.col-sm-offset-3 button#acp-gban-submit.btn.btn-danger Add ban + #acp-user-lookup.col-md-12(style="display: none") + h3 Users + .input-group(style="max-width: 25%") + input#acp-ulookup-name.form-control(type="text") + span.input-group-btn + button#acp-ulookup-btn.btn.btn-default Search + table.table.table-bordered.table-striped(style="margin-top: 20px") + thead + tr + th#acp-ulookup-id ID + th#acp-ulookup-name Name + th#acp-ulookup-rank Rank + th#acp-ulookup-email Email + #acp-channel-lookup.col-md-12(style="display: none") + h3 Channels + form.form-inline(action="javascript:void(0)", role="form") + .form-group + input#acp-clookup-value.form-control(type="text", placeholder="Name") + .form-group + select#acp-clookup-field.form-control + option(value="name") Channel Name + option(value="owner") Channel Owner + button#acp-clookup-submit.btn.btn-default Search + table.table.table-bordered.table-striped(style="margin-top: 20px") + thead + tr + th ID + th Name + th Owner + #acp-loaded-channels.col-md-12(style="display: none") + h3 Loaded Channels + button#acp-lchannels-refresh.btn.btn-default Refresh + table.table.table-bordered.table-striped(style="margin-top: 20px") + thead + tr + th Title + th Usercount + th Now Playing + th Registered + th Public + th Force Unload + #acp-eventlog.col-md-12(style="display: none") + h3 Event Log + strong Filter event types + select#acp-eventlog-filter.form-control(multiple="multiple", style="max-width: 25%") + pre#acp-eventlog-text(style="margin-top: 20px") + #acp-stats.col-md-12(style="display: none") + h3 User Count + canvas#stat_users(width="100%", height="400") + h3 Channel Count + canvas#stat_channels(width="100%", height="400") + h3 Memory Usage + canvas#stat_mem(width="100%", height="400") + include footer mixin footer() + script(src="/js/acp.js") diff --git a/www/css/acp.css b/www/css/acp.css index 271c5bd2..d006649e 100644 --- a/www/css/acp.css +++ b/www/css/acp.css @@ -7,4 +7,7 @@ border-top-left-radius: 0; border-top-right-radius: 0; border-top: none; + max-height: 500px; + overflow-y: scroll; + overflow-x: hidden; } diff --git a/www/js/acp.js b/www/js/acp.js new file mode 100644 index 00000000..d90d5036 --- /dev/null +++ b/www/js/acp.js @@ -0,0 +1,57 @@ +function addMenuItem(target, text) { + var ul = $("#nav-acp-section ul"); + var li = $("
").appendTo(ul); + var a = $("").attr("href", "javascript:void(0)") + .text(text) + .appendTo(li) + .click(function () { + $(".col-md-12").hide(); + $(target).show(); + }); +}; + +addMenuItem("#acp-logview", "Log Viewer"); +addMenuItem("#acp-announcements", "Announcements"); +addMenuItem("#acp-global-bans", "Global Bans"); +addMenuItem("#acp-user-lookup", "Users"); +addMenuItem("#acp-channel-lookup", "Channels"); +addMenuItem("#acp-loaded-channels", "Active Channels"); +addMenuItem("#acp-eventlog", "Event Log"); +addMenuItem("#acp-stats", "Stats"); + +function readSyslog() { + $.ajax(location.protocol + "//" + location.host + "/acp/syslog").done(function (data) { + $("#acp-log").text(data); + $("#acp-log").scrollTop($("#acp-log").prop("scrollHeight")); + }); +} + +function readErrlog() { + $.ajax(location.protocol + "//" + location.host + "/acp/errlog").done(function (data) { + $("#acp-log").text(data); + $("#acp-log").scrollTop($("#acp-log").prop("scrollHeight")); + }); +} + +function readHttplog() { + $.ajax(location.protocol + "//" + location.host + "/acp/httplog").done(function (data) { + $("#acp-log").text(data); + $("#acp-log").scrollTop($("#acp-log").prop("scrollHeight")); + }); +} + +function readChanlog(name) { + $.ajax(location.protocol + "//" + location.host + "/acp/chanlog/" + name).done(function (data) { + $("#acp-log").text(data); + $("#acp-log").scrollTop($("#acp-log").prop("scrollHeight")); + }); +} + +$("#acp-syslog-btn").click(readSyslog); +$("#acp-errlog-btn").click(readErrlog); +$("#acp-httplog-btn").click(readHttplog); +$("#acp-chanlog-name").keyup(function (ev) { + if (ev.keyCode === 13) { + readChanlog($("#acp-chanlog-name").val()); + } +});