forked from coolaj86/telebit.js
		
	more command and control
This commit is contained in:
		
							parent
							
								
									8383b06ca0
								
							
						
					
					
						commit
						2efbe331aa
					
				
							
								
								
									
										300
									
								
								bin/telebit.js
									
									
									
									
									
								
							
							
						
						
									
										300
									
								
								bin/telebit.js
									
									
									
									
									
								
							| @ -3,12 +3,12 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| var pkg = require('../package.json'); | var pkg = require('../package.json'); | ||||||
| console.log(pkg.name, pkg.version); | console.info(pkg.name, pkg.version); | ||||||
| 
 | 
 | ||||||
| var url = require('url'); | var url = require('url'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var remote = require('../'); | var http = require('http'); | ||||||
| var state = {}; | var state = { servernames: {}, ports: {} }; | ||||||
| 
 | 
 | ||||||
| var argv = process.argv.slice(2); | var argv = process.argv.slice(2); | ||||||
| 
 | 
 | ||||||
| @ -29,11 +29,26 @@ function help() { | |||||||
|   console.info(''); |   console.info(''); | ||||||
|   console.info('Usage:'); |   console.info('Usage:'); | ||||||
|   console.info(''); |   console.info(''); | ||||||
|   console.info('\ttelebit --config <path>'); |   console.info('\ttelebit [--config <path>] <module> <module-option>'); | ||||||
|   console.info(''); |   console.info(''); | ||||||
|   console.info('Example:'); |   console.info('Examples:'); | ||||||
|   console.info(''); |   console.info(''); | ||||||
|   console.info('\ttelebit --config /etc/telebit/telebit.yml'); |   console.info('\ttelebit --config ~/.config/telebit/telebit.yml status'); | ||||||
|  |   console.info(''); | ||||||
|  |   console.info('\ttelebit status'); | ||||||
|  |   console.info('\ttelebit enable'); | ||||||
|  |   console.info('\ttelebit disable'); | ||||||
|  |   console.info(''); | ||||||
|  |   console.info('\ttelebit list'); | ||||||
|  |   console.info(''); | ||||||
|  |   console.info('\ttelebit http 3000'); | ||||||
|  |   console.info('\ttelebit tcp 5050'); | ||||||
|  |   console.info(''); | ||||||
|  |   console.info('\ttelebit http default'); | ||||||
|  |   console.info('\ttelebit tcp default'); | ||||||
|  |   console.info(''); | ||||||
|  |   console.info('\ttelebit http /path/to/module'); | ||||||
|  |   console.info('\ttelebit tcp /path/to/module'); | ||||||
|   console.info(''); |   console.info(''); | ||||||
|   console.info('Config:'); |   console.info('Config:'); | ||||||
|   console.info(''); |   console.info(''); | ||||||
| @ -63,11 +78,13 @@ try { | |||||||
| } catch(e) { | } catch(e) { | ||||||
|   // ignore
 |   // ignore
 | ||||||
| } | } | ||||||
|  | var controlServer; | ||||||
| require('fs').readFile(confpath, 'utf8', function (err, text) { | require('fs').readFile(confpath, 'utf8', function (err, text) { | ||||||
|   var config; |   var config; | ||||||
| 
 | 
 | ||||||
|   var recase = require('recase').create({}); |   var recase = require('recase').create({}); | ||||||
|   var camelCopy = recase.camelCopy.bind(recase); |   var camelCopy = recase.camelCopy.bind(recase); | ||||||
|  |   var snakeCopy = recase.snakeCopy.bind(recase); | ||||||
| 
 | 
 | ||||||
|   if (err) { |   if (err) { | ||||||
|     console.error("\nCouldn't load config:\n\n\t" + err.message + "\n"); |     console.error("\nCouldn't load config:\n\n\t" + err.message + "\n"); | ||||||
| @ -100,21 +117,49 @@ require('fs').readFile(confpath, 'utf8', function (err, text) { | |||||||
|     console.warn("Choosing the first."); |     console.warn("Choosing the first."); | ||||||
|     console.warn(); |     console.warn(); | ||||||
|   } |   } | ||||||
|   state.config.token = token; |   state.token = token; | ||||||
| 
 | 
 | ||||||
|   function restartCmd() { |   if (!state.config.servernames) { | ||||||
|  |     state.config.servernames = {}; | ||||||
|  |   } | ||||||
|  |   if (!state.config.ports) { | ||||||
|  |     state.config.ports = {}; | ||||||
|  |   } | ||||||
|  |   state.servernames = JSON.parse(JSON.stringify(state.config.servernames)); | ||||||
|  |   state.ports = JSON.parse(JSON.stringify(state.config.ports)); | ||||||
|  | 
 | ||||||
|  |   function putConfig(service, args) { | ||||||
|     var http = require('http'); |     var http = require('http'); | ||||||
|     var req = http.get({ |     var req = http.get({ | ||||||
|       socketPath: state.config.sock || defaultSockname |       socketPath: state.config.sock || defaultSockname | ||||||
|     , method: 'POST' |     , method: 'POST' | ||||||
|     , path: '/rpc/restart' |     , path: '/rpc/' + service + '?_body=' + JSON.stringify(args) | ||||||
|     }, function (resp) { |     }, function (resp) { | ||||||
|       console.log('statusCode', resp.statusCode); | 
 | ||||||
|  |       function finish() { | ||||||
|         if (200 !== resp.statusCode) { |         if (200 !== resp.statusCode) { | ||||||
|         console.warn("May not have restarted." |           console.warn("'" + service + "' may have failed." | ||||||
|            + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); |            + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); | ||||||
|         } else { |         } else { | ||||||
|         console.log("restarted"); |           if (body) { | ||||||
|  |             console.info('Response'); | ||||||
|  |             console.info(body); | ||||||
|  |           } else { | ||||||
|  |             console.info("👌"); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var body = ''; | ||||||
|  |       if (resp.headers['content-length']) { | ||||||
|  |         resp.on('data', function (chunk) { | ||||||
|  |           body += chunk.toString(); | ||||||
|  |         }); | ||||||
|  |         resp.on('end', function () { | ||||||
|  |           finish(); | ||||||
|  |         }); | ||||||
|  |       } else { | ||||||
|  |         finish(); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     req.on('error', function (err) { |     req.on('error', function (err) { | ||||||
| @ -124,30 +169,136 @@ require('fs').readFile(confpath, 'utf8', function (err, text) { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   function controlServer() { |   var tun; | ||||||
|     var http = require('http'); |   function serveControls() { | ||||||
|     var server = http.createServer(function (req, res) { |     if (!state.config.disable) { | ||||||
|  |       tun = rawTunnel(); | ||||||
|  |     } | ||||||
|  |     controlServer = http.createServer(function (req, res) { | ||||||
|  |       var opts = url.parse(req.url, true); | ||||||
|  |       if (opts.query._body) { | ||||||
|  |         try { | ||||||
|  |           opts.body = JSON.parse(opts.query._body, true); | ||||||
|  |         } catch(e) { | ||||||
|  |           res.statusCode = 500; | ||||||
|  |           res.end('{"error":{"message":"?_body={{bad_format}}"}}'); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       if (/restart/.test(req.url)) { |       if (/enable/.test(opts.path)) { | ||||||
|  |         state.config.disable = undefined; | ||||||
|  |         if (!tun) { tun = rawTunnel(); } | ||||||
|  |         fs.writeFile(confpath, require('js-yaml').safeDump(snakeCopy(state.config)), function () { | ||||||
|  |           if (err) { | ||||||
|  |             res.statusCode = 500; | ||||||
|  |             res.end('{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|           res.end('{"success":true}'); |           res.end('{"success":true}'); | ||||||
|  |         }); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (/disable/.test(opts.path)) { | ||||||
|  |         state.config.disable = true; | ||||||
|  |         if (tun) { tun.end(); tun = null; } | ||||||
|  |         fs.writeFile(confpath, require('js-yaml').safeDump(snakeCopy(state.config)), function () { | ||||||
|  |           if (err) { | ||||||
|  |             res.statusCode = 500; | ||||||
|  |             res.end('{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           res.end('{"success":true}'); | ||||||
|  |         }); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (/status/.test(opts.path)) { | ||||||
|  |         res.end('{"status":' + (state.config.disable ? 'disabled' : 'enabled') + '}'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (/restart/.test(opts.path)) { | ||||||
|         tun.end(); |         tun.end(); | ||||||
|         process.nextTick(function () { |         res.end('{"success":true}'); | ||||||
|           server.close(function () { |         controlServer.close(function () { | ||||||
|           // TODO closeAll other things
 |           // TODO closeAll other things
 | ||||||
|  |           process.nextTick(function () { | ||||||
|  |             // system daemon will restart the process
 | ||||||
|             process.exit(); |             process.exit(); | ||||||
|           }); |           }); | ||||||
|         }); |         }); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       if (/list/.test(opts.path)) { | ||||||
|  |         res.end(JSON.stringify({ | ||||||
|  |           servernames: state.servernames | ||||||
|  |         , ports: state.ports | ||||||
|  |         })); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (/http/.test(opts.path)) { | ||||||
|  |         if (!opts.body) { | ||||||
|  |           res.statusCode = 422; | ||||||
|  |           res.end('{"error":{"message":"needs more arguments"}}'); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         if (opts.body[1]) { | ||||||
|  |           if (!state.servernames[opts.body[1]]) { | ||||||
|  |             res.statusCode = 400; | ||||||
|  |             res.end('{"error":{"message":"bad servername \'' + opts.body[1] + '\'"'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           state.servernames[opts.body[1]].handler = opts.body[0]; | ||||||
|  |         } else { | ||||||
|  |           Object.keys(state.servernames).forEach(function (key) { | ||||||
|  |             state.servernames[key].handler = opts.body[0]; | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         res.end('{"success":true}'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (/tcp/.test(opts.path)) { | ||||||
|  |         if (!opts.body) { | ||||||
|  |           res.statusCode = 422; | ||||||
|  |           res.end('{"error":{"message":"needs more arguments"}}'); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (opts.body[1]) { | ||||||
|  |           if (!state.servernames[opts.body[1]]) { | ||||||
|  |             res.statusCode = 400; | ||||||
|  |             res.end('{"error":{"message":"bad servername \'' + opts.body[1] + '\'"'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |           state.servernames[opts.body[1]].handler = opts.body[0]; | ||||||
|  |         } else { | ||||||
|  |           Object.keys(state.servernames).forEach(function (key) { | ||||||
|  |             state.servernames[key].handler = opts.body[0]; | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         res.end('{"success":true}'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       res.end('{"error":{"message":"unrecognized rpc"}}'); |       res.end('{"error":{"message":"unrecognized rpc"}}'); | ||||||
|     }); |     }); | ||||||
|     var pipename = (state.config.sock || defaultSockname); |     var pipename = (state.config.sock || defaultSockname); | ||||||
|  |     var fs = require('fs'); | ||||||
|  |     if (fs.existsSync(pipename)) { | ||||||
|  |       fs.unlinkSync(pipename); | ||||||
|  |     } | ||||||
|     if (/^win/i.test(require('os').platform())) { |     if (/^win/i.test(require('os').platform())) { | ||||||
|       pipename = '\\\\?\\pipe' + pipename.replace(/\//, '\\'); |       pipename = '\\\\?\\pipe' + pipename.replace(/\//, '\\'); | ||||||
|     } |     } | ||||||
|  |     // mask is so that processes owned by other users
 | ||||||
|  |     // can speak to this process, which is probably root-owned
 | ||||||
|     var oldUmask = process.umask(0x0000); |     var oldUmask = process.umask(0x0000); | ||||||
|     server.listen({ |     controlServer.listen({ | ||||||
|       path: pipename |       path: pipename | ||||||
|     , writableAll: true |     , writableAll: true | ||||||
|     , readableAll: true |     , readableAll: true | ||||||
| @ -157,18 +308,46 @@ require('fs').readFile(confpath, 'utf8', function (err, text) { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   console.log('argv', argv); |   // Two styles:
 | ||||||
|   if (-1 !== argv.indexOf('restart')) { |   //     http 3000
 | ||||||
|     restartCmd(); |   //     http modulename
 | ||||||
|     return; |   function makeRpc(key) { | ||||||
|  |     var cmdIndex = argv.indexOf(key); | ||||||
|  |     if (-1 !== cmdIndex) { | ||||||
|  |       putConfig(argv[cmdIndex], argv.slice(1)); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   controlServer(); |   if ([ 'status', 'enable', 'disable', 'restart', 'list' ].some(makeRpc)) { | ||||||
|   var tun = rawTunnel(); |     return; | ||||||
|  |   } | ||||||
|  |   if ([ 'http', 'tcp' ].some(function (key) { | ||||||
|  |     var cmdIndex = argv.indexOf(key); | ||||||
|  |     if (-1 !== cmdIndex && argv[cmdIndex + 1]) { | ||||||
|  |       putConfig(argv[cmdIndex], argv.slice(1)); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   })) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   serveControls(); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function connectTunnel() { | function connectTunnel() { | ||||||
|   state.net = { |   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(); | ||||||
|  |     controlServer.close(); | ||||||
|  |   } | ||||||
|  |   process.on('SIGINT', sigHandler); | ||||||
|  |   state.net = state.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, remoteFamily, remoteAddress, remotePort }
 |       // info = { data, servername, port, host, remoteFamily, remoteAddress, remotePort }
 | ||||||
| @ -180,9 +359,8 @@ function connectTunnel() { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state.greenlock = state.config.greenlock || {}; |   state.greenlock = state.config.greenlock || {}; | ||||||
|   if (!state.config.sortingHat) { |   state.sortingHat = state.config.sortingHat || path.resolve(__dirname, '..', 'lib/sorting-hat.js'); | ||||||
|     state.config.sortingHat = path.resolve(__dirname, '..', 'lib/sorting-hat.js'); | 
 | ||||||
|   } |  | ||||||
|   // TODO sortingHat.print(); ?
 |   // TODO sortingHat.print(); ?
 | ||||||
| 
 | 
 | ||||||
|   if (state.config.email && !state.token) { |   if (state.config.email && !state.token) { | ||||||
| @ -199,20 +377,23 @@ function connectTunnel() { | |||||||
|     console.info(); |     console.info(); | ||||||
|   } |   } | ||||||
|   // TODO Check undefined vs false for greenlock config
 |   // TODO Check undefined vs false for greenlock config
 | ||||||
|   var tun = remote.connect({ |   var remote = require('../'); | ||||||
|     relay: state.config.relay |   state.handlers = { | ||||||
|   , 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) { |     grant: function (grants) { | ||||||
|       console.info(""); |       console.info(""); | ||||||
|       console.info("Connect to your device by any of the following means:"); |       console.info("Connect to your device by any of the following means:"); | ||||||
|       console.info(""); |       console.info(""); | ||||||
|       grants.forEach(function (arr) { |       grants.forEach(function (arr) { | ||||||
|  |         if ('https' === arr[0]) { | ||||||
|  |           if (!state.servernames[arr[1]]) { | ||||||
|  |             state.servernames[arr[1]] = {}; | ||||||
|  |           } | ||||||
|  |         } else if ('tcp' === arr[0]) { | ||||||
|  |           if (!state.ports[arr[2]]) { | ||||||
|  |             state.ports[arr[2]] = {}; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if ('ssh+https' === arr[0]) { |         if ('ssh+https' === arr[0]) { | ||||||
|           console.info("SSH+HTTPS"); |           console.info("SSH+HTTPS"); | ||||||
|         } else if ('ssh' === arr[0]) { |         } else if ('ssh' === arr[0]) { | ||||||
| @ -222,7 +403,7 @@ function connectTunnel() { | |||||||
|         } else if ('https' === arr[0]) { |         } else if ('https' === arr[0]) { | ||||||
|           console.info("HTTPS"); |           console.info("HTTPS"); | ||||||
|         } |         } | ||||||
|           console.log('\t' + arr[0] + '://' + arr[1] + (arr[2] ? (':' + arr[2]) : '')); |         console.info('\t' + arr[0] + '://' + arr[1] + (arr[2] ? (':' + arr[2]) : '')); | ||||||
|         if ('ssh+https' === arr[0]) { |         if ('ssh+https' === arr[0]) { | ||||||
|           console.info("\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -quiet' " + arr[1] + " -p 443\n"); |           console.info("\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -quiet' " + arr[1] + " -p 443\n"); | ||||||
|         } else if ('ssh' === arr[0]) { |         } else if ('ssh' === arr[0]) { | ||||||
| @ -243,8 +424,8 @@ function connectTunnel() { | |||||||
|         console.error(e); |         console.error(e); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     } |   }; | ||||||
|   , greenlockConfig: { |   state.greenlockConfig = { | ||||||
|     version: state.greenlock.version || 'draft-11' |     version: state.greenlock.version || 'draft-11' | ||||||
|   , server: state.greenlock.server || 'https://acme-v02.api.letsencrypt.org/directory' |   , server: state.greenlock.server || 'https://acme-v02.api.letsencrypt.org/directory' | ||||||
|   , communityMember: state.greenlock.communityMember || state.config.communityMember |   , communityMember: state.greenlock.communityMember || state.config.communityMember | ||||||
| @ -270,24 +451,29 @@ function connectTunnel() { | |||||||
| 
 | 
 | ||||||
|       //cb(new Error("servername not found in allowed list"));
 |       //cb(new Error("servername not found in allowed list"));
 | ||||||
|     } |     } | ||||||
|     } |   }; | ||||||
|  |   state.insecure = state.config.relay_ignore_invalid_certificates; | ||||||
|  |   // { relay, config, servernames, ports, sortingHat, net, insecure, token, handlers, greenlockConfig }
 | ||||||
|  | 
 | ||||||
|  |   var tun = remote.connect({ | ||||||
|  |     relay: state.relay | ||||||
|  |   , config: state.config | ||||||
|  |   , sortingHat: state.sortingHat | ||||||
|  |   , net: state.net | ||||||
|  |   , insecure: state.insecure | ||||||
|  |   , token: state.token | ||||||
|  |   , servernames: state.servernames | ||||||
|  |   , ports: state.ports | ||||||
|  |   , handlers: state.handlers | ||||||
|  |   , greenlockConfig: state.greenlockConfig | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   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); |  | ||||||
|   return tun; |   return tun; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function rawTunnel() { | function rawTunnel() { | ||||||
|   if (!state.config.relay) { |   state.relay = state.config.relay; | ||||||
|  |   if (!state.relay) { | ||||||
|     throw new Error("'" + state._confpath + "' is missing 'relay'"); |     throw new Error("'" + state._confpath + "' is missing 'relay'"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -299,13 +485,13 @@ function rawTunnel() { | |||||||
|   } |   } | ||||||
|   */ |   */ | ||||||
| 
 | 
 | ||||||
|   var location = url.parse(state.config.relay); |   var location = url.parse(state.relay); | ||||||
|   if (!location.protocol || /\./.test(location.protocol)) { |   if (!location.protocol || /\./.test(location.protocol)) { | ||||||
|     state.config.relay = 'wss://' + state.config.relay; |     state.relay = 'wss://' + state.relay; | ||||||
|     location = url.parse(state.config.relay); |     location = url.parse(state.relay); | ||||||
|   } |   } | ||||||
|   var aud = location.hostname + (location.port ? ':' + location.port : ''); |   var aud = location.hostname + (location.port ? ':' + location.port : ''); | ||||||
|   state.config.relay = location.protocol + '//' + aud; |   state.relay = location.protocol + '//' + aud; | ||||||
| 
 | 
 | ||||||
|   if (!state.config.token && state.config.secret) { |   if (!state.config.token && state.config.secret) { | ||||||
|     var jwt = require('jsonwebtoken'); |     var jwt = require('jsonwebtoken'); | ||||||
|  | |||||||
| @ -7,8 +7,13 @@ secret: ''                      # Shared Secret with Telebit Relay for authoriza | |||||||
| #token: ''                       # Token created by 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 | ssh_auto: 22                    # forward ssh-looking packets, from any connection, to port 22 | ||||||
| servernames:                    # hostnames that direct to the Telebit Relay admin console | servernames:                    # hostnames that direct to the Telebit Relay admin console | ||||||
|   example.com: {} |   example.com: | ||||||
|   example.net: {} |     handler: 3000 | ||||||
|  |   example.net: | ||||||
|  |     handler: /path/to/module | ||||||
|  | ports: | ||||||
|  |   5050: | ||||||
|  |     handler: 54321 | ||||||
| greenlock: | greenlock: | ||||||
|   version: 'draft-11' |   version: 'draft-11' | ||||||
|   server: 'https://acme-staging-v02.api.letsencrypt.org/directory' |   server: 'https://acme-staging-v02.api.letsencrypt.org/directory' | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								lib/html/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								lib/html/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <title>Telebit</title> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |     <script>document.body.hidden = true;</script> | ||||||
|  | 
 | ||||||
|  |     <h1>Welcome Home <!-- as in 127.0.0.1, y'know ;) --></h1> | ||||||
|  | 
 | ||||||
|  |     <h2>You've claimed <span class="js-servername">{{servername}}</span></h2> | ||||||
|  |     <p>Here's same ways you can use it:</p> | ||||||
|  |     <pre><code>telebit http 3000</code></pre> | ||||||
|  | 
 | ||||||
|  |     <h2>You've claimed <span class="js-serviceport">{{serviceport}}</span></h2> | ||||||
|  |     <p>Here's same ways you can use it:</p> | ||||||
|  |     <pre><code>#telebit tcp 3000</code></pre> | ||||||
|  | 
 | ||||||
|  |     <script src="js/app.js"></script> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										36
									
								
								lib/html/js/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								lib/html/js/app.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | (function () { | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | document.body.hidden = false; | ||||||
|  | 
 | ||||||
|  | var hash = window.location.hash.slice(1); | ||||||
|  | 
 | ||||||
|  | function parseQuery(search) { | ||||||
|  |     var args = search.substring(1).split('&'); | ||||||
|  |     var argsParsed = {}; | ||||||
|  |     var i, arg, kvp, key, value; | ||||||
|  | 
 | ||||||
|  |     for (i=0; i < args.length; i++) { | ||||||
|  | 
 | ||||||
|  |         arg = args[i]; | ||||||
|  | 
 | ||||||
|  |         if (-1 === arg.indexOf('=')) { | ||||||
|  | 
 | ||||||
|  |             argsParsed[decodeURIComponent(arg).trim()] = true; | ||||||
|  | 
 | ||||||
|  |         } else { | ||||||
|  | 
 | ||||||
|  |             kvp = arg.split('='); | ||||||
|  |             key = decodeURIComponent(kvp[0]).trim(); | ||||||
|  |             value = decodeURIComponent(kvp[1]).trim(); | ||||||
|  |             argsParsed[key] = value; | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return argsParsed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | console.log(parseQuery(hash)); | ||||||
|  | 
 | ||||||
|  | }()); | ||||||
| @ -1,3 +1,7 @@ | |||||||
|  | 'use strict'; | ||||||
|  | var os = require('os'); | ||||||
|  | var path = require('path'); | ||||||
|  | 
 | ||||||
| module.exports.print = function (config) { | module.exports.print = function (config) { | ||||||
|   var services = { https: {}, http: {}, tcp: {} }; |   var services = { https: {}, http: {}, tcp: {} }; | ||||||
|   // Note: the remote needs to know:
 |   // Note: the remote needs to know:
 | ||||||
| @ -78,11 +82,13 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|     state.httpRedirectServer.emit('connection', socket); |     state.httpRedirectServer.emit('connection', socket); | ||||||
|   }; |   }; | ||||||
|   handlers.https = function (tlsSocket) { |   handlers.https = function (tlsSocket) { | ||||||
|     console.log('Enccrypted', tlsSocket.encrypted, tlsSocket.remoteAddress, tlsSocket.remotePort); |     console.log('Encrypted', tlsSocket.encrypted, tlsSocket.remoteAddress, tlsSocket.remotePort); | ||||||
|     if (!state.defaultHttpServer) { |     if (!state.defaultHttpServer) { | ||||||
|  |       state._finalHandler = require('finalhandler'); | ||||||
|  |       state._serveStatic = require('serve-static'); | ||||||
|  |       state._defaultServe = state._serveStatic(path.join(__dirname, 'html')); | ||||||
|       state.defaultHttpServer = require('http').createServer(function (req, res) { |       state.defaultHttpServer = require('http').createServer(function (req, res) { | ||||||
|         console.log('[hit http/s server]'); |         state._defaultServe(req, res, state._finalHandler(req, res)); | ||||||
|         res.end('Hello, Encrypted Tunnel World!'); |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     state.defaultHttpServer.emit('connection', tlsSocket); |     state.defaultHttpServer.emit('connection', tlsSocket); | ||||||
| @ -183,26 +189,27 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|     } |     } | ||||||
|     var handle = tun.name || tun.port; |     var handle = tun.name || tun.port; | ||||||
|     var handler; |     var handler; | ||||||
|  |     var handlerpath = conf.handler; | ||||||
|     var path = require('path'); |     var path = require('path'); | ||||||
|     var homedir = require('os').homedir(); |     var homedir = os.homedir(); | ||||||
|     var localshare = path.join(homedir, '.local/share/telebit/apps'); |     var localshare = path.join(homedir, '.local/share/telebit/apps'); | ||||||
| 
 | 
 | ||||||
|     if (/^~/.test(conf.handler)) { |     if (/^~/.test(handlerpath)) { | ||||||
|       conf.handler = require('path').join(require('os').homedir(), conf.handler.replace(/^~(\/?)/, '')); |       handlerpath = path.join(homedir, handlerpath.replace(/^~(\/?)/, '')); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       handler = require(conf.handler); |       handler = require(handlerpath); | ||||||
|       console.info("Handling '" + handle + ":" + id + "' with '" + conf.handler + "'"); |       console.info("Handling '" + handle + ":" + id + "' with '" + handlerpath + "'"); | ||||||
|       handler(tlsSocket, tun, id); |       handler(tlsSocket, tun, id); | ||||||
|     } catch(e1) { |     } catch(e1) { | ||||||
|       try { |       try { | ||||||
|         handler = require(path.join(localshare, conf.handler)); |         handler = require(path.join(localshare, handlerpath)); | ||||||
|         console.info("Handling '" + handle + ":" + id + "' with '" + conf.handler + "'"); |         console.info("Handling '" + handle + ":" + id + "' with '" + handlerpath + "'"); | ||||||
|         handler(tlsSocket, tun, id); |         handler(tlsSocket, tun, id); | ||||||
|       } catch(e2) { |       } catch(e2) { | ||||||
|         console.error("Failed to load '" + conf.handler + "':", e1.message); |         console.error("Failed to load '" + handlerpath + "':", e1.message); | ||||||
|         console.error("Failed to load '" + path.join(localshare, conf.handler) + "':", e2.message); |         console.error("Failed to load '" + path.join(localshare, handlerpath) + "':", e2.message); | ||||||
|         console.warn("Using default handler for '" + handle + ":" + id + "'"); |         console.warn("Using default handler for '" + handle + ":" + id + "'"); | ||||||
|         handlers.https(tlsSocket, tun, id); |         handlers.https(tlsSocket, tun, id); | ||||||
|       } |       } | ||||||
| @ -231,7 +238,7 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|           defineProps(tlsSocket, addr); |           defineProps(tlsSocket, addr); | ||||||
|           //console.log('[hit tls server]', tlsSocket.remoteFamily, tlsSocket.remoteAddress, tlsSocket.remotePort, tlsSocket.localPort);
 |           //console.log('[hit tls server]', tlsSocket.remoteFamily, tlsSocket.remoteAddress, tlsSocket.remotePort, tlsSocket.localPort);
 | ||||||
|           //console.log(addr);
 |           //console.log(addr);
 | ||||||
|           var conf = state.config.servernames[tlsSocket.servername]; |           var conf = state.servernames[tlsSocket.servername]; | ||||||
|           tlsSocket.once('data', function (firstChunk) { |           tlsSocket.once('data', function (firstChunk) { | ||||||
|             tlsSocket.pause(); |             tlsSocket.pause(); | ||||||
|             //tlsSocket.unshift(firstChunk);
 |             //tlsSocket.unshift(firstChunk);
 | ||||||
| @ -246,7 +253,7 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|                 return; |                 return; | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               if (!conf || !conf.handler) { |               if (!conf || !conf.handler || 'none' === conf.handler) { | ||||||
|                 console.log('https default handler'); |                 console.log('https default handler'); | ||||||
|                 handlers.https(tlsSocket); |                 handlers.https(tlsSocket); | ||||||
|                 return; |                 return; | ||||||
| @ -276,11 +283,6 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (!state.config.servernames) { |  | ||||||
|     state.config.servernames = {}; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   if ('http' === tun.service || 'https' === tun.service) { |   if ('http' === tun.service || 'https' === tun.service) { | ||||||
|     if (!tun.name) { |     if (!tun.name) { | ||||||
|       cb(new Error("No routing information for ':tun_id'. Service '" + tun.service + "' is missing 'name'.")); |       cb(new Error("No routing information for ':tun_id'. Service '" + tun.service + "' is missing 'name'.")); | ||||||
| @ -290,13 +292,13 @@ module.exports.assign = function (state, tun, cb) { | |||||||
| 
 | 
 | ||||||
|   if ('http' === tun.service) { |   if ('http' === tun.service) { | ||||||
|     // TODO match *.example.com
 |     // TODO match *.example.com
 | ||||||
|     handled = Object.keys(state.config.servernames).some(function (sn) { |     handled = Object.keys(state.servernames).some(function (sn) { | ||||||
|       if (sn !== tun.name) { return; } |       if (sn !== tun.name) { return; } | ||||||
| 
 | 
 | ||||||
|       console.log('Found config match for PLAIN', tun.name); |       console.log('Found config match for PLAIN', tun.name); | ||||||
|       if (!state.config.servernames[sn]) { return; } |       if (!state.servernames[sn]) { return; } | ||||||
| 
 | 
 | ||||||
|       if (false === state.config.servernames[sn].terminate) { |       if (false === state.servernames[sn].terminate) { | ||||||
|         cb(new Error("insecure http not supported yet")); |         cb(new Error("insecure http not supported yet")); | ||||||
|         return true; |         return true; | ||||||
|       } |       } | ||||||
| @ -313,13 +315,13 @@ module.exports.assign = function (state, tun, cb) { | |||||||
| 
 | 
 | ||||||
|   if ('https' === tun.service) { |   if ('https' === tun.service) { | ||||||
|     // TODO match *.example.com
 |     // TODO match *.example.com
 | ||||||
|     handled = Object.keys(state.config.servernames).some(function (sn) { |     handled = Object.keys(state.servernames).some(function (sn) { | ||||||
|       if (sn !== tun.name) { return; } |       if (sn !== tun.name) { return; } | ||||||
| 
 | 
 | ||||||
|       console.log('Found config match for TLS', tun.name); |       console.log('Found config match for TLS', tun.name); | ||||||
|       if (!state.config.servernames[sn]) { return; } |       if (!state.servernames[sn]) { return; } | ||||||
| 
 | 
 | ||||||
|       if (false === state.config.servernames[sn].terminate) { |       if (false === state.servernames[sn].terminate) { | ||||||
|         cb(new Error("insecure http not supported yet")); |         cb(new Error("insecure http not supported yet")); | ||||||
|         return true; |         return true; | ||||||
|       } |       } | ||||||
| @ -339,7 +341,15 @@ module.exports.assign = function (state, tun, cb) { | |||||||
|       if (conn) { cb(null, conn); return; } |       if (conn) { cb(null, conn); return; } | ||||||
|       // TODO add TCP handlers
 |       // TODO add TCP handlers
 | ||||||
|       console.log('Using echo server for tcp'); |       console.log('Using echo server for tcp'); | ||||||
|  |       var conf = state.ports[tun.serviceport]; | ||||||
|  |       if (!conf || !conf.handler || 'none' === conf.handler) { | ||||||
|         echoTcp(cb); |         echoTcp(cb); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var Packer = require('proxy-packer'); | ||||||
|  |       //var addr = Packer.socketToAddr(conn);
 | ||||||
|  |       var id = Packer.addrToId(tun); | ||||||
|  |       invokeHandler(conf, conn, tun, id); | ||||||
|     }); |     }); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -49,12 +49,14 @@ | |||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "bluebird": "^3.5.1", |     "bluebird": "^3.5.1", | ||||||
|     "commander": "^2.9.0", |     "commander": "^2.9.0", | ||||||
|  |     "finalhandler": "^1.1.1", | ||||||
|     "greenlock": "^2.2.19", |     "greenlock": "^2.2.19", | ||||||
|     "js-yaml": "^3.11.0", |     "js-yaml": "^3.11.0", | ||||||
|     "jsonwebtoken": "^7.1.9", |     "jsonwebtoken": "^7.1.9", | ||||||
|     "proxy-packer": "^1.4.3", |     "proxy-packer": "^1.4.3", | ||||||
|     "recase": "^1.0.4", |     "recase": "^1.0.4", | ||||||
|     "redirect-https": "^1.1.5", |     "redirect-https": "^1.1.5", | ||||||
|  |     "serve-static": "^1.13.2", | ||||||
|     "sni": "^1.0.0", |     "sni": "^1.0.0", | ||||||
|     "socket-pair": "^1.0.3", |     "socket-pair": "^1.0.3", | ||||||
|     "ws": "^2.2.3" |     "ws": "^2.2.3" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user