├── README.md ├── fix-executables.sh └── src ├── etc ├── init.d │ └── pineapd └── uci-defaults │ └── 93-pineap.sh └── pineapple ├── api ├── API.php ├── APIModule.php ├── Authentication.php ├── DatabaseConnection.php ├── Module.php ├── Modules.php ├── Notifications.php ├── Setup.php ├── SystemModule.php ├── index.php └── pineapple.php ├── changes ├── config.php ├── css ├── bootstrap.min.css └── main.css ├── html ├── clone-modal.html ├── hook-modal.html └── install-modal.html ├── img ├── browser_chrome.png ├── browser_ff.png ├── browser_ie.png ├── browser_opera.png ├── browser_safari.png ├── chevron_down.svg ├── chevron_down_gray.svg ├── chevron_up.svg ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── logo.png ├── logout.png ├── notify.png └── throbber.gif ├── index.html ├── js ├── controllers.js ├── directives.js ├── filters.js ├── helpers.js ├── pineapple.js ├── services.js └── vendor │ ├── angular-cookies.min.js │ ├── angular-route.min.js │ ├── angular.min.js │ ├── bootstrap.min.js │ └── jquery.min.js ├── modules ├── Advanced │ ├── api │ │ └── module.php │ ├── formatSD │ │ ├── fdisk_options │ │ └── format_sd │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Clients │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Configuration │ ├── api │ │ ├── landingpage_index.php │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Dashboard │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Filters │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Help │ ├── api │ │ └── module.php │ ├── files │ │ ├── debug │ │ └── dumpscan.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Logging │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── ModuleManager │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Networking │ ├── api │ │ ├── AccessPoint.php │ │ ├── ClientMode.php │ │ ├── Interfaces.php │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Notes │ ├── api │ │ └── module.php │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── PineAP │ ├── api │ │ ├── PineAPHelper.php │ │ └── module.php │ ├── executable │ │ └── executable │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Recon │ ├── api │ │ ├── WebSocketsHandler.py │ │ ├── dbhandler.py │ │ ├── module.php │ │ ├── reconpp.py │ │ └── wsauth.py │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Reporting │ ├── api │ │ └── module.php │ ├── files │ │ ├── getlog.php │ │ └── reporting │ ├── js │ │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg ├── Setup │ ├── eula.txt │ ├── js │ │ └── module.js │ ├── license.txt │ └── module.html └── Tracking │ ├── api │ └── module.php │ ├── js │ └── module.js │ ├── module.html │ ├── module.info │ └── module_icon.svg └── pineapple_version /README.md: -------------------------------------------------------------------------------- 1 | # Wifi Pineapple Panel 2 | 3 | The idea of this project is to have a better panel for the pineapple NANO/TETRA 4 | To install it, you just have to copy the contents of the src folder to / of the pineapple and reboot. 5 | 6 | 7 | # Changes 8 | 9 | The following functionalities are modified looking for a better user experience 10 | 11 | ## General: 12 | - Compress PNG images (size -55K) 13 | - Compress SVG images (size -7K) 14 | - Update Bootstrap to 3.4.1 (size +2K) 15 | - Rebuild mobile view 16 | - Add Chevron icon to accordions (size +1K) 17 | - Change notification time from 6000 to 30000 (decrease RPM from 10 to 2) 18 | - Added more refresh buttons 19 | - Fixed several bugs found in the panel 20 | - Expose AngularJS Pineapple API in JS window 21 | - Add timeout and prevent duplicated request in API service 22 | - Refactor all indexedDB code 23 | - Removed use of php7-mod-sockets and php7-mod-openssl (size -200K) 24 | - Implemented use of uclient-fetch as a replacement for wget and file_get_contents(https) 25 | 26 | ## Dashboard 27 | - Change update time from 5000 to 10000 (decrease RPM from 12 to 6) 28 | 29 | ## Recon: 30 | - Code refactor in module.php 31 | - Add results counter in titles with badges 32 | - Fix column alignment 33 | 34 | ## Clients: 35 | - Add loading indicator 36 | - Change default text logic 37 | 38 | ## PineAP: 39 | - Configure used monitor interface (pineapd pineap_interface) 40 | - Configure used source interface (pineapd source_mac grabber) 41 | - Show pineapd service errors 42 | 43 | ## Logging: 44 | - Fire data loading on open accordion 45 | - Add PineAP Logs loading indicator 46 | - Save filters in cookies 47 | 48 | ## Network: 49 | - Add tabs 50 | - Add "Wireless raw config editor" section 51 | - Add "Info" section 52 | - Add "Interface actions" section 53 | - Decrease initial requests (from 8 to 3) 54 | - Use new feed for out.txt updates 55 | 56 | ## Advanced: 57 | - Add tabs 58 | - Add "Manual upgrade" section 59 | - Decrease initial requests (from 8 to 3) 60 | - Use Universal Wifi pineapple hardware cloner downloads as update feed 61 | - Add "Keep settings and retain the current configuration" checkbox 62 | 63 | ## Modules: 64 | - Refactor in Modules.php 65 | - Add support for injectJS in modules manifest 66 | - Use new modules feed: https://github.com/xchwarze/wifi-pineapple-community/tree/main/modules 67 | 68 | 69 | # Notes 70 | 71 | 1. For edit notification timer you can use this 72 | ```bash 73 | # sed -i 's/OLD-VALUE/NEW-VALUE/' FILE 74 | sed -i 's/30000/60000/' src/pineapple/js/controllers.js 75 | ``` 76 | 77 | 2. To open the menu on hover uncomment this in src/pineapple/main.css 78 | ```css 79 | .sidebar:hover { 80 | margin-left: 0; 81 | } 82 | .menu-toggle { 83 | display: none !important; 84 | } 85 | ``` 86 | 87 | 3. To develop locally you can point your panel to the pinapple replacing the src/pineapple/api/index.php with this 88 | ```php 89 | /tmp/pineap.conf 9 | autostart = $(uci get pineap.@config[0].autostart) 10 | karma = $(uci get pineap.@config[0].karma) 11 | beacon_interval = $(uci get pineap.@config[0].beacon_interval) 12 | beacon_response_interval = $(uci get pineap.@config[0].beacon_response_interval) 13 | beacon_responses = $(uci get pineap.@config[0].beacon_responses) 14 | capture_ssids = $(uci get pineap.@config[0].capture_ssids) 15 | broadcast_ssid_pool = $(uci get pineap.@config[0].broadcast_ssid_pool) 16 | logging = $(uci get pineap.@config[0].logging) 17 | mac_filter = $(uci get pineap.@config[0].mac_filter) 18 | ssid_filter = $(uci get pineap.@config[0].ssid_filter) 19 | ap_channel = $(uci get pineap.@config[0].ap_channel) 20 | pineap_mac = $(ifconfig -a $(uci get pineap.@config[0].pineap_source_interface) | grep HWaddr | awk '{print $5}') 21 | target_mac = $(uci get pineap.@config[0].target_mac) 22 | pineap_interface = $(uci get pineap.@config[0].pineap_interface) 23 | recon_db_path = $(uci get pineap.@config[0].recon_db_path) 24 | hostapd_db_path = $(uci get pineap.@config[0].hostapd_db_path) 25 | ssid_db_path = $(uci get pineap.@config[0].ssid_db_path) 26 | connect_notifications = $(uci get pineap.@config[0].connect_notifications) 27 | disconnect_notifications = $(uci get pineap.@config[0].disconnect_notifications) 28 | rts_enabled = off 29 | rts_scan_id = -1 30 | EOF 31 | } 32 | 33 | start() { 34 | mkdir -p /tmp/handshakes 35 | gen_config 36 | 37 | pineap_interface=$(uci -q get pineap.@config[0].pineap_interface) 38 | pineap_interface_original=${pineap_interface/mon/''} 39 | 40 | #ifconfig wlan1mon &>/dev/null || airmon-ng start wlan1 &>/dev/null 41 | ifconfig ${pineap_interface} &>/dev/null || airmon-ng start ${pineap_interface_original} &>/dev/null 42 | /usr/sbin/pineapd /tmp/pineap.conf &>/dev/null & 43 | 44 | sleep 1 45 | if [[ $(/usr/bin/pgrep pineapd) ]]; then 46 | echo "Status: OK" 47 | elif [[ ! $(ifconfig -a ${pineap_interface} | grep HWaddr | awk '{print $5}') ]]; then 48 | echo "Status: Monitor interface won't start! Try to run manually airmon-ng start ${pineap_interface_original}" 49 | else 50 | echo "There is a problem with pineapd. Try to run it manually" 51 | fi 52 | } 53 | 54 | stop() { 55 | killall -9 pineapd 56 | rm /var/run/pineapd.sock 57 | rm /var/run/pineapd.lock 58 | } 59 | 60 | restart() { 61 | stop 62 | start 63 | } 64 | 65 | boot() { 66 | gen_config 67 | /usr/sbin/pineapd /tmp/pineap.conf --initdb 68 | if [[ $(uci get pineap.@config[0].autostart) == '1' ]]; then 69 | start 70 | fi 71 | logger =========== PineAP ============== 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/etc/uci-defaults/93-pineap.sh: -------------------------------------------------------------------------------- 1 | # -- Set up PineAP configuration 2 | 3 | touch /etc/config/pineap 4 | uci add pineap config 5 | uci set pineap.@config[0].autostart=0 6 | uci set pineap.@config[0].karma='off' 7 | uci set pineap.@config[0].beacon_interval='NORMAL' 8 | uci set pineap.@config[0].beacon_response_interval='NORMAL' 9 | uci set pineap.@config[0].beacon_responses='off' 10 | uci set pineap.@config[0].capture_ssids='off' 11 | uci set pineap.@config[0].broadcast_ssid_pool='off' 12 | uci set pineap.@config[0].logging='off' 13 | uci set pineap.@config[0].mac_filter='black' 14 | uci set pineap.@config[0].ssid_filter='black' 15 | uci set pineap.@config[0].connect_notifications='off' 16 | uci set pineap.@config[0].disconnect_notifications='off' 17 | uci set pineap.@config[0].ap_channel=11 18 | uci set pineap.@config[0].pineap_interface='wlan1mon' 19 | uci set pineap.@config[0].pineap_source_interface='wlan0' 20 | 21 | uci set pineap.@config[0].pineap_mac='00:11:22:33:44:55' 22 | uci set pineap.@config[0].target_mac='FF:FF:FF:FF:FF:FF' 23 | uci set pineap.@config[0].recon_db_path='/tmp/recon.db' 24 | uci set pineap.@config[0].hostapd_db_path='/tmp/log.db' 25 | uci set pineap.@config[0].ssid_db_path='/etc/pineapple/pineapple.db' 26 | 27 | uci set pineap.@config[0].pineape_passthrough='off' 28 | 29 | uci commit pineap 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /src/pineapple/api/APIModule.php: -------------------------------------------------------------------------------- 1 | request = $request; 14 | } 15 | 16 | public function getResponse() 17 | { 18 | if (empty($this->error) && !empty($this->response)) { 19 | return $this->response; 20 | } elseif (empty($this->error) && empty($this->response)) { 21 | return ['error' => 'API returned empty response']; 22 | } else { 23 | return ['error' => $this->error]; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/pineapple/api/Authentication.php: -------------------------------------------------------------------------------- 1 | dbConnection = new DatabaseConnection(self::DATABASE); 15 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS api_tokens (token VARCHAR NOT NULL, name VARCHAR NOT NULL);"); 16 | } 17 | 18 | public function getApiTokens() 19 | { 20 | $this->response = array("tokens" => $this->dbConnection->query("SELECT token,name FROM api_tokens;")); 21 | } 22 | 23 | public function checkApiToken() 24 | { 25 | if (isset($this->request->token)) { 26 | $token = $this->request->token; 27 | $result = $this->dbConnection->query("SELECT token FROM api_tokens WHERE token='%s';", $token); 28 | if (!empty($result) && isset($result[0]["token"]) && $result[0]["token"] === $token) { 29 | $this->response = array("valid" => true); 30 | return; 31 | } 32 | } 33 | $this->response = array("valid" => false); 34 | } 35 | 36 | public function addApiToken() 37 | { 38 | if (isset($this->request->token) && isset($this->request->name)) { 39 | $token = $this->request->token; 40 | $name = $this->request->name; 41 | $this->dbConnection->exec("INSERT INTO api_tokens(token, name) VALUES('%s','%s');", $token, $name); 42 | $this->response = array("success" => true); 43 | return; 44 | } 45 | $this->error = "Missing token or name"; 46 | } 47 | 48 | private function login() 49 | { 50 | if (isset($this->request->username) && isset($this->request->password)) { 51 | if ($this->verifyPassword($this->request->password)) { 52 | $_SESSION['logged_in'] = true; 53 | $this->response = array("logged_in" => true); 54 | if (!isset($this->request->time)) { 55 | return; 56 | } 57 | $epoch = intval($this->request->time); 58 | if ($epoch > 1) { 59 | exec('date -s @' . $epoch); 60 | } 61 | return; 62 | } 63 | } 64 | 65 | $this->response = array("logged_in" => false); 66 | } 67 | 68 | private function verifyPassword($password) 69 | { 70 | $shadowContents = file_get_contents('/etc/shadow'); 71 | $rootArray = explode(':', explode('root:', $shadowContents)[1]); 72 | $rootPass = $rootArray[0]; 73 | if (!empty($rootPass) && gettype($rootPass) === "string") { 74 | return hash_equals($rootPass, crypt($password, $rootPass)); 75 | } 76 | return false; 77 | } 78 | 79 | private function logout() 80 | { 81 | $this->response = array("logged_in" => false); 82 | unset($_COOKIE['XSRF-TOKEN']); 83 | setcookie('XSRF-TOKEN', '', time()-3600); 84 | unset($_SESSION['XSRF-TOKEN']); 85 | unset($_SESSION['logged_in']); 86 | session_destroy(); 87 | } 88 | 89 | private function checkAuth() 90 | { 91 | if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) { 92 | $this->response = array("authenticated" => true); 93 | } else { 94 | if (file_exists("/etc/pineapple/setupRequired")) { 95 | $this->response = array("error" => "Not Authenticated", "setupRequired" => true); 96 | } else { 97 | $this->response = array("error" => "Not Authenticated"); 98 | } 99 | } 100 | } 101 | 102 | public function route() 103 | { 104 | switch ($this->request->action) { 105 | case 'login': 106 | $this->login(); 107 | break; 108 | 109 | case 'logout': 110 | $this->logout(); 111 | break; 112 | 113 | case 'checkAuth': 114 | $this->checkAuth(); 115 | break; 116 | 117 | case 'checkApiToken': 118 | $this->checkApiToken(); 119 | break; 120 | 121 | case 'addApiToken': 122 | $this->addApiToken(); 123 | break; 124 | 125 | case 'getApiTokens': 126 | $this->getApiTokens(); 127 | break; 128 | 129 | default: 130 | $this->error = "Unknown action"; 131 | } 132 | 133 | session_write_close(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/pineapple/api/DatabaseConnection.php: -------------------------------------------------------------------------------- 1 | error = []; 12 | $this->databaseFile = $databaseFile; 13 | try { 14 | $this->dbConnection = new \SQLite3($this->databaseFile); 15 | $this->dbConnection->busyTimeout(20000); 16 | } catch (\Exception $e) { 17 | $this->error["databaseConnectionError"] = $e->getMessage(); 18 | } 19 | } 20 | 21 | public function strError() 22 | { 23 | foreach ($this->error as $errorType => $errorMessage) { 24 | switch ($errorType) { 25 | case 'databaseConnectionError': 26 | return "Could not connect to database: $errorMessage"; 27 | case 'databaseExecutionError': 28 | case 'databaseQueryError': 29 | return "Could not execute query: $errorMessage"; 30 | default: 31 | return "Unknown database error"; 32 | } 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public function getDatabaseFile() 39 | { 40 | return $this->databaseFile; 41 | } 42 | 43 | public function getDbConnection() 44 | { 45 | return $this->dbConnection; 46 | } 47 | 48 | public static function formatQuery(...$query) 49 | { 50 | $query = $query[0]; 51 | $sqlQuery = $query[0]; 52 | $sqlParameters = array_slice($query, 1); 53 | if (empty($sqlParameters)) { 54 | return $sqlQuery; 55 | } 56 | for ($i = 0; $i < count($sqlParameters); ++$i) { 57 | if (gettype($sqlParameters[$i]) === "string") { 58 | $escaped = \SQLite3::escapeString($sqlParameters[$i]); 59 | $sqlParameters[$i] = $escaped; 60 | } 61 | } 62 | return vsprintf($sqlQuery, $sqlParameters); 63 | } 64 | 65 | public function query(...$query) 66 | { 67 | $safeQuery = DatabaseConnection::formatQuery($query); 68 | $result = $this->dbConnection->query($safeQuery); 69 | if (!$result) { 70 | $this->error['databaseQueryError'] = $this->dbConnection->lastErrorMsg(); 71 | return $this->error; 72 | } 73 | $resultArray = []; 74 | while ($row = $result->fetchArray(SQLITE3_ASSOC)) { 75 | $resultArray[] = $row; 76 | } 77 | return $resultArray; 78 | } 79 | 80 | public function exec(...$query) 81 | { 82 | $safeQuery = DatabaseConnection::formatQuery($query); 83 | try { 84 | $result = $this->dbConnection->exec($safeQuery); 85 | } catch (\Exception $e) { 86 | $this->error['databaseExecutionError'] = $e; 87 | return $this->error; 88 | } 89 | return ['success' => $result]; 90 | } 91 | 92 | public function __destruct() 93 | { 94 | if ($this->dbConnection) { 95 | $this->dbConnection->close(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/pineapple/api/Module.php: -------------------------------------------------------------------------------- 1 | request = $request; 18 | $this->moduleClass = $moduleClass; 19 | $this->error = ''; 20 | } 21 | 22 | public function getResponse() 23 | { 24 | if (empty($this->error) && !empty($this->response)) { 25 | return $this->response; 26 | } elseif (!empty($this->streamFunction)) { 27 | header('Content-Type: text/plain'); 28 | $this->streamFunction->__invoke(); 29 | return false; 30 | } elseif (empty($this->error) && empty($this->response)) { 31 | return ['error' => 'Module returned empty response']; 32 | } else { 33 | return ['error' => $this->error]; 34 | } 35 | } 36 | 37 | public function execBackground($command) 38 | { 39 | return \helper\execBackground($command); 40 | } 41 | 42 | protected function isSDAvailable() 43 | { 44 | return \helper\isSDAvailable(); 45 | } 46 | 47 | protected function sdReaderPresent() { 48 | return \helper\sdReaderPresent(); 49 | } 50 | 51 | protected function sdCardPresent() { 52 | return \helper\sdCardPresent(); 53 | } 54 | 55 | protected function checkRunning($processName) 56 | { 57 | return \helper\checkRunning($processName); 58 | } 59 | 60 | protected function checkRunningFull($processString) { 61 | return \helper\checkRunningFull($processString); 62 | } 63 | 64 | public function uciGet($uciString) 65 | { 66 | return \helper\uciGet($uciString); 67 | } 68 | 69 | public function uciSet($settingString, $value) 70 | { 71 | \helper\uciSet($settingString, $value); 72 | } 73 | 74 | public function uciAddList($settingString, $value) 75 | { 76 | \helper\uciAddList($settingString, $value); 77 | } 78 | 79 | protected function downloadFile($file) 80 | { 81 | return \helper\downloadFile($file); 82 | } 83 | 84 | protected function getFirmwareVersion() 85 | { 86 | return \helper\getFirmwareVersion(); 87 | } 88 | 89 | protected function getDevice() 90 | { 91 | return \helper\getDevice(); 92 | } 93 | 94 | protected function getBoard() 95 | { 96 | return \helper\getBoard(); 97 | } 98 | 99 | protected function getDeviceConfig() 100 | { 101 | return \helper\getDeviceConfig(); 102 | } 103 | 104 | protected function getMacFromInterface($interface) 105 | { 106 | return \helper\getMacFromInterface($interface); 107 | } 108 | 109 | protected function installDependency($dependencyName, $installToSD = false) 110 | { 111 | if ($installToSD && !$this->isSDAvailable()) { 112 | return false; 113 | } 114 | 115 | $destination = $installToSD ? '--dest sd' : ''; 116 | $dependencyName = escapeshellarg($dependencyName); 117 | if (!$this->checkDependency($dependencyName)) { 118 | exec("opkg update"); 119 | exec("opkg install {$dependencyName} {$destination}"); 120 | } 121 | 122 | return $this->checkDependency($dependencyName); 123 | } 124 | 125 | protected function checkDependency($dependencyName) 126 | { 127 | return \helper\checkDependency($dependencyName); 128 | } 129 | 130 | protected function fileGetContentsSSL($url) 131 | { 132 | return \helper\fileGetContentsSSL($url); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/pineapple/api/Modules.php: -------------------------------------------------------------------------------- 1 | modules = [ 11 | 'systemModules' => [], 12 | 'userModules' => [] 13 | ]; 14 | } 15 | 16 | public function getModules() 17 | { 18 | require_once('DatabaseConnection.php'); 19 | 20 | $dir = scandir("../modules"); 21 | if ($dir === false) { 22 | $this->error = "Unable to access modules directory"; 23 | return $this->modules; 24 | } 25 | 26 | natcasesort($dir); 27 | foreach ($dir as $moduleFolder) { 28 | $modulePath = "../modules/{$moduleFolder}"; 29 | if ($moduleFolder[0] === '.' || !file_exists("{$modulePath}/module.info")) { 30 | continue; 31 | } 32 | 33 | $moduleInfo = @json_decode(file_get_contents("{$modulePath}/module.info")); 34 | if (json_last_error() !== JSON_ERROR_NONE || isset($moduleInfo->cliOnly)) { 35 | continue; 36 | } 37 | 38 | $jsonModulePath = "/modules/${moduleFolder}"; 39 | $module = [ 40 | "name" => $moduleFolder, 41 | "title" => isset($moduleInfo->title) ? $moduleInfo->title : $moduleFolder, 42 | "icon" => null, 43 | "injectJS" => isset($moduleInfo->injectJS) ? "${jsonModulePath}/{$moduleInfo->injectJS}" : null, 44 | ]; 45 | 46 | if (file_exists("$modulePath/module_icon.svg")) { 47 | $module["icon"] = "${jsonModulePath}/module_icon.svg"; 48 | } elseif (file_exists("$modulePath/module_icon.png")) { 49 | $module["icon"] = "${jsonModulePath}/module_icon.png"; 50 | } 51 | 52 | if (isset($moduleInfo->system)) { 53 | if (isset($moduleInfo->index)) { 54 | $this->modules['systemModules'][$moduleInfo->index] = $module; 55 | } 56 | } else { 57 | $this->modules['userModules'][] = $module; 58 | } 59 | } 60 | 61 | return $this->modules; 62 | } 63 | 64 | public function route() 65 | { 66 | switch ($this->request->action) { 67 | case "getModuleList": 68 | $this->getModules(); 69 | $this->response = ['modules' => $this->modules]; 70 | break; 71 | default: 72 | $this->error = "Unknown action: " . $this->request->action; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/pineapple/api/Notifications.php: -------------------------------------------------------------------------------- 1 | dbConnection = new DatabaseConnection(self::DATABASE); 15 | if (!empty($this->dbConnection->error)) { 16 | $this->error = $this->dbConnection->strError(); 17 | return; 18 | } 19 | $this->notifications = []; 20 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS notifications (message VARCHAR NOT NULL, time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);"); 21 | if (!empty($this->dbConnection->error)) { 22 | $this->error = $this->dbConnection->strError(); 23 | } 24 | } 25 | 26 | public function route() 27 | { 28 | switch ($this->request->action) { 29 | case 'listNotifications': 30 | $this->response = $this->getNotifications(); 31 | break; 32 | case 'addNotification': 33 | $this->response = $this->addNotification($this->request->message); 34 | break; 35 | case 'clearNotifications': 36 | $this->response = $this->clearNotifications(); 37 | break; 38 | default: 39 | $this->error = "Unknown action: " . $this->request->action; 40 | } 41 | } 42 | 43 | public function addNotification($message) 44 | { 45 | return $this->dbConnection->exec("INSERT INTO notifications (message) VALUES('%s');", $message); 46 | } 47 | 48 | public function getNotifications() 49 | { 50 | $result = $this->dbConnection->query("SELECT message,time from notifications ORDER BY time DESC;"); 51 | $this->notifications = $result; 52 | return $this->notifications; 53 | } 54 | 55 | public function clearNotifications() 56 | { 57 | $result = $this->dbConnection->exec('DELETE FROM notifications;'); 58 | unset($this->notifications); 59 | return $result; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pineapple/api/SystemModule.php: -------------------------------------------------------------------------------- 1 | handleRequest(); 9 | -------------------------------------------------------------------------------- /src/pineapple/api/pineapple.php: -------------------------------------------------------------------------------- 1 | = 1; 21 | } 22 | 23 | function sdReaderPresent() { 24 | return file_exists('/sd'); 25 | } 26 | 27 | function sdCardPresent() { 28 | return !file_exists('/sd/NO_SD'); 29 | } 30 | 31 | function checkRunning($processName) 32 | { 33 | $processName = escapeshellarg($processName); 34 | exec("/usr/bin/pgrep {$processName}", $output); 35 | return count($output) > 0; 36 | } 37 | 38 | function checkRunningFull($processString) { 39 | $processString = escapeshellarg($processString); 40 | exec("/usr/bin/pgrep -f {$processString}", $output); 41 | return count($output) > 0; 42 | } 43 | 44 | function uciGet($uciString, $autoBool = true) 45 | { 46 | $uciString = escapeshellarg($uciString); 47 | $result = exec("uci get {$uciString}"); 48 | if ($autoBool && ($result === "0" || $result === "1")) { 49 | return $result === "1"; 50 | } 51 | 52 | return $result; 53 | } 54 | 55 | function uciSet($settingString, $value, $autoCommit = true) 56 | { 57 | $settingString = escapeshellarg($settingString); 58 | if (!empty($value)) { 59 | $value = escapeshellarg($value); 60 | } 61 | 62 | if ($value === "''" || $value === "") { 63 | $value = "'0'"; 64 | } 65 | 66 | exec("uci set {$settingString}={$value}"); 67 | if ($autoCommit) { 68 | exec("uci commit {$settingString}"); 69 | } 70 | } 71 | 72 | function uciAddList($settingString, $value, $autoCommit = true) 73 | { 74 | $settingString = escapeshellarg($settingString); 75 | if (!empty($value)) { 76 | $value = escapeshellarg($value); 77 | } 78 | 79 | if ($value === "''" || $value === "") { 80 | $value = "'0'"; 81 | } 82 | 83 | exec("uci add_list {$settingString}={$value}"); 84 | if ($autoCommit) { 85 | exec("uci commit {$settingString}"); 86 | } 87 | } 88 | 89 | function uciCommit() 90 | { 91 | exec("uci commit"); 92 | } 93 | 94 | function downloadFile($file) 95 | { 96 | $token = hash('sha256', $file . time()); 97 | 98 | require_once('DatabaseConnection.php'); 99 | $database = new \pineapple\DatabaseConnection("/etc/pineapple/pineapple.db"); 100 | $database->exec("CREATE TABLE IF NOT EXISTS downloads (token VARCHAR NOT NULL, file VARCHAR NOT NULL, time timestamp default (strftime('%s', 'now')));"); 101 | $database->exec("INSERT INTO downloads (token, file) VALUES ('%s', '%s')", $token, $file); 102 | 103 | return $token; 104 | } 105 | 106 | function getFirmwareVersion() 107 | { 108 | return trim(file_get_contents('/pineapple/pineapple_version')); 109 | } 110 | 111 | function getDevice() 112 | { 113 | return \DeviceConfig::DEVICE_TYPE; 114 | } 115 | 116 | function getDeviceConfig() 117 | { 118 | return [ 119 | 'deviceType' => \DeviceConfig::DEVICE_TYPE, 120 | 'useInternalStorage' => \DeviceConfig::USE_INTERNAL_STORAGE, 121 | 'useUSBStorage' => \DeviceConfig::USE_USB_STORAGE, 122 | 'showFirewallConfig' => \DeviceConfig::SHOW_FIREWALL_CONFIG, 123 | 'showScanType' => \DeviceConfig::SHOW_SCAN_TYPE, 124 | 'hideWlan0Client' => \DeviceConfig::HIDE_WLAN0_CLIENT, 125 | ]; 126 | } 127 | 128 | function getMacFromInterface($interface) 129 | { 130 | $interface = escapeshellarg($interface); 131 | return trim(exec("ifconfig {$interface} | grep HWaddr | awk '{print $5}'")); 132 | } 133 | 134 | function getBoard() 135 | { 136 | $data = file_get_contents('/tmp/sysinfo/board_name'); 137 | if (!empty($data)) { 138 | return str_replace(',', '_', trim($data)); 139 | } 140 | 141 | return false; 142 | } 143 | 144 | function fileGetContentsSSL($url) 145 | { 146 | $url = escapeshellarg($url); 147 | return exec("uclient-fetch -q -T 10 -O - {$url}"); 148 | } 149 | -------------------------------------------------------------------------------- /src/pineapple/changes: -------------------------------------------------------------------------------- 1 | 43 | 44 |

45 | 🚀Wifi Pineapple Panel by DSR! - View full changelog in GitHub! 🚀
46 |

47 | -------------------------------------------------------------------------------- /src/pineapple/config.php: -------------------------------------------------------------------------------- 1 | 2 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/pineapple/html/install-modal.html: -------------------------------------------------------------------------------- 1 | 53 | 54 | -------------------------------------------------------------------------------- /src/pineapple/img/browser_chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_chrome.png -------------------------------------------------------------------------------- /src/pineapple/img/browser_ff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_ff.png -------------------------------------------------------------------------------- /src/pineapple/img/browser_ie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_ie.png -------------------------------------------------------------------------------- /src/pineapple/img/browser_opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_opera.png -------------------------------------------------------------------------------- /src/pineapple/img/browser_safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/browser_safari.png -------------------------------------------------------------------------------- /src/pineapple/img/chevron_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/img/chevron_down_gray.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/img/chevron_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon-16x16.png -------------------------------------------------------------------------------- /src/pineapple/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon-32x32.png -------------------------------------------------------------------------------- /src/pineapple/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/favicon.ico -------------------------------------------------------------------------------- /src/pineapple/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/logo.png -------------------------------------------------------------------------------- /src/pineapple/img/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/logout.png -------------------------------------------------------------------------------- /src/pineapple/img/notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/notify.png -------------------------------------------------------------------------------- /src/pineapple/img/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xchwarze/wifi-pineapple-panel/6225e99d847cb2b14bdb3dcb70f2a7a7e613f58f/src/pineapple/img/throbber.gif -------------------------------------------------------------------------------- /src/pineapple/js/controllers.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular.module('pineapple') 3 | .controller('NavigationController', ['$scope', '$api', '$routeParams', function($scope, $api, $routeParams) { 4 | $scope.systemModules = []; 5 | $scope.userModules = []; 6 | $scope.selectedIndex = -1; 7 | 8 | $scope.getClass = function(moduleName) { 9 | var status = ($routeParams.moduleName === $(".sidebar-nav li[module="+ moduleName +"]").attr("module")); 10 | return status ? 'active' : ''; 11 | }; 12 | 13 | $scope.getModuleClass = function() { 14 | return $(".module-nav li.active").length ? 'active' : ''; 15 | }; 16 | 17 | $scope.getModuleList = (function () { 18 | $api.request({ 19 | system: 'modules', 20 | action: 'getModuleList' 21 | }, function(data) { 22 | if (data.error === undefined){ 23 | $scope.systemModules = data.modules.systemModules; 24 | $scope.userModules = data.modules.userModules; 25 | $scope.processModulesExtras(); 26 | } 27 | }); 28 | }); 29 | 30 | $scope.processModulesExtras = (function () { 31 | angular.forEach($scope.userModules, function(value) { 32 | if (value.injectJS) { 33 | $('head').append( $('') ); 34 | } 35 | }); 36 | }); 37 | 38 | $scope.getAlias = function(moduleName) { 39 | return moduleName.match(/(\b\S)?/g).join('').match(/(^\S|\S$)?/g).join('').toUpperCase(); 40 | }; 41 | 42 | $api.registerNavbar($scope.getModuleList); 43 | $scope.getModuleList(); 44 | }]) 45 | 46 | 47 | .controller('NotificationController', ['$scope', '$api', '$interval', function($scope, $api, $interval){ 48 | $scope.notifications = []; 49 | 50 | $api.getNotifications(function(data){ 51 | $scope.notifications = data; 52 | }); 53 | 54 | $scope.clearNotifications = function(){ 55 | $scope.notifications = []; 56 | $api.clearNotifications(); 57 | }; 58 | 59 | $scope.notificationInterval = $interval(function() { 60 | $api.getNotifications(function(data){ 61 | $scope.notifications = data; 62 | }); 63 | }, 30000); 64 | 65 | $scope.$on('$destroy', function() { 66 | $interval.cancel($scope.notificationInterval); 67 | }); 68 | }]) 69 | 70 | 71 | .controller('AuthenticationController', ['$scope', '$api', function($scope, $api){ 72 | $scope.username = "root"; 73 | $scope.password = ""; 74 | $scope.message = ""; 75 | 76 | $scope.login = function(){ 77 | $api.login($scope.username, $scope.password, function(data){ 78 | if (data.logged_in !== undefined && data.logged_in === false) { 79 | $scope.message = "Invalid username or password."; 80 | } else { 81 | window.location.reload(); 82 | } 83 | }); 84 | }; 85 | 86 | $scope.logout = function(){ 87 | $api.logout(function(){ 88 | window.location.reload(); 89 | }); 90 | }; 91 | 92 | $scope.haltPineapple = (function() { 93 | if (confirm("Are you sure you want to shutdown your WiFi Pineapple?")) { 94 | $api.request({ 95 | module: "Configuration", 96 | action: "haltPineapple" 97 | }, function(response) { 98 | if (response.success !== undefined) { 99 | alert("Your WiFi Pineapple is now shutting down. Once the LED has turned off, it is safe to unplug."); 100 | } 101 | }); 102 | } 103 | }); 104 | 105 | $scope.rebootPineapple = (function() { 106 | if (confirm("Are you sure you want to reboot your WiFi Pineapple?")) { 107 | $api.request({ 108 | module: "Configuration", 109 | action: "rebootPineapple" 110 | }, function(response) { 111 | if (response.success !== undefined) { 112 | alert("Your WiFi Pineapple is now rebooting. You may need to reconnect once it is done."); 113 | } 114 | }); 115 | } 116 | }); 117 | }]); 118 | })(); -------------------------------------------------------------------------------- /src/pineapple/js/filters.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | angular.module('pineapple') 3 | .filter('timesince', function () { 4 | return function (input) { 5 | var then = new Date(input * 1000); 6 | var now = new Date(); 7 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60); 8 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000); 9 | var secondsSince = Math.round(Math.abs(now - then) / 1000); 10 | if (secondsSince >= 1 && secondsSince < 60) { 11 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago'; 12 | } else if (minutesSince >= 1 && minutesSince < 60) { 13 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago'; 14 | } else if (hoursSince >= 1 && hoursSince < 24) { 15 | return hoursSince + ' ' + (hoursSince === 1 ? 'hour' : 'hours') + ' ago'; 16 | } else { 17 | var hours = then.getHours(); 18 | var minutes = then.getMinutes(); 19 | var month = then.getMonth(); 20 | var day = then.getDay(); 21 | var year = then.getYear(); 22 | return 'at ' + (year + 1990) + '-' + month + '-' + day + ' ' + (hours % 12 === 0 ? '12' : (hours % 12).toString()) + ':' + minutes + (hours > 12 ? ' PM' : ' AM'); 23 | } 24 | } 25 | }) 26 | 27 | .filter('timesincedate', function () { 28 | return function (input) { 29 | if (input === undefined) { 30 | return ""; 31 | } 32 | var then = utcDate(input); 33 | var now = new Date(); 34 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60); 35 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000); 36 | var secondsSince = Math.round(Math.abs(now - then) / 1000); 37 | if (secondsSince >= 1 && secondsSince < 60) { 38 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago'; 39 | } else if (minutesSince >= 1 && minutesSince < 60) { 40 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago'; 41 | } else { 42 | var hours = ("0" + then.getHours()).slice(-2); 43 | var minutes = ("0" + then.getMinutes()).slice(-2); 44 | var month = ("0" + (then.getMonth() + 1)).slice(-2); 45 | var day = ("0" + then.getDate()).slice(-2); 46 | var year = then.getYear(); 47 | return 'at ' + (year + 1900) + '-' + month + '-' + day + ' ' + hours + ':' + minutes; 48 | } 49 | } 50 | }) 51 | 52 | .filter('timesinceepoch', function () { 53 | return function (input) { 54 | if (input === undefined) { 55 | return ""; 56 | } 57 | var then = new Date(input * 1000); 58 | var now = new Date(); 59 | var hoursSince = Math.round(Math.abs(now - then) / 1000 / 60 / 60); 60 | var minutesSince = Math.round(Math.abs(now - then) / 60 / 1000); 61 | var secondsSince = Math.round(Math.abs(now - then) / 1000); 62 | if (secondsSince >= 1 && secondsSince < 60) { 63 | return secondsSince + ' ' + (secondsSince === 1 ? 'second' : 'seconds') + ' ago'; 64 | } else if (minutesSince >= 1 && minutesSince < 60) { 65 | return minutesSince + ' ' + (minutesSince === 1 ? 'minute' : 'minutes') + ' ago'; 66 | } else { 67 | var hours = ("0" + then.getHours()).slice(-2); 68 | var minutes = ("0" + then.getMinutes()).slice(-2); 69 | var month = ("0" + then.getMonth()).slice(-2); 70 | var day = ("0" + then.getDay()).slice(-2); 71 | var year = then.getYear(); 72 | return 'at ' + (year + 1900) + '-' + month + '-' + day + ' ' + hours + ':' + minutes; 73 | } 74 | } 75 | }) 76 | 77 | .filter('utcToBrowser', function () { 78 | return function (input) { 79 | if (input === undefined) { 80 | return ""; 81 | } 82 | var d = new Date(input + " UTC"); 83 | 84 | var day = d.getDate(); 85 | var month = d.getMonth(); 86 | var year = d.getFullYear(); 87 | 88 | return day + ' ' + month+1 + ' ' + year; 89 | } 90 | }) 91 | 92 | .filter('rawHTML', ['$sce', function ($sce) { 93 | return function (input) { 94 | return $sce.trustAsHtml(input); 95 | } 96 | }]) 97 | 98 | .filter('roundCeil', function () { 99 | return function (input) { 100 | return Math.ceil(input); 101 | } 102 | }); 103 | })(); -------------------------------------------------------------------------------- /src/pineapple/js/helpers.js: -------------------------------------------------------------------------------- 1 | function registerController(name, controller) { 2 | angular.module('pineapple').controllerProvider.register(name, controller); 3 | } 4 | 5 | function resizeModuleContent() { 6 | var offset = 50; 7 | var height = ((window.innerHeight > 0) ? window.innerHeight : screen.height) - 1; 8 | height = height - offset; 9 | if (height < 1) height = 1; 10 | if (height > offset) { 11 | $(".module-content").css("min-height", (height) + "px"); 12 | } 13 | } 14 | 15 | function convertMACAddress(mac) { 16 | var pattern = /([-: ])/igm; 17 | return mac.replace(pattern, ":"); 18 | } 19 | 20 | function locallyAssigned(mac) { 21 | return (parseInt('0x' + mac.split(':')[0]) & 0x02) !== 0; 22 | } 23 | 24 | function annotateMacs() { 25 | var mac_rows = $('td, .autoselect').filter( 26 | function() { 27 | return /^[0-9a-f]{1,2}([.:-])[0-9a-f]{1,2}(?:\1[0-9a-f]{1,2}){4}$/i.test(this.textContent.trim()); 28 | }); 29 | mac_rows.filter(function() { 30 | return locallyAssigned(this.textContent.trim()); 31 | }).prop('title', 'This MAC was likely locally assigned and was not assigned by the hardware vendor. This could be the result of MAC randomization, Spoofing, or a vendor that has not registered with the IEEE Registration Authority.').css('color', '#31708f'); 32 | mac_rows.filter(function() { 33 | return !locallyAssigned(this.textContent.trim()); 34 | }).prop('title', 'This MAC was likely globally assigned by the hardware vendor. It has probably not been randomized for privacy.'); 35 | } 36 | 37 | function utcDate(timestampStr) { 38 | var a = timestampStr.split(' '); 39 | var dmy = a[0].split('-'); 40 | var hms = a[1].split(':'); 41 | 42 | return new Date(Date.UTC(dmy[0], dmy[1] - 1, dmy[2], hms[0], hms[1], hms[2])); 43 | } 44 | 45 | function selectElement(elem) { 46 | var selectRange = document.createRange(); 47 | selectRange.selectNodeContents(elem); 48 | var selection = window.getSelection(); 49 | selection.removeAllRanges(); 50 | selection.addRange(selectRange); 51 | } 52 | 53 | function loadActiveTabData() { 54 | $('.tab-pane.active .controller').each(function(index) { 55 | var controller = angular.element( $(this)[0] ).scope(); 56 | if (typeof controller.reloadData === "function") { 57 | controller.reloadData(); 58 | } 59 | }); 60 | } 61 | 62 | $('html').click(function(e){ 63 | var elem = e.toElement; 64 | if (elem !== undefined && elem.classList.contains('autoselect')) { 65 | selectElement(elem); 66 | } 67 | }); 68 | 69 | $(window).resize(function() { 70 | resizeModuleContent(); 71 | }); 72 | 73 | setInterval(annotateMacs, 1500); 74 | -------------------------------------------------------------------------------- /src/pineapple/js/pineapple.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var pineapple = angular.module('pineapple', ['ngRoute', 'ngCookies']) 3 | 4 | .config(['$routeProvider', '$controllerProvider', '$compileProvider', '$filterProvider', '$provide', function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) { 5 | pineapple.controllerProvider = $controllerProvider; 6 | pineapple.compileProvider = $compileProvider; 7 | pineapple.routeProvider = $routeProvider; 8 | pineapple.filterProvider = $filterProvider; 9 | pineapple.provide = $provide; 10 | }]) 11 | 12 | .run(['$api', function($api){ 13 | window.pineapple = $api; 14 | 15 | pineapple.routeProvider 16 | .when('/modules/:moduleName', { 17 | templateUrl: function(params) { 18 | return 'modules/'+ params.moduleName +'/module.html'; 19 | }, 20 | controller: function() { 21 | resizeModuleContent(); 22 | }, 23 | resolve: { 24 | jsLoader: ['$route', function($route) { 25 | return $.getScript('modules/'+ $route.current.params.moduleName +'/js/module.js'); 26 | }] 27 | } 28 | }) 29 | .otherwise({ 30 | redirectTo: '/modules/Dashboard' 31 | }); 32 | }]) 33 | })(); 34 | -------------------------------------------------------------------------------- /src/pineapple/js/vendor/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | (function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).info({angularVersion:"1.6.10"}).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore", 3 | ["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular); 4 | -------------------------------------------------------------------------------- /src/pineapple/js/vendor/angular-route.min.js: -------------------------------------------------------------------------------- 1 | (function(J,d){'use strict';function A(d){k&&d.get("$route")}function B(t,u,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,m){function v(){l&&(g.cancel(l),l=null);n&&(n.$destroy(),n=null);p&&(l=g.leave(p),l.done(function(a){!1!==a&&(l=null)}),p=null)}function E(){var b=t.current&&t.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=t.current;p=m(b,function(b){g.enter(b,null,p||f).done(function(b){!1===b||!d.isDefined(w)||w&&!a.$eval(w)||u()}); 2 | v()});n=c.scope=b;n.$emit("$viewContentLoaded");n.$eval(k)}else v()}var n,p,l,w=b.autoscroll,k=b.onload||"";a.$on("$routeChangeSuccess",E);E()}}}function C(d,k,g){return{restrict:"ECA",priority:-400,link:function(a,f){var b=g.current,c=b.locals;f.html(c.$template);var m=d(f.contents());if(b.controller){c.$scope=a;var v=k(b.controller,c);b.controllerAs&&(a[b.controllerAs]=v);f.data("$ngControllerController",v);f.children().data("$ngControllerController",v)}a[b.resolveAs||"$resolve"]=c;m(a)}}}var x, 3 | y,F,G,z=d.module("ngRoute",[]).info({angularVersion:"1.6.10"}).provider("$route",function(){function t(a,f){return d.extend(Object.create(a),f)}function u(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},g=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(a,b,d,c){a="?"===c||"*?"===c?"?":null;c="*"===c||"*?"===c?"*":null;g.push({name:d,optional:!!a});b=b||"";return""+(a?"":b)+"(?:"+(a?b:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([/$*])/g, 4 | "\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}x=d.isArray;y=d.isObject;F=d.isDefined;G=d.noop;var g={};this.when=function(a,f){var b;b=void 0;if(x(f)){b=b||[];for(var c=0,m=f.length;c /sys/bus/usb/drivers/usb/unbind > /dev/null 2>&1 12 | sleep 2 13 | echo '$DEVICE' > /sys/bus/usb/drivers/usb/bind > /dev/null 2>&1 14 | } 15 | 16 | touch /tmp/sd_format.progress 17 | 18 | umount /sd 19 | swapoff /dev/sdcard/sd2 20 | 21 | reset_sd 22 | sleep 5 23 | 24 | sleep 2 25 | cat /pineapple/modules/Advanced/formatSD/fdisk_options | fdisk /dev/sdcard/sd 26 | sleep 2 27 | 28 | umount /sd 29 | mkfs.ext4 -F /dev/sdcard/sd1 30 | 31 | sleep 2 32 | 33 | swapoff /dev/sdcard/sd2 34 | mkfs.ext4 -F /dev/sdcard/sd2 35 | 36 | 37 | mkswap /dev/sdcard/sd2 38 | 39 | mount /dev/sdcard/sd1 /sd 40 | swapon /dev/sdcard/sd2 41 | 42 | rm /tmp/sd_format.progress -------------------------------------------------------------------------------- /src/pineapple/modules/Advanced/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Information on system resources and firmware.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Advanced", 10 | "version": "1.0", 11 | "index": 12 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Advanced/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Clients/api/module.php: -------------------------------------------------------------------------------- 1 | dbConnection = false; 11 | 12 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path"); 13 | if (file_exists($dbLocation)) { 14 | $this->dbConnection = new DatabaseConnection($dbLocation); 15 | } 16 | } 17 | 18 | public function route() 19 | { 20 | switch ($this->request->action) { 21 | case 'getClientData': 22 | $this->getClientData(); 23 | break; 24 | case 'kickClient': 25 | $this->kickClient(); 26 | break; 27 | } 28 | } 29 | 30 | private function getLeases() { 31 | $dhcpReport = array(); 32 | $leases = explode("\n", @file_get_contents('/var/dhcp.leases')); 33 | if ($leases) { 34 | foreach ($leases as $lease) { 35 | $dhcpReport[explode(' ', $lease)[1]] = array_slice(explode(' ', $lease), 2, 2); 36 | } 37 | } 38 | return $dhcpReport; 39 | } 40 | 41 | private function getARPData() { 42 | $arpReport = array(); 43 | exec('cat /proc/net/arp | awk \'{ if ($1 != "IP") {printf "%s %s\n", $1, $4;}}\'', $arpEntries); 44 | foreach ($arpEntries as $arpEntry) { 45 | $arpEntryArray = explode(' ', $arpEntry); 46 | $arpReport[$arpEntryArray[1]] = $arpEntryArray[0]; 47 | } 48 | return $arpReport; 49 | } 50 | 51 | private function getSSIDData() 52 | { 53 | $ssidData = array(); 54 | $clientRows = $this->dbConnection->query("SELECT DISTINCT mac,ssid FROM log WHERE log_type=1 ORDER BY updated_at ASC;"); 55 | foreach ($clientRows as $row) { 56 | $ssidData[strtolower($row['mac'])] = $row['ssid']; 57 | } 58 | return $ssidData; 59 | } 60 | 61 | private function getStations() { 62 | $stationsReport = array(); 63 | exec(' 64 | iw dev wlan0 station dump | 65 | awk \'{ if ($1 == "Station") { printf "%s ", $2; } else if ($1 == "inactive") {print $3;} }\' 66 | ', $stations); 67 | foreach ($stations as $key => $station) { 68 | if (empty($station)) { 69 | continue; 70 | } 71 | $stationArray = explode(' ', $station); 72 | $stationsReport[$stationArray[0]] = $stationArray[1]; 73 | } 74 | exec(' 75 | iw dev wlan0-2 station dump | 76 | awk \'{ if ($1 == "Station") { printf "%s ", $2; } else if ($1 == "inactive") {print $3;} }\' 77 | ', $stations); 78 | foreach ($stations as $key => $station) { 79 | if (empty($station)) { 80 | continue; 81 | } 82 | $stationArray = explode(' ', $station); 83 | $stationsReport[$stationArray[0]] = $stationArray[1]; 84 | } 85 | return $stationsReport; 86 | } 87 | 88 | private function getClientData() 89 | { 90 | $connectedClients = array(); 91 | $stationData = $this->getStations(); 92 | $dhcpData = $this->getLeases(); 93 | $arpData = $this->getARPData(); 94 | $ssidData = $this->getSSIDData(); 95 | foreach ($stationData as $mac => $signal) { 96 | $connectedClients[] = [ 97 | 'mac' => $mac, 98 | 'ip' => $arpData[$mac], 99 | 'ssid' => $ssidData[$mac], 100 | 'host' => $dhcpData[$mac][1], 101 | ]; 102 | } 103 | $this->response = array( 104 | 'clients' => $connectedClients 105 | ); 106 | } 107 | 108 | private function kickClient() 109 | { 110 | exec("hostapd_cli -i wlan0 deauthenticate {$this->request->mac}"); 111 | exec("hostapd_cli -i wlan0 disassociate {$this->request->mac}"); 112 | exec("hostapd_cli -i wlan0-2 deauthenticate {$this->request->mac}"); 113 | exec("hostapd_cli -i wlan0-2 disassociate {$this->request->mac}"); 114 | $this->response = array('success' => true); 115 | } 116 | } -------------------------------------------------------------------------------- /src/pineapple/modules/Clients/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("ClientsController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout){ 2 | $scope.clients = []; 3 | $scope.loading = false; 4 | 5 | $scope.getClientData = function(){ 6 | $scope.loading = true; 7 | $api.request({ 8 | module: "Clients", 9 | action: "getClientData" 10 | }, function(response) { 11 | $scope.loading = false; 12 | $scope.parseClients(response.clients); 13 | }); 14 | }; 15 | 16 | $scope.parseClients = function($clients) { 17 | $scope.clients = $clients; 18 | }; 19 | 20 | $scope.kickClient = function(client){ 21 | $api.request({ 22 | module: "Clients", 23 | action: "kickClient", 24 | mac: client.mac 25 | }, function(){ 26 | client['kicking'] = true; 27 | $timeout(function() { 28 | client['kicking'] = false; 29 | $scope.getClientData(); 30 | }, 3000); 31 | }); 32 | }; 33 | 34 | $scope.getClientData(); 35 | }]); -------------------------------------------------------------------------------- /src/pineapple/modules/Clients/module.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Clients

6 |
7 |
8 |

9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 32 | 38 | 43 | 47 | 48 | 49 |
MAC AddressIP AddressSSIDHostnameKick Client
24 | 25 | {{ client['mac'] }} 26 | 28 | 29 | {{ client['ip'] == null ? "No IP" : client['ip'] }} 30 | 31 | 33 | 34 | 35 | {{ client['ssid'] == null ? 'No SSID' : client['ssid'] }} 36 | 37 | 39 | 40 | {{ client['host'] == null ? 'No Hostname' : client['host'] }} 41 | 42 | 44 | 45 | 46 |
50 |
51 |
52 | No clients found. 53 |
54 |
55 |
56 |
-------------------------------------------------------------------------------- /src/pineapple/modules/Clients/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "View and manage connected clients.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Clients", 10 | "version": "1.0", 11 | "index": 3 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Clients/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Configuration/api/landingpage_index.php: -------------------------------------------------------------------------------- 1 | exec('CREATE TABLE IF NOT EXISTS user_agents (browser TEXT NOT NULL);'); 11 | $statement = $sqlite->prepare('INSERT INTO user_agents (browser) VALUES(:browser);'); 12 | $statement->bindValue(':browser', $browser, SQLITE3_TEXT); 13 | try { 14 | $ret = $statement->execute(); 15 | } catch (Exception $e) { 16 | return false; 17 | } 18 | return $ret; 19 | } 20 | 21 | function identifyUserAgent($userAgent) 22 | { 23 | if (preg_match('/^Mozilla/', $userAgent)) { 24 | if (preg_match('/Chrome\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) { 25 | increment_browser('chrome'); 26 | } elseif (preg_match('/Safari\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) { 27 | increment_browser('safari'); 28 | } elseif (strpos($userAgent, 'MSIE ') || preg_match('/Trident\/[0-9]/', $userAgent)) { 29 | increment_browser('internet_explorer'); 30 | } elseif (preg_match('/Firefox\/[0-9]{1,3}\.[0-9]{1,3}/', $userAgent)) { 31 | increment_browser('firefox'); 32 | } else { 33 | increment_browser('other'); 34 | } 35 | } elseif (preg_match('/^Opera\/[0-9]{1,3}\.[0-9]/', $userAgent)) { 36 | increment_browser('opera'); 37 | } else { 38 | increment_browser('other'); 39 | } 40 | } 41 | 42 | identifyUserAgent($_SERVER['HTTP_USER_AGENT']); 43 | 44 | require_once('/etc/pineapple/landingpage.php'); 45 | -------------------------------------------------------------------------------- /src/pineapple/modules/Configuration/api/module.php: -------------------------------------------------------------------------------- 1 | request->action) { 8 | case 'getCurrentTimeZone': 9 | $this->getCurrentTimeZone(); 10 | break; 11 | case 'getLandingPageData': 12 | $this->getLandingPageData(); 13 | break; 14 | case 'saveLandingPage': 15 | $this->saveLandingPageData(); 16 | break; 17 | case 'changePass': 18 | $this->changePass(); 19 | break; 20 | case 'changeTimeZone': 21 | $this->changeTimeZone(); 22 | break; 23 | case 'resetPineapple': 24 | $this->resetPineapple(); 25 | break; 26 | case 'haltPineapple': 27 | $this->haltPineapple(); 28 | break; 29 | case 'rebootPineapple': 30 | $this->rebootPineapple(); 31 | break; 32 | case 'getLandingPageStatus': 33 | $this->getLandingPageStatus(); 34 | break; 35 | case 'getAutoStartStatus': 36 | $this->getAutoStartStatus(); 37 | break; 38 | case 'enableLandingPage': 39 | $this->enableLandingPage(); 40 | break; 41 | case 'disableLandingPage': 42 | $this->disableLandingPage(); 43 | break; 44 | case 'enableAutoStart': 45 | $this->enableAutoStart(); 46 | break; 47 | case 'disableAutoStart': 48 | $this->disableAutoStart(); 49 | break; 50 | case 'getButtonScript': 51 | $this->getButtonScript(); 52 | break; 53 | case 'saveButtonScript': 54 | $this->saveButtonScript(); 55 | break; 56 | case 'getDevice': 57 | $this->getDeviceName(); 58 | break; 59 | case 'getDeviceConfig': 60 | $this->getDeviceConfigArray(); 61 | break; 62 | } 63 | } 64 | 65 | private function haltPineapple() 66 | { 67 | $this->execBackground("sync && led all off && halt"); 68 | $this->response = array("success" => true); 69 | } 70 | 71 | private function rebootPineapple() 72 | { 73 | $this->execBackground("reboot"); 74 | $this->response = array("success" => true); 75 | } 76 | 77 | private function resetPineapple() 78 | { 79 | $this->execBackground("jffs2reset -y && reboot &"); 80 | $this->response = array("success" => true); 81 | } 82 | 83 | private function getCurrentTimeZone() 84 | { 85 | $currentTimeZone = exec('date +%Z%z'); 86 | $this->response = array("currentTimeZone" => $currentTimeZone); 87 | } 88 | 89 | private function changeTimeZone() 90 | { 91 | $timeZone = $this->request->timeZone; 92 | file_put_contents('/etc/TZ', $timeZone); 93 | $this->uciSet('system.@system[0].timezone', $timeZone); 94 | $this->response = array("success" => true); 95 | } 96 | 97 | private function getLandingPageData() 98 | { 99 | $landingPage = file_get_contents('/etc/pineapple/landingpage.php'); 100 | $this->response = array("landingPage" => $landingPage); 101 | } 102 | 103 | private function getLandingPageStatus() 104 | { 105 | if (!empty(exec("iptables -L -vt nat | grep 'www to:.*:80'"))) { 106 | $this->response = array("enabled" => true); 107 | return; 108 | } 109 | $this->response = array("enabled" => false); 110 | } 111 | 112 | private function enableLandingPage() 113 | { 114 | exec('iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination $(uci get network.lan.ipaddr):80'); 115 | exec('iptables -t nat -A POSTROUTING -j MASQUERADE'); 116 | copy('/pineapple/modules/Configuration/api/landingpage_index.php', '/www/index.php'); 117 | $this->response = array("success" => true); 118 | } 119 | 120 | private function disableLandingPage() 121 | { 122 | @unlink('/www/index.php'); 123 | exec('iptables -t nat -D PREROUTING -p tcp --dport 80 -j DNAT --to-destination $(uci get network.lan.ipaddr):80'); 124 | $this->response = array("success" => true); 125 | } 126 | 127 | private function getAutoStartStatus() 128 | { 129 | if($this->uciGet("landingpage.@settings[0].autostart") == 1) { 130 | $this->response = array("enabled" => true); 131 | } else { 132 | $this->response = array("enabled" => false); 133 | } 134 | } 135 | 136 | private function enableAutoStart() 137 | { 138 | $this->uciSet("landingpage.@settings[0].autostart", "1"); 139 | $this->response = array("success" => true); 140 | } 141 | 142 | private function disableAutoStart() 143 | { 144 | $this->uciSet("landingpage.@settings[0].autostart", "0"); 145 | $this->response = array("success" => true); 146 | } 147 | 148 | private function saveLandingPageData() 149 | { 150 | if (file_put_contents('/etc/pineapple/landingpage.php', $this->request->landingPageData) !== false) { 151 | $this->response = array("success" => true); 152 | } else { 153 | $this->error = "Error saving Landing Page."; 154 | } 155 | } 156 | 157 | private function getButtonScript() 158 | { 159 | if (file_exists('/etc/pineapple/button_script')) { 160 | $script = file_get_contents('/etc/pineapple/button_script'); 161 | $this->response = array("buttonScript" => $script); 162 | } else { 163 | $this->error = "The button script does not exist."; 164 | } 165 | } 166 | 167 | private function saveButtonScript() 168 | { 169 | if (file_exists('/etc/pineapple/button_script')) { 170 | file_put_contents('/etc/pineapple/button_script', $this->request->buttonScript); 171 | $this->response = array("success" => true); 172 | } else { 173 | $this->error = "The button script does not exist."; 174 | } 175 | } 176 | 177 | private function getDeviceName() 178 | { 179 | $this->response = array("device" => $this->getDevice()); 180 | } 181 | 182 | private function getDeviceConfigArray() 183 | { 184 | $this->response = array("config" => $this->getDeviceConfig()); 185 | } 186 | 187 | protected function changePass() 188 | { 189 | if ($this->request->newPassword === $this->request->newPasswordRepeat) { 190 | if (parent::changePassword($this->request->oldPassword, $this->request->newPassword) === true) { 191 | $this->response = array("success" => true); 192 | return; 193 | } 194 | } 195 | 196 | $this->response = array("success" => false); 197 | } 198 | } -------------------------------------------------------------------------------- /src/pineapple/modules/Configuration/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Manage general settings and the landing page.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Configuration", 10 | "version": "1.0", 11 | "index": 11 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Configuration/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Dashboard/api/module.php: -------------------------------------------------------------------------------- 1 | dbConnection = false; 12 | if (file_exists('/tmp/landingpage.db')) { 13 | $this->dbConnection = new DatabaseConnection('/tmp/landingpage.db'); 14 | } 15 | } 16 | 17 | public function route() 18 | { 19 | switch ($this->request->action) { 20 | case 'getOverviewData': 21 | $this->getOverviewData(); 22 | break; 23 | 24 | case 'getLandingPageData': 25 | $this->getLandingPageData(); 26 | break; 27 | 28 | case 'getBulletins': 29 | $this->getBulletins(); 30 | break; 31 | } 32 | } 33 | 34 | private function getOverviewData() 35 | { 36 | $this->response = [ 37 | "cpu" => $this->getCpu(), 38 | "uptime" => $this->getUptime(), 39 | "clients" => $this->getClients() 40 | ]; 41 | } 42 | 43 | private function getCpu() 44 | { 45 | $loads = sys_getloadavg(); 46 | $load = round($loads[0]/2*100, 1); 47 | 48 | if ($load > 100) { 49 | return '100'; 50 | } 51 | 52 | return $load; 53 | } 54 | 55 | private function getUptime() 56 | { 57 | $seconds = intval(explode('.', file_get_contents('/proc/uptime'))[0]); 58 | $days = floor($seconds / (24 * 60 * 60)); 59 | $hours = floor(($seconds % (24 * 60 * 60)) / (60 * 60)); 60 | if ($days > 0) { 61 | return $days . ($days == 1 ? " day, " : " days, ") . $hours . ($hours == 1 ? " hour" : " hours"); 62 | } 63 | $minutes = floor(($seconds % (60 * 60)) / 60); 64 | return $hours . ($hours == 1 ? " hour, " : " hours, ") . $minutes . ($minutes == 1 ? " minute" : " minutes"); 65 | } 66 | 67 | private function getClients() 68 | { 69 | return exec('iw dev wlan0 station dump | grep Station | wc -l'); 70 | } 71 | 72 | private function getLandingPageData() 73 | { 74 | if ($this->dbConnection !== false) { 75 | $stats = []; 76 | $stats['Chrome'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'chrome\';')); 77 | $stats['Safari'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'safari\';')); 78 | $stats['Firefox'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'firefox\';')); 79 | $stats['Opera'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'opera\';')); 80 | $stats['Internet Explorer'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'internet_explorer\';')); 81 | $stats['Other'] = count($this->dbConnection->query('SELECT browser FROM user_agents WHERE browser=\'other\';')); 82 | $this->response = $stats; 83 | } else { 84 | $this->error = "A connection to the database is not established."; 85 | } 86 | } 87 | 88 | 89 | private function getBulletins() 90 | { 91 | $bulletinData = @$this->fileGetContentsSSL(self::REMOTE_URL . "/json/news.json"); 92 | if ($bulletinData !== false) { 93 | $this->response = json_decode($bulletinData); 94 | if (json_last_error() === JSON_ERROR_NONE) { 95 | return; 96 | } 97 | } 98 | 99 | $this->error = "Error connecting to " . self::REMOTE_NAME . ". Please check your connection."; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/pineapple/modules/Dashboard/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("DashboardOverviewController", ['$api', '$scope', '$interval', function($api, $scope, $interval) { 2 | $scope.cpu = ""; 3 | $scope.uptime = ""; 4 | $scope.clients = ""; 5 | $scope.ssids = ""; 6 | $scope.newssids = ""; 7 | 8 | $scope.populateDashboard = (function() { 9 | $api.request({ 10 | module: "Dashboard", 11 | action: "getOverviewData" 12 | }, function(response) { 13 | $scope.cpu = response.cpu; 14 | $scope.uptime = response.uptime; 15 | $scope.clients = response.clients; 16 | }); 17 | $api.request({ 18 | module: "PineAP", 19 | action: "countSSIDs" 20 | }, function(response) { 21 | $scope.ssids = response.SSIDs; 22 | $scope.newssids = response.newSSIDs; 23 | }); 24 | }); 25 | 26 | $scope.populateInterval = $interval(function(){ 27 | $scope.populateDashboard(); 28 | }, 10000); 29 | 30 | $scope.populateDashboard(); 31 | $scope.$on('$destroy', function() { 32 | $interval.cancel($scope.populateInterval); 33 | }); 34 | }]); 35 | 36 | registerController("DashboardLandingPageController", ['$api', '$scope', function($api, $scope){ 37 | $scope.browsers = []; 38 | 39 | $api.request({ 40 | module: "Dashboard", 41 | action: "getLandingPageData" 42 | }, function(response){ 43 | if (response.error === undefined) { 44 | $scope.browsers = response; 45 | } 46 | }); 47 | }]); 48 | 49 | registerController("DashboardBulletinsController", ['$api', '$scope', function($api, $scope){ 50 | $scope.bulletins = []; 51 | 52 | $scope.getBulletins = function() { 53 | $scope.loading = true; 54 | 55 | $api.request({ 56 | module: "Dashboard", 57 | action: "getBulletins" 58 | }, function(response){ 59 | $scope.loading = false; 60 | if (response.error !== undefined) { 61 | $scope.error = response.error; 62 | } else { 63 | $scope.bulletins = response; 64 | $scope.error = false; 65 | } 66 | }); 67 | } 68 | }]); -------------------------------------------------------------------------------- /src/pineapple/modules/Dashboard/module.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

{{ uptime }}

7 |

UPTIME

8 |
9 | 12 |
13 |
14 | 15 |
16 |
17 | 23 | 26 |
27 |
28 | 29 |
30 |
31 | 37 | 40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 |

Landing Page Browser Stats

51 |
52 | 53 | 54 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 86 | 87 | 88 | 89 | 92 | 93 | 94 |
55 | chrome 56 | Chrome 57 | {{ browsers['Chrome'] }}
62 | Firefox 63 | Firefox 64 | {{ browsers['Firefox'] }}
69 | MSIE 70 | Internet Explorer 71 | {{ browsers['Internet Explorer'] }}
76 | opera 77 | Opera 78 | {{ browsers['Opera'] }}
83 | Safari 84 | Safari 85 | {{ browsers['Safari'] }}
90 | Other 91 | {{ browsers['Other'] }}
95 | 96 |
97 | No Landing Page Browser Stats Available 98 |
99 |
100 | 101 |
102 |
103 |

News

104 |
105 |
106 |
107 |
{{ bulletin.title }}{{ bulletin.date }}
108 |
109 |
110 |
111 | 112 |
113 |

114 | 115 |

116 |

117 | 120 |

121 |

122 | {{ error }} 123 |

124 |
125 | 126 |
127 |
128 |
129 | 130 |
131 |
132 |
133 |

134 | Notifications 135 | 136 | 139 | 140 |

141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 |
{{ notification.message }}{{ notification.time | timesincedate }}
150 | 151 |
152 | No Notifications 153 |
154 |
155 |
156 | 157 |
158 | -------------------------------------------------------------------------------- /src/pineapple/modules/Dashboard/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "At-a-glance view of the WiFi Pineapple.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Dashboard", 10 | "version": "1.0", 11 | "index": 1 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Dashboard/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Filters/js/module.js: -------------------------------------------------------------------------------- 1 | registerController('clientFilterController', ['$api', '$scope', function($api, $scope) { 2 | $scope.mode = ''; 3 | $scope.mac = ''; 4 | $scope.clientFilters = ''; 5 | 6 | $scope.clearAll = (function() { 7 | $api.request({ 8 | module: "Filters", 9 | action: "removeClients", 10 | clients: $scope.clientFilters.split("\n") 11 | }, function(response) { 12 | $scope.clientFilters = response.clientFilters; 13 | }); 14 | }); 15 | 16 | $scope.toggleMode = (function() { 17 | if ($scope.mode === 'Allow') { 18 | $scope.mode = 'Deny'; 19 | } else { 20 | $scope.mode = 'Allow'; 21 | } 22 | $api.request({ 23 | module: 'Filters', 24 | action: 'toggleClientMode', 25 | mode: $scope.mode 26 | }); 27 | }); 28 | 29 | $scope.addClient = (function() { 30 | $api.request({ 31 | module: 'Filters', 32 | action: 'addClient', 33 | mac: convertMACAddress($scope.mac) 34 | }, function(response) { 35 | if (response.error === undefined) { 36 | $scope.clientFilters = response.clientFilters; 37 | $scope.mac = ""; 38 | } 39 | }); 40 | }); 41 | 42 | $scope.removeClient = (function() { 43 | $api.request({ 44 | module: 'Filters', 45 | action: 'removeClient', 46 | mac: convertMACAddress($scope.mac) 47 | }, function(response) { 48 | if (response.error === undefined) { 49 | $scope.clientFilters = response.clientFilters; 50 | $scope.mac = ""; 51 | } 52 | }); 53 | }); 54 | 55 | $api.request({ 56 | module: 'Filters', 57 | action: 'getClientData' 58 | }, function(response) { 59 | if (response.error === undefined) { 60 | $scope.mode = response.mode; 61 | $scope.clientFilters = response.clientFilters; 62 | } 63 | }); 64 | }]); 65 | 66 | registerController('ssidFilterController', ['$api', '$scope', function($api, $scope) { 67 | $scope.mode = ''; 68 | $scope.ssid = ''; 69 | $scope.ssidFilters = ''; 70 | 71 | $scope.clearAll = (function() { 72 | $api.request({ 73 | module: "Filters", 74 | action: "removeSSIDs", 75 | ssids: $scope.ssidFilters.split("\n") 76 | }, function(response) { 77 | $scope.ssidFilters = response.ssidFilters; 78 | }); 79 | }); 80 | 81 | $scope.toggleMode = (function() { 82 | if ($scope.mode === 'Allow') { 83 | $scope.mode = 'Deny'; 84 | } else { 85 | $scope.mode = 'Allow'; 86 | } 87 | $api.request({ 88 | module: 'Filters', 89 | action: 'toggleSSIDMode', 90 | mode: $scope.mode 91 | }); 92 | }); 93 | 94 | $scope.addSSID = (function() { 95 | $api.request({ 96 | module: 'Filters', 97 | action: 'addSSID', 98 | ssid: $scope.ssid 99 | }, function(response) { 100 | if (response.error === undefined) { 101 | $scope.ssidFilters = response.ssidFilters; 102 | $scope.ssid = ""; 103 | } 104 | }); 105 | }); 106 | 107 | $scope.removeSSID = (function() { 108 | $api.request({ 109 | module: 'Filters', 110 | action: 'removeSSID', 111 | ssid: $scope.ssid 112 | }, function(response) { 113 | if (response.error === undefined) { 114 | $scope.ssidFilters = response.ssidFilters; 115 | $scope.ssid = ""; 116 | } 117 | }); 118 | }); 119 | 120 | $api.request({ 121 | module: 'Filters', 122 | action: 'getSSIDData' 123 | }, function(response) { 124 | if (response.error === undefined) { 125 | $scope.mode = response.mode; 126 | $scope.ssidFilters = response.ssidFilters; 127 | } 128 | }); 129 | }]); 130 | 131 | function getClientLineNumber(textarea) { 132 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length; 133 | var mac = textarea.value.split("\n")[lineNumber-1].trim(); 134 | $("input[name='mac']").val(mac).trigger('input'); 135 | } 136 | 137 | function getSSIDLineNumber(textarea) { 138 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length; 139 | var ssid = textarea.value.split("\n")[lineNumber-1].trim(); 140 | $("input[name='ssid']").val(ssid).trigger('input'); 141 | } -------------------------------------------------------------------------------- /src/pineapple/modules/Filters/module.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

6 | Client Filtering 7 | 8 | 11 | 15 | 16 |

17 |
18 |
19 |
20 | {{ mode }} Listed MAC(s) 21 | 22 | 23 | 24 |
25 |
26 |

27 | 29 |

30 |
31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |
39 |

Client filters specify which devices, by MAC address, are either explicitly allowed to 40 | connect or 41 | explicitly denied from connecting. In Allow Mode only the listed MAC Addresses are allowed to 42 | connect. In Deny Mode, the listed MAC addresses will be prevented from connecting.

43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |

52 | SSID Filtering 53 | 54 | 57 | 61 | 62 |

63 |
64 |
65 |
66 | {{ mode }} Listed SSID(s) 67 | 68 | 69 | 70 |
71 |
72 |

73 | 75 |

76 |
77 | 78 | 79 | 80 | 81 | 82 |
83 |
84 |
85 |

SSID filters specify the network names to which the WiFi Pineapple will respond. In Allow Mode, 86 | devices will only be allowed to associate with the WiFi Pineapple for SSID names listed. In Deny 87 | Mode, devices will be prevented from associating with the WiFi Pineapple for the listed SSID 88 | names.

89 |
90 |
91 |
92 |
93 |
-------------------------------------------------------------------------------- /src/pineapple/modules/Filters/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Filters", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Filters", 10 | "version": "1.0", 11 | "index": 5 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Filters/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Help/api/module.php: -------------------------------------------------------------------------------- 1 | request->action) { 8 | case 'generateDebugFile': 9 | $this->generateDebugFile(); 10 | break; 11 | 12 | case 'downloadDebugFile': 13 | $this->downloadDebugFile(); 14 | break; 15 | 16 | case 'getConsoleOutput': 17 | $this->getConsoleOutput(); 18 | break; 19 | } 20 | } 21 | 22 | private function generateDebugFile() 23 | { 24 | @unlink('/tmp/debug.log'); 25 | $this->execBackground("(/pineapple/modules/Help/files/debug 2>&1) > /tmp/debug_generation_output"); 26 | $this->response = array("success" => true); 27 | } 28 | 29 | private function downloadDebugFile() 30 | { 31 | if (!file_exists('/tmp/debug.log')) { 32 | $this->error = "The debug file is missing."; 33 | return; 34 | } 35 | $this->response = array("success" => true, "downloadToken" => $this->downloadFile("/tmp/debug.log")); 36 | } 37 | 38 | private function getConsoleOutput() 39 | { 40 | $output = ""; 41 | if (file_exists("/tmp/debug_generation_output")) { 42 | $output = file_get_contents("/tmp/debug_generation_output"); 43 | } 44 | $this->response = array("output" => $output); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pineapple/modules/Help/files/debug: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Simple script to gather log information 3 | LOG=/tmp/debug.log.tmp 4 | 5 | touch $LOG 6 | echo "Retrieving /proc/cpuinfo" 7 | echo "============CPUINFO========" > $LOG 8 | cat /proc/cpuinfo >> $LOG 9 | echo "Retrieving firmware version" 10 | echo -n "Firmware Version: " >> $LOG && cat /pineapple/pineapple_version >> $LOG 11 | echo "Retrieving lsusb output" 12 | echo "=============LSUSB=========" >> $LOG 13 | lsusb >> $LOG 14 | echo "Retrieving disk usage information" 15 | echo "==============DF===========" >> $LOG 16 | df -h >> $LOG 17 | echo "Retrieving iw device list" 18 | echo "==============IW===========" >> $LOG 19 | iw dev >> $LOG 20 | echo "Retrieving ifconfig interface list" 21 | echo "===========IFCONFIG========" >> $LOG 22 | ifconfig -a >> $LOG 23 | echo "Retrieving iwconfig device list" 24 | echo "===========IWCONFIG========" >> $LOG 25 | (iwconfig 2>&1) >> $LOG 26 | echo "Retrieving dmesg log" 27 | echo "============DMESG==========" >> $LOG 28 | dmesg >> $LOG 29 | echo "Retrieving syslog" 30 | echo "============LOGREAD========" >> $LOG 31 | logread >> $LOG 32 | echo "Retrieving /etc/config/wireless" 33 | echo "===== WIRELESS CONFIG======" >> $LOG 34 | cat /etc/config/wireless >> $LOG 35 | echo "Performing site survey" 36 | echo "============SURVEY=========" >> $LOG 37 | echo -e "\tEnsuring pineapd is started" 38 | (/etc/init.d/pineapd start 2>&1) >> $LOG 39 | scan_type=0 40 | cat /pineapple/config.php | grep "'tetra'" && scan_type=2 41 | echo -e "\tRunning scan type $scan_type for 15 seconds" 42 | (/usr/bin/pineap /tmp/pineap.conf run_scan 15 $scan_type 2>&1) >> $LOG 43 | sleep 2 44 | scan_id="$(/usr/bin/pineap /tmp/pineap.conf get_status | grep scanID | awk '{print $2}' | sed 's/,//')" 45 | echo -e "\tNew scan id: $scan_id" 46 | echo -e "\tWaiting for scan to finish" 47 | while [ "$(/usr/bin/pineap /tmp/pineap.conf get_status | grep 'scanRunning' | awk '{print $2}' | sed 's/,//')" == "true" ]; do sleep 1; done 48 | echo -e "\tRetrieving scan results, appending to debug log" 49 | chmod a+x /pineapple/modules/Help/files/dumpscan.php 50 | /pineapple/modules/Help/files/dumpscan.php $scan_id >> $LOG 51 | echo "Renaming debug file" 52 | mv $LOG /tmp/debug.log 53 | echo "Completed Debug Filed Generation" 54 | logger "Completed Debug File Generation" -------------------------------------------------------------------------------- /src/pineapple/modules/Help/files/dumpscan.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php-cgi -q 2 | error['databaseConnectionError'])) { 91 | exit($dbConnection->strError() . "\n"); 92 | } 93 | 94 | $data = array(); 95 | $data[$scanID] = array(); 96 | $aps = $dbConnection->query("SELECT scan_id, ssid, bssid, encryption, hidden, channel, signal, wps, last_seen FROM aps WHERE scan_id='%d';", $scanID); 97 | foreach ($aps as $ap_row) { 98 | $data[$scanID]['aps'][$ap_row['bssid']] = array(); 99 | $data[$scanID]['aps'][$ap_row['bssid']]['ssid'] = $ap_row['ssid']; 100 | $data[$scanID]['aps'][$ap_row['bssid']]['encryption'] = printEncryption($ap_row['encryption']); 101 | $data[$scanID]['aps'][$ap_row['bssid']]['hidden'] = $ap_row['hidden']; 102 | $data[$scanID]['aps'][$ap_row['bssid']]['channel'] = $ap_row['channel']; 103 | $data[$scanID]['aps'][$ap_row['bssid']]['signal'] = $ap_row['signal']; 104 | $data[$scanID]['aps'][$ap_row['bssid']]['wps'] = $ap_row['wps']; 105 | $data[$scanID]['aps'][$ap_row['bssid']]['last_seen'] = $ap_row['last_seen']; 106 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'] = array(); 107 | $clients = $dbConnection->query("SELECT scan_id, mac, bssid, last_seen FROM clients WHERE scan_id='%d' AND bssid='%s';", $ap_row['scan_id'], $ap_row['bssid']); 108 | foreach ($clients as $client_row) { 109 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']] = array(); 110 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']]['bssid'] = $client_row['bssid']; 111 | $data[$scanID]['aps'][$ap_row['bssid']]['clients'][$client_row['mac']]['last_seen'] = $client_row['last_seen']; 112 | } 113 | } 114 | 115 | $data[$scanID]['outOfRangeClients'] = array(); 116 | $clients = $dbConnection->query(" 117 | SELECT t1.mac, t1.bssid, t1.last_seen FROM clients t1 118 | LEFT JOIN aps t2 ON 119 | t2.bssid = t1.bssid WHERE t2.bssid IS NULL AND 120 | t1.bssid != 'FF:FF:FF:FF:FF:FF' COLLATE NOCASE AND t1.scan_id='%d'; 121 | ", $scanID); 122 | 123 | foreach ($clients as $client_row) { 124 | $data[$scanID]['outOfRangeClients'][$client_row['mac']] = array(); 125 | $data[$scanID]['outOfRangeClients'][$client_row['mac']] = $client_row['bssid']; 126 | } 127 | 128 | $data[$scanID]['unassociatedClients'] = array(); 129 | $clients = $dbConnection->query("SELECT mac FROM clients WHERE bssid='FF:FF:FF:FF:FF:FF' COLLATE NOCASE;"); 130 | 131 | foreach ($clients as $client_row) { 132 | $data[$scanID]['unassociatedClients'][] = $client_row['mac']; 133 | } 134 | 135 | file_put_contents("php://stdout", json_encode($data, JSON_PRETTY_PRINT)); -------------------------------------------------------------------------------- /src/pineapple/modules/Help/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("DebugController", ['$api', '$scope', '$timeout', '$interval', function($api, $scope, $timeout, $interval){ 2 | $scope.loading = false; 3 | $scope.debugStarted = false; 4 | $scope.output = ""; 5 | $scope.getOutputInterval = null; 6 | 7 | $scope.generateDebugFile = (function(){ 8 | $api.request({ 9 | module: "Help", 10 | action: "generateDebugFile" 11 | }, function(response) { 12 | if (response.success === true) { 13 | $scope.loading = true; 14 | $scope.debugStarted = true; 15 | $timeout($scope.downloadDebugFile, 15000); 16 | if ($scope.getOutputInterval === null) { 17 | $scope.getOutputInterval = $interval($scope.getOutput, 700); 18 | } 19 | } 20 | }) 21 | }); 22 | 23 | $scope.getOutput = (function() { 24 | $api.request({ 25 | module: "Help", 26 | action: "getConsoleOutput" 27 | }, function(response) { 28 | $scope.output = response.output; 29 | var el = $("#output"); 30 | if (el.length) { 31 | el.scrollTop(el[0].scrollHeight - el.height()) 32 | } 33 | }); 34 | }); 35 | 36 | $scope.tryAgainSoon = (function(){ 37 | $timeout($scope.downloadDebugFile, 2000); 38 | }); 39 | 40 | $scope.downloadDebugFile = (function(){ 41 | $api.request({ 42 | module: "Help", 43 | action: "downloadDebugFile" 44 | }, function(response) { 45 | if (response.success === true) { 46 | $scope.loading = false; 47 | window.location = '/api/?download=' + response.downloadToken; 48 | $interval.cancel($scope.getOutputInterval); 49 | } else { 50 | $scope.tryAgainSoon(); 51 | } 52 | }) 53 | }); 54 | }]); 55 | registerController("HelpController", ['$api', '$scope', function($api, $scope) { 56 | $scope.device = ""; 57 | 58 | $api.onDeviceIdentified(function(device, scope) { 59 | scope.device = device; 60 | }, $scope); 61 | }]); -------------------------------------------------------------------------------- /src/pineapple/modules/Help/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Information and debug log generation for the WiFi Pineapple.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Help", 10 | "version": "1.1", 11 | "index": 14 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Help/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Logging/api/module.php: -------------------------------------------------------------------------------- 1 | request->action) { 8 | case 'getSyslog': 9 | $this->getSyslog(); 10 | break; 11 | 12 | case 'getDmesg': 13 | $this->getDmesg(); 14 | break; 15 | 16 | case 'getReportingLog': 17 | $this->getReportingLog(); 18 | break; 19 | 20 | case 'getPineapLog': 21 | $this->getPineapLog(); 22 | break; 23 | 24 | case 'clearPineapLog': 25 | $this->clearPineapLog(); 26 | break; 27 | 28 | case 'getPineapLogLocation': 29 | $this->getPineapLogLocation(); 30 | break; 31 | 32 | case 'setPineapLogLocation': 33 | $this->setPineapLogLocation(); 34 | break; 35 | 36 | case 'downloadPineapLog': 37 | $this->downloadPineapLog(); 38 | break; 39 | } 40 | } 41 | 42 | private function downloadPineapLog() 43 | { 44 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path"); 45 | $db = new DatabaseConnection($dbLocation); 46 | $rows = $db->query("SELECT * FROM log ORDER BY updated_at ASC;"); 47 | $logFile = fopen("/tmp/pineap.log", 'w'); 48 | $count = "-"; 49 | foreach ($rows as $row) { 50 | switch ($row['log_type']) { 51 | case 0: 52 | $type = "Probe Request"; 53 | $count = $row['dups']; 54 | break; 55 | case 1: 56 | $type = "Association"; 57 | break; 58 | case 2: 59 | $type = "De-association"; 60 | break; 61 | default: 62 | $type = ""; 63 | break; 64 | } 65 | fwrite($logFile, "${row['created_at']},\t${type},\t${row['mac']},\t${row['ssid']},\t${count}\n"); 66 | } 67 | fclose($logFile); 68 | $this->response = array("download" => $this->downloadFile('/tmp/pineap.log')); 69 | } 70 | 71 | private function getSyslog() 72 | { 73 | exec("logread", $syslogOutput); 74 | $this->response = implode("\n", $syslogOutput); 75 | } 76 | 77 | private function getDmesg() 78 | { 79 | exec("dmesg", $dmesgOutput); 80 | $this->response = implode("\n", $dmesgOutput); 81 | } 82 | 83 | private function getReportingLog() 84 | { 85 | touch('/tmp/reporting.log'); 86 | $this->streamFunction = function () { 87 | $fp = fopen('/tmp/reporting.log', 'r'); 88 | while (($buf = fgets($fp)) !== false) { 89 | echo $buf; 90 | } 91 | fclose($fp); 92 | }; 93 | } 94 | 95 | private function getPineapLog() 96 | { 97 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path"); 98 | $db = new DatabaseConnection($dbLocation); 99 | $rows = $db->query("SELECT * FROM log ORDER BY updated_at DESC;"); 100 | $this->response = array("pineap_log" => $rows); 101 | } 102 | 103 | private function clearPineapLog() 104 | { 105 | $dbLocation = $this->uciGet("pineap.@config[0].hostapd_db_path"); 106 | $db = new DatabaseConnection($dbLocation); 107 | $db->exec("DELETE FROM log;"); 108 | $this->response = array('success' => true); 109 | } 110 | 111 | private function getPineapLogLocation() 112 | { 113 | $dbBasePath = dirname($this->uciGet("pineap.@config[0].hostapd_db_path")); 114 | $this->response = array('location' => $dbBasePath . "/"); 115 | } 116 | 117 | private function setPineapLogLocation() 118 | { 119 | $dbLocation = dirname($this->request->location . '/fake_file'); 120 | $this->uciSet("pineap.@config[0].hostapd_db_path", $dbLocation . '/log.db'); 121 | $this->response = array('success' => true); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/pineapple/modules/Logging/js/module.js: -------------------------------------------------------------------------------- 1 | registerController('PineAPLogController', ['$api', '$scope', '$timeout', '$cookies', function($api, $scope, $timeout, $cookies) { 2 | $scope.log = []; 3 | $scope.mac = ''; 4 | $scope.ssid = ''; 5 | $scope.logLocation = ''; 6 | $scope.locationModified = false; 7 | $scope.orderByName = 'log_time'; 8 | $scope.reverseSort = true; 9 | $scope.loadingPineapLog = false; 10 | 11 | $scope.checkboxOptions = { 12 | probes: $cookies.get('probesLog') !== undefined ? $cookies.get('probesLog') === 'true' : true, 13 | associations: $cookies.get('associationsLog') !== undefined ? $cookies.get('associationsLog') === 'true' : true, 14 | removeDuplicates: $cookies.get('removeDuplicatesLog') !== undefined ? $cookies.get('removeDuplicatesLog') === 'true' : false 15 | }; 16 | 17 | $scope.refreshLog = (function() { 18 | $scope.log = []; 19 | $scope.loadingPineapLog = true; 20 | $api.request({ 21 | module: 'Logging', 22 | action: 'getPineapLog' 23 | }, function(response) { 24 | $scope.loadingPineapLog = false; 25 | if (response.error === undefined) { 26 | $scope.log = response.pineap_log; 27 | $scope.applyFilter(); 28 | annotateMacs(); 29 | } 30 | }); 31 | }); 32 | 33 | $scope.downloadLog = (function() { 34 | $api.request({ 35 | module: 'Logging', 36 | action: 'downloadPineapLog' 37 | }, function(response) { 38 | if (response.error === undefined) { 39 | window.location = '/api/?download=' + response.download; 40 | } 41 | }); 42 | }); 43 | 44 | $scope.getPineapLogLocation = (function () { 45 | $api.request({ 46 | module: 'Logging', 47 | action: 'getPineapLogLocation' 48 | }, function(response) { 49 | if (response.error === undefined) { 50 | $scope.logLocation = response.location; 51 | } 52 | }); 53 | }); 54 | 55 | $scope.setPineapLogLocation = (function () { 56 | $api.request({ 57 | module: 'Logging', 58 | action: 'setPineapLogLocation', 59 | location: $scope.logLocation 60 | }, function(response) { 61 | if (response.error === undefined) { 62 | $scope.locationModified = true; 63 | $timeout(function() { 64 | $scope.locationModified = false; 65 | }, 3000); 66 | } 67 | }); 68 | }); 69 | 70 | $scope.checkMatch = (function(text, filter) { 71 | if (filter.trim() === '') { 72 | return true; 73 | } 74 | if (text.toLowerCase().indexOf(filter.toLowerCase()) !== -1) { 75 | return true; 76 | } 77 | try { 78 | var re = new RegExp(filter); 79 | if (text.match(re) !== null) { 80 | return true; 81 | } 82 | } 83 | catch (err) {} 84 | return false; 85 | }); 86 | 87 | $scope.applyFilter = (function() { 88 | $cookies.put('probesLog', $scope.checkboxOptions.probes); 89 | $cookies.put('associationsLog', $scope.checkboxOptions.associations); 90 | $cookies.put('removeDuplicatesLog', $scope.checkboxOptions.removeDuplicates); 91 | 92 | var hashArray = []; 93 | $.each($scope.log, function(i, value){ 94 | if (value.log_time !== '') { 95 | value.hidden = false; 96 | if ($scope.checkboxOptions.removeDuplicates) { 97 | var index = value.ssid + value.log_type + value.mac; 98 | if (hashArray[index] === undefined) { 99 | hashArray[index] = true; 100 | } else { 101 | value.hidden = true; 102 | return true; 103 | } 104 | } 105 | 106 | if (!$scope.checkboxOptions.probes && value.log_type === 0) { 107 | value.hidden = true; 108 | } 109 | 110 | if (!$scope.checkboxOptions.associations && (value.log_type === 1 || value.log_type === 2)) { 111 | value.hidden = true; 112 | } 113 | 114 | if (!$scope.checkMatch(value.mac, $scope.mac)) { 115 | value.hidden = true; 116 | } else if (!$scope.checkMatch(value.ssid, $scope.ssid)) { 117 | value.hidden = true; 118 | } 119 | } 120 | }); 121 | }); 122 | 123 | $scope.clearFilter = (function() { 124 | $scope.checkboxOptions = { 125 | probes: true, 126 | associations: true, 127 | removeDuplicates: false 128 | }; 129 | $scope.mac = ''; 130 | $scope.ssid = ''; 131 | 132 | $scope.applyFilter(); 133 | }); 134 | 135 | $scope.clearLog = (function() { 136 | $api.request({ 137 | module: 'Logging', 138 | action: 'clearPineapLog' 139 | }, function(response) { 140 | if (response.error === undefined) { 141 | $scope.log = []; 142 | } 143 | }); 144 | }); 145 | 146 | $scope.getPineapLogLocation(); 147 | $scope.refreshLog(); 148 | }]); 149 | 150 | registerController('SyslogController', ['$api', '$scope', function($api, $scope) { 151 | $scope.refreshLog = (function(force) { 152 | $scope.syslog = 'Loading...'; 153 | $api.request({ 154 | module: 'Logging', 155 | action: 'getSyslog' 156 | }, function(response) { 157 | if (response.error === undefined) { 158 | $scope.syslog = response; 159 | } 160 | }) 161 | }); 162 | }]); 163 | 164 | registerController('DmesgController', ['$api', '$scope', function($api, $scope) { 165 | $scope.refreshLog = (function() { 166 | $scope.dmesg = 'Loading...'; 167 | $api.request({ 168 | module: 'Logging', 169 | action: 'getDmesg' 170 | }, function(response) { 171 | if (response.error === undefined) { 172 | $scope.dmesg = response; 173 | } 174 | }) 175 | }); 176 | }]); 177 | 178 | registerController('ReportingLogController', ['$api', '$scope', function($api, $scope) { 179 | $scope.refreshLog = (function() { 180 | $scope.reportingLog = 'Loading...'; 181 | $api.request({ 182 | module: 'Logging', 183 | action: 'getReportingLog' 184 | }, function(response) { 185 | if (response.error === undefined) { 186 | $scope.reportingLog = response; 187 | } 188 | }) 189 | }); 190 | }]); 191 | -------------------------------------------------------------------------------- /src/pineapple/modules/Logging/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Manage and view various logs.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Logging", 10 | "version": "1.1", 11 | "index": 8 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Logging/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/ModuleManager/api/module.php: -------------------------------------------------------------------------------- 1 | request->action) { 8 | case 'getAvailableModules': 9 | $this->getAvailableModules(); 10 | break; 11 | 12 | case 'getInstalledModules': 13 | $this->getInstalledModules(); 14 | break; 15 | 16 | case 'installModule': 17 | $this->installModule(); 18 | break; 19 | 20 | case 'downloadModule': 21 | $this->downloadModule(); 22 | break; 23 | 24 | case 'checkDestination': 25 | $this->checkDestination(); 26 | break; 27 | 28 | case 'removeModule': 29 | $this->removeModule(); 30 | break; 31 | 32 | case 'downloadStatus': 33 | $this->downloadStatus(); 34 | break; 35 | 36 | case 'installStatus': 37 | $this->installStatus(); 38 | break; 39 | 40 | case 'restoreSDcardModules': 41 | if ($this->sdReaderPresent()) { 42 | $this->restoreSDcardModules(); 43 | } 44 | break; 45 | } 46 | } 47 | 48 | private function getAvailableModules() 49 | { 50 | $moduleData = @$this->fileGetContentsSSL(self::REMOTE_URL . "/modules/build/modules.json"); 51 | if ($moduleData !== false) { 52 | $moduleData = json_decode($moduleData); 53 | if (json_last_error() === JSON_ERROR_NONE) { 54 | $this->response = array('availableModules' => $moduleData); 55 | } 56 | } else { 57 | $this->error = 'Error connecting to ' . self::REMOTE_NAME . '. Please check your connection.'; 58 | } 59 | } 60 | 61 | private function getInstalledModules() 62 | { 63 | $modules = array(); 64 | $modulesDirectories = scandir('/pineapple/modules'); 65 | foreach ($modulesDirectories as $moduleDirectory) { 66 | if ($moduleDirectory[0] === ".") { 67 | continue; 68 | } 69 | 70 | $path = "/pineapple/modules/{$moduleDirectory}/module.info"; 71 | if (file_exists($path)) { 72 | $moduleData = json_decode(file_get_contents($path)); 73 | if (json_last_error() !== JSON_ERROR_NONE) { 74 | continue; 75 | } 76 | 77 | $module = array(); 78 | $module['title'] = $moduleData->title; 79 | $module['author'] = $moduleData->author; 80 | $module['version'] = $moduleData->version; 81 | $module['description'] = $moduleData->description; 82 | $module['size'] = exec("du -sh /pineapple/modules/$moduleDirectory/ | awk '{print $1;}'"); 83 | if (isset($moduleData->system)) { 84 | $module['type'] = "System"; 85 | } elseif (isset($moduleData->cliOnly)) { 86 | $module['type'] = "CLI"; 87 | } else { 88 | $module['type'] = "GUI"; 89 | } 90 | 91 | $modules[$moduleDirectory] = $module; 92 | } 93 | } 94 | $this->response = array("installedModules" => $modules); 95 | } 96 | 97 | private function downloadModule() 98 | { 99 | @unlink('/tmp/moduleDownloaded'); 100 | 101 | if ($this->request->destination === 'sd') { 102 | @mkdir('/sd/tmp/'); 103 | $dest = '/sd/tmp/'; 104 | } else { 105 | $dest = '/tmp/'; 106 | } 107 | 108 | $module = "{$this->request->moduleName}.tar.gz"; 109 | $this->execBackground("uclient-fetch -q -T 10 -O {$dest}{$module} '" . self::REMOTE_URL . "/modules/build/{$module}' && touch /tmp/moduleDownloaded"); 110 | $this->response = array('success' => true); 111 | } 112 | 113 | private function downloadStatus() 114 | { 115 | if (file_exists('/tmp/moduleDownloaded')) { 116 | if ($this->request->destination === 'sd') { 117 | $dest = '/sd/tmp/'; 118 | } else { 119 | $dest = '/tmp/'; 120 | } 121 | 122 | if (hash_file('sha256', "{$dest}{$this->request->moduleName}.tar.gz") == $this->request->checksum) { 123 | $this->response = array('success' => true); 124 | return; 125 | } 126 | } 127 | $this->response = array('success' => false); 128 | } 129 | 130 | private function installModule() 131 | { 132 | @unlink('/tmp/moduleInstalled'); 133 | $this->removeModule(); 134 | 135 | if ($this->request->destination === 'sd') { 136 | @mkdir('/sd/modules/'); 137 | $dest = '/sd/tmp/'; 138 | $installDest = '/sd/modules/'; 139 | exec("ln -s /sd/modules/{$this->request->moduleName} /pineapple/modules/{$this->request->moduleName}"); 140 | } else { 141 | $dest = '/tmp/'; 142 | $installDest = '/pineapple/modules/'; 143 | } 144 | 145 | $module = "{$this->request->moduleName}.tar.gz"; 146 | $this->execBackground("tar -xzvC {$installDest} -f {$dest}{$module} && rm {$dest}{$module} && touch /tmp/moduleInstalled"); 147 | $this->response = array('success' => true); 148 | } 149 | 150 | private function installStatus() 151 | { 152 | $this->response = array('success' => file_exists('/tmp/moduleInstalled')); 153 | } 154 | 155 | private function checkDestination() 156 | { 157 | $config = $this->getDeviceConfig(); 158 | $this->response = array( 159 | 'module' => $this->request->name, 160 | 'internal' => (disk_free_space('/') > ($this->request->size + 150000) && $config['useInternalStorage']), 161 | 'sd' => ($this->isSDAvailable() && $config['useUSBStorage']), 162 | ); 163 | } 164 | 165 | private function removeModule() 166 | { 167 | $path = "/pineapple/modules/{$this->request->moduleName}"; 168 | if (is_link($path)) { 169 | @unlink($path); 170 | exec("rm -rf /sd/modules/{$this->request->moduleName}"); 171 | } else { 172 | exec("rm -rf {$path}"); 173 | } 174 | 175 | $this->response = array('success' => true); 176 | } 177 | 178 | private function restoreSDcardModules() 179 | { 180 | $restored = false; 181 | $sdcardModules = @scandir('/sd/modules/'); 182 | foreach ($sdcardModules as $module) { 183 | if ($module[0] != '.' && !file_exists("/pineapple/modules/{$module}")) { 184 | $restored = true; 185 | exec("ln -s /sd/modules/{$module} /pineapple/modules/{$module}"); 186 | } 187 | } 188 | $this->response = array("restored" => $restored); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/pineapple/modules/ModuleManager/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("ModuleManagerController", ['$api', '$scope', '$timeout', '$interval', '$templateCache', '$rootScope', function($api, $scope, $timeout, $interval, $templateCache, $rootScope){ 2 | $rootScope.availableModules = []; 3 | $rootScope.installedModules = []; 4 | $scope.installedModule = ""; 5 | $scope.removedModule = ""; 6 | $scope.gotAvailableModules = false; 7 | $scope.connectionError = false; 8 | $scope.selectedModule = false; 9 | $scope.downloading = false; 10 | $scope.installing = false; 11 | $scope.linking = false; 12 | 13 | $scope.getAvailableModules = (function() { 14 | $scope.loading = true; 15 | $api.request({ 16 | module: "ModuleManager", 17 | action: "getAvailableModules" 18 | }, function(response) { 19 | $scope.loading = false; 20 | if (response.error === undefined) { 21 | $rootScope.availableModules = response.availableModules; 22 | $scope.compareModuleLists(); 23 | $scope.gotAvailableModules = true; 24 | $scope.connectionError = false; 25 | } else { 26 | $scope.connectionError = response.error; 27 | } 28 | }); 29 | }); 30 | 31 | $scope.getInstalledModules = (function() { 32 | $api.request({ 33 | module: "ModuleManager", 34 | action: "getInstalledModules" 35 | }, function(response) { 36 | $rootScope.installedModules = response.installedModules; 37 | if ($scope.gotAvailableModules) { 38 | $scope.compareModuleLists(); 39 | } 40 | }); 41 | }); 42 | 43 | $scope.compareModuleLists = (function() { 44 | angular.forEach($rootScope.availableModules, function(module, moduleName){ 45 | if ($rootScope.installedModules[moduleName] === undefined){ 46 | module['installable'] = true; 47 | } else if ($rootScope.availableModules[moduleName].version <= $rootScope.installedModules[moduleName].version) { 48 | module['installed'] = true; 49 | } 50 | }); 51 | }); 52 | 53 | $scope.removeModule = (function(name) { 54 | $api.request({ 55 | module: 'ModuleManager', 56 | action: 'removeModule', 57 | moduleName: name 58 | }, function(response) { 59 | if (response.success === true) { 60 | $scope.getInstalledModules(); 61 | $scope.removedModule = true; 62 | $api.reloadNavbar(); 63 | $timeout(function(){ 64 | $scope.removedModule = false; 65 | }, 2000); 66 | } 67 | }); 68 | }); 69 | 70 | $scope.restoreSDcardModules = (function() { 71 | $api.request({ 72 | module: 'ModuleManager', 73 | action: 'restoreSDcardModules' 74 | }, function(response) { 75 | if (response.restored === true) { 76 | $scope.restoreSDcardModules(); 77 | } else { 78 | $api.reloadNavbar(); 79 | $scope.getInstalledModules(); 80 | $scope.linking = false; 81 | } 82 | }); 83 | }); 84 | 85 | $scope.getInstalledModules(); 86 | }]); 87 | -------------------------------------------------------------------------------- /src/pineapple/modules/ModuleManager/module.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

Successfully Installed Module

6 |

Successfully Removed Module

7 | 8 |
9 | 10 | 11 |

12 |
13 |
14 | {{ connectionError }} 15 |
16 | 17 |
18 |
19 |

Available Modules

20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 42 | 45 | 48 | 51 | 54 | 58 | 59 | 60 |
ModuleVersionDescriptionAuthorSizeTypeAction
37 | {{ module['title'] }} 38 | 40 | {{ module['version'] }} 41 | 43 | {{ module['description'] }} 44 | 46 | {{ module['author'] }} 47 | 49 | {{ (module['size']/1024).toFixed(2) }}K 50 | 52 | {{ module['type'] }} 53 | 55 | 56 | 57 |
61 |
62 |
63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 |

Installed Modules

77 |
78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 96 | 99 | 102 | 105 | 108 | 111 | 114 | 115 | 116 |
ModuleVersionDescriptionSizeAuthorTypeAction
94 | {{ module['title'] }} 95 | 97 | {{ module['version'] }} 98 | 100 | {{ module['description'] }} 101 | 103 | {{ module['size'] }} 104 | 106 | {{ module['author'] }} 107 | 109 | {{ module['type'] }} 110 | 112 | 113 |
117 |
118 |
119 |
120 |
121 |
122 | -------------------------------------------------------------------------------- /src/pineapple/modules/ModuleManager/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Download new community modules, and system module updates.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Modules", 10 | "version": "1.2" 11 | } 12 | -------------------------------------------------------------------------------- /src/pineapple/modules/ModuleManager/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Networking/api/AccessPoint.php: -------------------------------------------------------------------------------- 1 | selectedChannel); 12 | 13 | uciSet('wireless.@wifi-iface[0].ssid', $apConfig->openSSID, false); 14 | uciSet('wireless.@wifi-iface[0].disabled', $apConfig->disableOpenAP, false); 15 | uciSet('wireless.@wifi-iface[0].hidden', $apConfig->hideOpenAP, false); 16 | uciSet('wireless.@wifi-iface[0].maxassoc', $apConfig->maxClients, false); 17 | 18 | uciSet('wireless.@wifi-iface[1].ssid', $apConfig->managementSSID, false); 19 | uciSet('wireless.@wifi-iface[1].key', $apConfig->managementKey, false); 20 | uciSet('wireless.@wifi-iface[1].disabled', $apConfig->disableManagementAP, false); 21 | uciSet('wireless.@wifi-iface[1].hidden', $apConfig->hideManagementAP, false); 22 | 23 | uciCommit(); 24 | execBackground('wifi'); 25 | 26 | return ["success" => true]; 27 | } 28 | 29 | public function getAPConfig($getChannelInfo = true) 30 | { 31 | $channels = []; 32 | if ($getChannelInfo) { 33 | exec("iwinfo phy0 freqlist", $output); 34 | preg_match_all("/\(Channel (\d+)\)$/m", implode("\n", $output), $channelList); 35 | 36 | // Remove radar detection channels 37 | foreach ($channelList[1] as $channel) { 38 | if ((int)$channel < 52 || (int)$channel > 140) { 39 | $channels[] = $channel; 40 | } 41 | } 42 | } 43 | 44 | return [ 45 | "selectedChannel" => uciGet("wireless.radio0.channel"), 46 | "availableChannels" => $channels, 47 | 48 | "openSSID" => uciGet("wireless.@wifi-iface[0].ssid"), 49 | "maxClients" => uciGet("wireless.@wifi-iface[0].maxassoc", false), 50 | "disableOpenAP" => uciGet("wireless.@wifi-iface[0].disabled"), 51 | "hideOpenAP" => uciGet("wireless.@wifi-iface[0].hidden"), 52 | 53 | "managementSSID" => uciGet("wireless.@wifi-iface[1].ssid"), 54 | "managementKey" => uciGet("wireless.@wifi-iface[1].key"), 55 | "disableManagementAP" => uciGet("wireless.@wifi-iface[1].disabled"), 56 | "hideManagementAP" => uciGet("wireless.@wifi-iface[1].hidden") 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pineapple/modules/Networking/api/ClientMode.php: -------------------------------------------------------------------------------- 1 | /dev/null"); 16 | } 17 | 18 | if (uciGet("wireless.@wifi-iface[{$uciID}].network") === 'wwan') { 19 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'lan'); 20 | exec("wifi up $radio"); 21 | sleep(2); 22 | } 23 | 24 | exec("iwinfo {$interface} scan", $apScan); 25 | if ($apScan[0] === 'No scan results') { 26 | return null; 27 | } 28 | 29 | $returnArray = []; 30 | $apArray = preg_split("/^Cell/m", implode("\n", $apScan)); 31 | foreach ($apArray as $apData) { 32 | $apData = explode("\n", $apData); 33 | $ssid = substr(trim($apData[1]), 8, -1); 34 | if (!$ssid || mb_detect_encoding($ssid, "auto") === false || $ssid === "unknown") { 35 | continue; 36 | } 37 | 38 | $channelString = explode(" ", trim($apData[2])); 39 | $signalString = explode(" ", trim($apData[3])); 40 | $security = substr(trim($apData[4]), 12); 41 | 42 | $returnArray[] = [ 43 | 'mac' => substr($apData[0], -17), 44 | 'ssid' => $ssid, 45 | 'channel' => intval(substr($channelString[1], 9)), 46 | 'signal' => substr($signalString[0], 8), 47 | 'quality' => substr($signalString[1], 9), 48 | 'security' => ($security === "none") ? "Open" : $security, 49 | ]; 50 | } 51 | 52 | return $returnArray; 53 | } 54 | 55 | public function connectToAP($uciID, $ap, $key, $radioID) 56 | { 57 | exec('[ ! -z "$(wifi config)" ] && wifi config >> /etc/config/wireless'); 58 | 59 | switch ($ap->security) { 60 | case 'Open': 61 | $encryption = "none"; 62 | break; 63 | case 'WPA (TKIP)': 64 | case 'WPA PSK (TKIP)': 65 | $encryption = "psk+tkip"; 66 | break; 67 | case 'WPA (CCMP)': 68 | case 'WPA PSK (CCMP)': 69 | $encryption = "psk+ccmp"; 70 | break; 71 | case 'WPA (TKIP, CCMP)': 72 | case 'WPA PSK (TKIP, CCMP)': 73 | $encryption = "psk+tkip+ccmp"; 74 | break; 75 | case 'WPA2 (TKIP)': 76 | case 'WPA2 PSK (TKIP)': 77 | $encryption = "psk2+tkip"; 78 | break; 79 | case 'WPA2 (CCMP)': 80 | case 'WPA2 PSK (CCMP)': 81 | $encryption = "psk2+ccmp"; 82 | break; 83 | case 'WPA2 (TKIP, CCMP)': 84 | case 'WPA2 PSK (TKIP, CCMP)': 85 | $encryption = "psk2+ccmp+tkip"; 86 | break; 87 | case 'mixed WPA/WPA2 (TKIP)': 88 | case 'mixed WPA/WPA2 PSK (TKIP)': 89 | $encryption = "psk-mixed+tkip"; 90 | break; 91 | case 'mixed WPA/WPA2 (CCMP)': 92 | case 'mixed WPA/WPA2 PSK (CCMP)': 93 | $encryption = "psk-mixed+ccmp"; 94 | break; 95 | case 'mixed WPA/WPA2 (TKIP, CCMP)': 96 | case 'mixed WPA/WPA2 PSK (TKIP, CCMP)': 97 | $encryption = "psk-mixed+ccmp+tkip"; 98 | break; 99 | default: 100 | $encryption = ""; 101 | } 102 | 103 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'wwan', false); 104 | uciSet("wireless.@wifi-iface[{$uciID}].mode", 'sta', false); 105 | uciSet("wireless.@wifi-iface[{$uciID}].ssid", $ap->ssid, false); 106 | uciSet("wireless.@wifi-iface[{$uciID}].encryption", $encryption, false); 107 | uciSet("wireless.@wifi-iface[{$uciID}].key", $key, false); 108 | uciCommit(); 109 | 110 | if ($radioID === false) { 111 | execBackground("wifi"); 112 | } else { 113 | execBackground("wifi reload {$radioID}"); 114 | execBackground("wifi up {$radioID}"); 115 | } 116 | 117 | return ["success" => true]; 118 | } 119 | 120 | public function checkConnection() 121 | { 122 | $connection = exec('iwconfig 2>&1 | grep ESSID:\"'); 123 | if (trim($connection)) { 124 | $interface = explode(" ", $connection)[0]; 125 | 126 | $ssidString = substr($connection, strpos($connection, 'ESSID:')); 127 | $ssid = substr($ssidString, 7, -1); 128 | $ip = exec("ifconfig " . escapeshellarg($interface) . " | grep -m 1 inet | awk '{print \$2}' | awk -F':' '{print \$2}'"); 129 | 130 | return ["connected" => true, "interface" => $interface, "ssid" => $ssid, "ip" => $ip]; 131 | } 132 | 133 | return ["connected" => false]; 134 | } 135 | 136 | public function disconnect($uciID, $radioID) 137 | { 138 | uciSet("wireless.@wifi-iface[{$uciID}].network", 'lan', false); 139 | uciSet("wireless.@wifi-iface[{$uciID}].ssid", '', false); 140 | uciSet("wireless.@wifi-iface[{$uciID}].encryption", 'none', false); 141 | uciSet("wireless.@wifi-iface[{$uciID}].key", '', false); 142 | uciCommit(); 143 | 144 | if ($radioID === false) { 145 | execBackground("wifi"); 146 | } else { 147 | execBackground("wifi reload {$radioID}"); 148 | execBackground("wifi up {$radioID}"); 149 | } 150 | 151 | return ["success" => true]; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/pineapple/modules/Networking/api/Interfaces.php: -------------------------------------------------------------------------------- 1 | $radioConfig) { 36 | if (isset($radioConfig->interfaces[0]->config->ifname) && 37 | $radioConfig->interfaces[0]->config->ifname === $interface) { 38 | return $radio; 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | 45 | public function setMac($random, $interface, $newMac, $forceReload) 46 | { 47 | $uciID = $this->getUciID($interface); 48 | $interface = escapeshellarg($interface); 49 | 50 | if ($random) { 51 | $mac = exec("ifconfig {$interface} down && macchanger -A {$interface} | grep New | awk '{print \$3}'"); 52 | } else { 53 | $requestMac = escapeshellarg($newMac); 54 | $mac = exec("ifconfig {$interface} down && macchanger -m {$requestMac} {$interface} | grep New | awk '{print \$3}'"); 55 | } 56 | 57 | uciSet("wireless.@wifi-iface[{$uciID}].macaddr", $mac); 58 | if ($forceReload) { 59 | exec("wifi"); 60 | } else { 61 | exec("ifconfig {$interface} up"); 62 | } 63 | 64 | return ["success" => true, "uci" => $uciID]; 65 | } 66 | 67 | public function resetMac($interface) 68 | { 69 | $uciID = $this->getUciID($interface); 70 | exec("uci set wireless.@wifi-iface[{$uciID}].macaddr=''"); 71 | exec("wifi"); 72 | return ["success" => true]; 73 | } 74 | 75 | public function resetWirelessConfig() 76 | { 77 | execBackground("wifi config > /etc/config/wireless && wifi"); 78 | return ["success" => true]; 79 | } 80 | 81 | public function getInterfaceList() 82 | { 83 | exec("ifconfig -a | grep encap:Ethernet | awk '{print \$1\",\"\$5}'", $interfaceArray); 84 | return $interfaceArray; 85 | } 86 | 87 | public function getClientInterfaces() 88 | { 89 | $clientInterfaces = []; 90 | exec("ifconfig -a | grep wlan | awk '{print \$1}'", $interfaceArray); 91 | foreach ($interfaceArray as $interface) { 92 | if (\DeviceConfig::HIDE_WLAN0_CLIENT && substr($interface, 0, 5) === "wlan0") { 93 | continue; 94 | } 95 | $clientInterfaces[] = $interface; 96 | } 97 | 98 | return array_reverse($clientInterfaces); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/pineapple/modules/Networking/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Make changes to Routing, Access Points, MAC Addresses and the Hostname.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Networking", 10 | "version": "1.1", 11 | "index": 10 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Networking/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Notes/api/module.php: -------------------------------------------------------------------------------- 1 | dbConnection = new DatabaseConnection(self::DATABASE); 13 | if (!empty($this->dbConnection->error)) { 14 | $this->error = $this->dbConnection->strError(); 15 | return; 16 | } 17 | $this->dbConnection->exec("CREATE TABLE IF NOT EXISTS notes (type INT, key TEXT UNIQUE NOT NULL, name TEXT, note TEXT);"); 18 | if (!empty($this->dbConnection->error)) { 19 | $this->error = $this->dbConnection->strError(); 20 | } 21 | } 22 | 23 | public function route() 24 | { 25 | switch ($this->request->action) { 26 | case 'setName': 27 | $this->response = $this->setName($this->request->type, $this->request->key, $this->request->name); 28 | break; 29 | case 'setNote': 30 | $this->response = $this->setNote($this->request->type, $this->request->key, $this->request->name, $this->request->note); 31 | break; 32 | case 'getNotes': 33 | $this->response = $this->getNotes(); 34 | break; 35 | case 'getNote': 36 | $this->response = $this->getNote($this->request->key); 37 | break; 38 | case 'deleteNote': 39 | $this->response = $this->deleteNote($this->request->key); 40 | break; 41 | case 'downloadNotes': 42 | $this->response = $this->downloadNotes(); 43 | break; 44 | case 'getKeys': 45 | $this->response = $this->getKeys(); 46 | break; 47 | } 48 | } 49 | 50 | public function setName($type, $key, $name) 51 | { 52 | return $this->dbConnection->exec("INSERT OR REPLACE INTO notes (type, key, name) VALUES('%d', '%s', '%s');", $type, $key, $name); 53 | } 54 | 55 | public function setNote($type, $key, $name, $note) 56 | { 57 | if (empty($name) && empty($note)) { 58 | return $this->deleteNote($key); 59 | } else { 60 | return $this->dbConnection->exec("INSERT OR REPLACE INTO notes (type, key, name, note) VALUES ('%d', '%s', '%s', '%s');", $type, $key, $name, $note); 61 | } 62 | } 63 | 64 | public function getNotes() 65 | { 66 | $macs = $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE type=0;"); 67 | $ssids = $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE type=1;"); 68 | return array("macs" => $macs, "ssids" => $ssids); 69 | } 70 | 71 | public function getNote($key) 72 | { 73 | return array("note" => $this->dbConnection->query("SELECT type, key, name, note FROM notes WHERE key='%s';", $key)); 74 | } 75 | 76 | public function deleteNote($key) 77 | { 78 | if (!isset($key)) { 79 | return array("success" => false); 80 | } 81 | $this->dbConnection->exec("DELETE FROM notes WHERE key='%s';", $key); 82 | return array("success" => true); 83 | } 84 | 85 | public function downloadNotes() 86 | { 87 | $noteData = $this->dbConnection->query('SELECT * FROM notes;'); 88 | foreach ($noteData as $idx => $note) { 89 | if ($note['type'] == 0) { 90 | $note['type'] = 'MAC'; 91 | } else if ($note['type'] == 1) { 92 | $note['type'] = 'SSID'; 93 | } 94 | $noteData[$idx] = $note; 95 | } 96 | $fileName = '/tmp/notes.json'; 97 | file_put_contents($fileName, json_encode($noteData, JSON_PRETTY_PRINT)); 98 | return array("download" => $this->downloadFile($fileName)); 99 | } 100 | 101 | public function getKeys() 102 | { 103 | $keys = array(); 104 | $res = $this->dbConnection->query("SELECT key FROM notes;"); 105 | foreach ($res as $idx => $key) { 106 | $keys[] = $key['key']; 107 | } 108 | return array("keys" => $keys); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/pineapple/modules/Notes/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("NotesController", ['$api', '$scope', function($api, $scope){ 2 | $scope.macs = []; 3 | $scope.ssids = []; 4 | $scope.error = null; 5 | 6 | $scope.getNotes = function() { 7 | $api.request({ 8 | module: "Notes", 9 | action: "getNotes" 10 | }, function(response) { 11 | if (response.error !== undefined) { 12 | $scope.error = response.error; 13 | } else { 14 | $scope.macs = response.macs; 15 | $scope.ssids = response.ssids; 16 | } 17 | }); 18 | }; 19 | 20 | $scope.deleteNote = function($event) { 21 | var key = $event.target.getAttribute('key'); 22 | $api.request({ 23 | module: "Notes", 24 | action: "deleteNote", 25 | key: key 26 | }, function() { 27 | $scope.getNotes(); 28 | }); 29 | }; 30 | 31 | $scope.downloadNotes = function() { 32 | $scope.getNotes(); 33 | $api.request({ 34 | module: "Notes", 35 | action: "downloadNotes" 36 | }, function(response) { 37 | if (response.error === undefined) { 38 | window.location = '/api?download=' + response.download; 39 | } 40 | }); 41 | }; 42 | 43 | $scope.getNotes(); 44 | }]); -------------------------------------------------------------------------------- /src/pineapple/modules/Notes/module.html: -------------------------------------------------------------------------------- 1 | 13 |
14 |
15 |
16 |
17 |

18 | MAC Notes 19 |
20 | 21 | 22 |
23 |

24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 48 | 53 | 56 | 57 | 58 |
NameMAC AddressNoteDelete
38 | 39 | {{ mac.name }} 40 | 41 | 43 | 44 | 45 | {{ mac.key }} 46 | 47 | 49 | 50 | {{ mac.note }} 51 | 52 | 54 | 55 |
59 |
60 |
61 | No MAC notes found. 62 |
63 |
64 |
65 |
66 |

67 | SSID Notes 68 |
69 | 70 | 71 |
72 |

73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 91 | 97 | 102 | 105 | 106 | 107 |
NameSSIDNoteDelete
87 | 88 | {{ ssid.name }} 89 | 90 | 92 | 93 | 94 | {{ ssid.key }} 95 | 96 | 98 | 99 | {{ ssid.note }} 100 | 101 | 103 | 104 |
108 |
109 |
110 | No SSID notes found. 111 |
112 |
113 |
114 |
-------------------------------------------------------------------------------- /src/pineapple/modules/Notes/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Easily take notes on different clients and APs.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Notes", 10 | "version": "1.0", 11 | "index": 13 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Notes/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/PineAP/executable/executable: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Simple wrapper script for PineAP 3 | 4 | ps | grep [p]ineapd -q && { 5 | [[ "$1" == "stop" ]] && { 6 | echo [*] Stopping PineAP 7 | /etc/init.d/pineapd stop &> /dev/null 8 | echo [*] PineAP successfully stopped 9 | exit 0 10 | } || { 11 | echo -e [*] Executing /usr/bin/pineap /tmp/pineap.conf "$@" "\n" 12 | /usr/bin/pineap /tmp/pineap.conf "$@" 13 | } 14 | } || { 15 | [[ "$1" == "start" ]] && { 16 | echo "[*] Starting PineAP, please wait." 17 | /etc/init.d/pineapd start &> /dev/null 18 | echo "[*] PineAP started successfully" 19 | } || { 20 | echo "PineAPd is not running. Start it with 'module PineAP start'" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pineapple/modules/PineAP/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "PineAP is an effective, modular rogue access point suite.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "PineAP", 10 | "version": "1.9", 11 | "index": 6 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/PineAP/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/api/WebSocketsHandler.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import SocketServer 3 | import time 4 | from base64 import b64encode 5 | from hashlib import sha1 6 | from mimetools import Message 7 | from StringIO import StringIO 8 | import wsauth 9 | 10 | # This class is taken and modified from https://gist.github.com/jkp/3136208 11 | class WebSocketsHandler(SocketServer.StreamRequestHandler): 12 | magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 13 | 14 | def setup(self): 15 | SocketServer.StreamRequestHandler.setup(self) 16 | self.server.websockets.append(self) 17 | self.handshake_done = False 18 | 19 | def handle(self): 20 | while self.server.running: 21 | if not self.handshake_done: 22 | self.handshake() 23 | else: 24 | time.sleep(5) 25 | 26 | def send_message(self, message): 27 | self.request.send(chr(129)) 28 | length = len(message) 29 | if length <= 125: 30 | self.request.send(chr(length)) 31 | elif length >= 126 and length <= 65535: 32 | self.request.send(chr(126)) 33 | self.request.send(struct.pack(">H", length)) 34 | else: 35 | self.request.send(chr(127)) 36 | self.request.send(struct.pack(">Q", length)) 37 | self.request.send(message) 38 | 39 | def handshake(self): 40 | data = self.request.recv(1024).strip() 41 | headers = Message(StringIO(data.split('\r\n', 1)[1])) 42 | try: 43 | uri = data.split('\r\n')[0].split(' ')[1] 44 | authtoken = dict(p.split('=') for p in uri.split('/')[-1].split('?')[1].split('&'))['authtoken'] 45 | if not wsauth.verify_token(authtoken): 46 | print "[!] Invalid auth token: {}".format(authtoken) 47 | return 48 | else: 49 | print "[+] Valid auth token: {}".format(authtoken) 50 | except Exception, e: 51 | print "Error checking token: {}".format(e) 52 | if headers.get("Upgrade", None) != "websocket": 53 | return 54 | key = headers['Sec-WebSocket-Key'] 55 | digest = b64encode(sha1(key + self.magic).hexdigest().decode('hex')) 56 | response = 'HTTP/1.1 101 Switching Protocols\r\n' 57 | response += 'Upgrade: websocket\r\n' 58 | response += 'Connection: Upgrade\r\n' 59 | response += 'Sec-WebSocket-Accept: %s\r\n\r\n' % digest 60 | self.handshake_done = self.request.send(response) 61 | -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/api/dbhandler.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import sqlite3 3 | from sqlite3 import Error 4 | import time 5 | import json 6 | import subprocess 7 | import sys 8 | import thread 9 | 10 | EncryptionFields = { 11 | "WPA": 0x01, 12 | "WPA2": 0x02, 13 | "WEP": 0x04, 14 | "WPA_PAIRWISE_WEP40": 0x08, 15 | "WPA_PAIRWISE_WEP104": 0x10, 16 | "WPA_PAIRWISE_TKIP": 0x20, 17 | "WPA_PAIRWISE_CCMP": 0x40, 18 | "WPA2_PAIRWISE_WEP40": 0x80, 19 | "WPA2_PAIRWISE_WEP104": 0x100, 20 | "WPA2_PAIRWISE_TKIP": 0x200, 21 | "WPA2_PAIRWISE_CCMP": 0x400, 22 | "WPA_AKM_PSK": 0x800, 23 | "WPA_AKM_ENTERPRISE": 0x1000, 24 | "WPA_AKM_ENTERPRISE_FT": 0x2000, 25 | "WPA2_AKM_PSK": 0x4000, 26 | "WPA2_AKM_ENTERPRISE": 0x8000, 27 | "WPA2_AKM_ENTERPRISE_FT": 0x10000, 28 | "WPA_GROUP_WEP40": 0x20000, 29 | "WPA_GROUP_WEP104": 0x40000, 30 | "WPA_GROUP_TKIP": 0x80000, 31 | "WPA_GROUP_CCMP": 0x100000, 32 | "WPA2_GROUP_WEP40": 0x200000, 33 | "WPA2_GROUP_WEP104": 0x400000, 34 | "WPA2_GROUP_TKIP": 0x800000, 35 | "WPA2_GROUP_CCMP": 0x1000000 36 | } 37 | 38 | 39 | 40 | class DBHandler(threading.Thread): 41 | def __init__(self, group=None, target=None, name=None, args=(), kwargs=None): 42 | threading.Thread.__init__(self, group=group, target=target, name=name) 43 | self.args = args 44 | self.kwargs = kwargs 45 | 46 | self.database = self.args[1] 47 | self.scanID = int(self.args[2]) 48 | 49 | self.accessPoints = {} 50 | self.realAPs = [] 51 | self.unassociatedClients = [] 52 | self.outOfRangeClients = {} 53 | return 54 | 55 | def createAPArray(self): 56 | self.accessPoints = {} 57 | cursor = self.conn.cursor() 58 | cursor.execute("SELECT ssid,bssid,encryption,channel,signal,wps,last_seen from aps WHERE scan_id = " + str(self.scanID) + ";") 59 | apRows = cursor.fetchall() 60 | 61 | for row in apRows: 62 | ap = {} 63 | ap["bssid"] = row[1] 64 | ap["ssid"] = row[0] 65 | ap["channel"] = row[3] 66 | ap["encryption"] = self.printEncryption(row[2]) 67 | ap["wps"] = row[5] 68 | ap["lastSeen"] = row[6] 69 | ap["power"] = row[4] 70 | ap["clients"] = [] 71 | self.accessPoints[row[1]] = ap 72 | 73 | self.realAPs = [] 74 | for key, value in self.accessPoints.iteritems(): 75 | self.realAPs.append(value) 76 | 77 | def createClientsArray(self): 78 | self.unassociatedClients = [] 79 | self.outOfRangeClients = {} 80 | cursor = self.conn.cursor() 81 | cursor.execute("SELECT mac,bssid,last_seen from clients WHERE scan_id = " + str(self.scanID) + ";") 82 | 83 | clientRows = cursor.fetchall() 84 | 85 | for row in clientRows: 86 | if row[1] == "FF:FF:FF:FF:FF:FF": 87 | self.unassociatedClients.append({'mac': row[0], 'lastSeen': row[2]}) 88 | elif row[1] in self.accessPoints: 89 | cliObj = {"mac": row[0], "lastSeen": row[2]} 90 | if cliObj not in self.accessPoints[row[1]]["clients"]: 91 | self.accessPoints[row[1]]["clients"].append(cliObj) 92 | else: 93 | self.outOfRangeClients[row[0]] = {"bssid": row[1], "lastSeen": row[2]} 94 | 95 | def printEncryption(self, encryptionType): 96 | retStr = "" 97 | if (encryptionType == 0): 98 | return "Open" 99 | if (encryptionType & EncryptionFields["WEP"]): 100 | return "WEP" 101 | elif ((encryptionType & EncryptionFields["WPA"]) and (encryptionType & EncryptionFields["WPA2"])): 102 | retStr += "WPA Mixed " 103 | elif (encryptionType & EncryptionFields["WPA"]): 104 | retStr += "WPA " 105 | elif (encryptionType & EncryptionFields["WPA2"]): 106 | retStr += "WPA2 " 107 | 108 | if ((encryptionType & EncryptionFields["WPA2_AKM_PSK"]) or (encryptionType & EncryptionFields["WPA_AKM_PSK"])): 109 | retStr += "PSK " 110 | elif ((encryptionType & EncryptionFields["WPA2_AKM_ENTERPRISE"]) or (encryptionType & EncryptionFields["WPA_AKM_ENTERPRISE"])): 111 | retStr += "Enterprise " 112 | elif ((encryptionType & EncryptionFields["WPA2_AKM_ENTERPRISE_FT"]) or (encryptionType & EncryptionFields["WPA_AKM_ENTERPRISE_FT"])): 113 | retStr += "Enterprise FT " 114 | 115 | retStr += "(" 116 | 117 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_CCMP"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_CCMP"])): 118 | retStr += "CCMP " 119 | 120 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_TKIP"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_TKIP"])): 121 | retStr += "TKIP " 122 | 123 | # Fix the code below - these never trigger. Make sure to set "return WEP" to retStr += WEP 124 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_WEP40"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_WEP40"])): 125 | retStr += "WEP40 " 126 | 127 | if ((encryptionType & EncryptionFields["WPA2_PAIRWISE_WEP104"]) or (encryptionType & EncryptionFields["WPA_PAIRWISE_WEP104"])): 128 | retStr += "WEP104 " 129 | 130 | retStr = retStr[:-1] 131 | retStr += ")" 132 | return retStr 133 | 134 | 135 | def run(self): 136 | try: 137 | self.conn = sqlite3.connect(self.database) 138 | except Error as e: 139 | print(e) 140 | return 141 | 142 | while True: 143 | if "scanID" not in subprocess.check_output(['/usr/bin/pineap', '/tmp/pineap.conf', 'get_status']): 144 | for websocket in self.args[0]: 145 | try: 146 | websocket.send_message(json.dumps({"scan_complete": True})) 147 | except Exception: 148 | pass 149 | thread.interrupt_main() 150 | 151 | self.createAPArray() 152 | self.createClientsArray() 153 | returnDictionary = {} 154 | returnDictionary["ap_list"] = self.realAPs 155 | returnDictionary["unassociated_clients"] = self.unassociatedClients 156 | returnDictionary["out_of_range_clients"] = self.outOfRangeClients 157 | returnDictionary["scan_complete"] = False 158 | 159 | for websocket in self.args[0]: 160 | try: 161 | websocket.send_message(json.dumps(returnDictionary)) 162 | except Exception, e: 163 | pass 164 | 165 | time.sleep(3) -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/api/reconpp.py: -------------------------------------------------------------------------------- 1 | import WebSocketsHandler 2 | import dbhandler 3 | import SocketServer 4 | import fcntl 5 | import sys 6 | import os 7 | import wsauth 8 | 9 | LOCKFILE = '/tmp/reconpp.lock' 10 | 11 | def acquire_lockfile(): 12 | with open(LOCKFILE, 'w') as f: 13 | try: 14 | fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) 15 | except Exception: 16 | return False 17 | return True 18 | 19 | def release_lockfile(): 20 | os.unlink(LOCKFILE) 21 | 22 | def main(): 23 | if len(sys.argv) < 3 or len(sys.argv) > 3: 24 | print("Usage: reconpp.py ") 25 | sys.exit(1) 26 | 27 | if not acquire_lockfile(): 28 | print("Failed to acquire lock file. Is reconpp already running?") 29 | sys.exit(1) 30 | 31 | wsauth.write_token() 32 | 33 | websockets = [] 34 | running = True 35 | 36 | database = sys.argv[1] 37 | scanID = sys.argv[2] 38 | 39 | dbHandler = dbhandler.DBHandler(args=(websockets, database, scanID)) 40 | dbHandler.setDaemon(True) 41 | 42 | SocketServer.ThreadingTCPServer.allow_reuse_address = 1 43 | server = SocketServer.ThreadingTCPServer(("", 1337), WebSocketsHandler.WebSocketsHandler) 44 | server.running = running 45 | server.websockets = websockets 46 | 47 | try: 48 | dbHandler.start() 49 | server.serve_forever() 50 | dbHandler.join() 51 | except KeyboardInterrupt: 52 | server.running = False 53 | server.server_close() 54 | finally: 55 | release_lockfile() 56 | wsauth.remove_token() 57 | 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/api/wsauth.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import os 4 | 5 | TOKENLEN = 64 6 | TOKENFILE = '/tmp/reconpp.token' 7 | 8 | def gen_token(n): 9 | return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(n)) 10 | 11 | def write_token(): 12 | if os.path.isfile(TOKENFILE): 13 | return 14 | with open(TOKENFILE, 'w') as f: 15 | f.write(gen_token(TOKENLEN)) 16 | 17 | def read_token(): 18 | with open(TOKENFILE) as f: 19 | return f.read() 20 | 21 | def remove_token(): 22 | os.unlink(TOKENFILE) 23 | 24 | def verify_token(token): 25 | return token == read_token() 26 | -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Paint a picture of the WiFi landscape.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Recon", 10 | "version": "1.3", 11 | "index": 2 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Recon/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Reporting/files/getlog.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php-cgi 2 | 1) { 9 | if ($argv[1] === "--probes") { 10 | $probesOnly = true; 11 | } 12 | } 13 | 14 | $logDBPath = exec("uci get pineap.@config[0].hostapd_db_path"); 15 | if (!file_exists($logDBPath)) { 16 | exit("File ${logDBPath} does not exist\n"); 17 | } 18 | $dbConnection = new DatabaseConnection($logDBPath); 19 | if ($dbConnection === NULL) { 20 | exit("Unable to create database connection\n"); 21 | } 22 | if (isset($dbConnection->error['databaseConnectionError'])) { 23 | exit($dbConnection->strError() . "\n"); 24 | } 25 | 26 | $sql = "SELECT * FROM log ORDER BY updated_at DESC;"; 27 | if ($probesOnly) { 28 | $sql = "SELECT * FROM log WHERE log_type=0 ORDER BY updated_at DESC;"; 29 | } 30 | $log = $dbConnection->query($sql); 31 | 32 | $clearlog = exec('uci get reporting.@settings[0].clear_log'); 33 | if ($clearlog == '1') { 34 | $dbConnection->exec('DELETE FROM log;'); 35 | } 36 | echo json_encode($log, JSON_PRETTY_PRINT); 37 | -------------------------------------------------------------------------------- /src/pineapple/modules/Reporting/files/reporting: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tmpdir="/tmp" 4 | force_email_flag=$1 5 | 6 | checkInternet() { 7 | ping -c1 -W3 -q 8.8.8.8 &> /dev/null && { 8 | echo "1" 9 | } || { 10 | echo "0" 11 | } 12 | } 13 | 14 | checkSDCard() { 15 | [[ $(mount | grep /sd) == "" ]] && { 16 | echo "1" 17 | } || { 18 | echo "0" 19 | } 20 | } 21 | 22 | sendEmail() { 23 | tar_gz=$1 24 | to=$(uci get reporting.@ssmtp[0].to) 25 | from=$(uci get reporting.@ssmtp[0].from) 26 | email_body="To:$to\nFrom:$from\nSubject:WiFi Pineapple Report: $(date)\n\nPlease see the attached file." 27 | 28 | date=$(date +%Y-%m-%d-%H:%M) 29 | 30 | echo -e $email_body | (cat - && cat $tar_gz | uuencode "WiFi_Pineapple_Report_$(date +%Y-%m-%d-%H-%M).tar.gz") | ssmtp $to &> /dev/null && { 31 | echo "$date: Successfully sent email to $to" >> /tmp/reporting.log 32 | } || { 33 | echo "$date: Failed to send email to $to" >> /tmp/reporting.log 34 | } 35 | } 36 | 37 | getPineAPReport() { 38 | [[ "$(uci get reporting.@settings[0].log)" == "1" ]] && { 39 | /pineapple/modules/Reporting/files/getlog.php > $tmpdir/report/pineap.log 40 | } 41 | } 42 | 43 | getClientReport() { 44 | [[ "$(uci get reporting.@settings[0].client)" == "1" ]] && { 45 | /pineapple/modules/Reporting/files/getlog.php --probes > $tmpdir/report/probing_clients.csv 46 | } 47 | } 48 | 49 | getSiteReport() { 50 | [[ "$(uci get reporting.@settings[0].survey)" == "1" ]] && { 51 | /etc/init.d/pineapd start 52 | sleep 3 53 | duration=$(uci get reporting.@settings[0].duration) 54 | scan_type=0 55 | cat /pineapple/config.php | grep "'tetra'" && scan_type=2 56 | echo -e "\tRunning scan type $scan_type for 15 seconds" 57 | (/usr/bin/pineap /tmp/pineap.conf run_scan $duration $scan_type 2>&1) >> /tmp/reporting.log 58 | sleep 2 59 | scan_id="$(/usr/bin/pineap /tmp/pineap.conf get_status | grep scanID | awk '{print $2}' | sed 's/,//')" 60 | echo -e "\tNew scan id: $scan_id" 61 | echo -e "\tWaiting for scan to finish" 62 | while [ "$(/usr/bin/pineap /tmp/pineap.conf get_status | grep 'scanRunning' | awk '{print $2}' | sed 's/,//')" == "true" ]; do sleep 1; done 63 | echo -e "\tRetrieving scan results, appending to debug log" 64 | chmod a+x /pineapple/modules/Help/files/dumpscan.php 65 | /pineapple/modules/Help/files/dumpscan.php $scan_id > $tmpdir/report/site_survey 66 | } 67 | } 68 | 69 | getTrackedClients() { 70 | [[ "$(uci get reporting.@settings[0].tracking)" == "1" ]] && { 71 | cp /tmp/tracking.report $tmpdir/report/tracked_clients &> /dev/null 72 | echo "" > /tmp/tracking.report 73 | } 74 | } 75 | 76 | generateReport() { 77 | rm -rf $tmpdir/report &> /dev/null 78 | mkdir -p $tmpdir/report &> /dev/null 79 | 80 | archive_name="WiFi_Pineapple_Report_$(date +%Y-%m-%d-%H-%M).tar.gz" 81 | 82 | echo getPineAPReport 83 | getPineAPReport 84 | echo getClientReport 85 | getClientReport 86 | echo getTrackedclients 87 | getTrackedClients 88 | echo getSiteReport 89 | getSiteReport 90 | 91 | echo tar 92 | tar -C $tmpdir -pczhf $tmpdir/$archive_name report 93 | 94 | [[ "$(uci get reporting.@settings[0].send_email)" == "1" ]] || [[ "$force_email_flag" == "force_email" ]] && { 95 | [[ "$(checkInternet)" == "1" ]] && { 96 | sendEmail $tmpdir/$archive_name 97 | } || { 98 | echo "$(date +%Y-%m-%d-%H:%M): Failed to email report - no internet connection available" >> /tmp/reporting.log 99 | } 100 | } 101 | 102 | [[ $(checkSDCard) == "1" ]] && { 103 | [[ "$(uci get reporting.@settings[0].save_report)" != "1" ]] && { 104 | rm -rf $tmpdir/$archive_name 105 | } || { 106 | mkdir -p /sd/wifipineapple_reports &> /dev/null 107 | mv $tmpdir/$archive_name /sd/wifipineapple_reports/$archive_name 108 | echo "$(date +%Y-%m-%d-%H:%M): Report saved to SD card" >> /tmp/reporting.log 109 | } 110 | } || { 111 | echo "$(date +%Y-%m-%d-%H:%M): Failed to save to SD card - no SD card found" >> /tmp/reporting.log 112 | } 113 | 114 | rm -rf $tmpdir/report &> /dev/null 115 | } 116 | 117 | tmpdir="/tmp" 118 | [[ $(checkSDCard) == "1" ]] && { 119 | tmpdir="/sd/tmp" 120 | } 121 | 122 | generateReport -------------------------------------------------------------------------------- /src/pineapple/modules/Reporting/js/module.js: -------------------------------------------------------------------------------- 1 | registerController('ReportConfigurationController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) { 2 | $scope.configSaved = false; 3 | $scope.sdDisabled = false; 4 | $scope.config = { 5 | generateReport: false, 6 | storeReport: false, 7 | sendReport: false, 8 | interval: 1 9 | }; 10 | 11 | $scope.saveConfiguration = (function() { 12 | $api.request({ 13 | module: 'Reporting', 14 | action: 'setReportConfiguration', 15 | config: $scope.config 16 | }, function(response) { 17 | if (response.error === undefined) { 18 | $scope.configSaved = true; 19 | $timeout(function() { 20 | $scope.configSaved = false; 21 | }, 2000); 22 | } 23 | }); 24 | }); 25 | 26 | $scope.getConfiguration = (function() { 27 | $api.request({ 28 | module: 'Reporting', 29 | action: 'getReportConfiguration' 30 | }, function(response) { 31 | $scope.config = response.config; 32 | $scope.sdDisabled = response.sdDisabled; 33 | }); 34 | }); 35 | 36 | $scope.getConfiguration(); 37 | }]); 38 | 39 | registerController('ReportContentController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) { 40 | $scope.configSaved = false; 41 | $scope.config = { 42 | pineAPLog: false, 43 | clearLog: false, 44 | siteSurvey: false, 45 | client: false, 46 | tracking: false, 47 | siteSurveyDuration: 15 48 | }; 49 | $scope.error = ""; 50 | 51 | $scope.saveConfiguration = (function() { 52 | $api.request({ 53 | module: 'Reporting', 54 | action: 'setReportContents', 55 | config: $scope.config 56 | }, function(response) { 57 | if (response.error === undefined) { 58 | $scope.configSaved = true; 59 | $timeout(function() { 60 | $scope.configSaved = false; 61 | }, 2000); 62 | } 63 | }); 64 | }); 65 | 66 | $api.request({ 67 | module: 'Reporting', 68 | action: 'getReportContents' 69 | }, function(response) { 70 | $scope.config = response.config 71 | }); 72 | }]); 73 | 74 | registerController('EmailConfigurationController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) { 75 | $scope.configSaved = false; 76 | $scope.testing = false; 77 | $scope.config = { 78 | from : "", 79 | to : "", 80 | server: "", 81 | port: "", 82 | domain: "", 83 | username: "", 84 | password: "", 85 | tls: true, 86 | starttls: true 87 | }; 88 | 89 | $scope.saveConfiguration = (function() { 90 | $api.request({ 91 | module: 'Reporting', 92 | action: 'setEmailConfiguration', 93 | config: $scope.config 94 | }, function(response) { 95 | if (response.error === undefined) { 96 | $scope.configSaved = true; 97 | $timeout(function() { 98 | $scope.configSaved = false; 99 | }, 2000); 100 | } 101 | }); 102 | }); 103 | 104 | $scope.testConfiguration = (function() { 105 | $scope.saveConfiguration(); 106 | 107 | if ($scope.config['from'] === "" || $scope.config['to'] === "" || $scope.config['server'] === "" || 108 | $scope.config['port'] === "" || $scope.config['domain'] === "" || $scope.config['username'] === "") { 109 | $scope.error = "You have not provided a correct configuration. Please check all fields and try again."; 110 | $timeout(function () { 111 | $scope.error = ""; 112 | }, 2000); 113 | } else { 114 | $api.request({ 115 | module: 'Reporting', 116 | action: 'testReportConfiguration' 117 | }, function (response) { 118 | if (response.error === undefined) { 119 | $scope.testing = true; 120 | $timeout(function () { 121 | $scope.testing = false; 122 | }, 2000); 123 | } 124 | }); 125 | } 126 | }); 127 | 128 | $api.request({ 129 | module: 'Reporting', 130 | action: 'getEmailConfiguration' 131 | }, function(response) { 132 | $scope.config = response.config; 133 | }); 134 | }]); 135 | -------------------------------------------------------------------------------- /src/pineapple/modules/Reporting/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Generate reports at specified intervals.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Reporting", 10 | "version": "1.1", 11 | "index": 9 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Reporting/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/modules/Tracking/api/module.php: -------------------------------------------------------------------------------- 1 | dbConnection = false; 13 | if (file_exists(self::DATABASE)) { 14 | $this->dbConnection = new DatabaseConnection(self::DATABASE); 15 | } 16 | } 17 | 18 | public function route() 19 | { 20 | switch ($this->request->action) { 21 | case 'getScript': 22 | $this->getScript(); 23 | break; 24 | 25 | case 'saveScript': 26 | $this->saveScript(); 27 | break; 28 | 29 | case 'getTrackingList': 30 | $this->getTrackingList(); 31 | break; 32 | 33 | case 'addMac': 34 | $this->addMac(); 35 | break; 36 | 37 | case 'removeMac': 38 | $this->removeMac(); 39 | break; 40 | 41 | case 'clearMacs': 42 | $this->clearMacs(); 43 | break; 44 | } 45 | } 46 | 47 | private function getScript() 48 | { 49 | $trackingScript = file_get_contents("/etc/pineapple/tracking_script_user"); 50 | $this->response = ["trackingScript" => $trackingScript]; 51 | } 52 | 53 | private function saveScript() 54 | { 55 | if (isset($this->request->trackingScript)) { 56 | file_put_contents("/etc/pineapple/tracking_script_user", $this->request->trackingScript); 57 | } 58 | $this->response = ["success" => true]; 59 | } 60 | 61 | private function getTrackingList() 62 | { 63 | $trackingList = ""; 64 | $result = $this->dbConnection->query("SELECT mac FROM tracking;"); 65 | 66 | foreach ($result as $row) { 67 | $trackingList .= $row['mac'] . "\n"; 68 | } 69 | $this->response = ["trackingList" => $trackingList]; 70 | } 71 | 72 | private function addMac() 73 | { 74 | if (isset($this->request->mac) && !empty($this->request->mac)) { 75 | $mac = strtoupper($this->request->mac); 76 | if(preg_match('^[a-fA-F0-9:]{17}|[a-fA-F0-9]{12}^', $mac)) { 77 | $this->dbConnection->exec("INSERT INTO tracking (mac) VALUES ('%s');", $mac); 78 | $this->getTrackingList(); 79 | } else { 80 | $this->error = "Please enter a valid MAC Address"; 81 | } 82 | } 83 | } 84 | 85 | private function removeMac() 86 | { 87 | if (isset($this->request->mac) && !empty($this->request->mac)) { 88 | $mac = strtoupper($this->request->mac); 89 | if(preg_match('^[a-fA-F0-9:]{17}|[a-fA-F0-9]{12}^', $mac)) { 90 | $this->dbConnection->exec("DELETE FROM tracking WHERE mac='%s' COLLATE NOCASE;", $mac); 91 | $this->getTrackingList(); 92 | } else { 93 | $this->error = "Please enter a valid MAC Address"; 94 | } 95 | } 96 | } 97 | 98 | private function clearMacs() 99 | { 100 | $this->dbConnection->exec("DELETE FROM tracking;"); 101 | $this->getTrackingList(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/pineapple/modules/Tracking/js/module.js: -------------------------------------------------------------------------------- 1 | registerController("TrackingListController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout) { 2 | $scope.trackingList = ""; 3 | $scope.mac = ""; 4 | $scope.error = ""; 5 | 6 | $api.request({ 7 | module: 'Tracking', 8 | action: 'getTrackingList' 9 | }, function(response) { 10 | if (response.error === undefined) { 11 | $scope.trackingList = response.trackingList.toUpperCase(); 12 | } 13 | }); 14 | 15 | $scope.addMac = (function() { 16 | $api.request({ 17 | module: 'Tracking', 18 | action: 'addMac', 19 | mac: convertMACAddress($scope.mac) 20 | }, function(response) { 21 | if (response.error === undefined) { 22 | $scope.trackingList = response.trackingList; 23 | $scope.mac = ""; 24 | $scope.error = ""; 25 | } else { 26 | $scope.error = response.error; 27 | $timeout(function() { 28 | $scope.error = ""; 29 | }, 2000); 30 | } 31 | }); 32 | }); 33 | 34 | $scope.removeMac = (function() { 35 | $api.request({ 36 | module: 'Tracking', 37 | action: 'removeMac', 38 | mac: convertMACAddress($scope.mac) 39 | }, function(response) { 40 | if (response.error === undefined) { 41 | $scope.trackingList = response.trackingList; 42 | $scope.mac = ""; 43 | } else { 44 | $scope.error = response.error; 45 | $timeout(function() { 46 | $scope.error = ""; 47 | }, 2000); 48 | } 49 | }); 50 | }); 51 | 52 | $scope.clearMacs = (function() { 53 | $api.request({ 54 | module: 'Tracking', 55 | action: 'clearMacs' 56 | }, function(response) { 57 | if (response.error === undefined) { 58 | $scope.trackingList = response.trackingList; 59 | } else { 60 | $scope.error = response.error; 61 | } 62 | }); 63 | }); 64 | }]); 65 | 66 | registerController("TrackingScriptController", ['$api', '$scope', '$timeout', function($api, $scope, $timeout) { 67 | $scope.trackingScript = ""; 68 | $scope.scriptSaved = false; 69 | 70 | $api.request({ 71 | module: 'Tracking', 72 | action: 'getScript' 73 | }, function(response) { 74 | if (response.error === undefined) { 75 | $scope.trackingScript = response.trackingScript; 76 | } 77 | }); 78 | 79 | $scope.saveScript = (function() { 80 | $api.request({ 81 | module: 'Tracking', 82 | action: 'saveScript', 83 | trackingScript: $scope.trackingScript 84 | }, function(response) { 85 | if (response.success === true) { 86 | $scope.scriptSaved = true; 87 | $timeout(function(){ 88 | $scope.scriptSaved = false; 89 | }, 2000); 90 | } 91 | }); 92 | }); 93 | }]); 94 | 95 | function getLineNumber(textarea) { 96 | var lineNumber = textarea.value.substr(0, textarea.selectionStart).split("\n").length; 97 | var mac = textarea.value.split("\n")[lineNumber-1].trim(); 98 | $("input[name='mac']").val(mac).trigger('input'); 99 | } -------------------------------------------------------------------------------- /src/pineapple/modules/Tracking/module.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

7 | Client Tracking List 8 | 9 | 12 | 15 | 16 |

17 |
18 |
19 |

20 | 21 |

22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 | {{ error }} 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |

Tracking Script

41 |
42 |
43 |

44 | 45 |

46 |

Script saved successfully

47 | 48 |
49 |
50 |
51 | 52 |
-------------------------------------------------------------------------------- /src/pineapple/modules/Tracking/module.info: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Hak5", 3 | "description": "Continuously scan for specified clients by MAC Addresses.", 4 | "devices": [ 5 | "nano", 6 | "tetra" 7 | ], 8 | "system": true, 9 | "title": "Tracking", 10 | "version": "1.0", 11 | "index": 4 12 | } 13 | -------------------------------------------------------------------------------- /src/pineapple/modules/Tracking/module_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pineapple/pineapple_version: -------------------------------------------------------------------------------- 1 | 2.9.2 2 | --------------------------------------------------------------------------------