var http = require('http'), fs = require('fs'), qs = require('querystring'), exec = require('child_process').exec, url = require('url'), multiparty = require('multiparty'), spawn = require('child_process').spawn, shell = require('shelljs'); var site = __dirname + '/public'; var urlobj; var injectStatusAfter = '<!-- errors will go here -->'; var injectPasswordSectionAfter = 'onsubmit="saveFields()">'; var supportedExtensions = { "css" : "text/css", "xml" : "text/xml", "htm" : "text/html", "html" : "text/html", "js" : "application/javascript", "json" : "application/json", "txt" : "text/plain", "bmp" : "image/bmp", "gif" : "image/gif", "jpeg" : "image/jpeg", "jpg" : "image/jpeg", "png" : "image/png" }; var PORT = 8000; var STATE_DIR = '/var/lib/gateway_config_tools'; var BIN_DIR = './bin'; var NETWORKS_FILE = STATE_DIR + '/networks.txt'; var COMMAND_OUTPUT = ""; var COMMAND_OUTPUT_MAX = 3072; // bytes // available when gateway is not in AP-mode. In AP-mode, however, all commands and files are available. // That's because AP-mode is considered somewhat more secure (credentials are derived from mac address and serial number on box) var WHITELIST_CMDS = { "/commandOutput": true }; var WHITELIST_PATHS = { "/index.html": true, "/": true, "/main.css": true, "/logo-rigado.png": true }; var WHITELIST_EXEC = { "./bin/configure_gateway": true, "sleep": true }; function resetCommandOutputBuffer() { COMMAND_OUTPUT = ""; } function appendToCommandOutputBuffer(newoutput) { if (COMMAND_OUTPUT_MAX - COMMAND_OUTPUT.length >= newoutput.length) { COMMAND_OUTPUT += newoutput; } } function getContentType(filename) { var i = filename.lastIndexOf('.'); if (i < 0) { return 'application/octet-stream'; } return supportedExtensions[filename.substr(i+1).toLowerCase()] || 'application/octet-stream'; } function injectStatus(in_text, statusmsg, iserr) { var injectStatusAt = in_text.indexOf(injectStatusAfter) + injectStatusAfter.length; var status = ""; if (statusmsg) { if (iserr){ status = '<div id="statusarea" name="statusarea" class="status errmsg">' + '</div>'; //console.log("status errmsg"); }else{ status = '<div id="statusarea" name="statusarea" class="status">' + '</div>'; //console.log("status good"); } } return in_text.substring(0, injectStatusAt) + status + in_text.substring(injectStatusAt, in_text.length); } function inject(my_text, after_string, in_text) { var at = in_text.indexOf(after_string) + after_string.length; return in_text.substring(0, at) + my_text + in_text.substring(at, in_text.length); } function pageNotFound(res) { res.statusCode = 404; res.end("The page at " + urlobj.pathname + " was not found."); } function isSane(inputarray) { for (var i = 0; i < inputarray.length; i++) { if (inputarray[i]) if (inputarray[i].search(/\s+/) !== -1) return false; } return true; } // --- end utility functions function getStateBasedIndexPage() { return fs.readFileSync(site + '/index.html', {encoding: 'utf8'}); } function setHost(params) { if (!params.name) { return {cmd: ""}; } if (!isSane([params.name])) { return {failure: 'Hostname must not contain whitespaces. Please try again.'}; } if (params.name.length < 5) { return {failure: "The name is too short (must be at least 5 characters). Please try again."}; } return {cmd: BIN_DIR+"/configure_gateway", args: ["--changeName", params.name]}; } function setWiFi(params) { var exec_cmd = "", errmsg = "Unknown error occurred.", exec_args=[]; if (!params.newwifi) { return {cmd: ""}; } else if (!params.protocol) { errmsg = "Please specify the network protocol (Open, WEP, etc.)"; } else if (params.protocol === "OPEN") { exec_cmd = BIN_DIR+"/configure_gateway"; exec_args.push("--changeWiFi"); exec_args.push("OPEN"); exec_args.push(params.newwifi); } else if (params.protocol === "WEP") { if (params.netpass.length == 5 || params.netpass.length == 13) { exec_cmd = BIN_DIR+"/configure_gateway"; exec_args.push("--changeWiFi"); exec_args.push("WEP"); exec_args.push(params.newwifi); exec_args.push(params.netpass); } else { errmsg = "The supplied password must be 5 or 13 characters long."; } } else if (params.protocol === "WPA-PSK") { if (params.netpass && params.netpass.length >= 8 && params.netpass.length <= 63) { exec_cmd = BIN_DIR+"/configure_gateway"; exec_args.push("--changeWiFi"); exec_args.push("WPA-PSK"); exec_args.push(params.newwifi); exec_args.push(params.netpass); } else { errmsg = "Password must be between 8 and 63 characters long."; } } else if (params.protocol === "WPA-EAP") { if (params.netuser && params.netpass) { exec_cmd = BIN_DIR+"/configure_gateway"; exec_args.push("--changeWiFi"); exec_args.push("WPA-EAP"); exec_args.push(params.newwifi); exec_args.push(params.netuser); exec_args.push(params.netpass); } else { errmsg = "Please specify both the username and the password."; } } else { errmsg = "The specified network protocol is not supported." } if (exec_cmd) { return {cmd: exec_cmd, args: exec_args}; } return {failure: errmsg}; } function doSleep() { return {cmd: 'sleep', args: [2]}; } function runCmd(i, commands) { if (i === commands.length) return; if (commands[i].cmd && !WHITELIST_EXEC[commands[1].cmd]) { console.log("Returning..."); return; } appendToCommandOutputBuffer("Executing " + commands[i].cmd + " " + commands[i].args[0] + "\n"); commands[i].proc = spawn(commands[i].cmd, commands[i].args); commands[i].proc.stdout.on('data', function (data) { appendToCommandOutputBuffer(data); }); commands[i].proc.stderr.on('data', function (data) { appendToCommandOutputBuffer(data); }); commands[i].proc.on('close', function (code) { appendToCommandOutputBuffer(commands[i].cmd + " " + commands[i].args[0] + " has finished\n"); setImmediate(runCmd, i+1, commands); }); commands[i].proc.on('error', function (err) { appendToCommandOutputBuffer(commands[i].cmd + " " + commands[i].args[0] + " encountered the following error:\n" + err + "\n"); setImmediate(runCmd, i+1, commands); }); } function submitForm(params, res, req) { resetCommandOutputBuffer(); var calls = [setHost, doSleep, setWiFi]; var result = null, commands = []; // check for errors and respond as soon as we find one for (var i = 0; i < calls.length; ++i) { result = calls[i](params); if (result.failure) { res.end(injectStatus(getStateBasedIndexPage(), result.failure, true)); return; } else { res.end(injectStatus(getStateBasedIndexPage(), params, true)); } if (result.cmd){ console.log(result); commands.push(result); } } // no errors occurred. Do success response. exec (BIN_DIR+'/configure_gateway --showNames', function (error, stdout, stderr) { var nameobj = {hostname: "unknown", ssid: "unknown", default_ssid: "unknown"}; try { nameobj = JSON.parse(stdout); } catch (ex) { console.log("Could not parse output of configure_gateway --showNames (may not be valid JSON)"); console.log(ex); } var hostname = nameobj.hostname, ssid = nameobj.ssid; var res_str; if (params.name) { hostname = ssid = params.name; } if (params.newwifi) { // WiFi is being configured res_str = fs.readFileSync(site + '/exit.html', {encoding: 'utf8'}) } else { res_str = fs.readFileSync(site + '/exiting-without-wifi.html', {encoding: 'utf8'}) } res_str = res_str.replace(/params_new_wifi/g, params.newwifi ? params.newwifi : ""); res_str = res_str.replace(/params_hostname/g, hostname + ".local"); res_str = res_str.replace(/params_ssid/g, ssid); res_str = res_str.replace(/params_curr_ssid/g, nameobj.ssid); res_str = res_str.replace(/params_curr_hostname/g, nameobj.hostname + ".local"); res.end(res_str); commands.push({cmd: BIN_DIR+"/configure_gateway", args: ["--disableOneTimeSetup", "--persist"]}); // Now execute the commands serially runCmd(0, commands); }); } function handlePostRequest(req, res) { console.log('handlePostRequest'); console.log(' urlobj.pathname: '+urlobj.pathname); if (urlobj.pathname === '/submitForm') { var payload = ""; req.on('data', function (data) { payload += data; }); req.on('end', function () { var params = qs.parse(payload); submitForm(params, res, req); }); } else { pageNotFound(res); } } function inWhiteList(path) { if (!path) return false; // if shell command succeeds and in host AP mode var result = shell.exec(BIN_DIR+'/configure_gateway --showWiFiMode', {silent:true}); console.log(BIN_DIR+'/configure_gateway --showWiFiMode'); if ((result.code != 0) || ((typeof result.stdout !== 'undefined') && (result.stdout.trim() === "Master"))) { return true; } return WHITELIST_PATHS[path] || WHITELIST_CMDS[path]; } // main request handler. GET requests are handled here. // POST requests are handled in handlePostRequest() function requestHandler(req, res) { urlobj = url.parse(req.url, true); console.log('handling request at '+urlobj.pathname); if (!inWhiteList(urlobj.pathname)) { pageNotFound(res); return; } // POST request. Get payload. if (req.method === 'POST') { handlePostRequest(req, res); return; } // GET request if (!urlobj.pathname || urlobj.pathname === '/' || urlobj.pathname === '/index.html') { res.setHeader('Access-Control-Allow-Origin', '*'); console.log(urlobj.pathname); var result = shell.exec(BIN_DIR+'/configure_gateway --showWiFiMode', {silent:true}); console.log(BIN_DIR+'/configure_gateway --showWiFiMode') if ((result.code != 0) || ((typeof result.output !== 'undefined') && (result.output.trim() != "Master"))) { var res_str = fs.readFileSync(site + '/status.html', {encoding: 'utf8'}); var myhostname, myipaddr; exec(BIN_DIR+'/configure_gateway --showWiFiIP', function (error, stdout, stderr) { if (error) { console.log("Error occurred:"); console.log(stderr); myipaddr = "unknown"; } else { myipaddr = stdout; } }); } else { res.end(getStateBasedIndexPage()); } } else if (urlobj.pathname === '/wifiNetworks') { if (fs.existsSync(NETWORKS_FILE)) { res.setHeader('content-type', getContentType(NETWORKS_FILE)); res.end(fs.readFileSync(NETWORKS_FILE, {encoding: 'utf8'})); } else { res.end("{}"); } } else if (urlobj.pathname === '/commandOutput') { res.setHeader('Access-Control-Allow-Origin', '*'); res.end(COMMAND_OUTPUT); } else { // for files like .css and images. if (!fs.existsSync(site + urlobj.pathname)) { pageNotFound(res); return; } res.setHeader('content-type', getContentType(urlobj.pathname)); res.end(fs.readFileSync(site + urlobj.pathname, {encoding: null})); } } exec(BIN_DIR+'/configure_gateway --showNames', function (error, stdout, stderr) { console.log(BIN_DIR+"/configure_gateway --showNames"); if (error) { console.log(" Error saving default SSID"); console.log(error); } }); http.createServer(requestHandler).listen(PORT); console.log("Server listening at localhost:"+PORT);