WIP: use API to find tunnel
This commit is contained in:
parent
13326a89a6
commit
8fbd49f0e6
|
@ -135,56 +135,40 @@ function askForConfig(answers, mainCb) {
|
||||||
console.info("");
|
console.info("");
|
||||||
console.info("What relay will you be using? (press enter for default)");
|
console.info("What relay will you be using? (press enter for default)");
|
||||||
console.info("");
|
console.info("");
|
||||||
function parseUrl(hostname) {
|
|
||||||
var url = require('url');
|
|
||||||
var location = url.parse(hostname);
|
|
||||||
if (!location.protocol || /\./.test(location.protocol)) {
|
|
||||||
hostname = 'https://' + hostname;
|
|
||||||
location = url.parse(hostname);
|
|
||||||
}
|
|
||||||
hostname = location.hostname + (location.port ? ':' + location.port : '');
|
|
||||||
hostname = location.protocol.replace(/https?/, 'https') + '//' + hostname + location.pathname;
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
rl.question('relay [default: telebit.cloud]: ', function (relay) {
|
rl.question('relay [default: telebit.cloud]: ', function (relay) {
|
||||||
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
|
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
|
||||||
if (!relay) { relay = 'telebit.cloud'; }
|
if (!relay) { relay = 'telebit.cloud'; }
|
||||||
answers.relay = relay.trim();
|
answers.relay = relay.trim();
|
||||||
var urlstr = parseUrl(answers.relay) + '_apis/telebit.cloud/index.json';
|
var urlstr = common.parseUrl(answers.relay) + common.apiDirectory;
|
||||||
https.get(urlstr, function (resp) {
|
common.urequest({ url: urlstr, json: true }, function (err, resp, body) {
|
||||||
var body = '';
|
if (err) {
|
||||||
|
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
|
||||||
|
console.error(e);
|
||||||
|
askRelay(cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (200 !== resp.statusCode) {
|
if (200 !== resp.statusCode) {
|
||||||
console.error("[" + resp.statusCode + " Error] Failed to retrieve '" + urlstr + "'");
|
console.error("[" + resp.statusCode + " Error] Failed to retrieve '" + urlstr + "'");
|
||||||
askRelay(cb);
|
askRelay(cb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resp.on('data', function (chunk) {
|
if (Buffer.isBuffer(body) || 'object' !== typeof body) {
|
||||||
body += chunk.toString('utf8');
|
console.error("[Parse Error] Failed to retrieve '" + urlstr + "'");
|
||||||
});
|
console.error(body);
|
||||||
resp.on('end', function () {
|
askRelay(cb);
|
||||||
try {
|
return;
|
||||||
body = JSON.parse(body);
|
}
|
||||||
} catch(e) {
|
if (!body.api_host) {
|
||||||
console.error("[Parse Error] Failed to retrieve '" + urlstr + "'");
|
console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
askRelay(cb);
|
askRelay(cb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(body.api_host)) {
|
if (body.pair_request) {
|
||||||
console.error("[API Error] API Index '" + urlstr + "' does not describe a known version of telebit.cloud");
|
answers._can_pair = true;
|
||||||
console.error(e);
|
}
|
||||||
askRelay(cb);
|
cb();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (body.pair_request) {
|
|
||||||
answers._can_pair = true;
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}).on('error', function (e) {
|
|
||||||
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
|
|
||||||
console.error(e);
|
|
||||||
askRelay(cb);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
115
bin/telebitd.js
115
bin/telebitd.js
|
@ -75,12 +75,8 @@ try {
|
||||||
var controlServer;
|
var controlServer;
|
||||||
|
|
||||||
var tun;
|
var tun;
|
||||||
function serveControls() {
|
|
||||||
if (!state.config.disable) {
|
function serveControlsHelper() {
|
||||||
if (state.config.relay && (state.config.token || state.config.agreeTos)) {
|
|
||||||
tun = rawTunnel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controlServer = http.createServer(function (req, res) {
|
controlServer = http.createServer(function (req, res) {
|
||||||
var opts = url.parse(req.url, true);
|
var opts = url.parse(req.url, true);
|
||||||
if (opts.query._body) {
|
if (opts.query._body) {
|
||||||
|
@ -101,13 +97,13 @@ function serveControls() {
|
||||||
, code: 'CONFIG'
|
, code: 'CONFIG'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (/\btelebit\.cloud\b/i.test(state.config.relay) && state.config.email && !state.token) {
|
if (state._can_pair && state.config.email && !state.token) {
|
||||||
dumpy.code = "AWAIT_AUTH";
|
dumpy.code = "AWAIT_AUTH";
|
||||||
dumpy.message = [
|
dumpy.message = [
|
||||||
"Check your email."
|
"Check your email."
|
||||||
, "You must verify your email address to activate this device."
|
, "You must verify your email address to activate this device."
|
||||||
, ""
|
, ""
|
||||||
, " Login Code (if needed): " + state.otp
|
, " Device Pairing Code: " + state.otp
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,26 +200,32 @@ function serveControls() {
|
||||||
|
|
||||||
if (tun) {
|
if (tun) {
|
||||||
tun.end(function () {
|
tun.end(function () {
|
||||||
tun = rawTunnel();
|
rawTunnel(saveAndReport);
|
||||||
});
|
});
|
||||||
tun = null;
|
tun = null;
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (!tun) { tun = rawTunnel(); }
|
if (!tun) {
|
||||||
|
rawTunnel(saveAndReport);
|
||||||
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
tun = rawTunnel();
|
rawTunnel(saveAndReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
function saveAndReport(err, _tun) {
|
||||||
if (err) {
|
if (err) { throw err; }
|
||||||
res.statusCode = 500;
|
tun = _tun;
|
||||||
res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'")
|
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||||
+ '.\nPerhaps check that the file exists and your user has permissions to write it?"}}');
|
if (err) {
|
||||||
return;
|
res.statusCode = 500;
|
||||||
}
|
res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'")
|
||||||
|
+ '.\nPerhaps check that the file exists and your user has permissions to write it?"}}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
listSuccess();
|
listSuccess();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -350,14 +352,17 @@ function serveControls() {
|
||||||
listSuccess();
|
listSuccess();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tun = rawTunnel();
|
rawTunnel(function (err, _tun) {
|
||||||
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
if (err) { throw err; }
|
||||||
if (err) {
|
tun = _tun;
|
||||||
res.statusCode = 500;
|
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
|
||||||
res.end('{"error":{"message":"Could not save config file. Perhaps you\'re user doesn\'t have permission?"}}');
|
if (err) {
|
||||||
return;
|
res.statusCode = 500;
|
||||||
}
|
res.end('{"error":{"message":"Could not save config file. Perhaps you\'re user doesn\'t have permission?"}}');
|
||||||
listSuccess();
|
return;
|
||||||
|
}
|
||||||
|
listSuccess();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -409,6 +414,20 @@ function serveControls() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serveControls() {
|
||||||
|
if (!state.config.disable) {
|
||||||
|
if (state.config.relay && (state.config.token || state.config.agreeTos)) {
|
||||||
|
rawTunnel(function (err, _tun) {
|
||||||
|
if (err) { throw err; }
|
||||||
|
tun = _tun;
|
||||||
|
serveControlsHelper();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serveControlsHelper();
|
||||||
|
}
|
||||||
|
|
||||||
function parseConfig(err, text) {
|
function parseConfig(err, text) {
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
|
@ -603,6 +622,7 @@ function connectTunnel() {
|
||||||
|
|
||||||
var tun = remote.connect({
|
var tun = remote.connect({
|
||||||
relay: state.relay
|
relay: state.relay
|
||||||
|
, wss: state.wss
|
||||||
, config: state.config
|
, config: state.config
|
||||||
, otp: state.otp
|
, otp: state.otp
|
||||||
, sortingHat: state.sortingHat
|
, sortingHat: state.sortingHat
|
||||||
|
@ -618,31 +638,20 @@ function connectTunnel() {
|
||||||
return tun;
|
return tun;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rawTunnel() {
|
function rawTunnel(cb) {
|
||||||
if (state.config.disable || !state.config.relay || !(state.config.token || state.config.agreeTos)) {
|
if (state.config.disable || !state.config.relay || !(state.config.token || state.config.agreeTos)) {
|
||||||
|
cb(null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.relay = state.config.relay;
|
state.relay = state.config.relay;
|
||||||
if (!state.relay) {
|
if (!state.relay) {
|
||||||
throw new Error("'" + state._confpath + "' is missing 'relay'");
|
cb(new Error("'" + state._confpath + "' is missing 'relay'"));
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (!(state.config.secret || state.config.token)) {
|
|
||||||
console.error("You must use --secret or --token with --relay");
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
var location = url.parse(state.relay);
|
state.relayUrl = common.parseUrl(state.relay);
|
||||||
if (!location.protocol || /\./.test(location.protocol)) {
|
state.relayHostname = common.parseHostname(state.relay);
|
||||||
state.relay = 'wss://' + state.relay;
|
|
||||||
location = url.parse(state.relay);
|
|
||||||
}
|
|
||||||
var aud = location.hostname + (location.port ? ':' + location.port : '');
|
|
||||||
state.relay = location.protocol + '//' + aud;
|
|
||||||
|
|
||||||
if (!state.config.token && state.config.secret) {
|
if (!state.config.token && state.config.secret) {
|
||||||
var jwt = require('jsonwebtoken');
|
var jwt = require('jsonwebtoken');
|
||||||
|
@ -662,10 +671,22 @@ function rawTunnel() {
|
||||||
}
|
}
|
||||||
state.token = state.token || state.config.token;
|
state.token = state.token || state.config.token;
|
||||||
|
|
||||||
// TODO sign token with own private key, including public key and thumbprint
|
common.urequest({ url: state.relayUrl + common.apiDirectory, json: true }, function (err, resp, body) {
|
||||||
// (much like ACME JOSE account)
|
state._apiDirectory = body;
|
||||||
|
state.wss = body.tunnel.method + '://' + body.api_host.replace(/:hostname/g, state.relayHostname) + body.tunnel.pathname
|
||||||
|
|
||||||
return connectTunnel();
|
if (token) {
|
||||||
|
cb(null, connectTunnel());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO sign token with own private key, including public key and thumbprint
|
||||||
|
// (much like ACME JOSE account)
|
||||||
|
|
||||||
|
// TODO do auth stuff
|
||||||
|
|
||||||
|
cb(null, connectTunnel());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
require('fs').readFile(confpath, 'utf8', parseConfig);
|
require('fs').readFile(confpath, 'utf8', parseConfig);
|
||||||
|
|
|
@ -26,6 +26,69 @@ common.pipename = function (config, newApi) {
|
||||||
};
|
};
|
||||||
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'var', 'run', 'telebit.sock');
|
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'var', 'run', 'telebit.sock');
|
||||||
|
|
||||||
|
common.parseUrl = function (hostname) {
|
||||||
|
var url = require('url');
|
||||||
|
var location = url.parse(hostname);
|
||||||
|
if (!location.protocol || /\./.test(location.protocol)) {
|
||||||
|
hostname = 'https://' + hostname;
|
||||||
|
location = url.parse(hostname);
|
||||||
|
}
|
||||||
|
hostname = location.hostname + (location.port ? ':' + location.port : '');
|
||||||
|
hostname = location.protocol.replace(/https?/, 'https') + '//' + hostname + location.pathname;
|
||||||
|
return hostname;
|
||||||
|
};
|
||||||
|
|
||||||
|
common.apiDirectory = '_apis/telebit.cloud/index.json';
|
||||||
|
common.urequest = function (opts, cb) {
|
||||||
|
// request.js behavior:
|
||||||
|
// encoding: null + json ? unknown
|
||||||
|
// json => attempt to parse, fail silently
|
||||||
|
// encoding => buffer.toString(encoding)
|
||||||
|
// null === encoding => Buffer.concat(buffers)
|
||||||
|
https.get(opts, function (resp) {
|
||||||
|
var encoding = opts.encoding;
|
||||||
|
if (null === encoding) {
|
||||||
|
resp._body = [];
|
||||||
|
} else {
|
||||||
|
resp._body = '';
|
||||||
|
}
|
||||||
|
if (!resp.headers['content-length'] || 0 === parseInt(resp.headers['content-length'], 10)) {
|
||||||
|
cb(resp);
|
||||||
|
}
|
||||||
|
resp._bodyLength = 0;
|
||||||
|
resp.on('data', function (chunk) {
|
||||||
|
if ('string' === typeof resp.body) {
|
||||||
|
resp.body += chunk.toString(encoding);
|
||||||
|
} else {
|
||||||
|
resp._body.push(chunk);
|
||||||
|
resp._bodyLength += chunk.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resp.on('end', function () {
|
||||||
|
if ('string' !== typeof resp.body) {
|
||||||
|
if (1 === resp._body.length) {
|
||||||
|
resp.body = resp._body[0];
|
||||||
|
} else {
|
||||||
|
resp.body = Buffer.concat(resp._body, resp._bodyLength);
|
||||||
|
}
|
||||||
|
resp._body = null;
|
||||||
|
}
|
||||||
|
if (opts.json && 'string' === typeof resp.body) {
|
||||||
|
// TODO I would parse based on Content-Type
|
||||||
|
// but request.js doesn't do that.
|
||||||
|
try {
|
||||||
|
resp.body = JSON.parse(resp.body);
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(null, resp, resp.body);
|
||||||
|
});
|
||||||
|
}).on('error', function (e) {
|
||||||
|
cb(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mkdirp.sync(path.join(__dirname, '..', 'var', 'log'));
|
mkdirp.sync(path.join(__dirname, '..', 'var', 'log'));
|
||||||
mkdirp.sync(path.join(__dirname, '..', 'var', 'run'));
|
mkdirp.sync(path.join(__dirname, '..', 'var', 'run'));
|
||||||
|
|
|
@ -399,7 +399,7 @@ function _connect(state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
, onOpen: function () {
|
, onOpen: function () {
|
||||||
console.info("[open] connected to '" + state.relay + "'");
|
console.info("[open] connected to '" + (state.wss || state.relay) + "'");
|
||||||
wsHandlers.refreshTimeout();
|
wsHandlers.refreshTimeout();
|
||||||
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
||||||
|
|
||||||
|
@ -498,8 +498,8 @@ function _connect(state) {
|
||||||
timeoutId = null;
|
timeoutId = null;
|
||||||
var machine = Packer.create(packerHandlers);
|
var machine = Packer.create(packerHandlers);
|
||||||
|
|
||||||
console.info("[connect] '" + state.relay + "'");
|
console.info("[connect] '" + (state.wss || state.relay) + "'");
|
||||||
var tunnelUrl = state.relay.replace(/\/$/, '') + '/'; // + auth;
|
var tunnelUrl = (state.wss || state.relay).replace(/\/$/, '') + '/'; // + auth;
|
||||||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
|
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
|
||||||
wstunneler.on('open', wsHandlers.onOpen);
|
wstunneler.on('open', wsHandlers.onOpen);
|
||||||
wstunneler.on('close', wsHandlers.onClose);
|
wstunneler.on('close', wsHandlers.onClose);
|
||||||
|
|
Loading…
Reference in New Issue