comportamento sftp come ftp
This commit is contained in:
@@ -113,8 +113,23 @@ class DatabaseAuthorizer(DummyAuthorizer):
|
|||||||
|
|
||||||
# Temporarily add user to in-memory table for this session
|
# Temporarily add user to in-memory table for this session
|
||||||
# This allows pyftpdlib to work correctly for the duration of the session
|
# This allows pyftpdlib to work correctly for the duration of the session
|
||||||
if username not in self.user_table:
|
# We add/update directly to avoid issues with add_user() checking if user exists
|
||||||
self.add_user(ftpuser, stored_hash, virtpath, perm)
|
if username in self.user_table:
|
||||||
|
# User already exists, just update credentials
|
||||||
|
self.user_table[username]['pwd'] = stored_hash
|
||||||
|
self.user_table[username]['home'] = virtpath
|
||||||
|
self.user_table[username]['perm'] = perm
|
||||||
|
self.user_table[username]['operms'] = {}
|
||||||
|
else:
|
||||||
|
# User doesn't exist, add to table directly with all required fields
|
||||||
|
self.user_table[username] = {
|
||||||
|
'pwd': stored_hash,
|
||||||
|
'home': virtpath,
|
||||||
|
'perm': perm,
|
||||||
|
'operms': {}, # Optional per-directory permissions
|
||||||
|
'msg_login': '230 Login successful.',
|
||||||
|
'msg_quit': '221 Goodbye.'
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(f"Successful login for user: {username}")
|
logger.info(f"Successful login for user: {username}")
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
def __init__(self, cfg):
|
def __init__(self, cfg):
|
||||||
"""Initialize SSH server with configuration."""
|
"""Initialize SSH server with configuration."""
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
|
self.user_home_dirs = {} # Store user home directories after authentication
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def connection_made(self, conn):
|
def connection_made(self, conn):
|
||||||
"""Called when connection is established."""
|
"""Called when connection is established."""
|
||||||
# Store config in connection for later use
|
# Store config in connection for later use
|
||||||
conn._cfg = self.cfg
|
conn._cfg = self.cfg
|
||||||
|
conn._ssh_server = self # Store reference to server for accessing user_home_dirs
|
||||||
logger.info(f"SSH connection from {conn.get_extra_info('peername')[0]}")
|
logger.info(f"SSH connection from {conn.get_extra_info('peername')[0]}")
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
@@ -62,7 +64,9 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
# Check if user is admin
|
# Check if user is admin
|
||||||
if username == self.cfg.adminuser[0]:
|
if username == self.cfg.adminuser[0]:
|
||||||
if self.cfg.adminuser[1] == password_hash:
|
if self.cfg.adminuser[1] == password_hash:
|
||||||
logger.info(f"Admin user '{username}' authenticated successfully")
|
# Store admin home directory
|
||||||
|
self.user_home_dirs[username] = self.cfg.adminuser[2]
|
||||||
|
logger.info(f"Admin user '{username}' authenticated successfully (home: {self.cfg.adminuser[2]})")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Failed admin login attempt for user: {username}")
|
logger.warning(f"Failed admin login attempt for user: {username}")
|
||||||
@@ -106,7 +110,10 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
logger.error(f"Failed to create directory for user {username}: {e}")
|
logger.error(f"Failed to create directory for user {username}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"Successful SFTP login for user: {username}")
|
# Store the user's home directory for chroot
|
||||||
|
self.user_home_dirs[username] = virtpath
|
||||||
|
|
||||||
|
logger.info(f"Successful SFTP login for user: {username} (home: {virtpath})")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -127,39 +134,79 @@ class SFTPFileHandler(asyncssh.SFTPServer):
|
|||||||
"""Extended SFTP server with file upload handling."""
|
"""Extended SFTP server with file upload handling."""
|
||||||
|
|
||||||
def __init__(self, chan):
|
def __init__(self, chan):
|
||||||
super().__init__(chan)
|
super().__init__(chan, chroot=self._get_user_home(chan))
|
||||||
self.cfg = chan.get_connection()._cfg
|
self.cfg = chan.get_connection()._cfg
|
||||||
|
self._open_files = {} # Track open files for processing
|
||||||
|
|
||||||
async def close(self):
|
@staticmethod
|
||||||
"""Handle session close."""
|
def _get_user_home(chan):
|
||||||
await super().close()
|
"""Get the home directory for the authenticated user."""
|
||||||
|
conn = chan.get_connection()
|
||||||
|
username = conn.get_extra_info('username')
|
||||||
|
ssh_server = getattr(conn, '_ssh_server', None)
|
||||||
|
|
||||||
# Override file operations to add custom handling
|
if ssh_server and username in ssh_server.user_home_dirs:
|
||||||
async def rename(self, oldpath, newpath):
|
return ssh_server.user_home_dirs[username]
|
||||||
"""
|
|
||||||
Handle file rename/move - called when upload completes.
|
|
||||||
This is where we trigger the CSV processing like in FTP.
|
|
||||||
"""
|
|
||||||
result = await super().rename(oldpath, newpath)
|
|
||||||
|
|
||||||
# Check if it's a CSV file that was uploaded
|
# Fallback for admin user
|
||||||
if newpath.lower().endswith('.csv'):
|
if hasattr(conn, '_cfg') and username == conn._cfg.adminuser[0]:
|
||||||
try:
|
return conn._cfg.adminuser[2]
|
||||||
# Trigger file processing (same as FTP on_file_received)
|
|
||||||
logger.info(f"CSV file uploaded via SFTP: {newpath}")
|
|
||||||
# Create a mock handler object with required attributes
|
|
||||||
mock_handler = type('obj', (object,), {
|
|
||||||
'cfg': self.cfg,
|
|
||||||
'username': self._chan.get_connection().get_extra_info('username')
|
|
||||||
})()
|
|
||||||
|
|
||||||
# Call the same file_management function used by FTP
|
return None
|
||||||
file_management.on_file_received(mock_handler, newpath)
|
|
||||||
except Exception as e:
|
def open(self, path, pflags, attrs):
|
||||||
logger.error(f"Error processing SFTP uploaded file {newpath}: {e}", exc_info=True)
|
"""Track files being opened for writing."""
|
||||||
|
result = super().open(path, pflags, attrs)
|
||||||
|
|
||||||
|
# If file is opened for writing (pflags contains FXF_WRITE)
|
||||||
|
if pflags & 0x02: # FXF_WRITE flag
|
||||||
|
real_path = self.map_path(path)
|
||||||
|
# Convert bytes to str if necessary
|
||||||
|
if isinstance(real_path, bytes):
|
||||||
|
real_path = real_path.decode('utf-8')
|
||||||
|
self._open_files[result] = real_path
|
||||||
|
logger.debug(f"File opened for writing: {real_path}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def close(self, file_obj):
|
||||||
|
"""Process file after it's closed."""
|
||||||
|
# Call parent close first (this doesn't return anything useful)
|
||||||
|
result = super().close(file_obj)
|
||||||
|
|
||||||
|
# Check if this file was tracked
|
||||||
|
if file_obj in self._open_files:
|
||||||
|
filepath = self._open_files.pop(file_obj)
|
||||||
|
|
||||||
|
# Process CSV files
|
||||||
|
if filepath.lower().endswith('.csv'):
|
||||||
|
try:
|
||||||
|
logger.info(f"CSV file closed after upload via SFTP: {filepath}")
|
||||||
|
|
||||||
|
# Get username
|
||||||
|
username = self._chan.get_connection().get_extra_info('username')
|
||||||
|
|
||||||
|
# Create a mock handler object with required attributes
|
||||||
|
mock_handler = type('obj', (object,), {
|
||||||
|
'cfg': self.cfg,
|
||||||
|
'username': username
|
||||||
|
})()
|
||||||
|
|
||||||
|
# Call the file processing function
|
||||||
|
from utils.connect import file_management
|
||||||
|
await file_management.on_file_received_async(mock_handler, filepath)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing SFTP file on close: {e}", exc_info=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def exit(self):
|
||||||
|
"""Handle session close."""
|
||||||
|
await super().exit()
|
||||||
|
|
||||||
|
# Note: File processing is handled in close() method, not here
|
||||||
|
# This avoids double-processing when both close and rename are called
|
||||||
|
|
||||||
|
|
||||||
async def start_sftp_server(cfg, host='0.0.0.0', port=22):
|
async def start_sftp_server(cfg, host='0.0.0.0', port=22):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -113,8 +113,23 @@ class DatabaseAuthorizer(DummyAuthorizer):
|
|||||||
|
|
||||||
# Temporarily add user to in-memory table for this session
|
# Temporarily add user to in-memory table for this session
|
||||||
# This allows pyftpdlib to work correctly for the duration of the session
|
# This allows pyftpdlib to work correctly for the duration of the session
|
||||||
if username not in self.user_table:
|
# We add/update directly to avoid issues with add_user() checking if user exists
|
||||||
self.add_user(ftpuser, stored_hash, virtpath, perm)
|
if username in self.user_table:
|
||||||
|
# User already exists, just update credentials
|
||||||
|
self.user_table[username]['pwd'] = stored_hash
|
||||||
|
self.user_table[username]['home'] = virtpath
|
||||||
|
self.user_table[username]['perm'] = perm
|
||||||
|
self.user_table[username]['operms'] = {}
|
||||||
|
else:
|
||||||
|
# User doesn't exist, add to table directly with all required fields
|
||||||
|
self.user_table[username] = {
|
||||||
|
'pwd': stored_hash,
|
||||||
|
'home': virtpath,
|
||||||
|
'perm': perm,
|
||||||
|
'operms': {}, # Optional per-directory permissions
|
||||||
|
'msg_login': '230 Login successful.',
|
||||||
|
'msg_quit': '221 Goodbye.'
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(f"Successful login for user: {username}")
|
logger.info(f"Successful login for user: {username}")
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
def __init__(self, cfg):
|
def __init__(self, cfg):
|
||||||
"""Initialize SSH server with configuration."""
|
"""Initialize SSH server with configuration."""
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
|
self.user_home_dirs = {} # Store user home directories after authentication
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def connection_made(self, conn):
|
def connection_made(self, conn):
|
||||||
"""Called when connection is established."""
|
"""Called when connection is established."""
|
||||||
# Store config in connection for later use
|
# Store config in connection for later use
|
||||||
conn._cfg = self.cfg
|
conn._cfg = self.cfg
|
||||||
|
conn._ssh_server = self # Store reference to server for accessing user_home_dirs
|
||||||
logger.info(f"SSH connection from {conn.get_extra_info('peername')[0]}")
|
logger.info(f"SSH connection from {conn.get_extra_info('peername')[0]}")
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
@@ -62,7 +64,9 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
# Check if user is admin
|
# Check if user is admin
|
||||||
if username == self.cfg.adminuser[0]:
|
if username == self.cfg.adminuser[0]:
|
||||||
if self.cfg.adminuser[1] == password_hash:
|
if self.cfg.adminuser[1] == password_hash:
|
||||||
logger.info(f"Admin user '{username}' authenticated successfully")
|
# Store admin home directory
|
||||||
|
self.user_home_dirs[username] = self.cfg.adminuser[2]
|
||||||
|
logger.info(f"Admin user '{username}' authenticated successfully (home: {self.cfg.adminuser[2]})")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Failed admin login attempt for user: {username}")
|
logger.warning(f"Failed admin login attempt for user: {username}")
|
||||||
@@ -106,7 +110,10 @@ class ASESSHServer(asyncssh.SSHServer):
|
|||||||
logger.error(f"Failed to create directory for user {username}: {e}")
|
logger.error(f"Failed to create directory for user {username}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"Successful SFTP login for user: {username}")
|
# Store the user's home directory for chroot
|
||||||
|
self.user_home_dirs[username] = virtpath
|
||||||
|
|
||||||
|
logger.info(f"Successful SFTP login for user: {username} (home: {virtpath})")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -127,39 +134,79 @@ class SFTPFileHandler(asyncssh.SFTPServer):
|
|||||||
"""Extended SFTP server with file upload handling."""
|
"""Extended SFTP server with file upload handling."""
|
||||||
|
|
||||||
def __init__(self, chan):
|
def __init__(self, chan):
|
||||||
super().__init__(chan)
|
super().__init__(chan, chroot=self._get_user_home(chan))
|
||||||
self.cfg = chan.get_connection()._cfg
|
self.cfg = chan.get_connection()._cfg
|
||||||
|
self._open_files = {} # Track open files for processing
|
||||||
|
|
||||||
async def close(self):
|
@staticmethod
|
||||||
"""Handle session close."""
|
def _get_user_home(chan):
|
||||||
await super().close()
|
"""Get the home directory for the authenticated user."""
|
||||||
|
conn = chan.get_connection()
|
||||||
|
username = conn.get_extra_info('username')
|
||||||
|
ssh_server = getattr(conn, '_ssh_server', None)
|
||||||
|
|
||||||
# Override file operations to add custom handling
|
if ssh_server and username in ssh_server.user_home_dirs:
|
||||||
async def rename(self, oldpath, newpath):
|
return ssh_server.user_home_dirs[username]
|
||||||
"""
|
|
||||||
Handle file rename/move - called when upload completes.
|
|
||||||
This is where we trigger the CSV processing like in FTP.
|
|
||||||
"""
|
|
||||||
result = await super().rename(oldpath, newpath)
|
|
||||||
|
|
||||||
# Check if it's a CSV file that was uploaded
|
# Fallback for admin user
|
||||||
if newpath.lower().endswith('.csv'):
|
if hasattr(conn, '_cfg') and username == conn._cfg.adminuser[0]:
|
||||||
try:
|
return conn._cfg.adminuser[2]
|
||||||
# Trigger file processing (same as FTP on_file_received)
|
|
||||||
logger.info(f"CSV file uploaded via SFTP: {newpath}")
|
|
||||||
# Create a mock handler object with required attributes
|
|
||||||
mock_handler = type('obj', (object,), {
|
|
||||||
'cfg': self.cfg,
|
|
||||||
'username': self._chan.get_connection().get_extra_info('username')
|
|
||||||
})()
|
|
||||||
|
|
||||||
# Call the same file_management function used by FTP
|
return None
|
||||||
file_management.on_file_received(mock_handler, newpath)
|
|
||||||
except Exception as e:
|
def open(self, path, pflags, attrs):
|
||||||
logger.error(f"Error processing SFTP uploaded file {newpath}: {e}", exc_info=True)
|
"""Track files being opened for writing."""
|
||||||
|
result = super().open(path, pflags, attrs)
|
||||||
|
|
||||||
|
# If file is opened for writing (pflags contains FXF_WRITE)
|
||||||
|
if pflags & 0x02: # FXF_WRITE flag
|
||||||
|
real_path = self.map_path(path)
|
||||||
|
# Convert bytes to str if necessary
|
||||||
|
if isinstance(real_path, bytes):
|
||||||
|
real_path = real_path.decode('utf-8')
|
||||||
|
self._open_files[result] = real_path
|
||||||
|
logger.debug(f"File opened for writing: {real_path}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def close(self, file_obj):
|
||||||
|
"""Process file after it's closed."""
|
||||||
|
# Call parent close first (this doesn't return anything useful)
|
||||||
|
result = super().close(file_obj)
|
||||||
|
|
||||||
|
# Check if this file was tracked
|
||||||
|
if file_obj in self._open_files:
|
||||||
|
filepath = self._open_files.pop(file_obj)
|
||||||
|
|
||||||
|
# Process CSV files
|
||||||
|
if filepath.lower().endswith('.csv'):
|
||||||
|
try:
|
||||||
|
logger.info(f"CSV file closed after upload via SFTP: {filepath}")
|
||||||
|
|
||||||
|
# Get username
|
||||||
|
username = self._chan.get_connection().get_extra_info('username')
|
||||||
|
|
||||||
|
# Create a mock handler object with required attributes
|
||||||
|
mock_handler = type('obj', (object,), {
|
||||||
|
'cfg': self.cfg,
|
||||||
|
'username': username
|
||||||
|
})()
|
||||||
|
|
||||||
|
# Call the file processing function
|
||||||
|
from utils.connect import file_management
|
||||||
|
await file_management.on_file_received_async(mock_handler, filepath)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing SFTP file on close: {e}", exc_info=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def exit(self):
|
||||||
|
"""Handle session close."""
|
||||||
|
await super().exit()
|
||||||
|
|
||||||
|
# Note: File processing is handled in close() method, not here
|
||||||
|
# This avoids double-processing when both close and rename are called
|
||||||
|
|
||||||
|
|
||||||
async def start_sftp_server(cfg, host='0.0.0.0', port=22):
|
async def start_sftp_server(cfg, host='0.0.0.0', port=22):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user