MAJOR: Updates for Authenticated Web UI and CLI #30
							
								
								
									
										685
									
								
								bin/telebitd.js
									
									
									
									
									
								
							
							
						
						
									
										685
									
								
								bin/telebitd.js
									
									
									
									
									
								
							| @ -326,356 +326,365 @@ controllers.ssh = function (req, res, opts) { | |||||||
|   state.config.sshAuto = sshAuto; |   state.config.sshAuto = sshAuto; | ||||||
|   sshSuccess(); |   sshSuccess(); | ||||||
| }; | }; | ||||||
| function serveControlsHelper() { |  | ||||||
|   controlServer = http.createServer(function (req, res) { |  | ||||||
|     var opts = url.parse(req.url, true); |  | ||||||
|     if (false && opts.query._body) { |  | ||||||
|       try { |  | ||||||
|         opts.body = JSON.parse(decodeURIComponent(opts.query._body, true)); |  | ||||||
|       } catch(e) { |  | ||||||
|         res.statusCode = 500; |  | ||||||
|         res.end('{"error":{"message":"?_body={{bad_format}}"}}'); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     function listSuccess() { | var serveStatic = require('serve-static')(path.join(__dirname, '../lib/admin/')); | ||||||
|       var dumpy = { | function handleRemoteClient(req, res) { | ||||||
|         servernames: state.servernames |   if (/^\/(rpc|api)\//.test(req.url)) { | ||||||
|       , ports: state.ports |     return handleApi(req, res); | ||||||
|       , ssh: state.config.sshAuto || 'disabled' |   } | ||||||
|       , code: 'CONFIG' |   serveStatic(req, res, require('finalhandler')(req, res)); | ||||||
|       }; | } | ||||||
|       if (state.otp) { | function handleApi(req, res) { | ||||||
|         dumpy.device_pair_code = state.otp; |   var opts = url.parse(req.url, true); | ||||||
|       } |   if (false && opts.query._body) { | ||||||
| 
 |     try { | ||||||
|       if (state._can_pair && state.config.email && !state.token) { |       opts.body = JSON.parse(decodeURIComponent(opts.query._body, true)); | ||||||
|         dumpy.code = "AWAIT_AUTH"; |     } catch(e) { | ||||||
|         dumpy.message = "Please run 'telebit init' to authenticate."; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       res.end(JSON.stringify(dumpy)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function getConfigOnly() { |  | ||||||
|       var resp = JSON.parse(JSON.stringify(state.config)); |  | ||||||
|       resp.version = pkg.version; |  | ||||||
|       res.setHeader('Content-Type', 'application/json'); |  | ||||||
|       res.end(JSON.stringify(resp)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     //
 |  | ||||||
|     // without proper config
 |  | ||||||
|     //
 |  | ||||||
|     function saveAndReport() { |  | ||||||
|       console.log('[DEBUG] saveAndReport config write', confpath); |  | ||||||
|       console.log(YAML.safeDump(snakeCopy(state.config))); |  | ||||||
|       fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |  | ||||||
|         if (err) { |  | ||||||
|           res.statusCode = 500; |  | ||||||
|           res.setHeader('Content-Type', 'application/json'); |  | ||||||
|           res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'") |  | ||||||
|             + '.\nPerhaps check that the file exists and your user has permissions to write it?"}}'); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         listSuccess(); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function initOrConfig() { |  | ||||||
|       var conf = {}; |  | ||||||
|       if (!opts.body) { |  | ||||||
|         res.statusCode = 422; |  | ||||||
|         res.end('{"error":{"message":"module \'init\' needs more arguments"}}'); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       // relay, email, agree_tos, servernames, ports
 |  | ||||||
|       //
 |  | ||||||
|       opts.body.forEach(function (opt) { |  | ||||||
|         var parts = opt.split(/:/); |  | ||||||
|         if ('true' === parts[1]) { |  | ||||||
|           parts[1] = true; |  | ||||||
|         } else if ('false' === parts[1]) { |  | ||||||
|           parts[1] = false; |  | ||||||
|         } else if ('null' === parts[1]) { |  | ||||||
|           parts[1] = null; |  | ||||||
|         } else if ('undefined' === parts[1]) { |  | ||||||
|           parts[1] = undefined; |  | ||||||
|         } |  | ||||||
|         conf[parts[0]] = parts[1]; |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       // TODO camelCase query
 |  | ||||||
|       state.config.email = conf.email || state.config.email || ''; |  | ||||||
|       if ('undefined' !== typeof conf.agreeTos |  | ||||||
|         || 'undefined' !== typeof conf.agreeTos ) { |  | ||||||
|         state.config.agreeTos = conf.agreeTos || conf.agree_tos; |  | ||||||
|       } |  | ||||||
|       state.otp = conf._otp; // this should only be done on the client side
 |  | ||||||
|       state.config.relay = conf.relay || state.config.relay || ''; |  | ||||||
|       console.log(); |  | ||||||
|       console.log('conf.token', typeof conf.token, conf.token); |  | ||||||
|       console.log('state.config.token', typeof state.config.token, state.config.token); |  | ||||||
|       state.config.token = conf.token || state.config.token || null; |  | ||||||
|       state.config.secret = conf.secret || state.config.secret || null; |  | ||||||
|       state.pretoken = conf.pretoken || state.config.pretoken || null; |  | ||||||
|       if (state.secret) { |  | ||||||
|         console.log('state.secret'); |  | ||||||
|         state.token = common.signToken(state); |  | ||||||
|       } |  | ||||||
|       if (!state.token) { |  | ||||||
|         console.log('!state.token'); |  | ||||||
|         state.token = conf._token; |  | ||||||
|       } |  | ||||||
|       console.log(); |  | ||||||
|       console.log('JSON.stringify(conf)'); |  | ||||||
|       console.log(JSON.stringify(conf)); |  | ||||||
|       console.log(); |  | ||||||
|       console.log('JSON.stringify(state)'); |  | ||||||
|       console.log(JSON.stringify(state)); |  | ||||||
|       console.log(); |  | ||||||
|       if ('undefined' !== typeof conf.newsletter) { |  | ||||||
|         state.config.newsletter = conf.newsletter; |  | ||||||
|       } |  | ||||||
|       if ('undefined' !== typeof conf.communityMember |  | ||||||
|         || 'undefined' !== typeof conf.community_member) { |  | ||||||
|         state.config.communityMember = conf.communityMember || conf.community_member; |  | ||||||
|       } |  | ||||||
|       if ('undefined' !== typeof conf.telemetry) { |  | ||||||
|         state.config.telemetry = conf.telemetry; |  | ||||||
|       } |  | ||||||
|       if (conf._servernames) { |  | ||||||
|         (conf._servernames||'').split(/,/g).forEach(function (key) { |  | ||||||
|           if (!state.config.servernames[key]) { |  | ||||||
|             state.config.servernames[key] = { sub: undefined }; |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|       if (conf._ports) { |  | ||||||
|         (conf._ports||'').split(/,/g).forEach(function (key) { |  | ||||||
|           if (!state.config.ports[key]) { |  | ||||||
|             state.config.ports[key] = {}; |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (!state.config.relay || !state.config.email || !state.config.agreeTos) { |  | ||||||
|         console.warn('missing config'); |  | ||||||
|         res.statusCode = 400; |  | ||||||
| 
 |  | ||||||
|         res.setHeader('Content-Type', 'application/json'); |  | ||||||
|         res.end(JSON.stringify({ |  | ||||||
|           error: { |  | ||||||
|             code: "E_INIT" |  | ||||||
|           , message: "Missing important config file params" |  | ||||||
|           , _params: JSON.stringify(conf) |  | ||||||
|           , _config: JSON.stringify(state.config) |  | ||||||
|           , _body: JSON.stringify(opts.body) |  | ||||||
|           } |  | ||||||
|         })); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       // init also means enable
 |  | ||||||
|       delete state.config.disable; |  | ||||||
|       safeStartTelebitRemote(true).then(saveAndReport).catch(handleError); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function restart() { |  | ||||||
|       console.info("[telebitd.js] server closing..."); |  | ||||||
|       state.keepAlive.state = false; |  | ||||||
|       if (myRemote) { |  | ||||||
|         myRemote.end(); |  | ||||||
|         myRemote.on('end', respondAndClose); |  | ||||||
|         // failsafe
 |  | ||||||
|         setTimeout(function () { |  | ||||||
|           console.info("[telebitd.js] closing too slowly, force quit"); |  | ||||||
|           respondAndClose(); |  | ||||||
|         }, 5 * 1000); |  | ||||||
|       } else { |  | ||||||
|         respondAndClose(); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       function respondAndClose() { |  | ||||||
|         res.setHeader('Content-Type', 'application/json'); |  | ||||||
|         res.end(JSON.stringify({ success: true })); |  | ||||||
|         controlServer.close(function () { |  | ||||||
|           console.info("[telebitd.js] server closed"); |  | ||||||
|           setTimeout(function () { |  | ||||||
|             // system daemon will restart the process
 |  | ||||||
|             process.exit(22); // use non-success exit code
 |  | ||||||
|           }, 100); |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function invalidConfig() { |  | ||||||
|       res.statusCode = 400; |  | ||||||
|       res.setHeader('Content-Type', 'application/json'); |  | ||||||
|       res.end(JSON.stringify({ |  | ||||||
|         error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } |  | ||||||
|       })); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function saveAndCommit() { |  | ||||||
|       state.config.servernames = state.servernames; |  | ||||||
|       state.config.ports = state.ports; |  | ||||||
|       fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |  | ||||||
|         if (err) { |  | ||||||
|           res.statusCode = 500; |  | ||||||
|           res.setHeader('Content-Type', 'application/json'); |  | ||||||
|           res.end(JSON.stringify({ |  | ||||||
|             "error":{"message":"Could not save config file. Perhaps you're not running as root?"} |  | ||||||
|           })); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         listSuccess(); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function handleError(err) { |  | ||||||
|       res.statusCode = 500; |       res.statusCode = 500; | ||||||
|  |       res.end('{"error":{"message":"?_body={{bad_format}}"}}'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function listSuccess() { | ||||||
|  |     var dumpy = { | ||||||
|  |       servernames: state.servernames | ||||||
|  |     , ports: state.ports | ||||||
|  |     , ssh: state.config.sshAuto || 'disabled' | ||||||
|  |     , code: 'CONFIG' | ||||||
|  |     }; | ||||||
|  |     if (state.otp) { | ||||||
|  |       dumpy.device_pair_code = state.otp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (state._can_pair && state.config.email && !state.token) { | ||||||
|  |       dumpy.code = "AWAIT_AUTH"; | ||||||
|  |       dumpy.message = "Please run 'telebit init' to authenticate."; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     res.end(JSON.stringify(dumpy)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getConfigOnly() { | ||||||
|  |     var resp = JSON.parse(JSON.stringify(state.config)); | ||||||
|  |     resp.version = pkg.version; | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.end(JSON.stringify(resp)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   //
 | ||||||
|  |   // without proper config
 | ||||||
|  |   //
 | ||||||
|  |   function saveAndReport() { | ||||||
|  |     console.log('[DEBUG] saveAndReport config write', confpath); | ||||||
|  |     console.log(YAML.safeDump(snakeCopy(state.config))); | ||||||
|  |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|  |       if (err) { | ||||||
|  |         res.statusCode = 500; | ||||||
|  |         res.setHeader('Content-Type', 'application/json'); | ||||||
|  |         res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'") | ||||||
|  |           + '.\nPerhaps check that the file exists and your user has permissions to write it?"}}'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       listSuccess(); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function initOrConfig() { | ||||||
|  |     var conf = {}; | ||||||
|  |     if (!opts.body) { | ||||||
|  |       res.statusCode = 422; | ||||||
|  |       res.end('{"error":{"message":"module \'init\' needs more arguments"}}'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // relay, email, agree_tos, servernames, ports
 | ||||||
|  |     //
 | ||||||
|  |     opts.body.forEach(function (opt) { | ||||||
|  |       var parts = opt.split(/:/); | ||||||
|  |       if ('true' === parts[1]) { | ||||||
|  |         parts[1] = true; | ||||||
|  |       } else if ('false' === parts[1]) { | ||||||
|  |         parts[1] = false; | ||||||
|  |       } else if ('null' === parts[1]) { | ||||||
|  |         parts[1] = null; | ||||||
|  |       } else if ('undefined' === parts[1]) { | ||||||
|  |         parts[1] = undefined; | ||||||
|  |       } | ||||||
|  |       conf[parts[0]] = parts[1]; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // TODO camelCase query
 | ||||||
|  |     state.config.email = conf.email || state.config.email || ''; | ||||||
|  |     if ('undefined' !== typeof conf.agreeTos | ||||||
|  |       || 'undefined' !== typeof conf.agreeTos ) { | ||||||
|  |       state.config.agreeTos = conf.agreeTos || conf.agree_tos; | ||||||
|  |     } | ||||||
|  |     state.otp = conf._otp; // this should only be done on the client side
 | ||||||
|  |     state.config.relay = conf.relay || state.config.relay || ''; | ||||||
|  |     console.log(); | ||||||
|  |     console.log('conf.token', typeof conf.token, conf.token); | ||||||
|  |     console.log('state.config.token', typeof state.config.token, state.config.token); | ||||||
|  |     state.config.token = conf.token || state.config.token || null; | ||||||
|  |     state.config.secret = conf.secret || state.config.secret || null; | ||||||
|  |     state.pretoken = conf.pretoken || state.config.pretoken || null; | ||||||
|  |     if (state.secret) { | ||||||
|  |       console.log('state.secret'); | ||||||
|  |       state.token = common.signToken(state); | ||||||
|  |     } | ||||||
|  |     if (!state.token) { | ||||||
|  |       console.log('!state.token'); | ||||||
|  |       state.token = conf._token; | ||||||
|  |     } | ||||||
|  |     console.log(); | ||||||
|  |     console.log('JSON.stringify(conf)'); | ||||||
|  |     console.log(JSON.stringify(conf)); | ||||||
|  |     console.log(); | ||||||
|  |     console.log('JSON.stringify(state)'); | ||||||
|  |     console.log(JSON.stringify(state)); | ||||||
|  |     console.log(); | ||||||
|  |     if ('undefined' !== typeof conf.newsletter) { | ||||||
|  |       state.config.newsletter = conf.newsletter; | ||||||
|  |     } | ||||||
|  |     if ('undefined' !== typeof conf.communityMember | ||||||
|  |       || 'undefined' !== typeof conf.community_member) { | ||||||
|  |       state.config.communityMember = conf.communityMember || conf.community_member; | ||||||
|  |     } | ||||||
|  |     if ('undefined' !== typeof conf.telemetry) { | ||||||
|  |       state.config.telemetry = conf.telemetry; | ||||||
|  |     } | ||||||
|  |     if (conf._servernames) { | ||||||
|  |       (conf._servernames||'').split(/,/g).forEach(function (key) { | ||||||
|  |         if (!state.config.servernames[key]) { | ||||||
|  |           state.config.servernames[key] = { sub: undefined }; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     if (conf._ports) { | ||||||
|  |       (conf._ports||'').split(/,/g).forEach(function (key) { | ||||||
|  |         if (!state.config.ports[key]) { | ||||||
|  |           state.config.ports[key] = {}; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!state.config.relay || !state.config.email || !state.config.agreeTos) { | ||||||
|  |       console.warn('missing config'); | ||||||
|  |       res.statusCode = 400; | ||||||
|  | 
 | ||||||
|       res.setHeader('Content-Type', 'application/json'); |       res.setHeader('Content-Type', 'application/json'); | ||||||
|       res.end(JSON.stringify({ |       res.end(JSON.stringify({ | ||||||
|         error: { message: err.message, code: err.code } |         error: { | ||||||
|  |           code: "E_INIT" | ||||||
|  |         , message: "Missing important config file params" | ||||||
|  |         , _params: JSON.stringify(conf) | ||||||
|  |         , _config: JSON.stringify(state.config) | ||||||
|  |         , _body: JSON.stringify(opts.body) | ||||||
|  |         } | ||||||
|       })); |       })); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function enable() { |  | ||||||
|       delete state.config.disable;// = undefined;
 |  | ||||||
|       state.keepAlive.state = true; |  | ||||||
| 
 |  | ||||||
|       fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |  | ||||||
|         if (err) { |  | ||||||
|           err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; |  | ||||||
|           handleError(err); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         // TODO XXX myRemote.active
 |  | ||||||
|         if (myRemote) { |  | ||||||
|           listSuccess(); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         safeStartTelebitRemote(true).then(listSuccess).catch(handleError); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function disable() { |  | ||||||
|       state.config.disable = true; |  | ||||||
|       state.keepAlive.state = false; |  | ||||||
| 
 |  | ||||||
|       if (myRemote) { myRemote.end(); myRemote = null; } |  | ||||||
|       fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { |  | ||||||
|         res.setHeader('Content-Type', 'application/json'); |  | ||||||
|         if (err) { |  | ||||||
|           err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; |  | ||||||
|           handleError(err); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         res.end('{"success":true}'); |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function getStatus() { |  | ||||||
|       res.setHeader('Content-Type', 'application/json'); |  | ||||||
|       res.end(JSON.stringify( |  | ||||||
|         { status: (state.config.disable ? 'disabled' : 'enabled') |  | ||||||
|         , ready: ((state.config.relay && (state.config.token || state.config.agreeTos)) ? true : false) |  | ||||||
|         , active: !!myRemote |  | ||||||
|         , connected: 'maybe (todo)' |  | ||||||
|         , version: pkg.version |  | ||||||
|         , servernames: state.servernames |  | ||||||
|         } |  | ||||||
|       )); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     function route() { |  | ||||||
|       if (/\b(config)\b/.test(opts.pathname) && /get/i.test(req.method)) { |  | ||||||
|         getConfigOnly(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/\b(init|config)\b/.test(opts.pathname)) { |  | ||||||
|         initOrConfig(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/restart/.test(opts.pathname)) { |  | ||||||
|         restart(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       //
 |  | ||||||
|       // Check for proper config
 |  | ||||||
|       //
 |  | ||||||
|       if (!state.config.relay || !state.config.email || !state.config.agreeTos) { |  | ||||||
|         invalidConfig(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       //
 |  | ||||||
|       // With proper config
 |  | ||||||
|       //
 |  | ||||||
|       if (/http/.test(opts.pathname)) { |  | ||||||
|         controllers.http(req, res, opts); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/tcp/.test(opts.pathname)) { |  | ||||||
|         controllers.tcp(req, res, opts); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/save|commit/.test(opts.pathname)) { |  | ||||||
|         saveAndCommit(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/ssh/.test(opts.pathname)) { |  | ||||||
|         controllers.ssh(req, res, opts); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/enable/.test(opts.pathname)) { |  | ||||||
|         enable(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/disable/.test(opts.pathname)) { |  | ||||||
|         disable(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/status/.test(opts.pathname)) { |  | ||||||
|         getStatus(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (/list/.test(opts.pathname)) { |  | ||||||
|         listSuccess(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       res.setHeader('Content-Type', 'application/json'); |  | ||||||
|       res.end(JSON.stringify({"error":{"message":"unrecognized rpc"}})); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!req.headers['content-length'] && !req.headers['content-type']) { |  | ||||||
|       route(); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     var body = ''; |     // init also means enable
 | ||||||
|     req.on('readable', function () { |     delete state.config.disable; | ||||||
|       var data; |     safeStartTelebitRemote(true).then(saveAndReport).catch(handleError); | ||||||
|       while (true) { |   } | ||||||
|         data = req.read(); | 
 | ||||||
|         if (!data) { break; } |   function restart() { | ||||||
|         body += data.toString(); |     console.info("[telebitd.js] server closing..."); | ||||||
|       } |     state.keepAlive.state = false; | ||||||
|     }); |     if (myRemote) { | ||||||
|     req.on('end', function () { |       myRemote.end(); | ||||||
|       try { |       myRemote.on('end', respondAndClose); | ||||||
|         opts.body = JSON.parse(body); |       // failsafe
 | ||||||
|       } catch(e) { |       setTimeout(function () { | ||||||
|         res.statusCode = 400; |         console.info("[telebitd.js] closing too slowly, force quit"); | ||||||
|         res.end('{"error":{"message":"POST body is not valid json"}}'); |         respondAndClose(); | ||||||
|  |       }, 5 * 1000); | ||||||
|  |     } else { | ||||||
|  |       respondAndClose(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function respondAndClose() { | ||||||
|  |       res.setHeader('Content-Type', 'application/json'); | ||||||
|  |       res.end(JSON.stringify({ success: true })); | ||||||
|  |       controlServer.close(function () { | ||||||
|  |         console.info("[telebitd.js] server closed"); | ||||||
|  |         setTimeout(function () { | ||||||
|  |           // system daemon will restart the process
 | ||||||
|  |           process.exit(22); // use non-success exit code
 | ||||||
|  |         }, 100); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function invalidConfig() { | ||||||
|  |     res.statusCode = 400; | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.end(JSON.stringify({ | ||||||
|  |       error: { code: "E_CONFIG", message: "Invalid config file. Please run 'telebit init'" } | ||||||
|  |     })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function saveAndCommit() { | ||||||
|  |     state.config.servernames = state.servernames; | ||||||
|  |     state.config.ports = state.ports; | ||||||
|  |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|  |       if (err) { | ||||||
|  |         res.statusCode = 500; | ||||||
|  |         res.setHeader('Content-Type', 'application/json'); | ||||||
|  |         res.end(JSON.stringify({ | ||||||
|  |           "error":{"message":"Could not save config file. Perhaps you're not running as root?"} | ||||||
|  |         })); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       route(); |       listSuccess(); | ||||||
|     }); |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function handleError(err) { | ||||||
|  |     res.statusCode = 500; | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.end(JSON.stringify({ | ||||||
|  |       error: { message: err.message, code: err.code } | ||||||
|  |     })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function enable() { | ||||||
|  |     delete state.config.disable;// = undefined;
 | ||||||
|  |     state.keepAlive.state = true; | ||||||
|  | 
 | ||||||
|  |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|  |       if (err) { | ||||||
|  |         err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; | ||||||
|  |         handleError(err); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       // TODO XXX myRemote.active
 | ||||||
|  |       if (myRemote) { | ||||||
|  |         listSuccess(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       safeStartTelebitRemote(true).then(listSuccess).catch(handleError); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function disable() { | ||||||
|  |     state.config.disable = true; | ||||||
|  |     state.keepAlive.state = false; | ||||||
|  | 
 | ||||||
|  |     if (myRemote) { myRemote.end(); myRemote = null; } | ||||||
|  |     fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) { | ||||||
|  |       res.setHeader('Content-Type', 'application/json'); | ||||||
|  |       if (err) { | ||||||
|  |         err.message = "Could not save config file. Perhaps you're user doesn't have permission?"; | ||||||
|  |         handleError(err); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       res.end('{"success":true}'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getStatus() { | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.end(JSON.stringify( | ||||||
|  |       { status: (state.config.disable ? 'disabled' : 'enabled') | ||||||
|  |       , ready: ((state.config.relay && (state.config.token || state.config.agreeTos)) ? true : false) | ||||||
|  |       , active: !!myRemote | ||||||
|  |       , connected: 'maybe (todo)' | ||||||
|  |       , version: pkg.version | ||||||
|  |       , servernames: state.servernames | ||||||
|  |       } | ||||||
|  |     )); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function route() { | ||||||
|  |     if (/\b(config)\b/.test(opts.pathname) && /get/i.test(req.method)) { | ||||||
|  |       getConfigOnly(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/\b(init|config)\b/.test(opts.pathname)) { | ||||||
|  |       initOrConfig(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/restart/.test(opts.pathname)) { | ||||||
|  |       restart(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     //
 | ||||||
|  |     // Check for proper config
 | ||||||
|  |     //
 | ||||||
|  |     if (!state.config.relay || !state.config.email || !state.config.agreeTos) { | ||||||
|  |       invalidConfig(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     //
 | ||||||
|  |     // With proper config
 | ||||||
|  |     //
 | ||||||
|  |     if (/http/.test(opts.pathname)) { | ||||||
|  |       controllers.http(req, res, opts); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/tcp/.test(opts.pathname)) { | ||||||
|  |       controllers.tcp(req, res, opts); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/save|commit/.test(opts.pathname)) { | ||||||
|  |       saveAndCommit(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/ssh/.test(opts.pathname)) { | ||||||
|  |       controllers.ssh(req, res, opts); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/enable/.test(opts.pathname)) { | ||||||
|  |       enable(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/disable/.test(opts.pathname)) { | ||||||
|  |       disable(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/status/.test(opts.pathname)) { | ||||||
|  |       getStatus(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (/list/.test(opts.pathname)) { | ||||||
|  |       listSuccess(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     res.setHeader('Content-Type', 'application/json'); | ||||||
|  |     res.end(JSON.stringify({"error":{"message":"unrecognized rpc"}})); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!req.headers['content-length'] && !req.headers['content-type']) { | ||||||
|  |     route(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   var body = ''; | ||||||
|  |   req.on('readable', function () { | ||||||
|  |     var data; | ||||||
|  |     while (true) { | ||||||
|  |       data = req.read(); | ||||||
|  |       if (!data) { break; } | ||||||
|  |       body += data.toString(); | ||||||
|  |     } | ||||||
|   }); |   }); | ||||||
|  |   req.on('end', function () { | ||||||
|  |     try { | ||||||
|  |       opts.body = JSON.parse(body); | ||||||
|  |     } catch(e) { | ||||||
|  |       res.statusCode = 400; | ||||||
|  |       res.end('{"error":{"message":"POST body is not valid json"}}'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     route(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | function serveControlsHelper() { | ||||||
|  |   controlServer = http.createServer(handleRemoteClient); | ||||||
| 
 | 
 | ||||||
|   if (fs.existsSync(state._ipc.path)) { |   if (fs.existsSync(state._ipc.path)) { | ||||||
|     fs.unlinkSync(state._ipc.path); |     fs.unlinkSync(state._ipc.path); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user