commit 5779325061a9c8ee49ad5ac54f4b4f9791715da0 Author: AJ ONeal Date: Mon Aug 6 12:27:33 2018 -0600 v1.0.0: initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..693448a --- /dev/null +++ b/LICENSE @@ -0,0 +1,41 @@ +Copyright 2018 AJ ONeal + +This is open source software; you can redistribute it and/or modify it under the +terms of either: + + a) the "MIT License" + b) the "Apache-2.0 License" + +MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Apache-2.0 License Summary + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ffe005 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +sclient.js +========== + +Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally. + +Also ideal for multiplexing a single port with multiple protocols using SNI. + +Unwrap a TLS connection + +```bash +$ sclient whatever.com:443 localhost:3000 +> [listening] telebit.cloud:443 <= localhost:3000 +``` + +Connect via Telnet + +```bash +$ telnet localhost 3000 +``` + +Connect via netcat (nc) + +```bash +$ nc telnet localhost 3000 +``` + +Install +======= + +### macOS, Linux, Windows + +First download and install the *current* version of [node.js](https://nodejs.org) + +```bash +npm install -g sclient +``` + +```bash +npx sclient example.com:443 localhost:3000 +``` + +Usage +===== + +```bash +sclient [-k | --insecure] +``` + +* remote + * must have servername (i.e. example.com) + * port is optional (default is 443) +* local + * address is optional (default is localhost) + * must have port (i.e. 3000) + +Examples +======== + +Bridge between `telebit.cloud` and +```bash +sclient telebit.cloud 3000 +``` + +```bash +sclient telebit.cloud:443 localhost:3000 +``` + +```bash +sclient badtls.telebit.cloud:443 localhost:3000 -k +``` diff --git a/bin/sclient.js b/bin/sclient.js new file mode 100755 index 0000000..1c5a131 --- /dev/null +++ b/bin/sclient.js @@ -0,0 +1,57 @@ +'use strict'; + +var pkg = require('../package.json'); +var remote = (process.argv[2]||'').split(':'); +var local = (process.argv[3]||'').split(':'); +var localAddress; +var localPort; +var rejectUnauthorized; + +function usage() { + console.info(""); + console.info("sclient.js v" + pkg.version); + console.info("Usage: sclient [-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(""); + process.exit(0); +} + +if (!remote[0]) { + usage(); + return; +} +if (!remote[1]) { + remote[1] = 443; +} + +if (!local[0]) { + usage(); + return; +} +if (local[0] === String(parseInt(local[0], 10))) { + localPort = parseInt(local[0], 10); + localAddress = 'localhost'; +} else { + localAddress = local[0]; + localPort = parseInt(local[1], 10); +} + +if (!localPort) { + usage(); + return; +} + +if (/^-k|--insecure$/.test(process.argv[4])) { + rejectUnauthorized = false; +} + +var opts = { + remoteAddr: remote[0] +, remotePort: remote[1] +, localAddress: localAddress +, localPort: localPort +, rejectUnauthorized: rejectUnauthorized +}; +require('../')(opts); diff --git a/index.js b/index.js new file mode 100644 index 0000000..7a815a5 --- /dev/null +++ b/index.js @@ -0,0 +1,51 @@ +'use strict'; + +var net = require('net'); +var tls = require('tls'); + +function listenForConns(opts) { + var server = net.createServer(function (c) { + var sclient = tls.connect({ + servername: opts.remoteAddr, host: opts.remoteAddr, port: opts.remotePort + , rejectUnauthorized: opts.rejectUnauthorized + }, function () { + console.info('[connect] ' + sclient.localAddress.replace('::1', 'localhost') + ":" + sclient.localPort + + " => " + opts.remoteAddr + ":" + opts.remotePort); + c.pipe(sclient); + sclient.pipe(c); + }); + sclient.on('error', function (err) { + console.error('[error] (remote) ' + err.toString()); + }); + c.on('error', function (err) { + console.error('[error] (local) ' + err.toString()); + }); + }); + server.on('error', function (err) { + console.error('[error] ' + err.toString()); + }); + server.listen({ + host: opts.localAddress + , port: opts.localPort + }, function () { + console.info('[listening] ' + opts.remoteAddr + ":" + opts.remotePort + + " <= " + opts.localAddress + ":" + opts.localPort); + }); +} + +function testConn(opts) { + // Test connection first + var tlsSock = tls.connect({ + servername: opts.remoteAddr, host: opts.remoteAddr, port: opts.remotePort + , rejectUnauthorized: opts.rejectUnauthorized + }, function () { + tlsSock.end(); + listenForConns(opts); + }); + tlsSock.on('error', function (err) { + console.warn("[warn] '" + opts.remoteAddr + ":" + opts.remotePort + "' may not be accepting connections: ", err.toString(), '\n'); + listenForConns(opts); + }); +} + +module.exports = testConn; diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c29408 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "sclient", + "version": "1.0.0", + "description": "Secure Client for exposing TLS (aka SSL) secured services as plain-text connections locally. Also ideal for multiplexing a single port with multiple protocols using SNI.", + "main": "index.js", + "bin": { + "sclient": "bin/sclient.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "files": [ + "bin", + "lib" + ], + "repository": { + "type": "git", + "url": "https://git.coolaj86.com/coolaj86/sclient.js.git" + }, + "keywords": [ + "stunnel", + "s_client", + "telebit", + "openssl" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "(MIT OR Apache-2.0)" +}