#!/usr/bin/env node 'use strict'; var pkg = require('../package.json'); var remote; var local; var isPiped = !process.stdin.isTTY; var localAddress; var localPort; function usage() { console.info(""); console.info("sclient.js v" + pkg.version); console.info("Usage: sclient [--servername ] [-k | --insecure] "); console.info(" ex: sclient whatever.com 3000"); console.info(" (whatever.com:443 localhost:3000)"); console.info(" ex: sclient whatever.com:4080 0.0.0.0:3000"); console.info(""); } function parseFlags(argv) { var args = argv.slice(); var flags = {}; args.some(function (arg, i) { if (/^-k|--?insecure$/.test(arg)) { flags.rejectUnauthorized = false; args.splice(i, 1); return true; } }); args.some(function (arg, i) { if (/^--?servername$/.test(arg)) { flags.servername = args[i + 1]; if (!flags.servername || /^-/.test(flags.servername)) { usage(); process.exit(2); } args.splice(i, 2); return true; } }); args.some(function (arg, i) { if (/^--?p(ort)?$/.test(arg)) { flags.port = args[i + 1]; if (!flags.port || /^-/.test(flags.port)) { usage(); process.exit(201); } args.splice(i, 2); return true; } }); args.some(function (arg, i) { if (/^--?ssh$/.test(arg)) { flags.wrapSsh = true; args.splice(i, 1); return true; } }); args.some(function (arg, i) { if (/^--?socks5$/.test(arg)) { flags.socks5 = args[i + 1]; if (!flags.socks5 || /^-/.test(flags.socks5)) { usage(); process.exit(202); } args.splice(i, 2); return true; } }); // This works for most (but not all) // of the ssh and rsync flags - because they mostly don't have arguments args.sort(function (a, b) { if ('-' === a[0]) { if ('-' === b[0]) { return 0; } return 1; } if ('-' === b[0]) { return -1; } return 0; }); return { flags: flags , args: args }; } var sclient = require('../'); function testRemote(opts) { var emitter = new (require('events').EventEmitter)(); sclient._test(opts).then(function () { // connected successfully (and closed) sclient._listen(emitter, opts); }).catch(function (err) { // did not connect succesfully sclient._listen(emitter, opts); console.warn("[warn] '" + opts.remoteAddr + ":" + opts.remotePort + "' may not be accepting connections: ", err.toString(), '\n'); }); emitter.once('listening', function (opts) { console.info('[listening] ' + opts.remoteAddr + ":" + opts.remotePort + " <= " + opts.localAddress + ":" + opts.localPort); if (opts.command) { var args = [ opts.remoteUser + 'localhost' , '-p', opts.localPort // we're _inverse_ proxying ssh, so we must alias the serveranem and ignore the IP , '-o', 'HostKeyAlias=' + opts.remoteAddr , '-o', 'CheckHostIP=no' ]; var spawn = require('child_process').spawn; if ('rsync' === opts.command) { var remote = args.shift() + ':' + opts.remotePath; args = [ remote, '-e', 'ssh ' + args.join(' ') ]; } if (opts.socks5) { args.push('-D'); args.push('localhost:' + opts.socks5); } args = args.concat(opts.args); var child = spawn(opts.command, args, { stdio: 'inherit' }); child.on('exit', function () { console.info('closing...'); }); child.on('close', function () { opts.server.close(); }); } }); emitter.on('connect', function (sock) { console.info('[connect] ' + sock.localAddress.replace('::1', 'localhost') + ":" + sock.localPort + " => " + opts.remoteAddr + ":" + opts.remotePort); }); emitter.on('remote-error', function (err) { console.error('[error] (remote) ' + err.toString()); }); emitter.on('local-error', function (err) { console.error('[error] (local) ' + err.toString()); }); } function main() { var cmd = parseFlags(process.argv); var binParam; var remoteUser; // Re-arrange argument order for ssh if (cmd.flags.wrapSsh) { cmd.args.splice(3, 0, 'ssh'); } else if (-1 !== [ 'ssh', 'rsync', 'vpn' ].indexOf((cmd.args[2]||'').split(':')[0])) { cmd.flags.wrapSsh = true; binParam = cmd.args.splice(2, 1); cmd.args.splice(3, 0, binParam[0]); } remoteUser = (cmd.args[2]||'').split('@'); if (remoteUser[1]) { // has 'user@' in front remote = (remoteUser[1]||'').split(':'); remoteUser = remoteUser[0] + '@'; } else { // no 'user@' in front remote = (remoteUser[0]||'').split(':'); remoteUser = ''; } local = (cmd.args[3]||'').split(':'); if (-1 !== [ 'ssh', 'rsync', 'vpn' ].indexOf(local[0])) { cmd.flags.wrapSsh = true; } if (cmd.flags.wrapSsh) { process.argv = cmd.args; } else if (4 !== cmd.args.length) { // arg 0 is node // arg 1 is sclient // arg 2 is remote // arg 3 is local (or ssh or rsync) if (isPiped) { local = ['|']; } else { usage(); process.exit(1); } } // Check for the first argument (what to connect to) if (!remote[0]) { usage(); return; } if (!local[0]) { usage(); return; } // check if it looks like a port number if (local[0] === String(parseInt(local[0], 10))) { localPort = parseInt(local[0], 10); localAddress = 'localhost'; } else { localAddress = local[0]; // potentially '-' or '|' or '$' localPort = parseInt(local[1], 10); } var opts = { remoteUser: remoteUser , remoteAddr: remote[0] , remotePort: remote[1] || 443 , localAddress: localAddress , localPort: localPort , rejectUnauthorized: cmd.flags.rejectUnauthorized , servername: cmd.flags.servername , stdin: null , stdout: null }; if ('-' === localAddress || '|' === localAddress) { opts.stdin = process.stdin; opts.stdout = process.stdout; // no need for port } else if (-1 !== [ 'ssh', 'rsync', 'vpn' ].indexOf(localAddress)) { cmd.flags.wrapSsh = true; opts.localAddress = 'localhost'; opts.localPort = local[1] || 0; // choose at random opts.command = localAddress; opts.args = cmd.args.slice(4); // node, sclient, ssh, addr opts.socks5 = cmd.flags.socks5; if ('rsync' === opts.command) { opts.remotePath = opts.remotePort; opts.remotePort = 0; } if ('vpn' === opts.command) { opts.command = 'ssh'; if (!opts.socks5) { usage(); return; } } if (!opts.remotePort) { opts.remotePort = cmd.flags.port || 443; } } else if (!localPort) { usage(); return; } testRemote(opts); } main();