├── .gitignore ├── .gitmodules ├── Dockerfile ├── Gruntfile.js ├── Makefile ├── README.md ├── package.json ├── release └── index.php ├── screenshots └── main.png └── src ├── close.tag.php ├── css └── webconsole.css ├── html └── head.html ├── index.html ├── js └── webconsole.js ├── useful ├── .htaccess └── favicon.ico ├── webconsole.includes.php ├── webconsole.main.php ├── webconsole.php └── webconsole.settings.php /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_Store 3 | 4 | # Node.js 5 | node_modules 6 | 7 | # Release 8 | release/parts 9 | release/webconsole* 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/vendor/jquery.terminal"] 2 | path = src/vendor/jquery.terminal 3 | url = https://github.com/jcubic/jquery.terminal.git 4 | [submodule "src/vendor/eazy-jsonrpc"] 5 | path = src/vendor/eazy-jsonrpc 6 | url = https://github.com/sergeyfast/eazy-jsonrpc.git 7 | [submodule "src/vendor/normalize"] 8 | path = src/vendor/normalize 9 | url = https://github.com/necolas/normalize.css.git 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=7.4.9 2 | 3 | FROM debian:buster-20201012-slim as builder 4 | ARG WEBCONSOLE_VERSION=0.9.7 5 | 6 | RUN apt-get update && apt-get -y upgrade 7 | RUN apt-get install -y unzip=6.0-23+deb10u1 wget=1.20.1-1.1 8 | 9 | RUN wget https://github.com/nickola/web-console/releases/download/v${WEBCONSOLE_VERSION}/webconsole-${WEBCONSOLE_VERSION}.zip 10 | RUN unzip webconsole-${WEBCONSOLE_VERSION}.zip 11 | RUN sed 's/^$USER.*/$USER = getenv("USER");;/' /webconsole/webconsole.php \ 12 | | sed 's/^$PASSWORD.*/$PASSWORD = getenv("PASSWORD");/' \ 13 | > index.php 14 | 15 | FROM php:${PHP_VERSION}-apache-buster 16 | COPY --from=builder /index.php /var/www/html 17 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | concat: { 5 | php: { 6 | options: { 7 | process: function(source, path) { 8 | source = source.replace(/'dev';/g, "'';"); 9 | return source.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 10 | } 11 | }, 12 | src: [ 13 | 'src/webconsole.settings.php', 14 | 'src/vendor/eazy-jsonrpc/src/BaseJsonRpcServer.php', 15 | 'src/close.tag.php', 16 | 'src/webconsole.main.php' 17 | ], 18 | dest : 'release/parts/webconsole.html' 19 | }, 20 | css: { 21 | src: [ 22 | 'src/vendor/normalize/normalize.css', 23 | 'src/vendor/jquery.terminal/css/jquery.terminal-0.11.12.min.css', 24 | 'src/css/webconsole.css' 25 | ], 26 | dest: 'release/parts/all.css' 27 | }, 28 | js: { 29 | options: { 30 | process: function(source, path) { 31 | return source.replace('webconsole.php', ''); 32 | } 33 | }, 34 | src: [ 35 | 'src/vendor/jquery.terminal/js/jquery-1.7.1.min.js', 36 | 'src/vendor/jquery.terminal/js/jquery.mousewheel-min.js', 37 | 'src/vendor/jquery.terminal/js/jquery.terminal-0.11.12.min.js', 38 | 'src/js/webconsole.js' 39 | ], 40 | dest: 'release/parts/all.js' 41 | }, 42 | html: { 43 | options: { 44 | process: function(source, path) { 45 | return source.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 46 | } 47 | }, 48 | src: 'src/html/head.html', 49 | dest : 'release/parts/head.html' 50 | }, 51 | text: { 52 | options: { 53 | process: function(source, path) { 54 | // Images 55 | source = source.replace(/!.*\n\s*/g, ''); 56 | 57 | // Quotes 58 | source = source.replace(/`(\$[^`]+)`/g, '$1'); 59 | source = source.replace(/`/g, '"'); 60 | 61 | // Download link 62 | source = source.replace(/\[(Download)\]\([^\)]+\)\s*/g, '$1 '); 63 | 64 | // Other links 65 | source = source.replace(/\[([^\]]+)\]\s*/g, '$1 '); 66 | 67 | // Lists 68 | // source = source.replace(/\s+- /g, '\n - '); 69 | 70 | // Trailing spaces 71 | return source.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 72 | } 73 | }, 74 | src: 'README.md', 75 | dest : 'release/parts/README.txt' 76 | }, 77 | }, 78 | cssmin: { 79 | css: { 80 | options: {'keepSpecialComments': 0}, 81 | src: '<%= concat.css.dest %>', 82 | dest: 'release/parts/all.min.css' 83 | } 84 | }, 85 | uglify: { 86 | js: { 87 | options: {'preserveComments': false}, 88 | src: '<%= concat.js.dest %>', 89 | dest: 'release/parts/all.min.js' 90 | } 91 | }, 92 | preprocess: { 93 | php: { 94 | options: { 95 | context : { 96 | DATE: grunt.template.today('yyyy-mm-dd'), 97 | YEAR: grunt.template.today('yyyy'), 98 | VERSION: '<%= pkg.version %>' 99 | } 100 | }, 101 | src: 'release/parts/webconsole.html', 102 | dest: 'release/parts/webconsole.php' 103 | } 104 | }, 105 | replace: { 106 | php: { 107 | options: { 108 | patterns: [ 109 | { 110 | match: /__NO_LOGIN__/g, 111 | replacement: '' 112 | } 113 | ] 114 | }, 115 | src: 'release/parts/webconsole.php', 116 | dest: 'release/webconsole.php' 117 | } 118 | }, 119 | compress: { 120 | all: { 121 | options: { 122 | archive: 'release/webconsole-<%= pkg.version %>.zip' 123 | }, 124 | files: [ 125 | {expand: true, cwd: 'release/', src: 'webconsole.php', dest: 'webconsole/'}, 126 | {expand: true, cwd: 'release/parts/', src: 'README.txt', dest: 'webconsole/'}, 127 | {expand: true, cwd: 'src/', src: 'useful/*', dest: 'webconsole/'}, 128 | {expand: true, cwd: 'src/', src: 'useful/.*', dest: 'webconsole/'} 129 | ] 130 | } 131 | } 132 | }); 133 | 134 | grunt.loadNpmTasks('grunt-contrib-concat'); 135 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 136 | grunt.loadNpmTasks('grunt-contrib-uglify'); 137 | grunt.loadNpmTasks('grunt-contrib-compress'); 138 | grunt.loadNpmTasks('grunt-preprocess'); 139 | grunt.loadNpmTasks('grunt-replace'); 140 | 141 | grunt.registerTask('default', ['concat', 'cssmin', 'uglify', 'preprocess', 'replace', 'compress']); 142 | }; 143 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: serve-src 2 | 3 | serve-src: 4 | @php -S localhost:8000 -t src 5 | 6 | serve-release: 7 | @php -S localhost:8000 -t release 8 | 9 | build: 10 | @grunt 11 | 12 | pull: 13 | @git submodule foreach git pull 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Web Console is a web-based application that allows to execute shell commands on a server directly from a browser (web-based shell). 4 | The application is very light, does not require any database and can be installed and configured in about 3 minutes. 5 | 6 | If you like Web Console, please consider an opportunity to support it in [any amount of Bitcoin](https://www.blockchain.com/btc/address/1NeDa2nXJLi5A8AN2CerBSSWD363vjdWaX). 7 | 8 | ![Web Console](https://raw.github.com/nickola/web-console/master/screenshots/main.png) 9 | 10 | # Installation 11 | 12 | Installation process is really simple: 13 | 14 | - [Download](https://github.com/nickola/web-console/releases/download/v0.9.7/webconsole-0.9.7.zip) latest version of the Web Console. 15 | - Unpack archive and open file `webconsole.php` in your favorite text editor. 16 | - At the beginning of the file enter your `$USER` and `$PASSWORD` credentials, edit any other settings that you like (see description in the comments). 17 | - Upload changed `webconsole.php` file to the web server and open it in the browser. 18 | 19 | # Docker 20 | 21 | Build and start container: 22 | 23 | ``` 24 | docker build -t web-console . 25 | docker run --rm --name web-console -e USER=admin -e PASSWORD=password -p 8000:80 web-console 26 | ``` 27 | 28 | Now you can visit: http://localhost:8000 29 | 30 | # About author 31 | 32 | Web Console has been developed by [Nickolay Kovalev](http://nickola.ru). Also, various third-party components are used, see below. 33 | 34 | # Used components 35 | 36 | - jQuery JavaScript Library: https://github.com/jquery/jquery 37 | - jQuery Terminal Emulator: https://github.com/jcubic/jquery.terminal 38 | - jQuery Mouse Wheel Plugin: https://github.com/brandonaaron/jquery-mousewheel 39 | - PHP JSON-RPC 2.0 Server/Client Implementation: https://github.com/sergeyfast/eazy-jsonrpc 40 | - Normalize.css: https://github.com/necolas/normalize.css 41 | 42 | # URLs 43 | 44 | - GitHub: https://github.com/nickola/web-console 45 | - Website: http://nickola.ru/projects/web-console 46 | - Author: http://nickola.ru 47 | 48 | # License 49 | 50 | Web Console is licensed under [GNU LGPL Version 3](http://www.gnu.org/licenses/lgpl.html) license. 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebConsole", 3 | "version": "0.9.7", 4 | "devDependencies": { 5 | "grunt": "~0.4.2", 6 | "grunt-contrib-concat": "~0.3.0", 7 | "grunt-contrib-cssmin": "~0.7.0", 8 | "grunt-contrib-uglify": "~0.3.2", 9 | "grunt-contrib-compress": "~0.6.1", 10 | "grunt-preprocess": "~4.0.0", 11 | "grunt-replace": "~1.0.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release/index.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickola/web-console/a529462ca49c798595ff97299db4d800ca00325f/screenshots/main.png -------------------------------------------------------------------------------- /src/close.tag.php: -------------------------------------------------------------------------------- 1 | ?> 2 | -------------------------------------------------------------------------------- /src/css/webconsole.css: -------------------------------------------------------------------------------- 1 | body { background-color: #000000; } 2 | 3 | body, .terminal, .cmd, .terminal .terminal-output div div, .terminal .prompt { 4 | color: #cccccc; 5 | font-family: monospace, fixed; 6 | font-size: 15px; 7 | line-height: 18px; 8 | } 9 | 10 | a, a:hover, .terminal a, .terminal a:hover { color: #6c71c4; } 11 | 12 | .spaced { margin: 15px 0; } 13 | .spaced-top { margin: 15px 0 0 0; } 14 | .spaced-bottom { margin: 0 0 15px 0; } 15 | 16 | .configure { margin: 20px; } 17 | .configure .variable { color: #d33682; } 18 | .configure p, .configure ul { margin: 5px 0 0 0; } 19 | -------------------------------------------------------------------------------- /src/html/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Web Console 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Web Console (Development) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/js/webconsole.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | var settings = {'url': 'webconsole.php', 4 | 'prompt_path_length': 32, 5 | 'domain': document.domain || window.location.host, 6 | 'is_small_window': $(document).width() < 625 ? true : false}; 7 | var environment = {'user': '', 'hostname': '', 'path': ''}; 8 | var no_login = typeof(__NO_LOGIN__) !== 'undefined' ? __NO_LOGIN__ : false; 9 | var silent_mode = false; 10 | 11 | // Default banner 12 | var banner_main = "Web Console"; 13 | var banner_link = 'http://web-console.org'; 14 | var banner_extra = banner_link + '\n'; 15 | 16 | // Big banner 17 | if (!settings.is_small_window) { 18 | banner_main = "" + 19 | " _ _ _ _____ _ " + 20 | "\n | | | | | | / __ \\ | | " + 21 | "\n | | | | ___| |__ | / \\/ ___ _ __ ___ ___ | | ___ " + 22 | "\n | |/\\| |/ _ \\ '_ \\| | / _ \\| '_ \\/ __|/ _ \\| |/ _ \\ " + 23 | "\n \\ /\\ / __/ |_) | \\__/\\ (_) | | | \\__ \\ (_) | | __/ " + 24 | "\n \\/ \\/ \\___|____/ \\____/\\___/|_| |_|___/\\___/|_|\\___| " + 25 | ""; 26 | banner_extra = '\n ' + banner_link + '\n'; 27 | } 28 | 29 | // Output 30 | function show_output(output) { 31 | if (output) { 32 | if (typeof output === 'string') terminal.echo(output); 33 | else if (output instanceof Array) terminal.echo($.map(output, function(object) { 34 | return $.json_stringify(object); 35 | }).join(' ')); 36 | else if (typeof output === 'object') terminal.echo($.json_stringify(output)); 37 | else terminal.echo(output); 38 | } 39 | } 40 | 41 | // Prompt 42 | function make_prompt() { 43 | var path = environment.path; 44 | if (path && path.length > settings.prompt_path_length) 45 | path = '...' + path.slice(path.length - settings.prompt_path_length + 3); 46 | 47 | return '[[b;#d33682;]' + (environment.user || 'user') + ']' + 48 | '@[[b;#6c71c4;]' + (environment.hostname || settings.domain || 'web-console') + '] ' + 49 | (path || '~') + 50 | '$ '; 51 | } 52 | 53 | function update_prompt(terminal) { 54 | terminal.set_prompt(make_prompt()); 55 | } 56 | 57 | // Environment 58 | function update_environment(terminal, data) { 59 | if (data) { 60 | $.extend(environment, data); 61 | update_prompt(terminal); 62 | } 63 | } 64 | 65 | // Service 66 | function service(terminal, method, parameters, success, error, options) { 67 | options = $.extend({'pause': true}, options); 68 | if (options.pause) terminal.pause(); 69 | 70 | $.jrpc(settings.url, method, parameters, 71 | function(json) { 72 | if (options.pause) terminal.resume(); 73 | 74 | if (!json.error) { 75 | if (success) success(json.result); 76 | } 77 | else if (error) error(); 78 | else { 79 | var message = $.trim(json.error.message || ''); 80 | var data = $.trim(json.error.data || ''); 81 | 82 | if (!message && data) { 83 | message = data; 84 | data = ''; 85 | } 86 | 87 | terminal.error('[ERROR]' + 88 | ' RPC: ' + (message || 'Unknown error') + 89 | (data ? (" (" + data + ")") : '')); 90 | } 91 | }, 92 | function(xhr, status, error_data) { 93 | if (options.pause) terminal.resume(); 94 | 95 | if (error) error(); 96 | else { 97 | if (status !== 'abort') { 98 | var response = $.trim(xhr.responseText || ''); 99 | 100 | terminal.error('[ERROR]' + 101 | ' AJAX: ' + (status || 'Unknown error') + 102 | (response ? ("\nServer reponse:\n" + response) : '')); 103 | } 104 | } 105 | }); 106 | } 107 | 108 | function service_authenticated(terminal, method, parameters, success, error, options) { 109 | var token = terminal.token(); 110 | if (token) { 111 | var service_parameters = [token, environment]; 112 | if (parameters && parameters.length) 113 | service_parameters.push.apply(service_parameters, parameters); 114 | service(terminal, method, service_parameters, success, error, options); 115 | } 116 | else { 117 | // Should never happen 118 | terminal.error('[ERROR] Access denied (no authentication token found)'); 119 | } 120 | } 121 | 122 | // Interpreter 123 | function interpreter(command, terminal) { 124 | command = $.trim(command || ''); 125 | if (!command) return; 126 | 127 | var command_parsed = $.terminal.splitCommand(command), 128 | method = null, parameters = []; 129 | 130 | if (command_parsed.name.toLowerCase() === 'cd') { 131 | method = 'cd'; 132 | parameters = [command_parsed.args.length ? command_parsed.args[0] : '']; 133 | } 134 | else { 135 | method = 'run'; 136 | parameters = [command]; 137 | } 138 | 139 | if (method) { 140 | service_authenticated(terminal, method, parameters, function(result) { 141 | update_environment(terminal, result.environment); 142 | show_output(result.output); 143 | }); 144 | } 145 | } 146 | 147 | // Login 148 | function login(user, password, callback) { 149 | user = $.trim(user || ''); 150 | password = $.trim(password || ''); 151 | 152 | if (user && password) { 153 | service(terminal, 'login', [user, password], function(result) { 154 | if (result && result.token) { 155 | environment.user = user; 156 | update_environment(terminal, result.environment); 157 | show_output(result.output); 158 | callback(result.token); 159 | } 160 | else callback(null); 161 | }, 162 | function() { callback(null); }); 163 | } 164 | else callback(null); 165 | } 166 | 167 | // Completion 168 | function completion(terminal, pattern, callback) { 169 | var view = terminal.export_view(); 170 | var command = view.command.substring(0, view.position); 171 | 172 | service_authenticated(terminal, 'completion', [pattern, command], function(result) { 173 | show_output(result.output); 174 | 175 | if (result.completion && result.completion.length) { 176 | result.completion.reverse(); 177 | callback(result.completion); 178 | } 179 | }, null, {pause: false}); 180 | } 181 | 182 | // Logout 183 | function logout() { 184 | silent_mode = true; 185 | 186 | try { 187 | terminal.clear(); 188 | terminal.logout(); 189 | } 190 | catch (error) {} 191 | 192 | silent_mode = false; 193 | } 194 | 195 | // Terminal 196 | var terminal = $('body').terminal(interpreter, { 197 | login: !no_login ? login : false, 198 | prompt: make_prompt(), 199 | greetings: !no_login ? "You are authenticated" : "", 200 | tabcompletion: true, 201 | completion: completion, 202 | onBlur: function() { return false; }, 203 | exceptionHandler: function(exception) { 204 | if (!silent_mode) terminal.exception(exception); 205 | } 206 | }); 207 | 208 | // Logout 209 | if (no_login) terminal.set_token('NO_LOGIN'); 210 | else { 211 | logout(); 212 | $(window).unload(function() { logout(); }); 213 | } 214 | 215 | // Banner 216 | if (banner_main) terminal.echo(banner_main); 217 | if (banner_extra) terminal.echo(banner_extra); 218 | }); 219 | })(jQuery); 220 | -------------------------------------------------------------------------------- /src/useful/.htaccess: -------------------------------------------------------------------------------- 1 | # Example '.htaccess' for Web Console 2 | 3 | Options -Indexes 4 | 5 | 6 | RewriteEngine On 7 | RewriteRule ^/?$ %{REQUEST_URI}webconsole.php [L] 8 | RewriteRule !^(webconsole\.php|favicon\.ico)$ - [F] 9 | 10 | 11 | 12 | Order Deny,Allow 13 | Deny from all 14 | 15 | 16 | Order Allow,Deny 17 | Allow from all 18 | 19 | 20 | 21 | Order Allow,Deny 22 | Allow from all 23 | 24 | 25 | 26 | # Errors 27 | ErrorDocument 401 "401: Authorization Required 28 | ErrorDocument 403 "403: Access Forbidden 29 | ErrorDocument 404 "404: File not found 30 | ErrorDocument 500 "500: Internal Server Error 31 | -------------------------------------------------------------------------------- /src/useful/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickola/web-console/a529462ca49c798595ff97299db4d800ca00325f/src/useful/favicon.ico -------------------------------------------------------------------------------- /src/webconsole.includes.php: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/webconsole.main.php: -------------------------------------------------------------------------------- 1 | = 1) ? true : false; 9 | 10 | // Utilities 11 | function is_empty_string($string) { 12 | return strlen($string) <= 0; 13 | } 14 | 15 | function is_equal_strings($string1, $string2) { 16 | return strcmp($string1, $string2) == 0; 17 | } 18 | 19 | function get_hash($algorithm, $string) { 20 | return hash($algorithm, trim((string) $string)); 21 | } 22 | 23 | // Command execution 24 | function execute_command($command) { 25 | $descriptors = array( 26 | 0 => array('pipe', 'r'), // STDIN 27 | 1 => array('pipe', 'w'), // STDOUT 28 | 2 => array('pipe', 'w') // STDERR 29 | ); 30 | 31 | $process = proc_open($command . ' 2>&1', $descriptors, $pipes); 32 | if (!is_resource($process)) die("Can't execute command."); 33 | 34 | // Nothing to push to STDIN 35 | fclose($pipes[0]); 36 | 37 | $output = stream_get_contents($pipes[1]); 38 | fclose($pipes[1]); 39 | 40 | $error = stream_get_contents($pipes[2]); 41 | fclose($pipes[2]); 42 | 43 | // All pipes must be closed before "proc_close" 44 | $code = proc_close($process); 45 | 46 | return $output; 47 | } 48 | 49 | // Command parsing 50 | function parse_command($command) { 51 | $value = ltrim((string) $command); 52 | 53 | if (!is_empty_string($value)) { 54 | $values = explode(' ', $value); 55 | $values_total = count($values); 56 | 57 | if ($values_total > 1) { 58 | $value = $values[$values_total - 1]; 59 | 60 | for ($index = $values_total - 2; $index >= 0; $index--) { 61 | $value_item = $values[$index]; 62 | 63 | if (substr($value_item, -1) == '\\') $value = $value_item . ' ' . $value; 64 | else break; 65 | } 66 | } 67 | } 68 | 69 | return $value; 70 | } 71 | 72 | // RPC Server 73 | class WebConsoleRPCServer extends BaseJsonRpcServer { 74 | protected $home_directory = ''; 75 | 76 | private function error($message) { 77 | throw new Exception($message); 78 | } 79 | 80 | // Authentication 81 | private function authenticate_user($user, $password) { 82 | $user = trim((string) $user); 83 | $password = trim((string) $password); 84 | 85 | if ($user && $password) { 86 | global $ACCOUNTS, $PASSWORD_HASH_ALGORITHM; 87 | 88 | if (isset($ACCOUNTS[$user]) && !is_empty_string($ACCOUNTS[$user])) { 89 | if ($PASSWORD_HASH_ALGORITHM) $password = get_hash($PASSWORD_HASH_ALGORITHM, $password); 90 | 91 | if (is_equal_strings($password, $ACCOUNTS[$user])) 92 | return $user . ':' . get_hash('sha256', $password); 93 | } 94 | } 95 | 96 | throw new Exception("Incorrect user or password"); 97 | } 98 | 99 | private function authenticate_token($token) { 100 | global $NO_LOGIN; 101 | if ($NO_LOGIN) return true; 102 | 103 | $token = trim((string) $token); 104 | $token_parts = explode(':', $token, 2); 105 | 106 | if (count($token_parts) == 2) { 107 | $user = trim((string) $token_parts[0]); 108 | $password_hash = trim((string) $token_parts[1]); 109 | 110 | if ($user && $password_hash) { 111 | global $ACCOUNTS; 112 | 113 | if (isset($ACCOUNTS[$user]) && !is_empty_string($ACCOUNTS[$user])) { 114 | $real_password_hash = get_hash('sha256', $ACCOUNTS[$user]); 115 | if (is_equal_strings($password_hash, $real_password_hash)) return $user; 116 | } 117 | } 118 | } 119 | 120 | throw new Exception("Incorrect user or password"); 121 | } 122 | 123 | private function get_home_directory($user) { 124 | global $HOME_DIRECTORY; 125 | 126 | if (is_string($HOME_DIRECTORY)) { 127 | if (!is_empty_string($HOME_DIRECTORY)) return $HOME_DIRECTORY; 128 | } 129 | else if (is_string($user) && !is_empty_string($user) && isset($HOME_DIRECTORY[$user]) && !is_empty_string($HOME_DIRECTORY[$user])) 130 | return $HOME_DIRECTORY[$user]; 131 | 132 | return getcwd(); 133 | } 134 | 135 | // Environment 136 | private function get_environment() { 137 | $hostname = function_exists('gethostname') ? gethostname() : null; 138 | return array('path' => getcwd(), 'hostname' => $hostname); 139 | } 140 | 141 | private function set_environment($environment) { 142 | $environment = !empty($environment) ? (array) $environment : array(); 143 | $path = (isset($environment['path']) && !is_empty_string($environment['path'])) ? $environment['path'] : $this->home_directory; 144 | 145 | if (!is_empty_string($path)) { 146 | if (is_dir($path)) { 147 | if (!@chdir($path)) return array('output' => "Unable to change directory to current working directory, updating current directory", 148 | 'environment' => $this->get_environment()); 149 | } 150 | else return array('output' => "Current working directory not found, updating current directory", 151 | 'environment' => $this->get_environment()); 152 | } 153 | } 154 | 155 | // Initialization 156 | private function initialize($token, $environment) { 157 | $user = $this->authenticate_token($token); 158 | $this->home_directory = $this->get_home_directory($user); 159 | $result = $this->set_environment($environment); 160 | 161 | if ($result) return $result; 162 | } 163 | 164 | // Methods 165 | public function login($user, $password) { 166 | $result = array('token' => $this->authenticate_user($user, $password), 167 | 'environment' => $this->get_environment()); 168 | 169 | $home_directory = $this->get_home_directory($user); 170 | if (!is_empty_string($home_directory)) { 171 | if (is_dir($home_directory)) $result['environment']['path'] = $home_directory; 172 | else $result['output'] = "Home directory not found: ". $home_directory; 173 | } 174 | 175 | return $result; 176 | } 177 | 178 | public function cd($token, $environment, $path) { 179 | $result = $this->initialize($token, $environment); 180 | if ($result) return $result; 181 | 182 | $path = trim((string) $path); 183 | if (is_empty_string($path)) $path = $this->home_directory; 184 | 185 | if (!is_empty_string($path)) { 186 | if (is_dir($path)) { 187 | if (!@chdir($path)) return array('output' => "cd: ". $path . ": Unable to change directory"); 188 | } 189 | else return array('output' => "cd: ". $path . ": No such directory"); 190 | } 191 | 192 | return array('environment' => $this->get_environment()); 193 | } 194 | 195 | public function completion($token, $environment, $pattern, $command) { 196 | $result = $this->initialize($token, $environment); 197 | if ($result) return $result; 198 | 199 | $scan_path = ''; 200 | $completion_prefix = ''; 201 | $completion = array(); 202 | 203 | if (!empty($pattern)) { 204 | if (!is_dir($pattern)) { 205 | $pattern = dirname($pattern); 206 | if ($pattern == '.') $pattern = ''; 207 | } 208 | 209 | if (!empty($pattern)) { 210 | if (is_dir($pattern)) { 211 | $scan_path = $completion_prefix = $pattern; 212 | if (substr($completion_prefix, -1) != '/') $completion_prefix .= '/'; 213 | } 214 | } 215 | else $scan_path = getcwd(); 216 | } 217 | else $scan_path = getcwd(); 218 | 219 | if (!empty($scan_path)) { 220 | // Loading directory listing 221 | $completion = array_values(array_diff(scandir($scan_path), array('..', '.'))); 222 | natsort($completion); 223 | 224 | // Prefix 225 | if (!empty($completion_prefix) && !empty($completion)) { 226 | foreach ($completion as &$value) $value = $completion_prefix . $value; 227 | } 228 | 229 | // Pattern 230 | if (!empty($pattern) && !empty($completion)) { 231 | // For PHP version that does not support anonymous functions (available since PHP 5.3.0) 232 | function filter_pattern($value) { 233 | global $pattern; 234 | return !strncmp($pattern, $value, strlen($pattern)); 235 | } 236 | 237 | $completion = array_values(array_filter($completion, 'filter_pattern')); 238 | } 239 | } 240 | 241 | return array('completion' => $completion); 242 | } 243 | 244 | public function run($token, $environment, $command) { 245 | $result = $this->initialize($token, $environment); 246 | if ($result) return $result; 247 | 248 | $output = ($command && !is_empty_string($command)) ? execute_command($command) : ''; 249 | if ($output && substr($output, -1) == "\n") $output = substr($output, 0, -1); 250 | 251 | return array('output' => $output); 252 | } 253 | } 254 | 255 | // Processing request 256 | if (array_key_exists('REQUEST_METHOD', $_SERVER) && $_SERVER['REQUEST_METHOD'] == 'POST') { 257 | $rpc_server = new WebConsoleRPCServer(); 258 | $rpc_server->Execute(); 259 | } 260 | else if (!$IS_CONFIGURED) { 261 | ?> 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
270 |

Web Console must be configured before use:

271 | 276 |

For more information visit Web Console website: http://web-console.org

277 |
278 | 279 | 280 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /src/webconsole.php: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/webconsole.settings.php: -------------------------------------------------------------------------------- 1 | () 3 | // 4 | // Author: Nickolay Kovalev (http://nickola.ru) 5 | // GitHub: https://github.com/nickola/web-console 6 | // URL: http://web-console.org 7 | 8 | // Disable login (don't ask for credentials, be careful) 9 | // Example: $NO_LOGIN = true; 10 | $NO_LOGIN = false; 11 | 12 | // Single-user credentials 13 | // Example: $USER = 'user'; $PASSWORD = 'password'; 14 | $USER = 'dev'; 15 | $PASSWORD = 'dev'; 16 | 17 | // Multi-user credentials 18 | // Example: $ACCOUNTS = array('user1' => 'password1', 'user2' => 'password2'); 19 | $ACCOUNTS = array(); 20 | 21 | // Password hash algorithm (password must be hashed) 22 | // Example: $PASSWORD_HASH_ALGORITHM = 'md5'; 23 | // $PASSWORD_HASH_ALGORITHM = 'sha256'; 24 | $PASSWORD_HASH_ALGORITHM = ''; 25 | 26 | // Home directory (multi-user mode supported) 27 | // Example: $HOME_DIRECTORY = '/tmp'; 28 | // $HOME_DIRECTORY = array('user1' => '/home/user1', 'user2' => '/home/user2'); 29 | $HOME_DIRECTORY = ''; 30 | 31 | // Code below is automatically generated from different components 32 | // For more information see: https://github.com/nickola/web-console 33 | // 34 | // Used components: 35 | // - jQuery JavaScript Library: https://github.com/jquery/jquery 36 | // - jQuery Terminal Emulator: https://github.com/jcubic/jquery.terminal 37 | // - jQuery Mouse Wheel Plugin: https://github.com/brandonaaron/jquery-mousewheel 38 | // - PHP JSON-RPC 2.0 Server/Client Implementation: https://github.com/sergeyfast/eazy-jsonrpc 39 | // - Normalize.css: https://github.com/necolas/normalize.css 40 | ?> 41 | --------------------------------------------------------------------------------