<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ob_start();
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
class AdvancedSFTPConnector {
private $connection;
private $sftp;
private $debug_info = [];
public function __construct() {
if (!extension_loaded('ssh2')) {
throw new Exception('SSH2 extension is not installed');
}
}
public function connect($host, $port, $username, $password) {
try {
$this->debug_info = [];
$this->debug_info['php_version'] = PHP_VERSION;
$this->debug_info['ssh2_version'] = phpversion('ssh2');
$this->debug_info['openssl_version'] = defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'Unknown';
// Step 1: Establish SSH connection with more compatible settings
$this->debug_info['step'] = 'Attempting SSH connection';
$methods = [
'kex' => 'diffie-hellman-group14-sha256,diffie-hellman-group-exchange-sha256,diffie-hellman-group1-sha1',
'hostkey' => 'rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss',
'client_to_server' => [
'crypt' => 'aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc',
'mac' => 'hmac-sha2-256,hmac-sha1',
'comp' => 'none'
],
'server_to_client' => [
'crypt' => 'aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc',
'mac' => 'hmac-sha2-256,hmac-sha1',
'comp' => 'none'
]
];
$this->connection = ssh2_connect($host, $port, $methods);
if (!$this->connection) {
throw new Exception("SSH connection failed to {$host}:{$port}");
}
$this->debug_info['ssh_connected'] = true;
// Step 2: Get server banner and fingerprint
$this->debug_info['server_fingerprint'] = ssh2_fingerprint($this->connection);
// Step 3: Authentication
$this->debug_info['step'] = 'Attempting authentication';
$auth_methods = ssh2_auth_none($this->connection, $username);
$this->debug_info['available_auth_methods'] = $auth_methods;
if (!ssh2_auth_password($this->connection, $username, $password)) {
throw new Exception("Authentication failed for user {$username}. Available methods: " . implode(', ', $auth_methods));
}
$this->debug_info['auth_success'] = true;
// Step 4: Test basic SSH functionality first
$this->debug_info['step'] = 'Testing SSH exec';
$stream = ssh2_exec($this->connection, 'echo "SSH_OK"');
if ($stream) {
stream_set_blocking($stream, true);
$output = stream_get_contents($stream);
fclose($stream);
$this->debug_info['ssh_exec_test'] = trim($output);
}
// Step 5: Try different approaches for SFTP initialization
$this->debug_info['step'] = 'Initializing SFTP subsystem';
// Approach 1: Direct SFTP initialization
$this->sftp = ssh2_sftp($this->connection);
if ($this->sftp) {
$this->debug_info['sftp_method'] = 'direct';
return $this->testSftpConnection();
}
// Approach 2: Wait and retry
usleep(1000000); // 1 second
$this->sftp = ssh2_sftp($this->connection);
if ($this->sftp) {
$this->debug_info['sftp_method'] = 'delayed_retry';
return $this->testSftpConnection();
}
// Approach 3: Check if sftp-server exists and retry
$stream = ssh2_exec($this->connection, 'which sftp-server || echo "NOT_FOUND"');
if ($stream) {
stream_set_blocking($stream, true);
$sftp_server_path = trim(stream_get_contents($stream));
fclose($stream);
$this->debug_info['sftp_server_path'] = $sftp_server_path;
if ($sftp_server_path !== 'NOT_FOUND' && !empty($sftp_server_path)) {
usleep(500000);
$this->sftp = ssh2_sftp($this->connection);
if ($this->sftp) {
$this->debug_info['sftp_method'] = 'after_server_check';
return $this->testSftpConnection();
}
}
}
// Approach 4: Try with explicit subsystem request
$stream = ssh2_exec($this->connection, '/usr/lib/openssh/sftp-server');
if ($stream) {
fclose($stream);
usleep(500000);
$this->sftp = ssh2_sftp($this->connection);
if ($this->sftp) {
$this->debug_info['sftp_method'] = 'explicit_subsystem';
return $this->testSftpConnection();
}
}
throw new Exception("Could not initialize SFTP subsystem after multiple attempts. Debug info: " . json_encode($this->debug_info));
} catch (Exception $e) {
if ($this->connection) {
ssh2_disconnect($this->connection);
}
return $e->getMessage();
}
}
private function testSftpConnection() {
try {
// Test SFTP functionality
$this->debug_info['step'] = 'Testing SFTP functionality';
// Try to stat the home directory
$stat = ssh2_sftp_stat($this->sftp, '.');
if ($stat !== false) {
$this->debug_info['sftp_stat_home'] = 'success';
} else {
$this->debug_info['sftp_stat_home'] = 'failed';
}
// Try to stat the root directory
$stat_root = ssh2_sftp_stat($this->sftp, '/');
if ($stat_root !== false) {
$this->debug_info['sftp_stat_root'] = 'success';
} else {
$this->debug_info['sftp_stat_root'] = 'failed';
}
// Try to get realpath
$realpath = ssh2_sftp_realpath($this->sftp, '.');
if ($realpath !== false) {
$this->debug_info['sftp_realpath'] = $realpath;
} else {
$this->debug_info['sftp_realpath'] = 'failed';
}
$this->debug_info['sftp_initialized'] = true;
return true;
} catch (Exception $e) {
$this->debug_info['sftp_test_error'] = $e->getMessage();
throw new Exception("SFTP subsystem initialized but functionality test failed: " . $e->getMessage());
}
}
public function getDebugInfo() {
return $this->debug_info;
}
public function listDirectory($path = '.') {
try {
if (!$this->sftp) {
throw new Exception("SFTP not initialized");
}
// Normalize path
if (empty($path) || $path === '') {
$path = '.';
}
$handle = opendir("ssh2.sftp://{$this->sftp}{$path}");
if (!$handle) {
// Try with realpath
$realPath = ssh2_sftp_realpath($this->sftp, $path);
if ($realPath) {
$handle = opendir("ssh2.sftp://{$this->sftp}{$realPath}");
}
if (!$handle) {
throw new Exception("Could not open directory: {$path}");
}
}
$files = [];
while (($file = readdir($handle)) !== false) {
if ($file !== '.' && $file !== '..') {
$fullPath = rtrim($path, '/') . '/' . $file;
$stat = ssh2_sftp_stat($this->sftp, $fullPath);
if ($stat !== false) {
$isDir = ($stat['mode'] & 0x4000) == 0x4000;
$files[] = [
'name' => $file,
'path' => $fullPath,
'size' => $stat['size'] ?? 0,
'modified' => isset($stat['mtime']) ? date('Y-m-d H:i:s', $stat['mtime']) : 'Unknown',
'is_dir' => $isDir
];
}
}
}
closedir($handle);
usort($files, function($a, $b) {
if ($a['is_dir'] && !$b['is_dir']) return -1;
if (!$a['is_dir'] && $b['is_dir']) return 1;
return strcasecmp($a['name'], $b['name']);
});
return $files;
} catch (Exception $e) {
return false;
}
}
public function deleteFile($remotePath) {
try {
$stat = ssh2_sftp_stat($this->sftp, $remotePath);
if ($stat && ($stat['mode'] & 0x4000) == 0x4000) {
if (ssh2_sftp_rmdir($this->sftp, $remotePath)) {
return true;
}
return "Failed to delete directory (may not be empty)";
} else {
if (ssh2_sftp_unlink($this->sftp, $remotePath)) {
return true;
}
return "Failed to delete file";
}
} catch (Exception $e) {
return $e->getMessage();
}
}
public function createDirectory($path) {
try {
if (ssh2_sftp_mkdir($this->sftp, $path, 0755, true)) {
return true;
}
return "Failed to create directory";
} catch (Exception $e) {
return $e->getMessage();
}
}
public function disconnect() {
if ($this->connection) {
ssh2_disconnect($this->connection);
}
}
}
// Handle API requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
ob_clean();
echo json_encode([
'success' => false,
'message' => 'Invalid JSON input: ' . json_last_error_msg(),
]);
exit;
}
$action = $input['action'] ?? '';
$response = ['success' => false, 'message' => '', 'data' => null];
try {
if (extension_loaded('ssh2')) {
$connector = new AdvancedSFTPConnector();
$connectorType = 'SSH2 Extension';
} else {
throw new Exception('SSH2 extension not available');
}
switch ($action) {
case 'connect':
$result = $connector->connect(
$input['host'] ?? '',
$input['port'] ?? 22,
$input['username'] ?? '',
$input['password'] ?? ''
);
if ($result === true) {
session_start();
$_SESSION['sftp_connected'] = true;
$_SESSION['sftp_config'] = [
'host' => $input['host'],
'port' => $input['port'] ?? 22,
'username' => $input['username'],
'password' => $input['password']
];
$response['success'] = true;
$response['message'] = "Connected successfully via {$connectorType}";
$response['debug'] = $connector->getDebugInfo();
} else {
$response['message'] = $result;
$response['debug'] = $connector->getDebugInfo();
}
break;
case 'list':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
$connector = new AdvancedSFTPConnector();
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
$response['debug'] = $connector->getDebugInfo();
break;
}
$files = $connector->listDirectory($input['path'] ?? '/');
if ($files !== false) {
$response['success'] = true;
$response['data'] = $files;
} else {
$response['message'] = 'Failed to list directory';
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'delete':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
$connector = new AdvancedSFTPConnector();
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
break;
}
$result = $connector->deleteFile($input['path'] ?? '');
if ($result === true) {
$response['success'] = true;
$response['message'] = 'File deleted successfully';
} else {
$response['message'] = $result;
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'create_folder':
session_start();
if (isset($_SESSION['sftp_connected'])) {
$config = $_SESSION['sftp_config'];
$connector = new AdvancedSFTPConnector();
$connectResult = $connector->connect(
$config['host'],
$config['port'],
$config['username'],
$config['password']
);
if ($connectResult !== true) {
$response['message'] = 'Reconnection failed: ' . $connectResult;
break;
}
$result = $connector->createDirectory($input['path'] ?? '');
if ($result === true) {
$response['success'] = true;
$response['message'] = 'Directory created successfully';
} else {
$response['message'] = $result;
}
} else {
$response['message'] = 'Not connected to SFTP server';
}
break;
case 'disconnect':
session_start();
unset($_SESSION['sftp_connected']);
unset($_SESSION['sftp_config']);
if (isset($connector)) {
$connector->disconnect();
}
$response['success'] = true;
$response['message'] = 'Disconnected successfully';
break;
case 'test':
$response['success'] = true;
$response['message'] = 'SFTP connector responding';
$response['data'] = [
'ssh2_loaded' => extension_loaded('ssh2'),
'ssh2_version' => extension_loaded('ssh2') ? phpversion('ssh2') : 'N/A',
'php_version' => PHP_VERSION,
'openssl_version' => defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : 'N/A',
'time' => date('Y-m-d H:i:s')
];
break;
default:
$response['message'] = 'Invalid action: ' . $action;
}
} catch (Exception $e) {
$response['message'] = 'Server error: ' . $e->getMessage();
$response['debug'] = $e->getTraceAsString();
}
ob_clean();
echo json_encode($response);
} else {
ob_clean();
echo json_encode([
'success' => false,
'message' => 'Only POST requests are allowed'
]);
}
?>