registerController("EvilPortalController", ['$api', '$scope', function ($api, $scope) { // status information about the module $scope.evilPortal = { "throbber": false, "sdAvailable": false, "running": false, "startOnBoot": false, "library": true }; // controls that belong in the Controls pane $scope.controls = [ { "title": "Captive Portal", "visible": true, "throbber": false, "status": "Start"}, {"title": "Start On Boot", "visible": true, "throbber": false, "status": "Enable"} ]; // messages to be displayed in the Messages pane $scope.messages = []; $scope.whiteList = {"clients": "", "toManipulate": null}; $scope.accessList = {"clients": "", "toManipulate": null}; // all of the portals that could be found $scope.portals = []; // a model of a new portal to create $scope.newPortal = {"type": "basic", "name": ""}; // deleting portal stuff $scope.portalToDelete = null; $scope.portalDeleteValidation = null; // the portal workshop $scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true}, "onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null }; // active log file $scope.activeLog = {"title": null, "path": null, "contents": null}; /** * Reset the workshop object to a blank slate with initial values. */ $scope.resetWorkshop = function () { $scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true}, "onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null }; }; /** * Push a message to the Evil Portal Messages Pane * @param t: The Title of the message * @param m: The message body */ $scope.sendMessage = function (t, m) { // Add a new message to the top of the list $scope.messages.unshift({title: t, msg: m}); // if there are 4 items in the list remove the 4th item if ($scope.messages.length === 4) { $scope.dismissMessage(3); } }; /** * Remove a message from the Evil Portal Messages pane * @param $index: The index of the message in the list to remove */ $scope.dismissMessage = function ($index) { $scope.messages.splice($index, 1); }; /** * Preform an action for a given control * This can be starting the captive portal or toggle on boot. * @param control: The control to handle */ $scope.handleControl = function(control) { control.throbber = true; var actionToPreform = null; switch(control.title) { case "Captive Portal": actionToPreform = "toggleCaptivePortal"; break; case "Start On Boot": actionToPreform = "toggleOnBoot"; break; } if (actionToPreform !== null) { $api.request({ module: "EvilPortal", action: actionToPreform }, function(response) { if (!response.success) { $scope.sendMessage(control.title, response.message); } getStatus(); }); } }; /** * Validates the information in the newPortal model and then makes an API request to create a new portal. * @param storage: The storage medium to create the portal on (internal or sd) */ $scope.createNewPortal = function(storage) { $api.request({ module: "EvilPortal", action: "createNewPortal", name: $scope.newPortal.name, type: $scope.newPortal.type, storage: storage }, function(response) { if (!response.success) { $scope.sendMessage('Error Creating Portal', response.message); return; } $scope.newPortal = {"type": "basic", "name": ""}; getPortals(); }); }; /** * Move a given portal between storage mediums if an SD card is present. * @param portal: The portal to move */ $scope.movePortal = function(portal) { if (!$scope.evilPortal.sdAvailable) { $scope.sendMessage("No SD Card.", "An SD card must be present to preform this action."); return; } $api.request({ module: "EvilPortal", action: "movePortal", name: portal.title, storage: portal.storage }, function(response) { if (response.success) { getPortals(); $scope.sendMessage("Moved Portal", response.message); } else { $scope.sendMessage("Error Moving " + portal.title, response.message); } }); }; /** * Delete a portal from the wifi pineapple * @param verified: Has the delete request been verified? If so then make the API request otherwise setup * @param portal: The portal to delete */ $scope.deletePortal = function(verified, portal) { if (!verified) { // if the request has not been verified then setup the shits $scope.portalToDelete = portal; return; } if ($scope.portalToDelete === null || $scope.portalToDelete.fullPath === null) { $scope.sendMessage("Unable To Delete Portal", "No portal was set for deletion."); return; } deleteFileOrDirectory($scope.portalToDelete.fullPath, function (response) { if (!response.success) { $scope.sendMessage("Error Deleting Portal", response.message); // push an error if deletion failed } else { $scope.sendMessage("Deleted Portal", "Successfully deleted " + $scope.portalToDelete.title + "."); $scope.portalToDelete = null; $scope.portalDeleteValidation = null; getPortals(); // refresh the library } }); }; /** * Activate a portal * @param portal: The portal to activate */ $scope.activatePortal = function(portal) { $api.request({ module: "EvilPortal", action: "activatePortal", name: portal.title, storage: portal.storage }, function(response) { console.log(response); if (response.success) { getPortals(); $scope.sendMessage("Activated Portal", portal.title + " has been activated successfully."); } else { $scope.sendMessage("Error Activating " + portal.title, response.message); } }); }; /** * Deactivate a given portal if its active * @param portal: The portal to deactivate */ $scope.deactivatePortal = function(portal) { $api.request({ module: "EvilPortal", action: "deactivatePortal", name: portal.title, storage: portal.storage }, function(response) { console.log(response); if (response.success) { getPortals(); $scope.sendMessage("Deactivated Portal", portal.title + " has been deactivated successfully."); } else { $scope.sendMessage("Error Deactivating " + portal.title, response.message); } }); }; /** * Load portal contents and open it up in the work bench * @param portal: The portal to get the contents of */ $scope.loadPortal = function (portal) { getFileOrDirectoryContent(portal.fullPath, function(response) { if (!response.success) { $scope.sendMessage("Error Getting Contents", response.message); return; } $scope.workshop.inRoot = true; $scope.workshop.portal = portal; $scope.workshop.dirContents = response.content; $scope.workshop.rootDirectory = portal.fullPath; $scope.evilPortal.library = false; console.log(response.content); }); }; /** * Load toggle commands for the current portal in the work bench. * These are the commands that are executed when a portal is enabled/disabled. */ $scope.loadToggleCommands = function() { [".enable", ".disable"].forEach(getScript); function getScript(scriptName) { getFileOrDirectoryContent($scope.workshop.rootDirectory + "/" + scriptName, function (response) { if (!response.success) { $scope.sendMessage("Error Getting Contents", response.message); return; } if (scriptName === ".enable") $scope.workshop.onEnable = response.content.fileContent; else $scope.workshop.onDisable = response.content.fileContent; }); } }; /** * Save toggle commands. * @param cmdFile: The commands to save (enable, disable) */ $scope.saveToggleCommands = function(cmdFile) { function sendData(f, content) { writeToFile($scope.workshop.rootDirectory + "/" + f, content, false, function (response) { if (!response.success) $scope.sendMessage("Error write to file " + f, response.message); }); } switch(cmdFile) { case "disable": sendData(".disable", $scope.workshop.onDisable); break; case "enable": sendData(".enable", $scope.workshop.onEnable); break; default: sendData(".enable", $scope.workshop.onEnable); sendData(".disable", $scope.workshop.onDisable); break; } }; /** * Load the rules for targeted portals */ $scope.loadTargetedRules = function() { $api.request({ module: "EvilPortal", action: "getRules", name: $scope.workshop.portal.title, storage: $scope.workshop.portal.storage }, function(response) { if (response.success) { $scope.workshop.concreteTargetedRules = response.data; $scope.workshop.workingTargetedRules = {"rules": {}}; // welcome to the realm of loops. I will be your guide // We have to turn each rule into a keyed set of rules with a rule index represented by var index // this is because we need a constant key for each rule when editing on the web interface // the index must be removed later before saving the results to the routes.json file // if you have a better way to do this you are my hero. Email me n3rdcav3@gmail.com or fork the repo :) // This first loop loops over each rule categories such as "mac", "ssid" and so on for (var key in response.data['rules']) { // we then create the a object with that key name in our workingData object $scope.workshop.workingTargetedRules['rules'][key] = {}; // Now its time to loop over each category specifier such as "exact" and "regex" for (var specifier in response.data['rules'][key]) { var index = 0; // We then create that specifier in our workingData $scope.workshop.workingTargetedRules['rules'][key][specifier] = {}; // finally we loop over the specific rules defined in the specifier for (var r in response.data['rules'][key][specifier]) { var obj = {}; obj['key'] = r; obj['destination'] = response.data['rules'][key][specifier][r]; $scope.workshop.workingTargetedRules['rules'][key][specifier][index] = obj; } // increment index index++; } } } else { $scope.sendMessage("Error", "There was an issue getting the portal rules."); } }); }; /** * Remove a targeted rule * @param rule * @param specifier * @param index */ $scope.removeTargetedRule = function(rule, specifier, index) { delete $scope.workshop.workingTargetedRules['rules'][rule][specifier][index]; }; /** * Create a new targeted rule * @param rule * @param specifier */ $scope.newTargetedRule = function(rule, specifier) { // make sure the specifier is set if ($scope.workshop.workingTargetedRules['rules'][rule][specifier] == undefined) { $scope.workshop.workingTargetedRules['rules'][rule][specifier] = {}; } var highest = 0; // get the highest index for (var i in $scope.workshop.workingTargetedRules['rules'][rule][specifier]) { if (parseInt(i) >= highest) { highest = i + 1; } } $scope.workshop.workingTargetedRules['rules'][rule][specifier][highest] = {"": ""}; }; /** * Build the targeted rules and sned them to the API for saving */ $scope.saveTargetedRules = function() { // build the rules for (var key in $scope.workshop.concreteTargetedRules.rules) { for (var specifier in $scope.workshop.concreteTargetedRules.rules[key]) { var obj = {}; for (var i in $scope.workshop.workingTargetedRules['rules'][key][specifier]) { obj[$scope.workshop.workingTargetedRules['rules'][key][specifier][i]['key']] = $scope.workshop.workingTargetedRules['rules'][key][specifier][i]['destination']; } $scope.workshop.concreteTargetedRules['rules'][key][specifier] = obj; } } console.log(JSON.stringify($scope.workshop.concreteTargetedRules)); $api.request({ module: "EvilPortal", action: "saveRules", name: $scope.workshop.portal.title, storage: $scope.workshop.portal.storage, rules: JSON.stringify($scope.workshop.concreteTargetedRules) }, function(response) { if (!response.success) { $scope.sendMessage("Error", response.message); } }); }; /** * Load logs for a given portal * @param portal: The portal to load logs for */ $scope.loadPortalLog = function(portal) { var basePath = (portal.storage === "sd") ? "/sd/portals/" : "/root/portals/"; var logPath = basePath + portal.title + "/.logs"; $scope.loadLog(logPath); } /** * check if a given object is empty. * @param obj: The object to check * @returns {boolean}: true if empty false if not empty */ $scope.isObjectEmpty = function(obj) { return (Object.keys(obj).length === 0); }; /** * Loads a file from filePath and puts the data into the activeLog object. */ $scope.loadLog = function(filePath) { getFileOrDirectoryContent(filePath, function(response) { console.log(response); if (!response.success) { $scope.activeLog = { "title": "Unknown", "path": null, "contents": null, "size": "0 Bytes" }; return; } $scope.activeLog = { "title": response.content.name, "path": response.content.path, "contents": response.content.fileContent, "size": response.content.size }; }); }; /** * Writes an empty string to whatever file path is in $scope.activeLog.path * On successful write, $scope.activeLog.contents is set to null. * * If $scope.activeLog.path is null, the request if not made. * */ $scope.clearLog = function() { if ($scope.activeLog.path == null) return; writeToFile($scope.activeLog.path, '', false, function(response) { if (!response.success) { $scope.sendMessage("Error Clearing Log", response.message); return; } $scope.activeLog.contents = response.content; $scope.activeLog.size = "0 Bytes"; }); } /** * Load the contents of a given file. * @param filePath: The path to the file to load */ $scope.loadFileContent = function(filePath) { getFileOrDirectoryContent(filePath, function(response) { if (!response.success) { $scope.sendMessage("Error Getting Contents", response.message); return; } $scope.workshop.editFile = { "name": response.content.name, "path": response.content.path, "size": response.content.size, "content": response.content.fileContent }; }); }; /** * Setup the workshop to create a new empty file. */ $scope.setupNewFile = function() { var basePath = ($scope.workshop.portal.storage === "sd") ? "/sd/portals/" : "/root/portals/"; $scope.workshop.editFile.path = basePath + $scope.workshop.portal.title + "/"; $scope.workshop.editFile.isNewFile = true; $scope.workshop.editFile.size = "0 Bytes"; }; /** * Write file content to the file system. * @param editFile: A portal.editFile object */ $scope.saveFileContent = function(editFile) { // new files wont have the filename in the path so make sure to set it here if needed. if (!editFile.path.includes(editFile.name)) editFile.path = editFile.path + editFile.name; writeToFile(editFile.path, editFile.content, false, function(response) { if (!response.success) $scope.sendMessage("Error write to file " + editFile.name, response.message); $scope.loadPortal($scope.workshop.portal); // refresh the portal }); }; /** * Delete a requested file. */ $scope.deleteFile = function() { deleteFileOrDirectory($scope.workshop.deleteFile.path, function(response){ if (!response.success) $scope.sendMessage("Error deleting file " + $scope.workshop.deleteFile.name, response.message); $scope.loadPortal($scope.workshop.portal); // refresh the portal }); }; $scope.download = function(filePath) { $api.request({ module: "EvilPortal", action: "download", filePath: filePath }, function (response) { if (!response.success) { $scope.sendMessage("Error", response.message); return; } window.location = "/api/?download=" + response.download; }) }; /** * Load either the white list or the authorized clients (access) list * @param listName: The name of the list: whiteList or accessList (authorized clients) */ $scope.getList = function (listName) { var whiteList = '/pineapple/modules/EvilPortal/data/allowed.txt'; var authorized = '/tmp/EVILPORTAL_CLIENTS.txt'; getFileOrDirectoryContent((listName === "whiteList") ? whiteList : authorized, function (response) { switch (listName) { case 'whiteList': $scope.whiteList.clients = response.content.fileContent; break; case 'accessList': $scope.accessList.clients = response.content.fileContent; break; } }) }; /** * Remove a client from either the white list (whiteList) the authorized clients list (accessList) * @param listName: whiteList or accessList */ $scope.removeClientFromList = function(listName) { var clientToRemove = (listName === 'whiteList') ? $scope.whiteList.toManipulate : $scope.accessList.toManipulate; console.log(clientToRemove); $api.request({ module: "EvilPortal", action: "removeClientFromList", clientIP: clientToRemove, listName: listName }, function(response) { if (!response.success) { $scope.sendMessage("Error", response.message); return; } $scope.getList(listName); switch (listName) { case 'whiteList': $scope.whiteList.toManipulate = null; break; case 'accessList': $scope.accessList.toManipulate = null; break; } }); }; /** * Add a new client to the white list */ $scope.addWhiteListClient = function() { writeToFile('/pineapple/modules/EvilPortal/data/allowed.txt', $scope.whiteList.toManipulate + "\n", true, function(response) { $scope.getList('whiteList'); }); $scope.whiteList.toManipulate = null; }; /** * Authorize a new client */ $scope.authorizeClient = function () { $api.request({ module: "EvilPortal", action: "authorizeClient", clientIP: $scope.accessList.toManipulate }, function (response) { $scope.getList('accessList'); $scope.accessList.toManipulate = null; }); }; /** * Get a line clicked in a text area and set that line as the text for a text input * @param textareaId: The id of the text area to grab from * @param inputname: The name of the input field to write to */ $scope.getClickedClient = function(textareaId, inputname) { var textarea = $('#' + textareaId); var lineNumber = textarea.val().substr(0, textarea[0].selectionStart).split('\n').length; var ssid = textarea.val().split('\n')[lineNumber-1].trim(); $("input[name='" + inputname + "']").val(ssid).trigger('input'); }; /** * Write given content to a given file on the file system. * @param filePath: The path to the file to write content to * @param fileContent: The content to write to the file * @param appendFile: Should the content be append to the file (true) or overwrite the file (false) * @param callback: A callback function to handle the API response */ function writeToFile(filePath, fileContent, appendFile, callback) { $api.request({ module: "EvilPortal", action: "writeFileContent", filePath: filePath, content: fileContent, append: appendFile }, function(response) { callback(response); }); } /** * Get the contents of a directory * @param pathToObject: The full path to the file or directory to get the contents of * @param callback: A function that handles the response from the API. */ function getFileOrDirectoryContent(pathToObject, callback) { $api.request({ module: "EvilPortal", action: "getFileContent", filePath: pathToObject }, function(response) { callback(response); }); } /** * Delete a file or directory from the pineapples filesystem. * This is intended to be used for only deleting portals and portal related files but anything can be delete. * @param fileOrDirectory: The path to the file to delete * @param callback: The callback function to handle the API response */ function deleteFileOrDirectory(fileOrDirectory, callback) { $api.request({ module: "EvilPortal", action: "deleteFile", filePath: fileOrDirectory }, function(response) { callback(response); }); } /** * Update the control models so they reflect the proper information */ function updateControls() { $scope.controls = [ { "title": "Captive Portal", "status": ($scope.evilPortal.running) ? "Stop" : "Start", "visible": true, "throbber": false }, { "title": "Start On Boot", "status": ($scope.evilPortal.startOnBoot) ? "Disable": "Enable", "visible": true, "throbber": false } ]; } /** * Get the status's for the controls in the Controls pane and other various information */ function getStatus() { $scope.evilPortal.throbber = true; $api.request({ module: "EvilPortal", action: "status" }, function (response) { for (var key in response) { if (response.hasOwnProperty(key) && $scope.evilPortal.hasOwnProperty(key)) { $scope.evilPortal[key] = response[key]; } } $scope.evilPortal.throbber = false; updateControls(); }); } /** * Get all of the portals on the Pineapple */ function getPortals() { $scope.evilPortal.throbber = true; $api.request({ module: "EvilPortal", action: "listAvailablePortals" }, function(response) { if (!response.success) { $scope.sendMessage("Error Listing Portals", "An error occurred while trying to get list of portals."); return; } $scope.portals = []; response.portals.forEach(function(item, index) { $scope.portals.unshift({ title: item.title, size: item.size, storage: item.storage, active: item.active, type: item.portalType, fullPath: item.location }); }); }); } // The status for the Evil Portal module as well as current portals should be retrieved when the controller loads. getStatus(); getPortals(); }]);