Compare commits

..

No commits in common. "c3e9bbaa5aa74cf3334c11fdc95ab108361c8c75" and "1fc04f05b58fc30ea384bcb357cd40ec797c9cc6" have entirely different histories.

3 changed files with 382 additions and 410 deletions

View File

@ -8,11 +8,11 @@ var os = require('os');
//var url = require('url'); //var url = require('url');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var http = require('http');
//var https = require('https'); //var https = require('https');
var YAML = require('js-yaml'); var YAML = require('js-yaml');
var TOML = require('toml'); var TOML = require('toml');
var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8')); var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8'));
/* /*
if ('function' !== typeof TOML.stringify) { if ('function' !== typeof TOML.stringify) {
TOML.stringify = require('json2toml'); TOML.stringify = require('json2toml');
@ -314,186 +314,92 @@ function askForConfig(state, mainCb) {
next(); next();
} }
var RC; var utils = {
request: function request(opts, fn) {
if (!opts) { opts = {}; }
var service = opts.service || 'config';
var req = http.request({
socketPath: state._ipc.path
, method: opts.method || 'GET'
, path: '/rpc/' + service
}, function (resp) {
var body = '';
function parseConfig(err, text) { function finish() {
function handleConfig(err, config) { if (200 !== resp.statusCode) {
//console.log('CONFIG'); console.warn(resp.statusCode);
//console.log(config); console.warn(body || ('get' + service + ' failed'));
state.config = config; //cb(new Error("not okay"), body);
var verstrd = [ pkg.name + ' daemon v' + state.config.version ]; return;
if (state.config.version && state.config.version !== pkg.version) {
console.info(verstr.join(' '), verstrd.join(' '));
} else {
console.info(verstr.join(' '));
} }
if (!body) { fn(null, null); return; }
try {
body = JSON.parse(body);
} catch(e) {
// ignore
}
fn(null, body);
}
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
// ECONNREFUSED - leftover socket just needs to be restarted
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
if (opts._taketwo) {
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to."); console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
console.error(err); console.error(err);
} else if ('ENOTSOCK' === err.code) { return;
}
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
if (err) { fn(err); return; }
opts._taketwo = true;
setTimeout(function () {
utils.request(opts, fn);
}, 2500);
});
return;
}
if ('ENOTSOCK' === err.code) {
console.error(err); console.error(err);
return; return;
} else { }
console.error(err); console.error(err);
} return;
if (err) { process.exit(101); return; }
//
// check for init first, before anything else
// because it has arguments that may help in
// the next steps
//
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, function (err) {
if (err) {
console.error("Error while initializing config [init]:");
throw err;
}
getToken(function (err) {
if (err) {
console.error("Error while getting token [init]:");
throw err;
}
parseCli(state);
}); });
}); req.end();
return;
} }
, putConfig: function putConfig(service, args, fn) {
var req = http.request({
socketPath: state._ipc.path
, method: 'POST'
, path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args))
}, function (resp) {
if (!state.config.relay || !state.config.token) { function finish() {
if (!state.config.relay) {
state.config.relay = 'telebit.cloud';
}
//console.log("question the user?", Date.now());
askForConfig(state, function (err, state) {
// no errors actually get passed, so this is just future-proofing
if (err) { throw err; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
}
//console.log("done questioning:", Date.now());
if (!state.token && !state.config.token) {
if (err) {
console.error("Error while initializing config [init]:");
throw err;
}
getToken(function (err) {
if (err) {
console.error("Error while getting token [init]:");
throw err;
}
parseCli(state);
});
} else {
parseCli(state);
}
});
return;
}
//console.log("no questioning:");
parseCli(state);
}
function parseCli(/*state*/) {
var special = [
'false', 'none', 'off', 'disable'
, 'true', 'auto', 'on', 'enable'
];
if (-1 !== argv.indexOf('init')) {
RC.request({ service: 'list', method: 'POST', data: [] }, handleRemoteRequest('list'));
return;
}
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
if (key !== argv[0]) {
return false;
}
if (argv[1]) {
if (String(argv[1]) === String(parseInt(argv[1], 10))) {
// looks like a port
argv[1] = parseInt(argv[1], 10);
} else if (/\/|\\/.test(argv[1])) {
// looks like a path
argv[1] = path.resolve(argv[1]);
// TODO make a default assignment here
} else if (-1 === special.indexOf(argv[1])) {
console.error("Not sure what you meant by '" + argv[1] + "'.");
console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'");
return true;
}
RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0]));
return true;
}
return true;
})) {
return;
}
// Two styles:
// http 3000
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
}
RC.request({ service: argv[0], method: 'POST', data: argv.slice(1) }, handleRemoteRequest(argv[0]));
return true;
}
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return;
}
help();
process.exit(11);
}
try {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
try {
state._clientConfig = TOML.parse(text || '');
} catch(e3) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
RC = require('../lib/remote-control-client.js').create(state);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info("");
}
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
function handleRemoteRequest(service, fn) {
return function (err, body) {
if ('function' === typeof fn) { if ('function' === typeof fn) {
fn(err, body); // XXX was resp fn(null, resp);
return; return;
} }
console.info(""); console.info("");
if (err) { if (200 !== resp.statusCode) {
console.warn("'" + service + "' may have failed." console.warn("'" + service + "' may have failed."
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
console.warn(err.statusCode, err.message); console.warn(resp.statusCode, body);
//cb(new Error("not okay"), body); //cb(new Error("not okay"), body);
return; return;
} }
@ -507,7 +413,6 @@ function parseConfig(err, text) {
body = JSON.parse(body); body = JSON.parse(body);
} catch(e) { } catch(e) {
// ignore // ignore
} }
if ("AWAIT_AUTH" === body.code) { if ("AWAIT_AUTH" === body.code) {
@ -534,10 +439,62 @@ function parseConfig(err, text) {
} }
console.info(); console.info();
} }
};
} }
function getToken(fn) { var body = '';
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
console.error('Put Config Error:');
console.error(err);
return;
});
req.end();
}
};
// Two styles:
// http 3000
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
function packConfig(config) {
return Object.keys(config).map(function (key) {
var val = config[key];
if ('undefined' === val) {
throw new Error("'undefined' used as a string value");
}
if ('undefined' === typeof val) {
//console.warn('[DEBUG]', key, 'is present but undefined');
return;
}
if (val && 'object' === typeof val && !Array.isArray(val)) {
val = JSON.stringify(val);
}
return key + ':' + val; // converts arrays to strings with ,
});
}
function getToken(err, state) {
if (err) {
console.error("Error while initializing config [init]:");
throw err;
}
state.relay = state.config.relay; state.relay = state.config.relay;
// { _otp, config: {} } // { _otp, config: {} }
@ -563,7 +520,17 @@ function parseConfig(err, text) {
state.config._otp = state.config._otp = authReq.otp; state.config._otp = state.config._otp = authReq.otp;
if (!state.config.token && state._can_pair) { if (!state.config.token && state._can_pair) {
console.info(TPLS.remote.code.replace(/0000/g, state.config._otp)); console.info("");
console.info("==============================================");
console.info(" Hey, Listen! ");
console.info("==============================================");
console.info(" ");
console.info(" GO CHECK YOUR EMAIL! ");
console.info(" ");
console.info(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, state.config._otp));
console.info(" ");
console.info("==============================================");
console.info("");
} }
next(); next();
@ -574,7 +541,7 @@ function parseConfig(err, text) {
state._connecting = true; state._connecting = true;
// TODO use php-style object querification // TODO use php-style object querification
RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) { utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) { if (err) {
state._error = err; state._error = err;
console.error("Error while initializing config [connect]:"); console.error("Error while initializing config [connect]:");
@ -583,7 +550,7 @@ function parseConfig(err, text) {
} }
console.info("waiting..."); console.info("waiting...");
next(); next();
})); });
} }
, offer: function (token, next) { , offer: function (token, next) {
//console.log("[offer] Pairing Enabled by Relay"); //console.log("[offer] Pairing Enabled by Relay");
@ -598,7 +565,7 @@ function parseConfig(err, text) {
} catch(e) { } catch(e) {
console.warn("[warning] could not decode token"); console.warn("[warning] could not decode token");
} }
RC.request({ service: 'config', method: 'POST', data: state.config }, handleRemoteRequest('config', function (err/*, body*/) { utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) { if (err) {
state._error = err; state._error = err;
console.error("Error while initializing config [offer]:"); console.error("Error while initializing config [offer]:");
@ -607,14 +574,14 @@ function parseConfig(err, text) {
} }
//console.log("Pairing Enabled Locally"); //console.log("Pairing Enabled Locally");
next(); next();
})); });
} }
, granted: function (_, next) { , granted: function (_, next) {
//console.log("[grant] Pairing complete!"); //console.log("[grant] Pairing complete!");
next(); next();
} }
, end: function () { , end: function () {
RC.request({ service: 'enable', method: 'POST', data: [] }, handleRemoteRequest('enable', function (err) { utils.putConfig('enable', [], function (err) {
if (err) { console.error(err); return; } if (err) { console.error(err); return; }
console.info("Success"); console.info("Success");
@ -634,14 +601,148 @@ function parseConfig(err, text) {
} }
// end workaround // end workaround
//parseCli(state); parseCli(state);
fn(); });
}));
} }
}); });
} }
RC.request({ service: 'config', method: 'GET' }, handleRemoteRequest('config', handleConfig)); function parseCli(/*state*/) {
var special = [
'false', 'none', 'off', 'disable'
, 'true', 'auto', 'on', 'enable'
];
if (-1 !== argv.indexOf('init')) {
utils.putConfig('list', []/*, function (err) {
}*/);
return;
}
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
if (key !== argv[0]) {
return false;
}
if (argv[1]) {
if (String(argv[1]) === String(parseInt(argv[1], 10))) {
// looks like a port
argv[1] = parseInt(argv[1], 10);
} else if (/\/|\\/.test(argv[1])) {
// looks like a path
argv[1] = path.resolve(argv[1]);
// TODO make a default assignment here
} else if (-1 === special.indexOf(argv[1])) {
console.error("Not sure what you meant by '" + argv[1] + "'.");
console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'");
return true;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
return true;
})) {
return;
}
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return;
}
help();
process.exit(11);
}
function handleConfig(err, config) {
//console.log('CONFIG');
//console.log(config);
state.config = config;
var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
if (state.config.version && state.config.version !== pkg.version) {
console.info(verstr.join(' '), verstrd.join(' '));
} else {
console.info(verstr.join(' '));
}
if (err) { console.error(err); process.exit(101); return; }
//
// check for init first, before anything else
// because it has arguments that may help in
// the next steps
//
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, getToken);
return;
}
if (!state.config.relay || !state.config.token) {
if (!state.config.relay) {
state.config.relay = 'telebit.cloud';
}
//console.log("question the user?", Date.now());
askForConfig(state, function (err, state) {
// no errors actually get passed, so this is just future-proofing
if (err) { throw err; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
}
//console.log("done questioning:", Date.now());
if (!state.token && !state.config.token) {
getToken(err, state);
} else {
parseCli(state);
}
});
return;
}
//console.log("no questioning:");
parseCli(state);
}
function parseConfig(err, text) {
try {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
try {
state._clientConfig = TOML.parse(text || '');
} catch(e3) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
common._init(
// make a default working dir and log dir
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
);
state._ipc = common.pipename(state._clientConfig, true);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info("");
}
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
utils.request({ service: 'config' }, handleConfig);
} }
var parsers = { var parsers = {

View File

@ -141,7 +141,7 @@ Use cases:
ssh = "Telebit SSH - The UNSTOPPABLE way to remote into your devices. ssh = "Telebit SSH - The UNSTOPPABLE way to remote into your devices.
usage: telebit ssh <auto|port|none> usage: telebit ssh <auto|port>
All https traffic will be inspected to see if it looks like ssh Once enabled all traffic that looks All https traffic will be inspected to see if it looks like ssh Once enabled all traffic that looks
@ -150,7 +150,6 @@ All https traffic will be inspected to see if it looks like ssh Once enabled all
ssh <port> forward ssh traffic to non-standard port ssh <port> forward ssh traffic to non-standard port
ex: telebit ssh 22 ex: explicitly forward ssh-looking packets to localhost:22 ex: telebit ssh 22 ex: explicitly forward ssh-looking packets to localhost:22
ssh none Disables ssh tunneling
Telebit SSH Client Telebit SSH Client
@ -452,17 +451,5 @@ The secret flags are:
[remote] [remote]
version = "telebit remote v{version}" version = "telebit remote v{version}"
code = "
==============================================
Hey, Listen!
==============================================
GO CHECK YOUR EMAIL!
DEVICE PAIR CODE: 0000
==============================================
"
[daemon] [daemon]
version = "telebit daemon v{version}" version = "telebit daemon v{version}"

View File

@ -1,116 +0,0 @@
'use strict';
var os = require('os');
var path = require('path');
var http = require('http');
var common = require('./cli-common.js');
function packConfig(config) {
return Object.keys(config).map(function (key) {
var val = config[key];
if ('undefined' === val) {
throw new Error("'undefined' used as a string value");
}
if ('undefined' === typeof val) {
//console.warn('[DEBUG]', key, 'is present but undefined');
return;
}
if (val && 'object' === typeof val && !Array.isArray(val)) {
val = JSON.stringify(val);
}
return key + ':' + val; // converts arrays to strings with ,
});
}
module.exports.create = function (state) {
common._init(
// make a default working dir and log dir
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
);
state._ipc = common.pipename(state._clientConfig, true);
function makeResponder(service, resp, fn) {
var body = '';
function finish() {
var err;
if (200 !== resp.statusCode) {
err = new Error(body || ('get' + service + ' failed'));
err.statusCode = resp.statusCode;
err.code = "E_REQUEST";
}
try {
body = JSON.parse(body);
} catch(e) {
// ignore
}
fn(err, body);
}
if (!resp.headers['content-length'] && !resp.headers['content-type']) {
finish();
return;
}
// TODO use readable
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', finish);
}
var RC = {};
RC.request = function request(opts, fn) {
if (!opts) { opts = {}; }
var service = opts.service || 'config';
var args = opts.data;
if (args && 'control' === service) {
args = packConfig(args);
}
var json = JSON.stringify(args);
var url = '/rpc/' + service;
if (json) {
url += ('?_body=' + encodeURIComponent(json));
}
var method = opts.method || (args && 'POST') || 'GET';
var req = http.request({
socketPath: state._ipc.path
, method: method
, path: url
}, function (resp) {
makeResponder(service, resp, fn);
});
req.on('error', function (err) {
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
// ECONNREFUSED - leftover socket just needs to be restarted
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
if (opts._taketwo) {
fn(err);
return;
}
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
if (err) { fn(err); return; }
opts._taketwo = true;
setTimeout(function () {
RC.request(opts, fn);
}, 2500);
});
return;
}
fn(err);
});
if ('POST' === method && opts.data) {
req.write(json || opts.data);
}
req.end();
};
return RC;
};