mirror of
https://git.coolaj86.com/coolaj86/telebit.js.git
synced 2025-04-22 12:00:36 +00:00
Compare commits
133 Commits
Author | SHA1 | Date | |
---|---|---|---|
d715d5a9d9 | |||
774e8a26da | |||
388e2f7a58 | |||
536afb2ad5 | |||
274c527768 | |||
553c385e34 | |||
3a8f5fee4e | |||
021a44d47a | |||
b9779715f8 | |||
7ec69f8d05 | |||
0c705061a2 | |||
82fb0ee8ac | |||
ac36474b9b | |||
b9065540db | |||
a0c32662a0 | |||
36f73c1051 | |||
e831774c34 | |||
6198223b46 | |||
4ec1f3b30a | |||
570c64f104 | |||
ffc045ab43 | |||
99d1e7cb3d | |||
a43934a405 | |||
e96a52212f | |||
9d920bc6f9 | |||
0af3e60b63 | |||
082866f300 | |||
b0444ec575 | |||
08348fae0c | |||
6d85f12409 | |||
f2f772a645 | |||
5e9a852375 | |||
9736dfe92b | |||
da78847d91 | |||
e361c48353 | |||
582fde8691 | |||
e21e7265e7 | |||
a5a78400bf | |||
d931896a73 | |||
05d317e443 | |||
0708357b86 | |||
7e7f419eb8 | |||
aca4342dd1 | |||
cbfbc71edf | |||
682f35f5a0 | |||
a8b8e34c60 | |||
09d6aa6df3 | |||
6748e81d5c | |||
c01fbe0b63 | |||
0097735152 | |||
b453281a6b | |||
986dc1c17f | |||
a5d3576ab6 | |||
65b77f994a | |||
182c4d8888 | |||
33063d3efe | |||
e85bd78905 | |||
9239153681 | |||
3a6df4db63 | |||
7a08220838 | |||
ef751e5b32 | |||
30d1ce89fe | |||
de1bea7b60 | |||
3cd10fda0c | |||
37591d11c6 | |||
6f343a875d | |||
a816e327e5 | |||
945f77449d | |||
157983018b | |||
f7685f8b6a | |||
e8c580d115 | |||
ca2e825fe7 | |||
3b2e1771df | |||
eeb6299268 | |||
333b06c33f | |||
26bfdd06a1 | |||
b58cb25077 | |||
f95ca1bdc0 | |||
4b0172a456 | |||
3f77cb16b3 | |||
ef9fc9d46d | |||
dc11cbbd63 | |||
|
c433c6ccaf | ||
|
2a7121f79f | ||
|
dcdd4f8142 | ||
|
f94df07219 | ||
|
ccaf1a517f | ||
|
e9c24efd5f | ||
|
bbc3ffaf6d | ||
|
7a00be681c | ||
|
6b57f6fae8 | ||
|
2831d09a4d | ||
|
a95f6ecead | ||
|
e3fbd768a2 | ||
|
6ea903a3f1 | ||
|
635523b155 | ||
|
00fbd2c27f | ||
|
6901a4ef2d | ||
|
425cf4bc24 | ||
4f9ea342f5 | |||
04f10090ea | |||
|
dbf8832aa9 | ||
|
160c591d7f | ||
|
d8bf926123 | ||
|
5a3fe4468a | ||
|
7a9d06e67b | ||
|
43ea38523b | ||
|
53185c1760 | ||
|
913f36043a | ||
|
76495e674d | ||
|
35d5f0c568 | ||
|
cbdff3e55b | ||
61a8d29c5e | |||
1784a13346 | |||
|
9eabc956dd | ||
|
646cfedabe | ||
ec245dff63 | |||
1cbb594ef6 | |||
a8d1448c08 | |||
dcb150daa6 | |||
c32c0bad05 | |||
a09a3835ae | |||
519911fa23 | |||
993f04a824 | |||
7f754688cc | |||
c00e3085e3 | |||
5719237a0c | |||
97f731ebb8 | |||
c9d83437e1 | |||
65f090315b | |||
5ee3a45cf8 | |||
5ade07e224 | |||
4eb6a42bb8 |
206
LICENSE
206
LICENSE
@ -1,198 +1,38 @@
|
|||||||
Apache License
|
Copyright 2016 AJ ONeal
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
This is open source software; you can redistribute it and/or modify it under the
|
||||||
|
terms of either:
|
||||||
|
|
||||||
1. Definitions.
|
a) the "MIT License"
|
||||||
|
b) the "Apache-2.0 License"
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
MIT License
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
the copyright owner that is granting the License.
|
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:
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
other entities that control, are controlled by, or are under common
|
copies or substantial portions of the Software.
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
exercising permissions granted by this License.
|
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.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
Apache-2.0 License Summary
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
438
README.md
438
README.md
@ -1,84 +1,280 @@
|
|||||||
<!-- BANNER_TPL_BEGIN -->
|
# Telebit™ Remote
|
||||||
|
|
||||||
About Daplie: We're taking back the Internet!
|
Because friends don't let friends localhost™
|
||||||
--------------
|
|
||||||
|
|
||||||
Down with Google, Apple, and Facebook!
|
| Sponsored by [ppl](https://ppl.family)
|
||||||
|
| **Telebit Remote**
|
||||||
|
| [Telebit Relay](https://git.coolaj86.com/coolaj86/telebitd.js)
|
||||||
|
|
|
||||||
|
|
||||||
We're re-decentralizing the web and making it read-write again - one home cloud system at a time.
|
Break out of localhost.
|
||||||
|
=======
|
||||||
|
|
||||||
Tired of serving the Empire? Come join the Rebel Alliance:
|
If you need to get bits from here to there, Telebit gets the job done.
|
||||||
|
|
||||||
<a href="mailto:jobs@daplie.com">jobs@daplie.com</a> | [Invest in Daplie on Wefunder](https://daplie.com/invest/) | [Pre-order Cloud](https://daplie.com/preorder/), The World's First Home Server for Everyone
|
Install Telebit Remote on any device - your laptop, raspberry pi, whatever -
|
||||||
|
and now you can access that device from anywhere, even securely in a web browser.
|
||||||
|
|
||||||
<!-- BANNER_TPL_END -->
|
How does it work?
|
||||||
|
It's a net server that uses a relay to allow multiplexed incoming connections
|
||||||
|
on any external port.
|
||||||
|
|
||||||
# stunnel.js
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
A client that works in combination with [stunneld.js](https://github.com/Daplie/node-tunnel-server)
|
* [x] Show your mom the web app you're working on
|
||||||
to allow you to serve http and https from any computer, anywhere through a secure tunnel.
|
* [x] Access your Raspberry Pi from behind a firewall
|
||||||
|
* [x] Watch Netflix without region restrictions while traveling
|
||||||
|
* [x] SSH over HTTPS on networks with restricted ports or protocols
|
||||||
|
* [x] Access your wife's laptop while she's on a flight
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
```
|
||||||
|
telebit --config /opt/telebit/etc/telebit.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Connect to your device by any of the following means:
|
||||||
|
|
||||||
|
SSH+HTTPS
|
||||||
|
ssh+https://young-grasshopper-37.telebit.cloud:443
|
||||||
|
ex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -quiet' young-grasshopper-37.telebit.cloud -p 443
|
||||||
|
|
||||||
|
SSH
|
||||||
|
ssh://ssh.telebit.cloud:32852
|
||||||
|
ex: ssh ssh.telebit.cloud -p 32852
|
||||||
|
|
||||||
|
TCP
|
||||||
|
tcp://tcp.telebit.cloud:32852
|
||||||
|
ex: netcat tcp.telebit.cloud 32852
|
||||||
|
|
||||||
|
HTTPS
|
||||||
|
https://young-grasshopper-37.telebit.cloud
|
||||||
|
ex: curl https://young-grasshopper-37.telebit.cloud
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- TODO use some imagery
|
||||||
|
```
|
||||||
|
telebit http /path/to/root
|
||||||
|
telebit http 3000
|
||||||
|
telebit http /path/to/handler.js
|
||||||
|
telebit ssh 22
|
||||||
|
telebit tcp 3000
|
||||||
|
telebit tcp echo
|
||||||
|
telebit tcp /path/to/handler.js
|
||||||
|
```
|
||||||
|
-->
|
||||||
|
|
||||||
|
Install
|
||||||
|
=======
|
||||||
|
|
||||||
|
Mac & Linux
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Open Terminal and run this install script:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -fsSL https://get.telebit.cloud | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
```
|
||||||
|
bash <( curl -fsSL https://get.telebit.cloud )
|
||||||
|
```
|
||||||
|
|
||||||
|
<small>
|
||||||
|
Note: **fish**, **zsh**, and other **non-bash** users should do this
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -fsSL https://get.telebit.cloud/ > get.sh; bash get.sh
|
||||||
|
```
|
||||||
|
</small>
|
||||||
|
-->
|
||||||
|
|
||||||
|
Of course, feel free to inspect the install script before you run it: `curl -fsSL https://get.telebit.cloud`
|
||||||
|
|
||||||
|
This will install Telebit Remote to `/opt/telebit` and
|
||||||
|
put a symlink to `/opt/telebit/bin/telebit.js` in `/usr/local/bin/telebit`
|
||||||
|
for convenience.
|
||||||
|
|
||||||
|
**You can customize the installation**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NODEJS_VER=v10.2
|
||||||
|
export TELEBIT_PATH=/opt/telebit
|
||||||
|
curl -fsSL https://get.telebit.cloud/
|
||||||
|
```
|
||||||
|
|
||||||
|
That will change the bundled version of node.js is bundled with Telebit Relay
|
||||||
|
and the path to which Telebit Relay installs.
|
||||||
|
|
||||||
|
You can get rid of the tos + email and server domain name prompts by providing them right away:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://get.telebit.cloud/ | bash -- jon@example.com example.com telebit.example.com xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows & Node.js
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
1. Install [node.js](https://nodejs.org)
|
||||||
|
2. Open _Node.js_
|
||||||
|
2. Run the command `npm install -g telebit`
|
||||||
|
|
||||||
|
**Note**: Use node.js v8.x or v10.x
|
||||||
|
|
||||||
|
There is [a bug](https://github.com/nodejs/node/issues/20241) in node v9.x that causes telebit to crash.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
====
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telebit --config /opt/telebit/etc/telebit.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Options
|
||||||
|
|
||||||
|
`/opt/telebit/etc/telebit.yml:`
|
||||||
|
```
|
||||||
|
email: 'jon@example.com' # must be valid (for certificate recovery and security alerts)
|
||||||
|
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
||||||
|
relay: wss://telebit.cloud # a Telebit Relay instance
|
||||||
|
community_member: true # receive infrequent relevant but non-critical updates
|
||||||
|
telemetry: true # contribute to project telemetric data
|
||||||
|
secret: '' # Secret with which to sign Tokens for authorization
|
||||||
|
#token: '' # A signed Token for authorization
|
||||||
|
ssh_auto: 22 # forward ssh-looking packets, from any connection, to port 22
|
||||||
|
servernames: # servernames that will be forwarded here
|
||||||
|
example.com: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Choosing A Relay
|
||||||
|
================
|
||||||
|
|
||||||
|
You can create a free or paid account at https://telebit.cloud
|
||||||
|
or you can run [Telebit Relay](https://git.coolaj86.com/coolaj86/telebitd.js)
|
||||||
|
open source on a VPS (Vultr, Digital Ocean)
|
||||||
|
or your Raspberry Pi at home (with port-forwarding).
|
||||||
|
|
||||||
|
Only connect to Telebit Relays that you trust.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## Important Defaults
|
||||||
|
|
||||||
|
The default behaviors work great for newbies,
|
||||||
|
but can be confusing or annoying to experienced networking veterans.
|
||||||
|
|
||||||
|
See the **Advanced Configuration** section below for more details.
|
||||||
|
|
||||||
|
```
|
||||||
|
redirect:
|
||||||
|
example.com/foo: /bar
|
||||||
|
'*': whatever.com/
|
||||||
|
vhost: # securely serve local sites from this path (or false)
|
||||||
|
example.com: /srv/example.com # (uses template string, i.e. /var/www/:hostname/public)
|
||||||
|
'*': /srv/www/:hostname
|
||||||
|
reverse_proxy: /srv/
|
||||||
|
example.com: 3000
|
||||||
|
'*': 3000
|
||||||
|
terminate_tls:
|
||||||
|
'example.com': 3000
|
||||||
|
'*': 3000
|
||||||
|
tls:
|
||||||
|
'example.com': 8443
|
||||||
|
'*': 8443
|
||||||
|
port_forward:
|
||||||
|
2020: 2020
|
||||||
|
'*': 4040
|
||||||
|
|
||||||
|
greenlock:
|
||||||
|
store: le-store-certbot # certificate storage plugin
|
||||||
|
config_dir: /etc/acme # directory for ssl certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
Using Telebit with node.js
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Telebit has two parts:
|
||||||
|
* the local server
|
||||||
|
* the relay service
|
||||||
|
|
||||||
|
This repository is for the local server, which you run on the computer or device that you would like to access.
|
||||||
|
|
||||||
|
This is the portion that runs on your computer
|
||||||
|
You will need both Telebit (this, telebit.js) and a Telebit Relay
|
||||||
|
(such as [telebitd.js](https://git.coolaj86.com/coolaj86/telebitd.js)).
|
||||||
|
|
||||||
|
You can **integrate telebit.js into your existing codebase** or use the **standalone CLI**.
|
||||||
|
|
||||||
* CLI
|
* CLI
|
||||||
* Library
|
* Node.js Library
|
||||||
|
* Browser Library
|
||||||
|
|
||||||
CLI
|
Telebit CLI
|
||||||
===
|
-----------
|
||||||
|
|
||||||
Installs as `stunnel.js` with the alias `jstunnel`
|
Installs Telebit Remote as `telebit`
|
||||||
(for those that regularly use `stunnel` but still like commandline completion).
|
(for those that regularly use `telebit` but still like commandline completion).
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g stunnel
|
npm install -g telebit
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Usage
|
|
||||||
|
|
||||||
How to use `stunnel.js` with your own instance of `stunneld.js`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
stunnel.js --locals john.example.com --stunneld wss://tunnel.example.com:443 --secret abc123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
stunnel.js \
|
npm install -g 'https://git.coolaj86.com/coolaj86/telebit.js.git#v1'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you want to bow down to the kings of the centralized dictator-net:
|
||||||
|
|
||||||
|
How to use Telebit Remote with your own instance of Telebit Relay:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telebit \
|
||||||
|
--locals <<external domain name>> \
|
||||||
|
--relay wss://<<tunnel domain>>:<<tunnel port>> \
|
||||||
|
--secret <<128-bit hex key>>
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telebit --locals john.example.com --relay wss://tunnel.example.com:443 --secret abc123
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telebit \
|
||||||
|
--locals <<protocol>>:<<external domain name>>:<<local port>> \
|
||||||
|
--relay wss://<<tunnel domain>>:<<tunnel port>> \
|
||||||
|
--secret <<128-bit hex key>>
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telebit \
|
||||||
--locals http:john.example.com:3000,https:john.example.com \
|
--locals http:john.example.com:3000,https:john.example.com \
|
||||||
--stunneld wss://tunnel.example.com:443 \
|
--relay wss://tunnel.example.com:443 \
|
||||||
--secret abc123
|
--secret abc123
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
--secret the same secret used by stunneld (used for authentication)
|
--secret the same secret used by the Telebit Relay (for authentication)
|
||||||
--locals comma separated list of <proto>:<servername>:<port> to which
|
--locals comma separated list of <proto>:<servername>:<port> to which
|
||||||
incoming http and https should be forwarded
|
incoming http and https should be forwarded
|
||||||
--stunneld the domain or ip address at which you are running stunneld.js
|
--relay the domain or ip address at which you are running Telebit Relay
|
||||||
-k, --insecure ignore invalid ssl certificates from stunneld
|
-k, --insecure ignore invalid ssl certificates from relay
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
Node.js Library
|
||||||
|
|
||||||
**NOT YET IMPLEMENTED**
|
|
||||||
|
|
||||||
Daplie's tunneling service is not yet publicly available.
|
|
||||||
|
|
||||||
**Terms of Service**: The Software and Services shall be used for Good, not Evil.
|
|
||||||
Examples of good: education, business, pleasure. Examples of evil: crime, abuse, extortion.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
stunnel.js --agree-tos --email john@example.com --locals http:john.example.com:4080,https:john.example.com:8443
|
|
||||||
```
|
|
||||||
|
|
||||||
Library
|
|
||||||
=======
|
=======
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var stunnel = require('stunnel');
|
var Telebit = require('telebit');
|
||||||
|
|
||||||
stunnel.connect({
|
Telebit.connect({
|
||||||
stunneld: 'wss://tunnel.example.com'
|
relay: 'wss://tunnel.example.com'
|
||||||
, token: '...'
|
, token: '...'
|
||||||
, locals: [
|
, locals: [
|
||||||
// defaults to sending http to local port 80 and https to local port 443
|
// defaults to sending http to local port 80 and https to local port 443
|
||||||
@ -118,52 +314,140 @@ local handler and the tunnel handler.
|
|||||||
You could do a little magic like this:
|
You could do a little magic like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var Dup = {
|
Telebit.connect({
|
||||||
write: function (chunk, encoding, cb) {
|
|
||||||
this.__my_socket.write(chunk, encoding);
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
, read: function (size) {
|
|
||||||
var x = this.__my_socket.read(size);
|
|
||||||
if (x) { this.push(x); }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
stunnel.connect({
|
|
||||||
// ...
|
// ...
|
||||||
, net: {
|
, net: {
|
||||||
createConnection: function (info, cb) {
|
createConnection: function (info, cb) {
|
||||||
// data is the hello packet / first chunk
|
// data is the hello packet / first chunk
|
||||||
// info = { data, servername, port, host, remoteAddress: { family, address, port } }
|
// info = { data, servername, port, host, remoteAddress: { family, address, port } }
|
||||||
|
|
||||||
var myDuplex = new (require('stream').Duplex)();
|
var streamPair = require('stream-pair');
|
||||||
var myDuplex2 = new (require('stream').Duplex)();
|
|
||||||
|
// here "reader" means the socket that looks like the connection being accepted
|
||||||
|
var writer = streamPair.create();
|
||||||
|
// here "writer" means the remote-looking part of the socket that driving the connection
|
||||||
|
var reader = writer.other;
|
||||||
// duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
|
// duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
|
||||||
|
|
||||||
myDuplex2.__my_socket = myDuplex;
|
reader.remoteFamily = info.remoteFamily;
|
||||||
myDuplex2._write = Dup.write;
|
reader.remoteAddress = info.remoteAddress;
|
||||||
myDuplex2._read = Dup.read;
|
reader.remotePort = info.remotePort;
|
||||||
|
|
||||||
myDuplex.__my_socket = myDuplex2;
|
|
||||||
myDuplex._write = Dup.write;
|
|
||||||
myDuplex._read = Dup.read;
|
|
||||||
|
|
||||||
myDuplex.remoteFamily = info.remoteFamily;
|
|
||||||
myDuplex.remoteAddress = info.remoteAddress;
|
|
||||||
myDuplex.remotePort = info.remotePort;
|
|
||||||
|
|
||||||
// socket.local{Family,Address,Port}
|
// socket.local{Family,Address,Port}
|
||||||
myDuplex.localFamily = 'IPv4';
|
reader.localFamily = 'IPv4';
|
||||||
myDuplex.localAddress = '127.0.01';
|
reader.localAddress = '127.0.01';
|
||||||
myDuplex.localPort = info.port;
|
reader.localPort = info.port;
|
||||||
|
|
||||||
httpsServer.emit('connection', myDuplex);
|
httpsServer.emit('connection', reader);
|
||||||
|
|
||||||
if (cb) {
|
if (cb) {
|
||||||
process.nextTick(cb);
|
process.nextTick(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return myDuplex2;
|
return writer;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Advanced Configuration
|
||||||
|
======================
|
||||||
|
|
||||||
|
There is no configuration for these yet,
|
||||||
|
but we believe it is important to add them.
|
||||||
|
|
||||||
|
### http to https
|
||||||
|
|
||||||
|
By default http connections are redirected to https.
|
||||||
|
|
||||||
|
If for some reason you need raw access to unencrypted http
|
||||||
|
you'll need to set it manually.
|
||||||
|
|
||||||
|
Proposed configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
insecure_http:
|
||||||
|
proxy: true # add X-Forward-* headers
|
||||||
|
port: 3000 # connect to port 3000
|
||||||
|
hostnames: # only these hostnames will be left insecure
|
||||||
|
- example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: In the future unencrypted connections will only be allowed
|
||||||
|
on self-hosted and paid-hosted Telebit Relays. We don't want the
|
||||||
|
legal liability of transmitting your data in the clear, thanks. :p
|
||||||
|
|
||||||
|
### TLS Termination (Secure SSL decryption)
|
||||||
|
|
||||||
|
Telebit is designed for end-to-end security.
|
||||||
|
|
||||||
|
For convenience the Telebit Remote client uses Greenlock to handle all
|
||||||
|
HTTPS connections and then connect to a local webserver with the correct proxy headers.
|
||||||
|
|
||||||
|
However, if you want to handle the encrypted connection directly, you can:
|
||||||
|
|
||||||
|
Proposed Configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
tls:
|
||||||
|
example.com: 3000 # specific servername
|
||||||
|
'*': 3000 # all servernames
|
||||||
|
'!': 3000 # missing servername
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
Install for user
|
||||||
|
* https://wiki.archlinux.org/index.php/Systemd/User
|
||||||
|
* https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
|
||||||
|
* `sudo launchctl load -w ~/Library/LaunchAgents/cloud.telebit.remote`
|
||||||
|
* https://serverfault.com/questions/194832/how-to-start-stop-restart-launchd-services-from-the-command-line
|
||||||
|
-->
|
||||||
|
|
||||||
|
Check Logs
|
||||||
|
==========
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo journalctl -xefu telebit
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS**:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo tail -f /opt/telebit/var/log/info.log
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo tail -f /opt/telebit/var/log/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Uninstall
|
||||||
|
=======
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo systemctl disable telebit; sudo systemctl stop telebit
|
||||||
|
sudo rm -rf /etc/systemd/system/telebit.service /opt/telebit /usr/local/bin/telebit
|
||||||
|
rm -rf ~/.config/telebit ~/.local/share/telebit
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS**:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo launchctl unload -w /Library/LaunchDaemons/cloud.telebit.remote.plist
|
||||||
|
sudo rm -rf /Library/LaunchDaemons/cloud.telebit.remote.plist /opt/telebit /usr/local/bin/telebit
|
||||||
|
rm -rf ~/.config/telebit ~/.local/share/telebit
|
||||||
|
```
|
||||||
|
|
||||||
|
Browser Library
|
||||||
|
=======
|
||||||
|
|
||||||
|
This is implemented with websockets, so you should be able to
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
=======
|
||||||
|
|
||||||
|
Copyright 2016 AJ ONeal
|
||||||
|
6
TODO.md
6
TODO.md
@ -1,6 +0,0 @@
|
|||||||
TODO
|
|
||||||
|
|
||||||
* [*] Work with Secure WebSockets
|
|
||||||
* [ ] Hijack HTTPS connection directly (without WebSockets)
|
|
||||||
* [p] Raw TCP (for transporting https once, not twice) (partial)
|
|
||||||
* [ ] Let's Encrypt Support (for connecting to a plain http server locally)
|
|
118
bin/stunnel.js
118
bin/stunnel.js
@ -1,118 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var pkg = require('../package.json');
|
|
||||||
|
|
||||||
var program = require('commander');
|
|
||||||
var url = require('url');
|
|
||||||
var stunnel = require('../wsclient.js');
|
|
||||||
|
|
||||||
function collectProxies(val, memo) {
|
|
||||||
var vals = val.split(/,/g);
|
|
||||||
|
|
||||||
function parseProxy(location) {
|
|
||||||
// http:john.example.com:3000
|
|
||||||
// http://john.example.com:3000
|
|
||||||
var parts = location.split(':');
|
|
||||||
var dual = false;
|
|
||||||
if (/\./.test(parts[0])) {
|
|
||||||
//dual = true;
|
|
||||||
parts[2] = parts[1];
|
|
||||||
parts[1] = parts[0];
|
|
||||||
parts[0] = 'https';
|
|
||||||
dual = true;
|
|
||||||
}
|
|
||||||
parts[0] = parts[0].toLowerCase();
|
|
||||||
parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*';
|
|
||||||
parts[2] = parseInt(parts[2], 10) || 0;
|
|
||||||
if (!parts[2]) {
|
|
||||||
// TODO grab OS list of standard ports?
|
|
||||||
if ('http' === parts[0]) {
|
|
||||||
parts[2] = 80;
|
|
||||||
}
|
|
||||||
else if ('https' === parts[0]) {
|
|
||||||
parts[2] = 443;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error("port must be specified - ex: tls:*:1337");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memo.push({
|
|
||||||
protocol: parts[0]
|
|
||||||
, hostname: parts[1]
|
|
||||||
, port: parts[2]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dual) {
|
|
||||||
memo.push({
|
|
||||||
protocol: 'http'
|
|
||||||
, hostname: parts[1]
|
|
||||||
, port: parts[2]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vals.map(function (val) {
|
|
||||||
return parseProxy(val);
|
|
||||||
});
|
|
||||||
|
|
||||||
return memo;
|
|
||||||
}
|
|
||||||
|
|
||||||
program
|
|
||||||
.version(pkg.version)
|
|
||||||
//.command('jsurl <url>')
|
|
||||||
.arguments('<url>')
|
|
||||||
.action(function (url) {
|
|
||||||
program.url = url;
|
|
||||||
})
|
|
||||||
.option('-k --insecure', 'Allow TLS connections to stunneld without valid certs (rejectUnauthorized: false)')
|
|
||||||
.option('--locals <LINE>', 'comma separated list of <proto>:<//><servername>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https://john.example.com,tls:*:1337', collectProxies, [ ]) // --reverse-proxies
|
|
||||||
.option('--stunneld <URL>', 'the domain (or ip address) at which you are running stunneld.js (the proxy)') // --proxy
|
|
||||||
.option('--secret <STRING>', 'the same secret used by stunneld (used for JWT authentication)')
|
|
||||||
.option('--token <STRING>', 'a pre-generated token for use with stunneld (instead of generating one with --secret)')
|
|
||||||
.parse(process.argv)
|
|
||||||
;
|
|
||||||
|
|
||||||
program.stunneld = program.stunneld || 'wss://tunnel.daplie.com';
|
|
||||||
|
|
||||||
var jwt = require('jsonwebtoken');
|
|
||||||
var domainsMap = {};
|
|
||||||
var tokenData = {
|
|
||||||
domains: null
|
|
||||||
};
|
|
||||||
var location = url.parse(program.stunneld);
|
|
||||||
|
|
||||||
if (!location.protocol || /\./.test(location.protocol)) {
|
|
||||||
program.stunneld = 'wss://' + program.stunneld;
|
|
||||||
location = url.parse(program.stunneld);
|
|
||||||
}
|
|
||||||
program.stunneld = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
|
|
||||||
|
|
||||||
program.locals.forEach(function (proxy) {
|
|
||||||
domainsMap[proxy.hostname] = true;
|
|
||||||
});
|
|
||||||
tokenData.domains = Object.keys(domainsMap);
|
|
||||||
|
|
||||||
program.token = program.token || jwt.sign(tokenData, program.secret || 'shhhhh');
|
|
||||||
|
|
||||||
program.net = {
|
|
||||||
createConnection: function (info, cb) {
|
|
||||||
// data is the hello packet / first chunk
|
|
||||||
// info = { data, servername, port, host, remoteFamily, remoteAddress, remotePort }
|
|
||||||
var net = require('net');
|
|
||||||
// socket = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
|
|
||||||
var socket = net.createConnection({ port: info.port, host: info.host }, cb);
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
program.locals.forEach(function (proxy) {
|
|
||||||
console.log('[local proxy]', proxy.protocol + '://' + proxy.hostname + ':' + proxy.port);
|
|
||||||
});
|
|
||||||
|
|
||||||
stunnel.connect(program);
|
|
||||||
|
|
||||||
}());
|
|
431
bin/telebit.js
Executable file
431
bin/telebit.js
Executable file
@ -0,0 +1,431 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var pkg = require('../package.json');
|
||||||
|
console.log(pkg.name, pkg.version);
|
||||||
|
|
||||||
|
var url = require('url');
|
||||||
|
var path = require('path');
|
||||||
|
var remote = require('../');
|
||||||
|
var state = {};
|
||||||
|
|
||||||
|
var argv = process.argv.slice(2);
|
||||||
|
|
||||||
|
var confIndex = argv.indexOf('--config');
|
||||||
|
var confpath;
|
||||||
|
if (-1 === confIndex) {
|
||||||
|
confIndex = argv.indexOf('-c');
|
||||||
|
}
|
||||||
|
confpath = argv[confIndex + 1];
|
||||||
|
|
||||||
|
function help() {
|
||||||
|
console.info('');
|
||||||
|
console.info('Telebit Remote v' + pkg.version);
|
||||||
|
console.info('');
|
||||||
|
console.info('Usage:');
|
||||||
|
console.info('');
|
||||||
|
console.info('\ttelebit --config <path>');
|
||||||
|
console.info('');
|
||||||
|
console.info('Example:');
|
||||||
|
console.info('');
|
||||||
|
console.info('\ttelebit --config /etc/telebit/telebit.yml');
|
||||||
|
console.info('');
|
||||||
|
console.info('Config:');
|
||||||
|
console.info('');
|
||||||
|
console.info('\tSee https://git.coolaj86.com/coolaj86/telebit.js');
|
||||||
|
console.info('');
|
||||||
|
console.info('');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-1 === confIndex) {
|
||||||
|
confpath = path.join(require('os').homedir(), '.config/telebit/telebit.yml');
|
||||||
|
console.info('Using default --config "' + confpath + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-1 !== argv.indexOf('-h') || -1 !== argv.indexOf('--help')) {
|
||||||
|
help();
|
||||||
|
}
|
||||||
|
if (!confpath || /^--/.test(confpath)) {
|
||||||
|
help();
|
||||||
|
}
|
||||||
|
var tokenfile = 'access_token.txt';
|
||||||
|
var tokenpath = path.join(path.dirname(confpath), tokenfile);
|
||||||
|
var token;
|
||||||
|
try {
|
||||||
|
token = require('fs').readFileSync(tokenpath, 'ascii').trim();
|
||||||
|
} catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
require('fs').readFile(confpath, 'utf8', function (err, text) {
|
||||||
|
var config;
|
||||||
|
|
||||||
|
var recase = require('recase').create({});
|
||||||
|
var camelCopy = recase.camelCopy.bind(recase);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error("\nCouldn't load config:\n\n\t" + err.message + "\n");
|
||||||
|
process.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config = JSON.parse(text);
|
||||||
|
} catch(e1) {
|
||||||
|
try {
|
||||||
|
config = require('js-yaml').safeLoad(text);
|
||||||
|
} catch(e2) {
|
||||||
|
console.error(e1.message);
|
||||||
|
console.error(e2.message);
|
||||||
|
process.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state._confpath = confpath;
|
||||||
|
state.config = camelCopy(config);
|
||||||
|
if (state.config.token && token) {
|
||||||
|
console.warn();
|
||||||
|
console.warn("Found two tokens:");
|
||||||
|
console.warn();
|
||||||
|
console.warn("\t1. " + tokenpath);
|
||||||
|
console.warn("\n2. " + confpath);
|
||||||
|
console.warn();
|
||||||
|
console.warn("Choosing the first.");
|
||||||
|
console.warn();
|
||||||
|
}
|
||||||
|
state.config.token = token;
|
||||||
|
rawTunnel();
|
||||||
|
});
|
||||||
|
|
||||||
|
function connectTunnel() {
|
||||||
|
state.net = {
|
||||||
|
createConnection: function (info, cb) {
|
||||||
|
// data is the hello packet / first chunk
|
||||||
|
// info = { data, servername, port, host, remoteFamily, remoteAddress, remotePort }
|
||||||
|
var net = require('net');
|
||||||
|
// socket = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
|
||||||
|
var socket = net.createConnection({ port: info.port, host: info.host }, cb);
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.greenlock = state.config.greenlock || {};
|
||||||
|
if (!state.config.sortingHat) {
|
||||||
|
state.config.sortingHat = path.resolve(__dirname, '..', 'lib/sorting-hat.js');
|
||||||
|
}
|
||||||
|
// TODO sortingHat.print(); ?
|
||||||
|
|
||||||
|
if (state.config.email && !state.token) {
|
||||||
|
console.info();
|
||||||
|
console.info('==================================');
|
||||||
|
console.info('= HEY! LISTEN! =');
|
||||||
|
console.info('==================================');
|
||||||
|
console.info('= =');
|
||||||
|
console.info('= 1. Open your email =');
|
||||||
|
console.info('= 2. Click the magic login link =');
|
||||||
|
console.info('= 3. Check back here for deets =');
|
||||||
|
console.info('= =');
|
||||||
|
console.info('==================================');
|
||||||
|
console.info();
|
||||||
|
}
|
||||||
|
// TODO Check undefined vs false for greenlock config
|
||||||
|
var tun = remote.connect({
|
||||||
|
relay: state.config.relay
|
||||||
|
, config: state.config
|
||||||
|
, _confpath: confpath
|
||||||
|
, sortingHat: state.config.sortingHat
|
||||||
|
, net: state.net
|
||||||
|
, insecure: state.config.relay_ignore_invalid_certificates
|
||||||
|
, token: state.token
|
||||||
|
, handlers: {
|
||||||
|
grant: function (grants) {
|
||||||
|
console.info("");
|
||||||
|
console.info("Connect to your device by any of the following means:");
|
||||||
|
console.info("");
|
||||||
|
grants.forEach(function (arr) {
|
||||||
|
if ('ssh+https' === arr[0]) {
|
||||||
|
console.info("SSH+HTTPS");
|
||||||
|
} else if ('ssh' === arr[0]) {
|
||||||
|
console.info("SSH");
|
||||||
|
} else if ('tcp' === arr[0]) {
|
||||||
|
console.info("TCP");
|
||||||
|
} else if ('https' === arr[0]) {
|
||||||
|
console.info("HTTPS");
|
||||||
|
}
|
||||||
|
console.log('\t' + arr[0] + '://' + arr[1] + (arr[2] ? (':' + arr[2]) : ''));
|
||||||
|
if ('ssh+https' === arr[0]) {
|
||||||
|
console.info("\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -quiet' " + arr[1] + " -p 443\n");
|
||||||
|
} else if ('ssh' === arr[0]) {
|
||||||
|
console.info("\tex: ssh " + arr[1] + " -p " + arr[2] + "\n");
|
||||||
|
} else if ('tcp' === arr[0]) {
|
||||||
|
console.info("\tex: netcat " + arr[1] + " " + arr[2] + "\n");
|
||||||
|
} else if ('https' === arr[0]) {
|
||||||
|
console.info("\tex: curl https://" + arr[1] + "\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, access_token: function (opts) {
|
||||||
|
console.info("Updating '" + tokenpath + "' with new token:");
|
||||||
|
try {
|
||||||
|
require('fs').writeFileSync(tokenpath, opts.jwt);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Token not saved:");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, greenlockConfig: {
|
||||||
|
version: state.greenlock.version || 'draft-11'
|
||||||
|
, server: state.greenlock.server || 'https://acme-v02.api.letsencrypt.org/directory'
|
||||||
|
, communityMember: state.greenlock.communityMember || state.config.communityMember
|
||||||
|
, telemetry: state.greenlock.telemetry || state.config.telemetry
|
||||||
|
, configDir: state.greenlock.configDir || path.resolve(__dirname, '..', '/etc/acme/')
|
||||||
|
// TODO, store: require(state.greenlock.store.name || 'le-store-certbot').create(state.greenlock.store.options || {})
|
||||||
|
, approveDomains: function (opts, certs, cb) {
|
||||||
|
// Certs being renewed are listed in certs.altnames
|
||||||
|
if (certs) {
|
||||||
|
opts.domains = certs.altnames;
|
||||||
|
cb(null, { options: opts, certs: certs });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// by virtue of the fact that it's being tunneled through a
|
||||||
|
// trusted source that is already checking, we're good
|
||||||
|
//if (-1 !== state.config.servernames.indexOf(opts.domains[0])) {
|
||||||
|
opts.email = state.greenlock.email || state.config.email;
|
||||||
|
opts.agreeTos = state.greenlock.agree || state.config.agreeTos;
|
||||||
|
cb(null, { options: opts, certs: certs });
|
||||||
|
return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//cb(new Error("servername not found in allowed list"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sigHandler() {
|
||||||
|
console.info('Received kill signal. Attempting to exit cleanly...');
|
||||||
|
|
||||||
|
// We want to handle cleanup properly unless something is broken in our cleanup process
|
||||||
|
// that prevents us from exitting, in which case we want the user to be able to send
|
||||||
|
// the signal again and exit the way it normally would.
|
||||||
|
process.removeListener('SIGINT', sigHandler);
|
||||||
|
tun.end();
|
||||||
|
}
|
||||||
|
process.on('SIGINT', sigHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rawTunnel() {
|
||||||
|
if (!state.config.relay) {
|
||||||
|
throw 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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var location = url.parse(state.config.relay);
|
||||||
|
if (!location.protocol || /\./.test(location.protocol)) {
|
||||||
|
state.config.relay = 'wss://' + state.config.relay;
|
||||||
|
location = url.parse(state.config.relay);
|
||||||
|
}
|
||||||
|
var aud = location.hostname + (location.port ? ':' + location.port : '');
|
||||||
|
state.config.relay = location.protocol + '//' + aud;
|
||||||
|
|
||||||
|
if (!state.config.token && state.config.secret) {
|
||||||
|
var jwt = require('jsonwebtoken');
|
||||||
|
var tokenData = {
|
||||||
|
domains: Object.keys(state.config.servernames || {}).filter(function (name) { return /\./.test(name); })
|
||||||
|
, aud: aud
|
||||||
|
, iss: Math.round(Date.now() / 1000)
|
||||||
|
};
|
||||||
|
|
||||||
|
state.token = jwt.sign(tokenData, state.config.secret);
|
||||||
|
}
|
||||||
|
state.token = state.token || state.config.token;
|
||||||
|
|
||||||
|
// TODO sign token with own private key, including public key and thumbprint
|
||||||
|
// (much like ACME JOSE account)
|
||||||
|
|
||||||
|
connectTunnel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var domainsMap = {};
|
||||||
|
var services = {};
|
||||||
|
|
||||||
|
function collectDomains(val, memo) {
|
||||||
|
var vals = val.split(/,/g);
|
||||||
|
|
||||||
|
function parseProxy(location) {
|
||||||
|
// john.example.com
|
||||||
|
// http:john.example.com:3000
|
||||||
|
// http://john.example.com:3000
|
||||||
|
var parts = location.split(':');
|
||||||
|
if (1 === parts.length) {
|
||||||
|
// john.example.com -> :john.example.com:0
|
||||||
|
parts[1] = parts[0];
|
||||||
|
|
||||||
|
parts[0] = '';
|
||||||
|
parts[2] = 0;
|
||||||
|
}
|
||||||
|
else if (2 === parts.length) {
|
||||||
|
throw new Error("invalid arguments for --domains, should use the format <domainname> or <scheme>:<domainname>:<local-port>");
|
||||||
|
}
|
||||||
|
if (!parts[1]) {
|
||||||
|
throw new Error("invalid arguments for --domains, should use the format <domainname> or <scheme>:<domainname>:<local-port>");
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0] = parts[0].toLowerCase();
|
||||||
|
parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '');
|
||||||
|
parts[2] = parseInt(parts[2], 10) || 0;
|
||||||
|
|
||||||
|
memo.push({
|
||||||
|
protocol: parts[0]
|
||||||
|
, hostname: parts[1]
|
||||||
|
, port: parts[2]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.map(function (val) {
|
||||||
|
return parseProxy(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
function collectProxies(val, memo) {
|
||||||
|
var vals = val.split(/,/g);
|
||||||
|
|
||||||
|
function parseProxy(location) {
|
||||||
|
// john.example.com
|
||||||
|
// https:3443
|
||||||
|
// http:john.example.com:3000
|
||||||
|
// http://john.example.com:3000
|
||||||
|
var parts = location.split(':');
|
||||||
|
var dual = false;
|
||||||
|
if (1 === parts.length) {
|
||||||
|
// john.example.com -> :john.example.com:0
|
||||||
|
parts[1] = parts[0];
|
||||||
|
|
||||||
|
parts[0] = '';
|
||||||
|
parts[2] = 0;
|
||||||
|
|
||||||
|
dual = true;
|
||||||
|
}
|
||||||
|
else if (2 === parts.length) {
|
||||||
|
// https:3443 -> https:*:3443
|
||||||
|
parts[2] = parts[1];
|
||||||
|
|
||||||
|
parts[1] = '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0] = parts[0].toLowerCase();
|
||||||
|
parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*';
|
||||||
|
parts[2] = parseInt(parts[2], 10) || 0;
|
||||||
|
if (!parts[2]) {
|
||||||
|
// TODO grab OS list of standard ports?
|
||||||
|
if (!parts[0] || 'http' === parts[0]) {
|
||||||
|
parts[2] = 80;
|
||||||
|
}
|
||||||
|
else if ('https' === parts[0]) {
|
||||||
|
parts[2] = 443;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("port must be specified - ex: tls:*:1337");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memo.push({
|
||||||
|
protocol: parts[0] || 'https'
|
||||||
|
, hostname: parts[1]
|
||||||
|
, port: parts[2] || 443
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dual) {
|
||||||
|
memo.push({
|
||||||
|
protocol: 'http'
|
||||||
|
, hostname: parts[1]
|
||||||
|
, port: 80
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vals.map(function (val) {
|
||||||
|
return parseProxy(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
var program = require('commander');
|
||||||
|
program
|
||||||
|
.version(pkg.version)
|
||||||
|
//.command('jsurl <url>')
|
||||||
|
.arguments('<url>')
|
||||||
|
.action(function (url) {
|
||||||
|
program.url = url;
|
||||||
|
})
|
||||||
|
.option('-k --insecure', 'Allow TLS connections to a Telebit Relay without valid certs (rejectUnauthorized: false)')
|
||||||
|
.option('--locals <LIST>', 'comma separated list of <proto>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https:8443,smtps:8465', collectProxies, [ ]) // --reverse-proxies
|
||||||
|
.option('--domains <LIST>', 'comma separated list of domain names to set to the tunnel (to capture a specific protocol to a specific local port use the format https:example.com:1337 instead). Ex: example.com,example.net', collectDomains, [ ])
|
||||||
|
.option('--device [HOSTNAME]', 'Tunnel all domains associated with this device instead of specific domainnames. Use with --locals <proto>:<port>. Ex: macbook-pro.local (the output of `hostname`)')
|
||||||
|
.option('--relay <URL>', 'the domain (or ip address) at which you are running Telebit Relay (the proxy)') // --proxy
|
||||||
|
.option('--secret <STRING>', 'the same secret used by the Telebit Relay (used for JWT authentication)')
|
||||||
|
.option('--token <STRING>', 'a pre-generated token for use with the Telebit Relay (instead of generating one with --secret)')
|
||||||
|
.option('--agree-tos', 'agree to the Telebit Terms of Service (requires user validation)')
|
||||||
|
.option('--email <EMAIL>', 'email address (or cloud address) for user validation')
|
||||||
|
.option('--oauth3-url <URL>', 'Cloud Authentication to use (default: https://oauth3.org)')
|
||||||
|
.parse(process.argv)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
program.locals = (program.locals || []).concat(program.domains || []);
|
||||||
|
program.locals.forEach(function (proxy) {
|
||||||
|
// Create a map from which we can derive a list of all domains we want forwarded to us.
|
||||||
|
if (proxy.hostname && proxy.hostname !== '*') {
|
||||||
|
domainsMap[proxy.hostname] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map of which port different protocols should be forwarded to, allowing for specific
|
||||||
|
// domains to go to different ports if need be (though that only works for HTTP and HTTPS).
|
||||||
|
if (proxy.protocol && proxy.port) {
|
||||||
|
services[proxy.protocol] = services[proxy.protocol] || {};
|
||||||
|
|
||||||
|
if (/http/.test(proxy.protocol) && proxy.hostname && proxy.hostname !== '*') {
|
||||||
|
services[proxy.protocol][proxy.hostname] = proxy.port;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (services[proxy.protocol]['*'] && services[proxy.protocol]['*'] !== proxy.port) {
|
||||||
|
console.error('cannot forward generic', proxy.protocol, 'traffic to multiple ports');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
services[proxy.protocol]['*'] = proxy.port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(domainsMap).length === 0) {
|
||||||
|
console.error('no domains specified');
|
||||||
|
process.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we have generic ports for HTTP and HTTPS
|
||||||
|
services.https = services.https || {};
|
||||||
|
services.https['*'] = services.https['*'] || 8443;
|
||||||
|
|
||||||
|
services.http = services.http || {};
|
||||||
|
services.http['*'] = services.http['*'] || services.https['*'];
|
||||||
|
|
||||||
|
program.services = services;
|
||||||
|
*/
|
||||||
|
|
||||||
|
}());
|
112
client.js
112
client.js
@ -1,112 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var net = require('net');
|
|
||||||
var jwt = require('jsonwebtoken');
|
|
||||||
var sni = require('sni');
|
|
||||||
// TODO ask oauth3.org where to connect
|
|
||||||
// TODO reconnect on disconnect
|
|
||||||
|
|
||||||
// Assumption: will not get next tcp packet unless previous packet succeeded
|
|
||||||
//var services = { 'ssh': 22, 'http': 80, 'https': 443 };
|
|
||||||
var services = { 'ssh': 22, 'http': 4080, 'https': 8443 };
|
|
||||||
var hostname = 'aj.daplie.me'; // 'test.hellabit.com'
|
|
||||||
|
|
||||||
function addrToId(address) {
|
|
||||||
return address.family + ',' + address.address + ',' + address.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
function socketToAddr(socket) {
|
|
||||||
return { family: socket.remoteFamily, address: socket.remoteAddress, port: socket.remotePort };
|
|
||||||
}
|
|
||||||
|
|
||||||
function socketToId(socket) {
|
|
||||||
return addrToId(socketToAddr(socket));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
var tunneler = net.connect({ port: 5443 , host: hostname }, function () {
|
|
||||||
var token = jwt.sign({ name: hostname }, 'shhhhh');
|
|
||||||
var localclients = {};
|
|
||||||
|
|
||||||
setInterval(function () {
|
|
||||||
console.log('');
|
|
||||||
console.log('localclients.length:', Object.keys(localclients).length);
|
|
||||||
console.log('');
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
tunneler.write(token);
|
|
||||||
|
|
||||||
// BaaS / Backendless / noBackend / horizon.io
|
|
||||||
// user authentication
|
|
||||||
// a place to store data
|
|
||||||
// file management
|
|
||||||
// Synergy Teamwork Paradigm = Jabberwocky
|
|
||||||
var pack = require('tunnel-packer').pack;
|
|
||||||
|
|
||||||
function onMessage(opts) {
|
|
||||||
var id = addrToId(opts);
|
|
||||||
var service = 'https';
|
|
||||||
var port = services[service];
|
|
||||||
var lclient;
|
|
||||||
|
|
||||||
if (opts.data.byteLength < 20) {
|
|
||||||
if ('|__ERROR__|' === opts.data.toString('utf8')
|
|
||||||
|| '|__END__|' === opts.data.toString('utf8')) {
|
|
||||||
|
|
||||||
console.log("end '" + opts.address + "'");
|
|
||||||
if (localclients[id]) {
|
|
||||||
localclients[id].end();
|
|
||||||
delete localclients[id];
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localclients[id]) {
|
|
||||||
console.log("received data from '" + opts.address + "'", opts.data.byteLength);
|
|
||||||
localclients[id].write(opts.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var servername = sni(opts.data);
|
|
||||||
|
|
||||||
if (!servername) {
|
|
||||||
console.warn("no servername found for '" + id + "'");
|
|
||||||
tunneler.write(pack(opts, Buffer.from('|__ERROR__|')));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("servername: '" + servername + "'");
|
|
||||||
|
|
||||||
lclient = localclients[id] = net.createConnection({ port: port, host: '127.0.0.1' }, function () {
|
|
||||||
|
|
||||||
lclient.on('data', function (chunk) {
|
|
||||||
console.log("client '" + opts.address + "' sent ", chunk.byteLength, "bytes");
|
|
||||||
tunneler.write(pack(opts, chunk));
|
|
||||||
});
|
|
||||||
lclient.on('error', function (err) {
|
|
||||||
console.error('client Error');
|
|
||||||
console.error(err);
|
|
||||||
delete localclients[id];
|
|
||||||
tunneler.write(pack(opts, Buffer.from('|__ERROR__|')));
|
|
||||||
});
|
|
||||||
lclient.on('end', function () {
|
|
||||||
console.log('client End');
|
|
||||||
delete localclients[id];
|
|
||||||
tunneler.write(pack(opts, Buffer.from('|__END__|')));
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('received data', opts.data.byteLength);
|
|
||||||
lclient.write(opts.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var machine = require('tunnel-packer').create({ onMessage: onMessage });
|
|
||||||
|
|
||||||
tunneler.on('data', machine.fns.addChunk);
|
|
||||||
|
|
||||||
tunneler.on('end', function () {
|
|
||||||
console.log('end');
|
|
||||||
});
|
|
||||||
});
|
|
15
examples/telebit.yml
Normal file
15
examples/telebit.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
email: 'jon@example.com' # must be valid (for certificate recovery and security alerts)
|
||||||
|
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
||||||
|
community_member: true # receive infrequent relevant updates
|
||||||
|
telemetry: true # contribute to project telemetric data
|
||||||
|
relay: wss://telebit.cloud # Which Telebit Relay to use
|
||||||
|
secret: '' # Shared Secret with Telebit Relay for authorization
|
||||||
|
#token: '' # Token created by Telebit Relay for authorization
|
||||||
|
ssh_auto: 22 # forward ssh-looking packets, from any connection, to port 22
|
||||||
|
servernames: # hostnames that direct to the Telebit Relay admin console
|
||||||
|
example.com: {}
|
||||||
|
example.net: {}
|
||||||
|
greenlock:
|
||||||
|
version: 'draft-11'
|
||||||
|
server: 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||||
|
config_dir: '/opt/telebit/etc/acme.staging/'
|
@ -1,13 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var request = require('request');
|
|
||||||
|
|
||||||
function run(copts) {
|
|
||||||
var tunnelUrl = 'https://tunnel.daplie.com/?access_token=' + copts.token;
|
|
||||||
request.get(tunnelUrl, { rejectUnauthorized: false }, function (err, resp) {
|
|
||||||
console.log('resp.body');
|
|
||||||
console.log(resp.body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.connect = run;
|
|
616
lib/remote.js
Normal file
616
lib/remote.js
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var WebSocket = require('ws');
|
||||||
|
var PromiseA = require('bluebird');
|
||||||
|
var sni = require('sni');
|
||||||
|
var Packer = require('proxy-packer');
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
|
function timeoutPromise(duration) {
|
||||||
|
return new PromiseA(function (resolve) {
|
||||||
|
setTimeout(resolve, duration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _connect(state) {
|
||||||
|
// jshint latedef:false
|
||||||
|
var defaultHttpTimeout = (2 * 60);
|
||||||
|
var activityTimeout = state.activityTimeout || (defaultHttpTimeout - 5) * 1000;
|
||||||
|
var pongTimeout = state.pongTimeout || 10*1000;
|
||||||
|
// Allow the tunnel client to be created with no token. This will prevent the connection from
|
||||||
|
// being established initialy and allows the caller to use `.append` for the first token so
|
||||||
|
// they can get a promise that will provide feedback about invalid tokens.
|
||||||
|
var tokens = [];
|
||||||
|
var auth;
|
||||||
|
if (state.token) {
|
||||||
|
tokens.push(state.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wstunneler;
|
||||||
|
var authenticated = false;
|
||||||
|
var authsent = false;
|
||||||
|
|
||||||
|
var localclients = {};
|
||||||
|
var pausedClients = [];
|
||||||
|
var clientHandlers = {
|
||||||
|
add: function (conn, cid, tun) {
|
||||||
|
localclients[cid] = conn;
|
||||||
|
console.info("[connect] new client '" + cid + "' for '" + tun.name + ":" + tun.serviceport + "' "
|
||||||
|
+ "(" + clientHandlers.count() + " clients)");
|
||||||
|
|
||||||
|
conn.tunnelCid = cid;
|
||||||
|
conn.tunnelRead = tun.data.byteLength;
|
||||||
|
conn.tunnelWritten = 0;
|
||||||
|
|
||||||
|
conn.on('data', function onLocalData(chunk) {
|
||||||
|
if (conn.tunnelClosing) {
|
||||||
|
console.warn("[onLocalData] received data for '"+cid+"' over socket after connection was ended");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This value is bytes written to the tunnel (ie read from the local connection)
|
||||||
|
conn.tunnelWritten += chunk.byteLength;
|
||||||
|
|
||||||
|
// If we have a lot of buffered data waiting to be sent over the websocket we want to slow
|
||||||
|
// down the data we are getting to send over. We also want to pause all active connections
|
||||||
|
// if any connections are paused to make things more fair so one connection doesn't get
|
||||||
|
// stuff waiting for all other connections to finish because it tried writing near the border.
|
||||||
|
var bufSize = wsHandlers.sendMessage(Packer.pack(tun, chunk));
|
||||||
|
if (pausedClients.length || bufSize > 1024*1024) {
|
||||||
|
// console.log('[onLocalData] paused connection', cid, 'to allow websocket to catch up');
|
||||||
|
conn.pause();
|
||||||
|
pausedClients.push(conn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var sentEnd = false;
|
||||||
|
conn.on('end', function onLocalEnd() {
|
||||||
|
console.info("[onLocalEnd] connection '" + cid + "' ended, will probably close soon");
|
||||||
|
conn.tunnelClosing = true;
|
||||||
|
if (!sentEnd) {
|
||||||
|
wsHandlers.sendMessage(Packer.pack(tun, null, 'end'));
|
||||||
|
sentEnd = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
conn.on('error', function onLocalError(err) {
|
||||||
|
console.info("[onLocalError] connection '" + cid + "' errored:", err);
|
||||||
|
if (!sentEnd) {
|
||||||
|
wsHandlers.sendMessage(Packer.pack(tun, {message: err.message, code: err.code}, 'error'));
|
||||||
|
sentEnd = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
conn.on('close', function onLocalClose(hadErr) {
|
||||||
|
delete localclients[cid];
|
||||||
|
console.log('[onLocalClose] closed "' + cid + '" read:'+conn.tunnelRead+', wrote:'+conn.tunnelWritten+' (' + clientHandlers.count() + ' clients)');
|
||||||
|
if (!sentEnd) {
|
||||||
|
wsHandlers.sendMessage(Packer.pack(tun, null, hadErr && 'error' || 'end'));
|
||||||
|
sentEnd = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, write: function (cid, opts) {
|
||||||
|
var conn = localclients[cid];
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//console.log("[=>] received data from '" + cid + "' =>", opts.data.byteLength);
|
||||||
|
|
||||||
|
if (conn.tunnelClosing) {
|
||||||
|
console.warn("[onmessage] received data for '"+cid+"' over socket after connection was ended");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.write(opts.data);
|
||||||
|
// It might seem weird to increase the "read" value in a function named `write`, but this
|
||||||
|
// is bytes read from the tunnel and written to the local connection.
|
||||||
|
conn.tunnelRead += opts.data.byteLength;
|
||||||
|
|
||||||
|
if (!conn.remotePaused && conn.bufferSize > 1024*1024) {
|
||||||
|
wsHandlers.sendMessage(Packer.pack(opts, conn.tunnelRead, 'pause'));
|
||||||
|
conn.remotePaused = true;
|
||||||
|
|
||||||
|
conn.once('drain', function () {
|
||||||
|
wsHandlers.sendMessage(Packer.pack(opts, conn.tunnelRead, 'resume'));
|
||||||
|
conn.remotePaused = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
, closeSingle: function (cid) {
|
||||||
|
if (!localclients[cid]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[closeSingle]', cid);
|
||||||
|
PromiseA.resolve().then(function () {
|
||||||
|
var conn = localclients[cid];
|
||||||
|
conn.tunnelClosing = true;
|
||||||
|
conn.end();
|
||||||
|
|
||||||
|
// If no data is buffered for writing then we don't need to wait for it to drain.
|
||||||
|
if (!conn.bufferSize) {
|
||||||
|
return timeoutPromise(500);
|
||||||
|
}
|
||||||
|
// Otherwise we want the connection to be able to finish, but we also want to impose
|
||||||
|
// a time limit for it to drain, since it shouldn't have more than 1MB buffered.
|
||||||
|
return new PromiseA(function (resolve) {
|
||||||
|
var timeoutId = setTimeout(resolve, 60*1000);
|
||||||
|
conn.once('drain', function () {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setTimeout(resolve, 500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
if (localclients[cid]) {
|
||||||
|
console.warn('[closeSingle]', cid, 'connection still present after calling `end`');
|
||||||
|
localclients[cid].destroy();
|
||||||
|
return timeoutPromise(500);
|
||||||
|
}
|
||||||
|
}).then(function () {
|
||||||
|
if (localclients[cid]) {
|
||||||
|
console.error('[closeSingle]', cid, 'connection still present after calling `destroy`');
|
||||||
|
delete localclients[cid];
|
||||||
|
}
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('[closeSingle] failed to close connection', cid, err.toString());
|
||||||
|
delete localclients[cid];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, closeAll: function () {
|
||||||
|
console.log('[closeAll]');
|
||||||
|
Object.keys(localclients).forEach(function (cid) {
|
||||||
|
clientHandlers.closeSingle(cid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, count: function () {
|
||||||
|
return Object.keys(localclients).length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var pendingCommands = {};
|
||||||
|
function sendCommand(name) {
|
||||||
|
var id = Math.ceil(1e9 * Math.random());
|
||||||
|
var cmd = [id, name].concat(Array.prototype.slice.call(arguments, 1));
|
||||||
|
if (state.debug) { console.log('[DEBUG] command sending', cmd); }
|
||||||
|
|
||||||
|
wsHandlers.sendMessage(Packer.pack(null, cmd, 'control'));
|
||||||
|
setTimeout(function () {
|
||||||
|
if (pendingCommands[id]) {
|
||||||
|
console.warn('command', name, id, 'timed out');
|
||||||
|
pendingCommands[id]({
|
||||||
|
message: 'response not received in time'
|
||||||
|
, code: 'E_TIMEOUT'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, pongTimeout);
|
||||||
|
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
pendingCommands[id] = function (err, result) {
|
||||||
|
delete pendingCommands[id];
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAllTokens() {
|
||||||
|
if (auth) {
|
||||||
|
authsent = true;
|
||||||
|
sendCommand('auth', auth).catch(function (err) { console.error('1', err); });
|
||||||
|
}
|
||||||
|
tokens.forEach(function (jwtoken) {
|
||||||
|
if (state.debug) { console.log('[DEBUG] send token'); }
|
||||||
|
authsent = true;
|
||||||
|
sendCommand('add_token', jwtoken)
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error('failed re-adding token', jwtoken, 'after reconnect', err);
|
||||||
|
// Not sure if we should do something like remove the token here. It worked
|
||||||
|
// once or it shouldn't have stayed in the list, so it's less certain why
|
||||||
|
// it would have failed here.
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function noHandler(cmd) {
|
||||||
|
console.warn("[telebit] state.handlers['" + cmd[1] + "'] not set");
|
||||||
|
console.warn(cmd[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var connCallback;
|
||||||
|
|
||||||
|
var packerHandlers = {
|
||||||
|
oncontrol: function (opts) {
|
||||||
|
var cmd, err;
|
||||||
|
try {
|
||||||
|
cmd = JSON.parse(opts.data.toString());
|
||||||
|
} catch (err) {}
|
||||||
|
if (!Array.isArray(cmd) || typeof cmd[0] !== 'number') {
|
||||||
|
console.warn('received bad command "' + opts.data.toString() + '"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd[0] < 0) {
|
||||||
|
var cb = pendingCommands[-cmd[0]];
|
||||||
|
if (!cb) {
|
||||||
|
console.warn('received response for unknown request:', cmd);
|
||||||
|
} else {
|
||||||
|
cb.apply(null, cmd.slice(1));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd[0] === 0) {
|
||||||
|
console.warn('received dis-associated error from server', cmd[1]);
|
||||||
|
if (connCallback) {
|
||||||
|
connCallback(cmd[1]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd[1] === 'hello') {
|
||||||
|
if (state.debug) { console.log('[DEBUG] hello received'); }
|
||||||
|
sendAllTokens();
|
||||||
|
if (connCallback) {
|
||||||
|
connCallback();
|
||||||
|
}
|
||||||
|
// TODO: handle the versions and commands provided by 'hello' - isn't super important
|
||||||
|
// yet since there is only one version and set of commands.
|
||||||
|
err = null;
|
||||||
|
} else if (cmd[1] === 'grant') {
|
||||||
|
authenticated = true;
|
||||||
|
if (state.handlers[cmd[1]]) {
|
||||||
|
state.handlers[cmd[1]](cmd[2]);
|
||||||
|
} else {
|
||||||
|
noHandler(cmd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (cmd[1] === 'access_token') {
|
||||||
|
authenticated = true;
|
||||||
|
if (state.handlers[cmd[1]]) {
|
||||||
|
state.handlers[cmd[1]](cmd[2]);
|
||||||
|
} else {
|
||||||
|
noHandler(cmd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
err = { message: 'unknown command "'+cmd[1]+'"', code: 'E_UNKNOWN_COMMAND' };
|
||||||
|
}
|
||||||
|
|
||||||
|
wsHandlers.sendMessage(Packer.pack(null, [-cmd[0], err], 'control'));
|
||||||
|
}
|
||||||
|
|
||||||
|
, onmessage: function (tun) {
|
||||||
|
var cid = tun._id = Packer.addrToId(tun);
|
||||||
|
var str;
|
||||||
|
var m;
|
||||||
|
|
||||||
|
if ('http' === tun.service) {
|
||||||
|
str = tun.data.toString();
|
||||||
|
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
||||||
|
tun._name = tun._hostname = (m && m[1].toLowerCase() || '').split(':')[0];
|
||||||
|
}
|
||||||
|
else if ('https' === tun.service || 'tls' === tun.service) {
|
||||||
|
tun._name = tun._servername = sni(tun.data);
|
||||||
|
} else {
|
||||||
|
tun._name = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientHandlers.write(cid, tun)) { return; }
|
||||||
|
|
||||||
|
wstunneler.pause();
|
||||||
|
require(state.sortingHat).assign(state, tun, function (err, conn) {
|
||||||
|
if (err) {
|
||||||
|
err.message = err.message.replace(/:tun_id/, tun._id);
|
||||||
|
packerHandlers._onConnectError(cid, tun, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clientHandlers.add(conn, cid, tun);
|
||||||
|
if (tun.data) { conn.write(tun.data); }
|
||||||
|
wstunneler.resume();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, onpause: function (opts) {
|
||||||
|
var cid = Packer.addrToId(opts);
|
||||||
|
if (localclients[cid]) {
|
||||||
|
console.log("[TunnelPause] pausing '"+cid+"', remote received", opts.data.toString(), 'of', localclients[cid].tunnelWritten, 'sent');
|
||||||
|
localclients[cid].manualPause = true;
|
||||||
|
localclients[cid].pause();
|
||||||
|
} else {
|
||||||
|
console.log('[TunnelPause] remote tried pausing finished connection', cid);
|
||||||
|
// Often we have enough latency that we've finished sending before we're told to pause, so
|
||||||
|
// don't worry about sending back errors, since we won't be sending data over anyway.
|
||||||
|
// wsHandlers.sendMessage(Packer.pack(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, onresume: function (opts) {
|
||||||
|
var cid = Packer.addrToId(opts);
|
||||||
|
if (localclients[cid]) {
|
||||||
|
console.log("[TunnelResume] resuming '"+cid+"', remote received", opts.data.toString(), 'of', localclients[cid].tunnelWritten, 'sent');
|
||||||
|
localclients[cid].manualPause = false;
|
||||||
|
localclients[cid].resume();
|
||||||
|
} else {
|
||||||
|
console.log('[TunnelResume] remote tried resuming finished connection', cid);
|
||||||
|
// wsHandlers.sendMessage(Packer.pack(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, onend: function (opts) {
|
||||||
|
var cid = Packer.addrToId(opts);
|
||||||
|
//console.log("[end] '" + cid + "'");
|
||||||
|
clientHandlers.closeSingle(cid);
|
||||||
|
}
|
||||||
|
, onerror: function (opts) {
|
||||||
|
var cid = Packer.addrToId(opts);
|
||||||
|
//console.log("[error] '" + cid + "'", opts.code || '', opts.message);
|
||||||
|
clientHandlers.closeSingle(cid);
|
||||||
|
}
|
||||||
|
|
||||||
|
, _onConnectError: function (cid, opts, err) {
|
||||||
|
console.info("[_onConnectError] opening '" + cid + "' failed because " + err.message);
|
||||||
|
wsHandlers.sendMessage(Packer.pack(opts, null, 'error'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var lastActivity;
|
||||||
|
var timeoutId;
|
||||||
|
var wsHandlers = {
|
||||||
|
refreshTimeout: function () {
|
||||||
|
lastActivity = Date.now();
|
||||||
|
}
|
||||||
|
, checkTimeout: function () {
|
||||||
|
if (!wstunneler) {
|
||||||
|
console.warn('checkTimeout called when websocket already closed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine how long the connection has been "silent", ie no activity.
|
||||||
|
var silent = Date.now() - lastActivity;
|
||||||
|
|
||||||
|
// If we have had activity within the last activityTimeout then all we need to do is
|
||||||
|
// call this function again at the soonest time when the connection could be timed out.
|
||||||
|
if (silent < activityTimeout) {
|
||||||
|
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout-silent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we check to see if the pong has also timed out, and if not we send a ping
|
||||||
|
// and call this function again when the pong will have timed out.
|
||||||
|
else if (silent < activityTimeout + pongTimeout) {
|
||||||
|
console.log('pinging tunnel server');
|
||||||
|
try {
|
||||||
|
wstunneler.ping();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('failed to ping tunnel server', err);
|
||||||
|
}
|
||||||
|
timeoutId = setTimeout(wsHandlers.checkTimeout, pongTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last case means the ping we sent before didn't get a response soon enough, so we
|
||||||
|
// need to close the websocket connection.
|
||||||
|
else {
|
||||||
|
console.log('connection timed out');
|
||||||
|
wstunneler.close(1000, 'connection timeout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, onOpen: function () {
|
||||||
|
console.info("[open] connected to '" + state.relay + "'");
|
||||||
|
wsHandlers.refreshTimeout();
|
||||||
|
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout);
|
||||||
|
|
||||||
|
wstunneler._socket.on('drain', function () {
|
||||||
|
// the websocket library has it's own buffer apart from node's socket buffer, but that one
|
||||||
|
// is much more difficult to watch, so we watch for the lower level buffer to drain and
|
||||||
|
// then check to see if the upper level buffer is still too full to write to. Note that
|
||||||
|
// the websocket library buffer has something to do with compression, so I'm not requiring
|
||||||
|
// that to be 0 before we start up again.
|
||||||
|
if (wstunneler.bufferedAmount > 128*1024) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pausedClients.forEach(function (conn) {
|
||||||
|
if (!conn.manualPause) {
|
||||||
|
// console.log('resuming connection', conn.tunnelCid, 'now the websocket has caught up');
|
||||||
|
conn.resume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pausedClients.length = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, onClose: function () {
|
||||||
|
console.log('ON CLOSE');
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
wstunneler = null;
|
||||||
|
clientHandlers.closeAll();
|
||||||
|
|
||||||
|
var error = new Error('websocket connection closed before response');
|
||||||
|
error.code = 'E_CONN_CLOSED';
|
||||||
|
Object.keys(pendingCommands).forEach(function (id) {
|
||||||
|
pendingCommands[id](error);
|
||||||
|
});
|
||||||
|
if (connCallback) {
|
||||||
|
connCallback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authenticated) {
|
||||||
|
console.info('[close] failed on first attempt... check authentication.');
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
else if (tokens.length) {
|
||||||
|
console.info('[retry] disconnected and waiting...');
|
||||||
|
timeoutId = setTimeout(connect, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, onError: function (err) {
|
||||||
|
console.error("[tunnel error] " + err.message);
|
||||||
|
console.error(err);
|
||||||
|
if (connCallback) {
|
||||||
|
connCallback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, sendMessage: function (msg) {
|
||||||
|
if (wstunneler) {
|
||||||
|
try {
|
||||||
|
wstunneler.send(msg, {binary: true});
|
||||||
|
return wstunneler.bufferedAmount;
|
||||||
|
} catch (err) {
|
||||||
|
// There is a chance that this occurred after the websocket was told to close
|
||||||
|
// and before it finished, in which case we don't need to log the error.
|
||||||
|
if (wstunneler.readyState !== wstunneler.CLOSING) {
|
||||||
|
console.warn('[sendMessage] error sending websocket message', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
if (wstunneler) {
|
||||||
|
console.warn('attempted to connect with connection already active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tokens.length) {
|
||||||
|
if (state.config.email) {
|
||||||
|
auth = {
|
||||||
|
subject: state.config.email
|
||||||
|
, subject_scheme: 'mailto'
|
||||||
|
// TODO create domains list earlier
|
||||||
|
, scope: Object.keys(state.config.servernames || {}).join(',')
|
||||||
|
, hostname: os.hostname()
|
||||||
|
// Used for User-Agent
|
||||||
|
, os_type: os.type()
|
||||||
|
, os_platform: os.platform()
|
||||||
|
, os_release: os.release()
|
||||||
|
, os_arch: os.arch()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeoutId = null;
|
||||||
|
var machine = Packer.create(packerHandlers);
|
||||||
|
|
||||||
|
console.info("[connect] '" + state.relay + "'");
|
||||||
|
var tunnelUrl = state.relay.replace(/\/$/, '') + '/'; // + auth;
|
||||||
|
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
|
||||||
|
wstunneler.on('open', wsHandlers.onOpen);
|
||||||
|
wstunneler.on('close', wsHandlers.onClose);
|
||||||
|
wstunneler.on('error', wsHandlers.onError);
|
||||||
|
|
||||||
|
// Our library will automatically handle sending the pong respose to ping requests.
|
||||||
|
wstunneler.on('ping', wsHandlers.refreshTimeout);
|
||||||
|
wstunneler.on('pong', wsHandlers.refreshTimeout);
|
||||||
|
wstunneler.on('message', function (data, flags) {
|
||||||
|
wsHandlers.refreshTimeout();
|
||||||
|
if (data.error || '{' === data[0]) {
|
||||||
|
console.log(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
machine.fns.addChunk(data, flags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
connect();
|
||||||
|
|
||||||
|
var connPromise;
|
||||||
|
return {
|
||||||
|
end: function() {
|
||||||
|
tokens.length = 0;
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wstunneler) {
|
||||||
|
try {
|
||||||
|
wstunneler.close();
|
||||||
|
} catch(e) {
|
||||||
|
console.error("[error] wstunneler.close()");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, append: function (token) {
|
||||||
|
if (tokens.indexOf(token) >= 0) {
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
tokens.push(token);
|
||||||
|
var prom;
|
||||||
|
if (tokens.length === 1 && !wstunneler) {
|
||||||
|
// We just added the only token in the list, and the websocket connection isn't up
|
||||||
|
// so we need to restart the connection.
|
||||||
|
if (timeoutId) {
|
||||||
|
// Handle the case were the last token was removed and this token added between
|
||||||
|
// reconnect attempts to make sure we don't try openning multiple connections.
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want this case to behave as much like the other case as we can, but we don't have
|
||||||
|
// the same kind of reponses when we open brand new connections, so we have to rely on
|
||||||
|
// the 'hello' and the 'un-associated' error commands to determine if the token is good.
|
||||||
|
prom = connPromise = new PromiseA(function (resolve, reject) {
|
||||||
|
connCallback = function (err) {
|
||||||
|
connCallback = null;
|
||||||
|
connPromise = null;
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
else if (connPromise) {
|
||||||
|
prom = connPromise.then(function () {
|
||||||
|
return sendCommand('add_token', token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prom = sendCommand('add_token', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
prom.catch(function (err) {
|
||||||
|
console.error('adding token', token, 'failed:', err);
|
||||||
|
// Most probably an invalid token of some kind, so we don't really want to keep it.
|
||||||
|
tokens.splice(tokens.indexOf(token), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return prom;
|
||||||
|
}
|
||||||
|
, clear: function (token) {
|
||||||
|
if (typeof token === 'undefined') {
|
||||||
|
token = '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token === '*') {
|
||||||
|
tokens.length = 0;
|
||||||
|
} else {
|
||||||
|
var index = tokens.indexOf(token);
|
||||||
|
if (index < 0) {
|
||||||
|
return PromiseA.resolve();
|
||||||
|
}
|
||||||
|
tokens.splice(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
var prom = sendCommand('delete_token', token);
|
||||||
|
prom.catch(function (err) {
|
||||||
|
console.error('clearing token', token, 'failed:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return prom;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.connect = _connect;
|
||||||
|
module.exports.createConnection = _connect;
|
||||||
|
|
||||||
|
}());
|
372
lib/sorting-hat.js
Normal file
372
lib/sorting-hat.js
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
module.exports.print = function (config) {
|
||||||
|
var services = { https: {}, http: {}, tcp: {} };
|
||||||
|
// Note: the remote needs to know:
|
||||||
|
// what servernames to forward
|
||||||
|
// what ports to forward
|
||||||
|
// what udp ports to forward
|
||||||
|
// redirect http to https automatically
|
||||||
|
// redirect www to nowww automatically
|
||||||
|
if (config.http) {
|
||||||
|
Object.keys(config.http).forEach(function (hostname) {
|
||||||
|
if ('*' === hostname) {
|
||||||
|
config.servernames.forEach(function (servername) {
|
||||||
|
services.https[servername] = config.http[hostname];
|
||||||
|
services.http[servername] = 'redirect-https';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
services.https[hostname] = config.http[hostname];
|
||||||
|
services.http[hostname] = 'redirect-https';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Object.keys(config.localPorts).forEach(function (port) {
|
||||||
|
var proto = config.localPorts[port];
|
||||||
|
if (!proto) { return; }
|
||||||
|
if ('http' === proto) {
|
||||||
|
config.servernames.forEach(function (servername) {
|
||||||
|
services.http[servername] = port;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('https' === proto) {
|
||||||
|
config.servernames.forEach(function (servername) {
|
||||||
|
services.https[servername] = port;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (true === proto) { proto = 'tcp'; }
|
||||||
|
if ('tcp' !== proto) { throw new Error("unsupported protocol '" + proto + "'"); }
|
||||||
|
//services[proxy.protocol]['*'] = proxy.port;
|
||||||
|
//services[proxy.protocol][proxy.hostname] = proxy.port;
|
||||||
|
services[proto]['*'] = port;
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.keys(services).forEach(function (protocol) {
|
||||||
|
var subServices = services[protocol];
|
||||||
|
Object.keys(subServices).forEach(function (hostname) {
|
||||||
|
console.info('[local proxy]', protocol + '://' + hostname + ' => ' + subServices[hostname]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.info('');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.assign = function (state, tun, cb) {
|
||||||
|
console.log('first message from', tun);
|
||||||
|
var net = state.net || require('net');
|
||||||
|
|
||||||
|
function trySsh(tun, cb) {
|
||||||
|
// https://security.stackexchange.com/questions/43231/plausibly-deniable-ssh-does-it-make-sense?rq=1
|
||||||
|
// https://tools.ietf.org/html/rfc4253#section-4.2
|
||||||
|
if (false === state.config.ssh_auto || 'SSH-2.0-' !== tun.data.slice(0, 8).toString()) {
|
||||||
|
cb(null, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(null, getNetConn(state.config.sshPort || 22));
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlers = {};
|
||||||
|
handlers.http = function (socket) {
|
||||||
|
if (!state.greenlock) {
|
||||||
|
state.greenlock = require('greenlock').create(state.greenlockConfig);
|
||||||
|
}
|
||||||
|
if (!state.httpRedirectServer) {
|
||||||
|
state.redirectHttps = require('redirect-https')();
|
||||||
|
state.httpRedirectServer = require('http').createServer(state.greenlock.middleware(state.redirectHttps));
|
||||||
|
}
|
||||||
|
state.httpRedirectServer.emit('connection', socket);
|
||||||
|
};
|
||||||
|
handlers.https = function (tlsSocket) {
|
||||||
|
console.log('Enccrypted', tlsSocket.encrypted, tlsSocket.remoteAddress, tlsSocket.remotePort);
|
||||||
|
if (!state.defaultHttpServer) {
|
||||||
|
state.defaultHttpServer = require('http').createServer(function (req, res) {
|
||||||
|
console.log('[hit http/s server]');
|
||||||
|
res.end('Hello, Encrypted Tunnel World!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
state.defaultHttpServer.emit('connection', tlsSocket);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getNetConn(port) {
|
||||||
|
var netOpts = {
|
||||||
|
port: port
|
||||||
|
, host: '127.0.0.1'
|
||||||
|
|
||||||
|
, servername: tun.name
|
||||||
|
, name: tun.name
|
||||||
|
, serviceport: tun.serviceport
|
||||||
|
, data: tun.data
|
||||||
|
, remoteFamily: tun.family
|
||||||
|
, remoteAddress: tun.address
|
||||||
|
, remotePort: tun.port
|
||||||
|
};
|
||||||
|
var conn = net.createConnection(netOpts, function () {
|
||||||
|
// this will happen before 'data' or 'readable' is triggered
|
||||||
|
// We use the data from the netOpts object so that the createConnection function has
|
||||||
|
// the oppurtunity of removing/changing it if it wants/needs to handle it differently.
|
||||||
|
});
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectHttp(cb) {
|
||||||
|
var socketPair = require('socket-pair');
|
||||||
|
var conn = socketPair.create(function (err, other) {
|
||||||
|
if (err) { cb(err); return; }
|
||||||
|
handlers.http(other);
|
||||||
|
cb(null, conn);
|
||||||
|
});
|
||||||
|
//if (tun.data) { conn.write(tun.data); }
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function echoTcp(cb) {
|
||||||
|
var socketPair = require('socket-pair');
|
||||||
|
var conn = socketPair.create(function (err, other) {
|
||||||
|
if (err) { cb(err); return; }
|
||||||
|
|
||||||
|
other.on('data', function (chunk) {
|
||||||
|
other.write(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
other.on('end', function () {
|
||||||
|
other.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
cb(null, conn);
|
||||||
|
|
||||||
|
other.write("[Telebit Echo Server] v1.0\nPlease edit your config file to port forward somewhere more useful.\n\n");
|
||||||
|
});
|
||||||
|
//if (tun.data) { conn.write(tun.data); }
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineProps(other, tun) {
|
||||||
|
Object.defineProperty(other, 'remoteFamily', {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return tun.family;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(other, 'remoteAddress', {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return tun.address;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(other, 'remotePort', {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return parseInt(tun.port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.defineProperty(other, 'localPort', {
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
get: function() {
|
||||||
|
return parseInt(tun.serviceport);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeHandler(conf, tlsSocket, tun, id) {
|
||||||
|
if (parseInt(conf.handler, 10)) {
|
||||||
|
// TODO http-proxy with proper headers and ws support
|
||||||
|
var conn = getNetConn(conf.handler);
|
||||||
|
console.info("Port-Forwarding '" + (tun.name || tun.serviceport) + "' to '" + conf.handler + "'");
|
||||||
|
conn.pipe(tlsSocket);
|
||||||
|
tlsSocket.pipe(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var handle = tun.name || tun.port;
|
||||||
|
var handler;
|
||||||
|
var path = require('path');
|
||||||
|
var homedir = require('os').homedir();
|
||||||
|
var localshare = path.join(homedir, '.local/share/telebit/apps');
|
||||||
|
|
||||||
|
if (/^~/.test(conf.handler)) {
|
||||||
|
conf.handler = require('path').join(require('os').homedir(), conf.handler.replace(/^~(\/?)/, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
handler = require(conf.handler);
|
||||||
|
console.info("Handling '" + handle + ":" + id + "' with '" + conf.handler + "'");
|
||||||
|
handler(tlsSocket, tun, id);
|
||||||
|
} catch(e1) {
|
||||||
|
try {
|
||||||
|
handler = require(path.join(localshare, conf.handler));
|
||||||
|
console.info("Handling '" + handle + ":" + id + "' with '" + conf.handler + "'");
|
||||||
|
handler(tlsSocket, tun, id);
|
||||||
|
} catch(e2) {
|
||||||
|
console.error("Failed to load '" + conf.handler + "':", e1.message);
|
||||||
|
console.error("Failed to load '" + path.join(localshare, conf.handler) + "':", e2.message);
|
||||||
|
console.warn("Using default handler for '" + handle + ":" + id + "'");
|
||||||
|
handlers.https(tlsSocket, tun, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function terminateTls(tun, cb) {
|
||||||
|
var socketPair = require('socket-pair');
|
||||||
|
var conn = socketPair.create(function (err, other) {
|
||||||
|
if (err) { cb(err); return; }
|
||||||
|
|
||||||
|
//console.log('[hit tcp connection]', other.remoteFamily, other.remoteAddress, other.remotePort, other.localPort);
|
||||||
|
defineProps(other, tun);
|
||||||
|
//console.log('[hit tcp connection]', other.remoteFamily, other.remoteAddress, other.remotePort, other.localPort);
|
||||||
|
|
||||||
|
if (!state.greenlock) {
|
||||||
|
state.greenlock = require('greenlock').create(state.greenlockConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.terminatorServer) {
|
||||||
|
state.terminatorServer = require('tls').createServer(state.greenlock.tlsOptions, function (tlsSocket) {
|
||||||
|
var Packer = require('proxy-packer');
|
||||||
|
var addr = Packer.socketToAddr(tlsSocket);
|
||||||
|
var id = Packer.addrToId(addr);
|
||||||
|
|
||||||
|
defineProps(tlsSocket, addr);
|
||||||
|
//console.log('[hit tls server]', tlsSocket.remoteFamily, tlsSocket.remoteAddress, tlsSocket.remotePort, tlsSocket.localPort);
|
||||||
|
//console.log(addr);
|
||||||
|
var conf = state.config.servernames[tlsSocket.servername];
|
||||||
|
tlsSocket.once('data', function (firstChunk) {
|
||||||
|
tlsSocket.pause();
|
||||||
|
//tlsSocket.unshift(firstChunk);
|
||||||
|
tlsSocket._handle.onread(firstChunk.length, firstChunk);
|
||||||
|
|
||||||
|
trySsh({ data: firstChunk }, function (err, conn) {
|
||||||
|
process.nextTick(function () { tlsSocket.resume(); });
|
||||||
|
|
||||||
|
if (conn) {
|
||||||
|
conn.pipe(tlsSocket);
|
||||||
|
tlsSocket.pipe(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conf || !conf.handler) {
|
||||||
|
console.log('https default handler');
|
||||||
|
handlers.https(tlsSocket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('https invokeHandler');
|
||||||
|
invokeHandler(conf, tlsSocket, tun, id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('[hit tcp connection]', other.remoteFamily, other.remoteAddress, other.remotePort, other.localPort);
|
||||||
|
state.terminatorServer.emit('connection', other);
|
||||||
|
cb(null, conn);
|
||||||
|
});
|
||||||
|
//if (tun.data) { conn.write(tun.data); }
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handled;
|
||||||
|
|
||||||
|
if (!tun.name && !tun.serviceport) {
|
||||||
|
console.log('tun:\n',tun);
|
||||||
|
//console.warn(tun.data.toString());
|
||||||
|
cb(new Error("No routing information for ':tun_id'. Missing both 'name' and 'serviceport'."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.config.servernames) {
|
||||||
|
state.config.servernames = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ('http' === tun.service || 'https' === tun.service) {
|
||||||
|
if (!tun.name) {
|
||||||
|
cb(new Error("No routing information for ':tun_id'. Service '" + tun.service + "' is missing 'name'."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('http' === tun.service) {
|
||||||
|
// TODO match *.example.com
|
||||||
|
handled = Object.keys(state.config.servernames).some(function (sn) {
|
||||||
|
if (sn !== tun.name) { return; }
|
||||||
|
|
||||||
|
console.log('Found config match for PLAIN', tun.name);
|
||||||
|
if (!state.config.servernames[sn]) { return; }
|
||||||
|
|
||||||
|
if (false === state.config.servernames[sn].terminate) {
|
||||||
|
cb(new Error("insecure http not supported yet"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Redirecting HTPTP for', tun.name);
|
||||||
|
redirectHttp(cb);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!handled) {
|
||||||
|
redirectHttp(cb);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('https' === tun.service) {
|
||||||
|
// TODO match *.example.com
|
||||||
|
handled = Object.keys(state.config.servernames).some(function (sn) {
|
||||||
|
if (sn !== tun.name) { return; }
|
||||||
|
|
||||||
|
console.log('Found config match for TLS', tun.name);
|
||||||
|
if (!state.config.servernames[sn]) { return; }
|
||||||
|
|
||||||
|
if (false === state.config.servernames[sn].terminate) {
|
||||||
|
cb(new Error("insecure http not supported yet"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Terminating TLS for', tun.name);
|
||||||
|
terminateTls(tun, cb);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!handled) {
|
||||||
|
terminateTls(tun, cb);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('tcp' === tun.service) {
|
||||||
|
trySsh(tun, function (err, conn) {
|
||||||
|
if (conn) { cb(null, conn); return; }
|
||||||
|
// TODO add TCP handlers
|
||||||
|
console.log('Using echo server for tcp');
|
||||||
|
echoTcp(cb);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Unknown service '" + tun.service + "'");
|
||||||
|
|
||||||
|
/*
|
||||||
|
var portList = state.services[service];
|
||||||
|
var port;
|
||||||
|
port = portList[tun.name];
|
||||||
|
if (!port) {
|
||||||
|
// Check for any wildcard domains, sorted longest to shortest so the one with the
|
||||||
|
// biggest natural match will be found first.
|
||||||
|
Object.keys(portList).filter(function (pattern) {
|
||||||
|
return pattern[0] === '*' && pattern.length > 1;
|
||||||
|
}).sort(function (a, b) {
|
||||||
|
return b.length - a.length;
|
||||||
|
}).some(function (pattern) {
|
||||||
|
var subPiece = pattern.slice(1);
|
||||||
|
if (subPiece === tun.name.slice(-subPiece.length)) {
|
||||||
|
port = portList[pattern];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!port) {
|
||||||
|
port = portList['*'];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
};
|
31
package.json
31
package.json
@ -1,19 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "stunnel",
|
"name": "telebit",
|
||||||
"version": "0.8.1",
|
"version": "0.12.0",
|
||||||
"description": "A pure-JavaScript tunnel client for http and https similar to localtunnel.me, but uses TLS (SSL) with ServerName Indication (SNI) over https to work even in harsh network conditions such as in student dorms and behind HOAs, corporate firewalls, public libraries, airports, airplanes, etc. Can also tunnel tls and plain tcp.",
|
"description": "Break out of localhost. Connect to any device from anywhere over any tcp port or securely in a browser. A secure tunnel. A poor man's reverse VPN.",
|
||||||
"main": "wsclient.js",
|
"main": "lib/remote.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jstunnel": "bin/stunnel.js",
|
"telebit": "bin/telebit.js"
|
||||||
"stunnel.js": "bin/stunnel.js",
|
|
||||||
"stunnel-js": "bin/stunnel.js"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@github.com/Daplie/node-tunnel-client.git"
|
"url": "https://git.coolaj86.com/coolaj86/telebit.js.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
@ -32,6 +30,9 @@
|
|||||||
"tunnel",
|
"tunnel",
|
||||||
"localtunnel",
|
"localtunnel",
|
||||||
"localtunnel.me",
|
"localtunnel.me",
|
||||||
|
"underpass",
|
||||||
|
"ngrok",
|
||||||
|
"ngrok.io",
|
||||||
"proxy",
|
"proxy",
|
||||||
"reverse",
|
"reverse",
|
||||||
"reverse-proxy",
|
"reverse-proxy",
|
||||||
@ -42,14 +43,20 @@
|
|||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||||
"license": "(MIT OR Apache-2.0)",
|
"license": "(MIT OR Apache-2.0)",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Daplie/node-tunnel-client/issues"
|
"url": "https://git.coolaj86.com/coolaj86/telebit.js/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Daplie/node-tunnel-client#readme",
|
"homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.1",
|
||||||
"commander": "^2.9.0",
|
"commander": "^2.9.0",
|
||||||
|
"greenlock": "^2.2.19",
|
||||||
|
"js-yaml": "^3.11.0",
|
||||||
"jsonwebtoken": "^7.1.9",
|
"jsonwebtoken": "^7.1.9",
|
||||||
|
"proxy-packer": "^1.4.3",
|
||||||
|
"recase": "^1.0.4",
|
||||||
|
"redirect-https": "^1.1.5",
|
||||||
"sni": "^1.0.0",
|
"sni": "^1.0.0",
|
||||||
"tunnel-packer": "^1.1.0",
|
"socket-pair": "^1.0.3",
|
||||||
"ws": "^1.1.1"
|
"ws": "^2.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var WebSocket = require('ws');
|
|
||||||
var jwt = require('jsonwebtoken');
|
|
||||||
var hostname = 'example.daplie.me';
|
|
||||||
var token = jwt.sign({ name: hostname }, 'shhhhh');
|
|
||||||
var url = 'wss://stunnel.hellabit.com:3000/?access_token=' + token;
|
|
||||||
var wstunneler = new WebSocket(url, { rejectUnauthorized: false });
|
|
||||||
|
|
||||||
wstunneler.on('open', function () {
|
|
||||||
console.log('open');
|
|
||||||
});
|
|
||||||
|
|
||||||
wstunneler.on('error', function (err) {
|
|
||||||
console.error(err.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
}());
|
|
0
usr/share/.gitkeep
Normal file
0
usr/share/.gitkeep
Normal file
57
usr/share/dist/Library/LaunchDaemons/cloud.telebit.remote.plist
vendored
Normal file
57
usr/share/dist/Library/LaunchDaemons/cloud.telebit.remote.plist
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>Telebit Remote</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/opt/telebit/bin/node</string>
|
||||||
|
<string>/opt/telebit/bin/telebit.js</string>
|
||||||
|
<string>--config</string>
|
||||||
|
<string>/opt/telebit/etc/telebit.yml</string>
|
||||||
|
</array>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>TELEBIT_PATH</key>
|
||||||
|
<string>/opt/telebit</string>
|
||||||
|
<key>NODE_PATH</key>
|
||||||
|
<string>/opt/telebit/lib/node_modules</string>
|
||||||
|
<key>NPM_CONFIG_PREFIX</key>
|
||||||
|
<string>/opt/telebit</string>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>root</string>
|
||||||
|
<key>GroupName</key>
|
||||||
|
<string>wheel</string>
|
||||||
|
<key>InitGroups</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<dict>
|
||||||
|
<key>Crashed</key>
|
||||||
|
<true/>
|
||||||
|
<key>SuccessfulExit</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>SoftResourceLimits</key>
|
||||||
|
<dict>
|
||||||
|
<key>NumberOfFiles</key>
|
||||||
|
<integer>8192</integer>
|
||||||
|
</dict>
|
||||||
|
<key>HardResourceLimits</key>
|
||||||
|
<dict/>
|
||||||
|
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/opt/telebit</string>
|
||||||
|
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/opt/telebit/var/log/error.log</string>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/opt/telebit/var/log/info.log</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
61
usr/share/dist/etc/skel/.config/systemd/user/telebit.service
vendored
Normal file
61
usr/share/dist/etc/skel/.config/systemd/user/telebit.service
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Pre-req
|
||||||
|
# sudo adduser telebit --home /opt/telebit
|
||||||
|
# sudo mkdir -p /opt/telebit/
|
||||||
|
# sudo chown -R telebit:telebit /opt/telebit/
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Telebit Remote
|
||||||
|
Documentation=https://git.coolaj86.com/coolaj86/telebit.js/
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
# Restart on crash (bad signal), but not on 'clean' failure (error exit code)
|
||||||
|
# Allow up to 3 restarts within 10 seconds
|
||||||
|
# (it's unlikely that a user or properly-running script will do this)
|
||||||
|
Restart=always
|
||||||
|
StartLimitInterval=10
|
||||||
|
StartLimitBurst=3
|
||||||
|
|
||||||
|
# https://wiki.archlinux.org/index.php/Systemd/User
|
||||||
|
# ~/.local/share/systemd/user/
|
||||||
|
WorkingDirectory=%h/.config/telebit
|
||||||
|
# custom directory cannot be set and will be the place where gitea exists, not the working directory
|
||||||
|
ExecStart=/opt/telebit/bin/node /opt/telebit/bin/telebit.js --config /etc/telebit/telebit.yml
|
||||||
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
|
||||||
|
# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings.
|
||||||
|
# Unmodified gitea is not expected to use more than this.
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
LimitNPROC=64
|
||||||
|
|
||||||
|
# Use private /tmp and /var/tmp, which are discarded after gitea stops.
|
||||||
|
PrivateTmp=true
|
||||||
|
# Use a minimal /dev
|
||||||
|
PrivateDevices=true
|
||||||
|
# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
|
||||||
|
ProtectHome=true
|
||||||
|
# Make /usr, /boot, /etc and possibly some more folders read-only.
|
||||||
|
ProtectSystem=full
|
||||||
|
# ... except /opt/gitea because we want a place for the database
|
||||||
|
# and /var/log/gitea because we want a place where logs can go.
|
||||||
|
# This merely retains r/w access rights, it does not add any new.
|
||||||
|
# Must still be writable on the host!
|
||||||
|
ReadWriteDirectories=/opt/telebit /etc/telebit
|
||||||
|
|
||||||
|
# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories
|
||||||
|
; ReadWritePaths=/opt/telebit /etc/telebit
|
||||||
|
|
||||||
|
# The following additional security directives only work with systemd v229 or later.
|
||||||
|
# They further retrict privileges that can be gained by gitea.
|
||||||
|
# Note that you may have to add capabilities required by any plugins in use.
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
# Caveat: Some features may need additional capabilities.
|
||||||
|
# For example an "upload" may need CAP_LEASE
|
||||||
|
; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE
|
||||||
|
; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE
|
||||||
|
; NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
65
usr/share/dist/etc/systemd/system/telebit.service
vendored
Normal file
65
usr/share/dist/etc/systemd/system/telebit.service
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Pre-req
|
||||||
|
# sudo adduser telebit --home /opt/telebit
|
||||||
|
# sudo mkdir -p /opt/telebit/
|
||||||
|
# sudo chown -R telebit:telebit /opt/telebit/
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Telebit Remote
|
||||||
|
Documentation=https://git.coolaj86.com/coolaj86/telebit.js/
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target systemd-networkd-wait-online.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
# Restart on crash (bad signal), but not on 'clean' failure (error exit code)
|
||||||
|
# Allow up to 3 restarts within 10 seconds
|
||||||
|
# (it's unlikely that a user or properly-running script will do this)
|
||||||
|
Restart=always
|
||||||
|
StartLimitInterval=10
|
||||||
|
StartLimitBurst=3
|
||||||
|
|
||||||
|
# User and group the process will run as
|
||||||
|
# (git is the de facto standard on most systems)
|
||||||
|
User=telebit
|
||||||
|
Group=telebit
|
||||||
|
|
||||||
|
WorkingDirectory=/opt/telebit
|
||||||
|
# custom directory cannot be set and will be the place where this exists, not the working directory
|
||||||
|
ExecStart=/opt/telebit/bin/node /opt/telebit/bin/telebit.js --config /opt/telebit/etc/telebit.yml
|
||||||
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
|
||||||
|
# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings.
|
||||||
|
# Unmodified, this is not expected to use more than this.
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
LimitNPROC=64
|
||||||
|
|
||||||
|
# Use private /tmp and /var/tmp, which are discarded after this stops.
|
||||||
|
PrivateTmp=true
|
||||||
|
# Use a minimal /dev
|
||||||
|
PrivateDevices=true
|
||||||
|
# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
|
||||||
|
ProtectHome=true
|
||||||
|
# Make /usr, /boot, /etc and possibly some more folders read-only.
|
||||||
|
ProtectSystem=full
|
||||||
|
# ... except /opt/telebit because we want a place for config, logs, etc
|
||||||
|
# This merely retains r/w access rights, it does not add any new.
|
||||||
|
# Must still be writable on the host!
|
||||||
|
ReadWriteDirectories=/opt/telebit
|
||||||
|
|
||||||
|
# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories
|
||||||
|
; ReadWritePaths=/opt/telebit
|
||||||
|
|
||||||
|
# The following additional security directives only work with systemd v229 or later.
|
||||||
|
# They further retrict privileges that can be gained.
|
||||||
|
# Note that you may have to add capabilities required by any plugins in use.
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
# Caveat: Some features may need additional capabilities.
|
||||||
|
# For example an "upload" may need CAP_LEASE
|
||||||
|
; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE
|
||||||
|
; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE
|
||||||
|
; NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
81
usr/share/install.sh
Normal file
81
usr/share/install.sh
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#<pre><code>
|
||||||
|
|
||||||
|
# This script does exactly 3 things for 1 good reason:
|
||||||
|
#
|
||||||
|
# What this does:
|
||||||
|
#
|
||||||
|
# 1. Detects either curl or wget and wraps them in helpers
|
||||||
|
# 2. Exports the helpers for the real installer
|
||||||
|
# 3. Downloads and runs the real installer
|
||||||
|
#
|
||||||
|
# Why
|
||||||
|
#
|
||||||
|
# 1. 'curl <smth> | bash -- some args here` breaks interactive input
|
||||||
|
# See https://stackoverflow.com/questions/16854041/bash-read-is-being-skipped-when-run-from-curl-pipe
|
||||||
|
#
|
||||||
|
# 2. It also has practical risks of running a partially downloaded script, which could be dangeresque
|
||||||
|
# See https://news.ycombinator.com/item?id=12767636
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# #
|
||||||
|
# http_get #
|
||||||
|
# boilerplate for curl / wget #
|
||||||
|
# #
|
||||||
|
###############################
|
||||||
|
|
||||||
|
# See https://git.coolaj86.com/coolaj86/snippets/blob/master/bash/http-get.sh
|
||||||
|
|
||||||
|
export _my_http_get=""
|
||||||
|
export _my_http_opts=""
|
||||||
|
export _my_http_out=""
|
||||||
|
|
||||||
|
detect_http_get()
|
||||||
|
{
|
||||||
|
set +e
|
||||||
|
if type -p curl >/dev/null 2>&1; then
|
||||||
|
_my_http_get="curl"
|
||||||
|
_my_http_opts="-fsSL"
|
||||||
|
_my_http_out="-o"
|
||||||
|
elif type -p wget >/dev/null 2>&1; then
|
||||||
|
_my_http_get="wget"
|
||||||
|
_my_http_opts="--quiet"
|
||||||
|
_my_http_out="-O"
|
||||||
|
else
|
||||||
|
echo "Aborted, could not find curl or wget"
|
||||||
|
return 7
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
}
|
||||||
|
|
||||||
|
http_get()
|
||||||
|
{
|
||||||
|
$_my_http_get $_my_http_opts $_my_http_out "$2" "$1"
|
||||||
|
touch "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
http_bash()
|
||||||
|
{
|
||||||
|
_http_url=$1
|
||||||
|
my_args=${2:-}
|
||||||
|
my_tmp=$(mktemp)
|
||||||
|
$_my_http_get $_my_http_opts $_my_http_out "$my_tmp" "$_http_url"; bash "$my_tmp" $my_args; rm "$my_tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_http_get
|
||||||
|
export -f http_get
|
||||||
|
export -f http_bash
|
||||||
|
|
||||||
|
###############################
|
||||||
|
## END HTTP_GET ##
|
||||||
|
###############################
|
||||||
|
|
||||||
|
my_branch=master
|
||||||
|
if [ -e "usr/share/install_helper.sh" ]; then
|
||||||
|
bash usr/share/install_helper.sh "$@"
|
||||||
|
else
|
||||||
|
http_bash https://git.coolaj86.com/coolaj86/telebit.js/raw/branch/$my_branch/usr/share/install_helper.sh "$@"
|
||||||
|
fi
|
391
usr/share/install_helper.sh
Normal file
391
usr/share/install_helper.sh
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#<pre><code>
|
||||||
|
|
||||||
|
# What does this do.. and why?
|
||||||
|
# (and why is it so complicated?)
|
||||||
|
#
|
||||||
|
# What this does
|
||||||
|
#
|
||||||
|
# 1. Sets some vars and asks some questions
|
||||||
|
# 2. Installs everything into a single place
|
||||||
|
# (inculding deps like node.js, with the correct version)
|
||||||
|
# 3. Depending on OS, creates a user for the service
|
||||||
|
# 4. Depending on OS, register with system launcher
|
||||||
|
#
|
||||||
|
# Why
|
||||||
|
#
|
||||||
|
# So that you can get a fully configured, running product,
|
||||||
|
# with zero manual configuration in a matter of seconds -
|
||||||
|
# and have an uninstall that's just as easy.
|
||||||
|
#
|
||||||
|
# Why so complicated?
|
||||||
|
#
|
||||||
|
# To support nuance differences between various versions of
|
||||||
|
# Linux, macOS, and Android, including whether it's being
|
||||||
|
# installed with user privileges, as root, wit a system user
|
||||||
|
# system daemon launcher, etc. Also, this is designed to be
|
||||||
|
# reusable with many apps and services, so it's very variabled...
|
||||||
|
|
||||||
|
# hack to allow calling script to finish before this executes
|
||||||
|
sleep 0.1
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
### http_bash exported by get.sh
|
||||||
|
|
||||||
|
my_email=${1:-}
|
||||||
|
my_relay=${2:-}
|
||||||
|
my_servernames=${3:-}
|
||||||
|
my_secret=${4:-}
|
||||||
|
my_user="telebit"
|
||||||
|
my_app_pkg_name="cloud.telebit.remote"
|
||||||
|
my_app="telebit"
|
||||||
|
my_bin="telebit.js"
|
||||||
|
my_name="Telebit Remote"
|
||||||
|
my_repo="telebit.js"
|
||||||
|
my_root=${my_root:-} # todo better install script
|
||||||
|
sudo_cmd="sudo"
|
||||||
|
sudo_cmde="sudo "
|
||||||
|
exec 3<>/dev/tty
|
||||||
|
read_cmd="read -u 3"
|
||||||
|
# TODO detect if rsync is available and use rsync -a (more portable)
|
||||||
|
rsync_cmd="cp -pPR"
|
||||||
|
|
||||||
|
set +e
|
||||||
|
my_edit=$(basename "${EDITOR:-}")
|
||||||
|
if [ -z "$my_edit" ]; then
|
||||||
|
my_edit=$(basename "$(type -p edit)")
|
||||||
|
fi
|
||||||
|
if [ -z "$my_edit" ]; then
|
||||||
|
my_edit=$(basename "$(type -p nano)")
|
||||||
|
fi
|
||||||
|
if [ -z "$my_edit" ]; then
|
||||||
|
my_edit=$(basename "$(type -p vim)")
|
||||||
|
fi
|
||||||
|
if [ -z "$my_edit" ]; then
|
||||||
|
my_edit=$(basename "$(type -p vi)")
|
||||||
|
fi
|
||||||
|
if [ -z "$my_edit" ]; then
|
||||||
|
my_edit="nano"
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "root" == $(whoami) ] || [ 0 == $(id -u) ]; then
|
||||||
|
sudo_cmd=" "
|
||||||
|
sudo_cmde=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${my_email}" ]; then
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "Telebit uses Greenlock for free automated ssl through Let's Encrypt."
|
||||||
|
echo ""
|
||||||
|
echo "To accept the Terms of Service for Telebit, Greenlock and Let's Encrypt,"
|
||||||
|
echo "please enter your email."
|
||||||
|
echo ""
|
||||||
|
$read_cmd -p "email: " my_email
|
||||||
|
echo ""
|
||||||
|
# UX - just want a smooth transition
|
||||||
|
sleep 0.5
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${my_relay}" ]; then
|
||||||
|
echo "What self-hosted relay will you be using?"
|
||||||
|
#echo "What relay will you be using? (press enter for default)"
|
||||||
|
echo ""
|
||||||
|
#$read_cmd -p "relay [default: wss://telebit.cloud]: " my_relay
|
||||||
|
$read_cmd -p "relay: " my_relay
|
||||||
|
echo ""
|
||||||
|
my_relay=${my_relay:-wss://telebit.cloud}
|
||||||
|
# UX - just want a smooth transition
|
||||||
|
sleep 0.5
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${my_servernames}" ]; then
|
||||||
|
#echo "What servername(s) will you be relaying here? (press enter for default)"
|
||||||
|
echo "What servername(s) will you be relaying here?"
|
||||||
|
echo ""
|
||||||
|
#$read_cmd -p "domain [default: <random>.telebit.cloud]: " my_servernames
|
||||||
|
$read_cmd -p "domain: " my_servernames
|
||||||
|
echo ""
|
||||||
|
# UX - just want a smooth transition
|
||||||
|
sleep 0.5
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${my_secret}" ]; then
|
||||||
|
#echo "What's your authorization for the relay server? (press enter for default)"
|
||||||
|
echo "What's your authorization for the relay server?"
|
||||||
|
echo ""
|
||||||
|
#$read_cmd -p "auth [default: new account]: " my_secret
|
||||||
|
$read_cmd -p "secret: " my_secret
|
||||||
|
echo ""
|
||||||
|
# UX - just want a smooth transition
|
||||||
|
sleep 0.5
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -z "${TELEBIT_PATH:-}" ]; then
|
||||||
|
echo 'TELEBIT_PATH="'${TELEBIT_PATH:-}'"'
|
||||||
|
TELEBIT_PATH=/opt/$my_app
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing $my_name to '$TELEBIT_PATH'"
|
||||||
|
|
||||||
|
echo "Installing node.js dependencies into '$TELEBIT_PATH'"
|
||||||
|
# v10.2+ has much needed networking fixes, but breaks ursa. v9.x has severe networking bugs. v8.x has working ursa, but requires tls workarounds"
|
||||||
|
NODEJS_VER="${NODEJS_VER:-v10}"
|
||||||
|
export NODEJS_VER
|
||||||
|
export NODE_PATH="$TELEBIT_PATH/lib/node_modules"
|
||||||
|
export NPM_CONFIG_PREFIX="$TELEBIT_PATH"
|
||||||
|
export PATH="$TELEBIT_PATH/bin:$PATH"
|
||||||
|
sleep 0.5
|
||||||
|
echo "(your password may be required to complete installation)"
|
||||||
|
http_bash https://git.coolaj86.com/coolaj86/node-installer.sh/raw/branch/master/install.sh --no-dev-deps >/dev/null 2>/dev/null
|
||||||
|
|
||||||
|
my_tree="telebit" # my_branch
|
||||||
|
my_node="$TELEBIT_PATH/bin/node"
|
||||||
|
my_npm="$my_node $TELEBIT_PATH/bin/npm"
|
||||||
|
my_tmp="$(mktemp -d)"
|
||||||
|
mkdir -p $my_tmp
|
||||||
|
|
||||||
|
echo "${sudo_cmde}mkdir -p '$TELEBIT_PATH'"
|
||||||
|
$sudo_cmd mkdir -p "$TELEBIT_PATH"
|
||||||
|
$sudo_cmd mkdir -p "$TELEBIT_PATH/etc"
|
||||||
|
$sudo_cmd mkdir -p "$TELEBIT_PATH/var/log"
|
||||||
|
$sudo_cmd chown -R $(id -u -n):$(id -g -n) "$TELEBIT_PATH"
|
||||||
|
#echo "${sudo_cmde}mkdir -p '/etc/$my_app/'"
|
||||||
|
#$sudo_cmd mkdir -p "/etc/$my_app/"
|
||||||
|
#$sudo_cmd chown $(id -u -n):$(id -g -n) "/etc/$my_app/"
|
||||||
|
|
||||||
|
#https://git.coolaj86.com/coolaj86/telebit.js.git
|
||||||
|
#https://git.coolaj86.com/coolaj86/telebit.js/archive/:tree:.tar.gz
|
||||||
|
#https://git.coolaj86.com/coolaj86/telebit.js/archive/:tree:.zip
|
||||||
|
set +e
|
||||||
|
my_unzip=$(type -p unzip)
|
||||||
|
my_tar=$(type -p tar)
|
||||||
|
if [ -n "$my_unzip" ]; then
|
||||||
|
rm -f $my_tmp/$my_app-$my_tree.zip
|
||||||
|
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$my_tree.zip $my_tmp/$my_app-$my_tree.zip
|
||||||
|
# -o means overwrite, and there is no option to strip
|
||||||
|
$my_unzip -o $my_tmp/$my_app-$my_tree.zip -d $TELEBIT_PATH/ > /dev/null 2>&1
|
||||||
|
$rsync_cmd $TELEBIT_PATH/$my_repo/* $TELEBIT_PATH/ > /dev/null
|
||||||
|
rm -rf $TELEBIT_PATH/$my_bin
|
||||||
|
elif [ -n "$my_tar" ]; then
|
||||||
|
rm -f $my_tmp/$my_app-$my_tree.tar.gz
|
||||||
|
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$my_tree.tar.gz $my_tmp/$my_app-$my_tree.tar.gz
|
||||||
|
ls -lah $my_tmp/$my_app-$my_tree.tar.gz
|
||||||
|
$my_tar -xzf $my_tmp/$my_app-$my_tree.tar.gz --strip 1 -C $TELEBIT_PATH/
|
||||||
|
else
|
||||||
|
echo "Neither tar nor unzip found. Abort."
|
||||||
|
exit 13
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
pushd $TELEBIT_PATH >/dev/null
|
||||||
|
$my_npm install >/dev/null 2>/dev/null
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
cat << EOF > $TELEBIT_PATH/bin/$my_app
|
||||||
|
#!/bin/bash
|
||||||
|
$my_node $TELEBIT_PATH/bin/$my_bin
|
||||||
|
EOF
|
||||||
|
chmod a+x $TELEBIT_PATH/bin/$my_app
|
||||||
|
|
||||||
|
# Create uninstall script based on the install script variables
|
||||||
|
cat << EOF > $TELEBIT_PATH/bin/${my_app}_uninstall
|
||||||
|
#!/bin/bash
|
||||||
|
if [ "$(type -p launchctl)" ]; then
|
||||||
|
sudo launchctl unload -w /Library/LaunchDaemons/${my_app_pkg_name}.plist
|
||||||
|
sudo rm -rf /Library/LaunchDaemons/cloud.telebit.remote.plist
|
||||||
|
fi
|
||||||
|
if [ "$(type -p systemctl)" ]; then
|
||||||
|
sudo systemctl disable telebit; sudo systemctl stop telebit
|
||||||
|
sudo rm -rf /etc/systemd/system/$my_app.service
|
||||||
|
fi
|
||||||
|
sudo rm -rf $TELEBIT_PATH /usr/local/bin/$my_app
|
||||||
|
rm -rf ~/.config/$my_app ~/.local/share/$my_app
|
||||||
|
EOF
|
||||||
|
chmod a+x $TELEBIT_PATH/bin/${my_app}_uninstall
|
||||||
|
|
||||||
|
echo "${sudo_cmde}ln -sf $TELEBIT_PATH/bin/$my_app /usr/local/bin/$my_app"
|
||||||
|
$sudo_cmd ln -sf $TELEBIT_PATH/bin/$my_app /usr/local/bin/$my_app
|
||||||
|
|
||||||
|
set +e
|
||||||
|
if type -p setcap >/dev/null 2>&1; then
|
||||||
|
#echo "Setting permissions to allow $my_app to run on port 80 and port 443 without sudo or root"
|
||||||
|
echo "${sudo_cmde}setcap cap_net_bind_service=+ep $TELEBIT_PATH/bin/node"
|
||||||
|
$sudo_cmd setcap cap_net_bind_service=+ep $TELEBIT_PATH/bin/node
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
set +e
|
||||||
|
# TODO for macOS https://apple.stackexchange.com/questions/286749/how-to-add-a-user-from-the-command-line-in-macos
|
||||||
|
if type -p adduser >/dev/null 2>/dev/null; then
|
||||||
|
if [ -z "$(cat $my_root/etc/passwd | grep $my_user)" ]; then
|
||||||
|
$sudo_cmd adduser --home $TELEBIT_PATH --gecos '' --disabled-password $my_user >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
#my_user=$my_app_name
|
||||||
|
my_group=$my_user
|
||||||
|
elif [ -n "$(cat /etc/passwd | grep www-data:)" ]; then
|
||||||
|
# Linux (Ubuntu)
|
||||||
|
my_user=www-data
|
||||||
|
my_group=www-data
|
||||||
|
elif [ -n "$(cat /etc/passwd | grep _www:)" ]; then
|
||||||
|
# Mac
|
||||||
|
my_user=_www
|
||||||
|
my_group=_www
|
||||||
|
else
|
||||||
|
# Unsure
|
||||||
|
my_user=$(id -u -n) # $(whoami)
|
||||||
|
my_group=$(id -g -n)
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# TODO don't create this in TMP_PATH if it exists in TELEBIT_PATH
|
||||||
|
my_config="$TELEBIT_PATH/etc/$my_app.yml"
|
||||||
|
mkdir -p "$(dirname $my_config)"
|
||||||
|
if [ ! -e "$my_config" ]; then
|
||||||
|
#$rsync_cmd examples/$my_app.yml "$my_config"
|
||||||
|
if [ -n "$my_email" ]; then
|
||||||
|
echo "email: $my_email" >> "$my_config"
|
||||||
|
echo "agree_tos: true" >> "$my_config"
|
||||||
|
fi
|
||||||
|
if [ -n "$my_relay" ]; then
|
||||||
|
echo "relay: $my_relay" >> "$my_config"
|
||||||
|
fi
|
||||||
|
if [ -n "$my_secret" ]; then
|
||||||
|
echo "secret: $my_secret" >> "$my_config"
|
||||||
|
fi
|
||||||
|
if [ -n "$my_servernames" ]; then
|
||||||
|
# TODO could use printf or echo -e,
|
||||||
|
# just not sure how portable they are
|
||||||
|
echo "servernames:" >> "$my_config"
|
||||||
|
echo " $my_servernames: {}" >> "$my_config"
|
||||||
|
fi
|
||||||
|
#echo "dynamic_ports:\n []" >> "$my_config"
|
||||||
|
cat $TELEBIT_PATH/usr/share/$my_app.tpl.yml >> "$my_config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#my_config_link="/etc/$my_app/$my_app.yml"
|
||||||
|
#if [ ! -e "$my_config_link" ]; then
|
||||||
|
# echo "${sudo_cmde}ln -sf '$my_config' '$my_config_link'"
|
||||||
|
# #$sudo_cmd mkdir -p /etc/$my_app
|
||||||
|
# $sudo_cmd ln -sf "$my_config" "$my_config_link"
|
||||||
|
#fi
|
||||||
|
|
||||||
|
my_config="$HOME/.config/$my_app/$my_app.yml"
|
||||||
|
mkdir -p "$(dirname $my_config)"
|
||||||
|
if [ ! -e "$my_config" ]; then
|
||||||
|
echo "cli: true" >> "$my_config"
|
||||||
|
if [ -n "$my_email" ]; then
|
||||||
|
echo "email: $my_email" >> "$my_config"
|
||||||
|
echo "agree_tos: true" >> "$my_config"
|
||||||
|
fi
|
||||||
|
if [ -n "$my_relay" ]; then
|
||||||
|
echo "relay: $my_relay" >> "$my_config"
|
||||||
|
fi
|
||||||
|
if [ -n "$my_secret" ]; then
|
||||||
|
echo "secret: $my_secret" >> "$my_config"
|
||||||
|
fi
|
||||||
|
cat $TELEBIT_PATH/usr/share/$my_app.tpl.yml >> "$my_config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${sudo_cmde}chown -R $my_user '$TELEBIT_PATH' # '/etc/$my_app'"
|
||||||
|
$sudo_cmd chown -R $my_user "$TELEBIT_PATH" # "/etc/$my_app"
|
||||||
|
|
||||||
|
# ~/.config/systemd/user/
|
||||||
|
# %h/.config/telebit/telebit.yml
|
||||||
|
echo "### Adding $my_app is a system service"
|
||||||
|
# TODO detect with type -p
|
||||||
|
my_system_launcher=""
|
||||||
|
if [ -d "/Library/LaunchDaemons" ]; then
|
||||||
|
my_system_launcher="launchd"
|
||||||
|
my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist"
|
||||||
|
echo "${sudo_cmde}$rsync_cmd $TELEBIT_PATH/usr/share/dist/$my_app_launchd_service /$my_app_launchd_service"
|
||||||
|
$sudo_cmd $rsync_cmd "$TELEBIT_PATH/usr/share/dist/$my_app_launchd_service" "/$my_app_launchd_service"
|
||||||
|
|
||||||
|
echo "${sudo_cmde}chown root:wheel $my_root/$my_app_launchd_service"
|
||||||
|
$sudo_cmd chown root:wheel "$my_root/$my_app_launchd_service"
|
||||||
|
echo "${sudo_cmde}launchctl unload -w $my_root/$my_app_launchd_service >/dev/null 2>/dev/null"
|
||||||
|
$sudo_cmd launchctl unload -w "$my_root/$my_app_launchd_service" >/dev/null 2>/dev/null
|
||||||
|
echo "${sudo_cmde}launchctl load -w $my_root/$my_app_launchd_service"
|
||||||
|
$sudo_cmd launchctl load -w "$my_root/$my_app_launchd_service"
|
||||||
|
|
||||||
|
elif [ -d "$my_root/etc/systemd/system" ]; then
|
||||||
|
my_system_launcher="systemd"
|
||||||
|
echo "${sudo_cmde}$rsync_cmd $TELEBIT_PATH/usr/share/dist/etc/systemd/system/$my_app.service /etc/systemd/system/$my_app.service"
|
||||||
|
$sudo_cmd $rsync_cmd "$TELEBIT_PATH/usr/share/dist/etc/systemd/system/$my_app.service" "/etc/systemd/system/$my_app.service"
|
||||||
|
|
||||||
|
$sudo_cmd systemctl daemon-reload
|
||||||
|
echo "${sudo_cmde}systemctl enable $my_app"
|
||||||
|
$sudo_cmd systemctl enable $my_app
|
||||||
|
echo "${sudo_cmde}systemctl start $my_app"
|
||||||
|
$sudo_cmd systemctl restart $my_app
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "=============================================="
|
||||||
|
echo " Privacy Settings in Config"
|
||||||
|
echo "=============================================="
|
||||||
|
echo ""
|
||||||
|
echo "The default config file $TELEBIT_PATH/etc/$my_app.yml opts-in to"
|
||||||
|
echo "contributing telemetrics and receiving infrequent relevant updates"
|
||||||
|
echo "(probably once per quarter or less) such as important notes on"
|
||||||
|
echo "a new release, an important API change, etc. No spam."
|
||||||
|
echo ""
|
||||||
|
echo "Please edit the config file to meet your needs before starting."
|
||||||
|
echo ""
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "=============================================="
|
||||||
|
echo "Installed successfully. Last steps:"
|
||||||
|
echo "=============================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "systemd" == "$my_system_launcher" ]; then
|
||||||
|
|
||||||
|
echo "Edit the config and restart, if desired:"
|
||||||
|
echo ""
|
||||||
|
echo " ${sudo_cmde}$my_edit $TELEBIT_PATH/etc/$my_app.yml"
|
||||||
|
echo " ${sudo_cmde}systemctl restart $my_app"
|
||||||
|
echo ""
|
||||||
|
echo "Or disabled the service and start manually:"
|
||||||
|
echo ""
|
||||||
|
echo " ${sudo_cmde}systemctl stop $my_app"
|
||||||
|
echo " ${sudo_cmde}systemctl disable $my_app"
|
||||||
|
echo " $my_app --config $TELEBIT_PATH/etc/$my_app.yml"
|
||||||
|
|
||||||
|
elif [ "launchd" == "$my_system_launcher" ]; then
|
||||||
|
|
||||||
|
echo "Edit the config and restart, if desired:"
|
||||||
|
echo ""
|
||||||
|
echo " ${sudo_cmde}$my_edit $TELEBIT_PATH/etc/$my_app.yml"
|
||||||
|
echo " ${sudo_cmde}launchctl unload $my_root/$my_app_launchd_service"
|
||||||
|
echo " ${sudo_cmde}launchctl load -w $my_root/$my_app_launchd_service"
|
||||||
|
echo ""
|
||||||
|
echo "Or disabled the service and start manually:"
|
||||||
|
echo ""
|
||||||
|
echo " ${sudo_cmde}launchctl unload -w $my_root/$my_app_launchd_service"
|
||||||
|
echo " $my_app --config $TELEBIT_PATH/etc/$my_app.yml"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
echo "Edit the config, if desired:"
|
||||||
|
echo ""
|
||||||
|
echo " ${sudo_cmde}$my_edit $my_config"
|
||||||
|
echo ""
|
||||||
|
echo "Or disabled the service and start manually:"
|
||||||
|
echo ""
|
||||||
|
echo " $my_app --config $my_config"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
sleep 1
|
4
usr/share/telebit.tpl.yml
Normal file
4
usr/share/telebit.tpl.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
|
||||||
|
community_member: true # receive infrequent relevant updates
|
||||||
|
telemetry: true # contribute to project telemetric data
|
||||||
|
ssh_auto: 22 # forward ssh-looking packets, from any connection, to port 22
|
241
wsclient.js
241
wsclient.js
@ -1,241 +0,0 @@
|
|||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var WebSocket = require('ws');
|
|
||||||
var sni = require('sni');
|
|
||||||
var Packer = require('tunnel-packer');
|
|
||||||
var authenticated = false;
|
|
||||||
|
|
||||||
function run(copts) {
|
|
||||||
// TODO pair with hostname / sni
|
|
||||||
copts.services = {};
|
|
||||||
copts.locals.forEach(function (proxy) {
|
|
||||||
//program.services = { 'ssh': 22, 'http': 80, 'https': 443 };
|
|
||||||
copts.services[proxy.protocol] = proxy.port;
|
|
||||||
});
|
|
||||||
|
|
||||||
var tunnelUrl = copts.stunneld.replace(/\/$/, '') + '/?access_token=' + copts.token;
|
|
||||||
var wstunneler;
|
|
||||||
var localclients = {};
|
|
||||||
// BaaS / Backendless / noBackend / horizon.io
|
|
||||||
// user authentication
|
|
||||||
// a place to store data
|
|
||||||
// file management
|
|
||||||
// Synergy Teamwork Paradigm = Jabberwocky
|
|
||||||
var handlers = {
|
|
||||||
onmessage: function (opts) {
|
|
||||||
var net = copts.net || require('net');
|
|
||||||
var cid = Packer.addrToId(opts);
|
|
||||||
var service = opts.service;
|
|
||||||
var port = copts.services[service];
|
|
||||||
var servername;
|
|
||||||
var str;
|
|
||||||
var m;
|
|
||||||
|
|
||||||
authenticated = true;
|
|
||||||
|
|
||||||
if (localclients[cid]) {
|
|
||||||
//console.log("[=>] received data from '" + cid + "' =>", opts.data.byteLength);
|
|
||||||
localclients[cid].write(opts.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if ('http' === service) {
|
|
||||||
str = opts.data.toString();
|
|
||||||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
|
|
||||||
servername = (m && m[1].toLowerCase() || '').split(':')[0];
|
|
||||||
}
|
|
||||||
else if ('https' === service) {
|
|
||||||
servername = sni(opts.data);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
handlers._onLocalError(cid, opts, new Error("unsupported service '" + service + "'"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!servername) {
|
|
||||||
console.info("[error] missing servername for '" + cid + "'", opts.data.byteLength);
|
|
||||||
//console.warn(opts.data.toString());
|
|
||||||
wstunneler.send(Packer.pack(opts, null, 'error'), { binary: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info("[connect] new client '" + cid + "' for '" + servername + "' (" + (handlers._numClients() + 1) + " clients)");
|
|
||||||
|
|
||||||
console.log('port', port, opts.port, service, copts.services);
|
|
||||||
localclients[cid] = net.createConnection({
|
|
||||||
port: port
|
|
||||||
, host: '127.0.0.1'
|
|
||||||
|
|
||||||
, servername: servername
|
|
||||||
, data: opts.data
|
|
||||||
, remoteFamily: opts.family
|
|
||||||
, remoteAddress: opts.address
|
|
||||||
, remotePort: opts.port
|
|
||||||
}, function () {
|
|
||||||
//console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength);
|
|
||||||
// this will happen before 'data' is triggered
|
|
||||||
localclients[cid].write(opts.data);
|
|
||||||
});
|
|
||||||
// 'data'
|
|
||||||
/*
|
|
||||||
localclients[cid].on('data', function (chunk) {
|
|
||||||
//console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes");
|
|
||||||
//console.log(JSON.stringify(chunk.toString()));
|
|
||||||
wstunneler.send(Packer.pack(opts, chunk), { binary: true });
|
|
||||||
});
|
|
||||||
//*/
|
|
||||||
///*
|
|
||||||
localclients[cid].on('readable', function (size) {
|
|
||||||
var chunk;
|
|
||||||
|
|
||||||
if (!localclients[cid]) {
|
|
||||||
console.error("[error] localclients[cid]", cid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!localclients[cid].read) {
|
|
||||||
console.error("[error] localclients[cid].read", cid);
|
|
||||||
console.log(localclients[cid]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
chunk = localclients[cid].read(size);
|
|
||||||
//console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes");
|
|
||||||
//console.log(JSON.stringify(chunk.toString()));
|
|
||||||
if (chunk) {
|
|
||||||
wstunneler.send(Packer.pack(opts, chunk), { binary: true });
|
|
||||||
}
|
|
||||||
} while (chunk);
|
|
||||||
});
|
|
||||||
//*/
|
|
||||||
localclients[cid].on('error', function (err) {
|
|
||||||
handlers._onLocalError(cid, opts, err);
|
|
||||||
});
|
|
||||||
localclients[cid].on('end', function () {
|
|
||||||
console.info("[end] closing client '" + cid + "' for '" + servername + "' (" + (handlers._numClients() - 1) + " clients)");
|
|
||||||
handlers._onLocalClose(cid, opts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, onend: function (opts) {
|
|
||||||
var cid = Packer.addrToId(opts);
|
|
||||||
//console.log("[end] '" + cid + "'");
|
|
||||||
handlers._onend(cid);
|
|
||||||
}
|
|
||||||
, onerror: function (opts) {
|
|
||||||
var cid = Packer.addrToId(opts);
|
|
||||||
//console.log("[error] '" + cid + "'", opts.code || '', opts.message);
|
|
||||||
handlers._onend(cid);
|
|
||||||
}
|
|
||||||
, _onend: function (cid) {
|
|
||||||
console.log('[_onend]');
|
|
||||||
if (localclients[cid]) {
|
|
||||||
try {
|
|
||||||
localclients[cid].end();
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete localclients[cid];
|
|
||||||
}
|
|
||||||
, _onLocalClose: function (cid, opts, err) {
|
|
||||||
console.log('[_onLocalClose]');
|
|
||||||
try {
|
|
||||||
wstunneler.send(Packer.pack(opts, null, err && 'error' || 'end'), { binary: true });
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
delete localclients[cid];
|
|
||||||
}
|
|
||||||
, _onLocalError: function (cid, opts, err) {
|
|
||||||
console.info("[error] closing '" + cid + "' because '" + err.message + "' (" + (handlers._numClients() - 1) + " clients)");
|
|
||||||
handlers._onLocalClose(cid, opts, err);
|
|
||||||
}
|
|
||||||
, _numClients: function () {
|
|
||||||
return Object.keys(localclients).length;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var wsHandlers = {
|
|
||||||
onOpen: function () {
|
|
||||||
console.info("[open] connected to '" + copts.stunneld + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
, retry: true
|
|
||||||
, closeClients: function () {
|
|
||||||
console.log('[close clients]');
|
|
||||||
Object.keys(localclients).forEach(function (cid) {
|
|
||||||
try {
|
|
||||||
localclients[cid].end();
|
|
||||||
} catch(e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
delete localclients[cid];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
, onClose: function () {
|
|
||||||
console.log('ON CLOSE');
|
|
||||||
if (!authenticated) {
|
|
||||||
console.info('[close] failed on first attempt... check authentication.');
|
|
||||||
}
|
|
||||||
else if (wsHandlers.retry) {
|
|
||||||
console.info('[retry] disconnected and waiting...');
|
|
||||||
setTimeout(run, 5000, copts);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.info('[close] closing tunnel to exit...');
|
|
||||||
}
|
|
||||||
|
|
||||||
process.removeListener('exit', wsHandlers.onExit);
|
|
||||||
process.removeListener('SIGINT', wsHandlers.onExit);
|
|
||||||
wsHandlers.closeClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
, onError: function (err) {
|
|
||||||
console.error("[tunnel error] " + err.message);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
, onExit: function () {
|
|
||||||
console.log('[wait] closing wstunneler...');
|
|
||||||
wsHandlers.retry = false;
|
|
||||||
wsHandlers.closeClients();
|
|
||||||
try {
|
|
||||||
wstunneler.close();
|
|
||||||
} catch(e) {
|
|
||||||
console.error("[error] wstunneler.close()");
|
|
||||||
console.error(e);
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var machine = require('tunnel-packer').create(handlers);
|
|
||||||
|
|
||||||
console.info("[connect] '" + copts.stunneld + "'");
|
|
||||||
|
|
||||||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !copts.insecure });
|
|
||||||
wstunneler.on('open', wsHandlers.onOpen);
|
|
||||||
wstunneler.on('message', function (data, flags) {
|
|
||||||
if (data.error || '{' === data[0]) {
|
|
||||||
console.log(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
machine.fns.addChunk(data, flags);
|
|
||||||
});
|
|
||||||
wstunneler.on('close', wsHandlers.onClose);
|
|
||||||
wstunneler.on('error', wsHandlers.onError);
|
|
||||||
process.on('beforeExit', function (x) {
|
|
||||||
console.log('[beforeExit] event loop closing?', x);
|
|
||||||
});
|
|
||||||
process.on('exit', function (x) {
|
|
||||||
console.log('[exit] loop closed', x);
|
|
||||||
//wsHandlers.onExit(x);
|
|
||||||
});
|
|
||||||
process.on('SIGINT', function (x) {
|
|
||||||
console.log('SIGINT');
|
|
||||||
wsHandlers.onExit(x);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.connect = run;
|
|
||||||
|
|
||||||
}());
|
|
Loading…
x
Reference in New Issue
Block a user