Repository navigation
Expand file tree
/
Copy pathexecute_command.php
More file actions
129 lines (99 loc) · 3.67 KB
/
Copy pathexecute_command.php
File metadata and controls
129 lines (99 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<?php
declare(strict_types=1);
require_once('./include/init.php');
// Set JSON response header
header('Content-Type: application/json');
// Initialize response
$response = [
'success' => false,
'message' => '',
'log_file' => ''
];
try {
// Verify CSRF token
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
throw new Exception('Invalid CSRF token');
}
// Get and validate command
$cmd = $_POST['cmd'] ?? '';
if (empty($cmd)) {
throw new Exception('No command provided');
}
// Sanitize command (basic validation)
$cmd = trim($cmd);
if (strlen($cmd) > 5000) {
throw new Exception('Command too long (max 5000 characters)');
}
// Ensure command_logs directory exists with proper permissions
$command_logs_dir = __DIR__ . '/command_logs';
if (!file_exists($command_logs_dir)) {
if (!mkdir($command_logs_dir, 0755, true)) {
throw new Exception('Failed to create command_logs directory');
}
}
if (!is_writable($command_logs_dir)) {
if (!chmod($command_logs_dir, 0755)) {
throw new Exception('command_logs directory is not writable');
}
}
// Generate unique log filename
// Must end in .log — that is what the Actions page lists and what
// delete_log accepts. These were written as .txt, so every background
// command's output was invisible in the UI and could never be cleaned up.
$log_filename = date('Y-m-d_H-i-s') . '_' . substr(md5($cmd . microtime()), 0, 8) . '.log';
$log_path = $command_logs_dir . '/' . $log_filename;
$relative_log_path = './command_logs/' . $log_filename;
// Connect via SSH
$connection = @ssh2_connect('localhost', (int)SSH_PORT);
if ($connection === false) {
throw new Exception('Failed to connect to SSH server');
}
// Authenticate
if (!@ssh2_auth_password($connection, SSH_USER, SSH_PASS)) {
throw new Exception('SSH authentication failed');
}
// Prepare command with output redirection
$full_command = sprintf(
'%s > %s 2>&1 &',
$cmd,
escapeshellarg($log_path)
);
// Execute command
$stream = @ssh2_exec($connection, $full_command);
if ($stream === false) {
throw new Exception('Failed to execute command');
}
// Set stream to blocking mode to get immediate output
stream_set_blocking($stream, true);
// Get STDIO stream
$stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
// Read any immediate output
$immediate_output = stream_get_contents($stream_out);
// Close stream
fclose($stream_out);
// Close SSH connection
@ssh2_exec($connection, 'exit');
unset($connection);
// Wait a moment for log file to be created
usleep(100000); // 100ms
// Verify log file was created
if (!file_exists($log_path)) {
// Create empty log file if it doesn't exist
file_put_contents($log_path, "Command executed: " . date('Y-m-d H:i:s') . "\n");
}
$response['success'] = true;
$response['message'] = 'Command executed successfully in background';
$response['log_file'] = $relative_log_path;
if (!empty($immediate_output)) {
$response['immediate_output'] = trim($immediate_output);
}
} catch (Exception $e) {
$response['success'] = false;
$response['message'] = 'Error: ' . $e->getMessage();
// Log error for debugging
error_log('execute_command.php error: ' . $e->getMessage());
}
// Output JSON response
echo json_encode($response, JSON_PRETTY_PRINT);
exit;