From 955903511829239c090040823704334cc97373fc Mon Sep 17 00:00:00 2001 From: Xaekai Date: Fri, 15 Jul 2016 22:06:27 -0700 Subject: [PATCH 1/3] Add a service socket to enable out of band access to the process commandline --- config.template.yaml | 6 +++++ index.js | 30 ++++++++++++++++++++++++ servcmd.sh.js | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/config.js | 4 ++++ src/servsock.js | 53 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100755 servcmd.sh.js create mode 100644 src/servsock.js diff --git a/config.template.yaml b/config.template.yaml index 269428ad..e88873e1 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -237,3 +237,9 @@ setuid: # concurrent updates), then run `node lib/channel-storage/migrate.js`. channel-storage: type: 'file' + +# Allows for external services to access the system commandline +# Useful for setups where stdin isn't available such as when using PM2 +service-socket: + enabled: false + socket: 'service.sock' diff --git a/index.js b/index.js index 729d6cb4..f82a3176 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,7 @@ process.stdin.on("data", function (data) { } }); +var net = require('net'); function handleLine(line) { if (line === "/reload") { Logger.syslog.log("Reloading config"); @@ -75,5 +76,34 @@ function handleLine(line) { } } else if (line.indexOf("/reload-partitions") === 0) { sv.reloadPartitionMap(); + } else if (line.indexOf("/globalban") === 0) { + var args = line.split(/\s+/); args.shift(); + if (args.length >= 2 && net.isIP(args[0]) !== 0) { + var ip = args.shift(); + var comment = args.join(' '); + require("./lib/database").globalBanIP(ip, comment, function (err, res) { + if (!err) { + Logger.eventlog.log("[acp] " + "SYSTEM" + " global banned " + ip); + } + }) + } + } else if (line.indexOf("/unglobalban") === 0) { + var args = line.split(/\s+/); args.shift(); + if (args.length === 1 && net.isIP(args[0]) !== 0) { + var ip = args.shift(); + require("./lib/database").globalUnbanIP(ip, function (err, res) { + if (!err) { + Logger.eventlog.log("[acp] " + "SYSTEM" + " un-global banned " + ip); + } + }) + } } } + +// Go Go Gadget Service Socket +if (Config.get("service-socket.enabled")) { + Logger.syslog.log("Opening service socket"); + var ServiceSocket = require('./lib/servsock'); + var server = new ServiceSocket; + server.init(handleLine, Config.get("service-socket.socket")); +} diff --git a/servcmd.sh.js b/servcmd.sh.js new file mode 100755 index 00000000..c90d4228 --- /dev/null +++ b/servcmd.sh.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node +/* +** CyTube Service Socket Commandline +*/ + +var Config = require("./lib/config"); +Config.load("config.yaml"); + +if(!Config.get("service-socket.enabled")){ + console.error('The Service Socket is not enabled.'); + process.exit(1); +} + +const SOCKETFILE = Config.get("service-socket.socket"); +var net = require('net'); + +var client = net.createConnection(SOCKETFILE) + .on('connect', () => { + console.log("Connected."); + }) + .on('data', (msg) => { + msg = msg.toString(); + + if(msg === '__disconnect'){ + console.log('Server shutting down.'); + return cleanup(); + } + + // Generic message handler + console.info('Server:', data) + }) + .on('error', (data) => { + console.error('Unable to connect to Service Socket.'); + process.exit(1); + }) + ; + +var inputbuffer = ""; +process.stdin.on("data", (data) => { + inputbuffer += data; + if (inputbuffer.indexOf("\n") !== -1) { + var line = inputbuffer.substring(0, inputbuffer.indexOf("\n")); + inputbuffer = inputbuffer.substring(inputbuffer.indexOf("\n") + 1); + // Let the client escape + if(line === 'exit'){ return cleanup(); } + if(line === 'quit'){ return cleanup(); } + client.write(line); + } +}); + +function cleanup(){ + console.log('\n',"Terminating.",'\n'); + client.end(); + process.exit(0); +} +process.on('SIGINT', cleanup); diff --git a/src/config.js b/src/config.js index 6b24f2ae..e3d0ca82 100644 --- a/src/config.js +++ b/src/config.js @@ -116,6 +116,10 @@ var defaults = { }, "channel-storage": { type: "file" + }, + "service-socket": { + enabled: false, + socket: "service.sock" } }; diff --git a/src/servsock.js b/src/servsock.js new file mode 100644 index 00000000..b368112d --- /dev/null +++ b/src/servsock.js @@ -0,0 +1,53 @@ +var fs = require('fs'); +var net = require('net'); + +export default class ServiceSocket { + + constructor() { + this.connections = {}; + } + + init(handler, socket){ + this.handler = handler; + this.socket = socket; + + fs.stat(this.socket, (err, stats) => { + if (err) { + return this.openServiceSocket(); + } + fs.unlink(this.socket, (err) => { + if(err){ + console.error(err); process.exit(0); + } + return this.openServiceSocket(); + }); + }); + } + + openServiceSocket(){ + this.server = net.createServer((stream) => { + let id = Date.now(); + this.connections[id] = stream; + stream.on('end', () => { + delete this.connections[id]; + }); + stream.on('data', (msg) => { + this.handler(msg.toString()); + }); + }).listen(this.socket); + process.on('exit', this.closeServiceSocket.bind(this)); + } + + closeServiceSocket() { + if(Object.keys(this.connections).length){ + let clients = Object.keys(this.connections); + while(clients.length){ + let client = clients.pop(); + this.connections[client].write('__disconnect'); + this.connections[client].end(); + } + } + this.server.close(); + } + +} From 670cb97e79c2b1fe5b89cb15b38491137c50e5d2 Mon Sep 17 00:00:00 2001 From: Xaekai Date: Wed, 20 Jul 2016 03:01:34 -0700 Subject: [PATCH 2/3] Complete rewrite of the service socket client Add one more command to the service commandline --- index.js | 12 ++++++ servcmd.sh.js | 114 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index f82a3176..ed8ecac9 100644 --- a/index.js +++ b/index.js @@ -97,6 +97,18 @@ function handleLine(line) { } }) } + } else if (line.indexOf("/unloadchan") === 0) { + var args = line.split(/\s+/); args.shift(); + if(args.length){ + var name = args.shift(); + var chan = sv.getChannel(name); + var users = Array.prototype.slice.call(chan.users); + chan.emit("empty"); + users.forEach(function (u) { + u.kick("Channel shutting down"); + }); + Logger.eventlog.log("[acp] " + "SYSTEM" + " forced unload of " + name); + } } } diff --git a/servcmd.sh.js b/servcmd.sh.js index c90d4228..1786171e 100755 --- a/servcmd.sh.js +++ b/servcmd.sh.js @@ -3,6 +3,23 @@ ** CyTube Service Socket Commandline */ +const readline = require('readline'); +const spawn = require('child_process').spawn; +const util = require('util'); +const net = require('net'); +const fs = require('fs'); + +const COMPLETIONS = [ + "/delete_old_tables", + "/gc", + "/globalban", + "/reload", + "/reload-partitions", + "/switch", + "/unglobalban", + "/unloadchan" +]; + var Config = require("./lib/config"); Config.load("config.yaml"); @@ -12,13 +29,56 @@ if(!Config.get("service-socket.enabled")){ } const SOCKETFILE = Config.get("service-socket.socket"); -var net = require('net'); -var client = net.createConnection(SOCKETFILE) - .on('connect', () => { - console.log("Connected."); - }) - .on('data', (msg) => { +// Wipe the TTY +process.stdout.write('\x1Bc'); + +var commandline, eventlog, syslog; +var client = net.createConnection(SOCKETFILE).on('connect', () => { + commandline = readline.createInterface({ + input: process.stdin, + output: process.stdout, + completer: tabcomplete + }); + commandline.setPrompt("> ", 2); + commandline.on("line", function(line) { + if(line === 'exit'){ return cleanup(); } + if(line === 'quit'){ return cleanup(); } + if(line.match(/^\/globalban/) && line.split(/\s+/).length === 2){ + console.log('You must provide a reason') + return commandline.prompt(); + } + client.write(line); + commandline.prompt(); + }); + commandline.on('close', function() { + return cleanup(); + }); + commandline.on("SIGINT", function() { + commandline.clearLine(); + commandline.question("Terminate connection? ", function(answer) { + return answer.match(/^y(es)?$/i) ? cleanup() : commandline.output.write("> "); + }); + }); + commandline.prompt(); + + console.log = function() { cmdouthndlr("log", arguments); } + console.warn = function() { cmdouthndlr("warn", arguments); } + console.error = function() { cmdouthndlr("error", arguments); } + // console.info is reserved in this script for the exit message + // this prevents an extraneous final prompt from readline on terminate + + eventlog = spawn('tail', ['-f', 'events.log']); + eventlog.stdout.on('data', function (data) { + console.log(data.toString().replace(/^(.+)$/mg, 'events: $1')); + }); + + syslog = spawn('tail', ['-f', 'sys.log']); + syslog.stdout.on('data', function (data) { + console.log(data.toString().replace(/^(.+)$/mg, 'sys: $1')); + }); + + }).on('data', (msg) => { msg = msg.toString(); if(msg === '__disconnect'){ @@ -27,30 +87,30 @@ var client = net.createConnection(SOCKETFILE) } // Generic message handler - console.info('Server:', data) - }) - .on('error', (data) => { - console.error('Unable to connect to Service Socket.'); - process.exit(1); - }) - ; + console.log('server: ', data) -var inputbuffer = ""; -process.stdin.on("data", (data) => { - inputbuffer += data; - if (inputbuffer.indexOf("\n") !== -1) { - var line = inputbuffer.substring(0, inputbuffer.indexOf("\n")); - inputbuffer = inputbuffer.substring(inputbuffer.indexOf("\n") + 1); - // Let the client escape - if(line === 'exit'){ return cleanup(); } - if(line === 'quit'){ return cleanup(); } - client.write(line); - } -}); + }).on('error', (data) => { + console.error('Unable to connect to Service Socket.', data); + process.exit(1); + }); + +function cmdouthndlr(type, args) { + var t = Math.ceil((commandline.line.length + 3) / process.stdout.columns); + var text = util.format.apply(console, args); + commandline.output.write("\n\x1B[" + t + "A\x1B[0J"); + commandline.output.write(text + "\n"); + commandline.output.write(Array(t).join("\n\x1B[E")); + commandline._refreshLine(); +} function cleanup(){ - console.log('\n',"Terminating.",'\n'); + console.info('\n',"Terminating.",'\n'); + eventlog.stdin.end(); + syslog.stdin.end(); client.end(); process.exit(0); } -process.on('SIGINT', cleanup); + +function tabcomplete(line) { + return [COMPLETIONS.filter((cv)=>{ return cv.indexOf(line) == 0; }), line]; +} From 9655d2635a1d3c463126070b0e7dbeae223da130 Mon Sep 17 00:00:00 2001 From: Xaekai Date: Thu, 21 Jul 2016 18:17:38 -0700 Subject: [PATCH 3/3] Minor fixes --- index.js | 6 +++--- servcmd.sh.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ed8ecac9..a364c7a1 100644 --- a/index.js +++ b/index.js @@ -45,7 +45,7 @@ process.stdin.on("data", function (data) { } }); -var net = require('net'); +var validIP = require('net').isIP; function handleLine(line) { if (line === "/reload") { Logger.syslog.log("Reloading config"); @@ -78,7 +78,7 @@ function handleLine(line) { sv.reloadPartitionMap(); } else if (line.indexOf("/globalban") === 0) { var args = line.split(/\s+/); args.shift(); - if (args.length >= 2 && net.isIP(args[0]) !== 0) { + if (args.length >= 2 && validIP(args[0]) !== 0) { var ip = args.shift(); var comment = args.join(' '); require("./lib/database").globalBanIP(ip, comment, function (err, res) { @@ -89,7 +89,7 @@ function handleLine(line) { } } else if (line.indexOf("/unglobalban") === 0) { var args = line.split(/\s+/); args.shift(); - if (args.length === 1 && net.isIP(args[0]) !== 0) { + if (args.length >= 1 && validIP(args[0]) !== 0) { var ip = args.shift(); require("./lib/database").globalUnbanIP(ip, function (err, res) { if (!err) { diff --git a/servcmd.sh.js b/servcmd.sh.js index 1786171e..bb493368 100755 --- a/servcmd.sh.js +++ b/servcmd.sh.js @@ -105,8 +105,8 @@ function cmdouthndlr(type, args) { function cleanup(){ console.info('\n',"Terminating.",'\n'); - eventlog.stdin.end(); - syslog.stdin.end(); + eventlog.kill('SIGTERM'); + syslog.kill('SIGTERM'); client.end(); process.exit(0); }