├── .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 | 
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 |
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 |
--------------------------------------------------------------------------------