No Global IP whitelist - fixing failed auth response hang, fixing sender authentication.
This commit is contained in:
@@ -32,7 +32,7 @@ class EnhancedAuthenticator:
|
||||
def __call__(self, server, session, envelope, mechanism, auth_data):
|
||||
if not isinstance(auth_data, LoginPassword):
|
||||
logger.warning(f'Invalid auth data format: {type(auth_data)}')
|
||||
return AuthResult(success=False, handled=True, message='535 Authentication failed')
|
||||
return AuthResult(success=False, handled=False, message='535 Authentication failed')
|
||||
|
||||
# Decode bytes to string if necessary
|
||||
username = auth_data.login
|
||||
@@ -73,7 +73,7 @@ class EnhancedAuthenticator:
|
||||
message=f'Invalid credentials for {username}'
|
||||
)
|
||||
logger.warning(f'Authentication failed for {username}: invalid credentials')
|
||||
return AuthResult(success=False, handled=True, message='535 Authentication failed')
|
||||
return AuthResult(success=False, handled=False, message='535 Authentication failed')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Authentication error for {username}: {e}')
|
||||
@@ -84,7 +84,7 @@ class EnhancedAuthenticator:
|
||||
success=False,
|
||||
message=f'Authentication error: {str(e)}'
|
||||
)
|
||||
return AuthResult(success=False, handled=True, message='451 Internal server error')
|
||||
return AuthResult(success=False, handled=False, message='451 Internal server error')
|
||||
|
||||
class EnhancedIPAuthenticator:
|
||||
"""
|
||||
|
||||
@@ -117,7 +117,7 @@ async def start_server(shutdown_event=None):
|
||||
)
|
||||
controller_tls.start()
|
||||
logger.debug(f' - Plain SMTP (IP whitelist): {BIND_IP}:{SMTP_PORT}')
|
||||
logger.debug(f' - STARTTLS SMTP (auth required): {BIND_IP}:{SMTP_TLS_PORT}')
|
||||
logger.debug(f' - Direct TLS SMTP (SMTPS, auth required): {BIND_IP}:{SMTP_TLS_PORT}')
|
||||
logger.debug('Management available via web interface at: http://localhost:5000/email')
|
||||
|
||||
try:
|
||||
|
||||
@@ -14,8 +14,8 @@ DEFAULTS = {
|
||||
'; Server configuration for SMTP ports and hostname': None,
|
||||
'; Plain SMTP port for internal/whitelisted IPs': None,
|
||||
'SMTP_PORT': '4025',
|
||||
'; STARTTLS SMTP port for authenticated users': None,
|
||||
'SMTP_TLS_PORT': '40587',
|
||||
'; TLS SMTP port for authenticated users': None,
|
||||
'SMTP_TLS_PORT': '40465',
|
||||
'; Server hostname for HELO/EHLO identification': None,
|
||||
'HOSTNAME': 'mail.example.com',
|
||||
'; Override HELO hostname': None,
|
||||
|
||||
@@ -21,7 +21,7 @@ from email_server.tool_box import get_logger
|
||||
logger = get_logger()
|
||||
|
||||
class CustomSMTP(AIOSMTP):
|
||||
"""Custom SMTP class with configurable banner."""
|
||||
"""Custom SMTP class with configurable banner and secure AUTH handling."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Sets Custom SMTP banner from settings
|
||||
@@ -30,11 +30,31 @@ class CustomSMTP(AIOSMTP):
|
||||
if _banner_message == '""':
|
||||
_banner_message = ''
|
||||
self.custom_banner = _banner_message
|
||||
|
||||
|
||||
# Store authenticator and auth_require_tls for later use
|
||||
self._custom_authenticator = kwargs.get('authenticator', None)
|
||||
self._custom_auth_require_tls = kwargs.get('auth_require_tls', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
# Override the __ident__ to use our custom banner
|
||||
self.__ident__ = self.custom_banner
|
||||
|
||||
def _get_auth_methods(self):
|
||||
# Only advertise AUTH if authenticator is set and (not auth_require_tls or connection is secure)
|
||||
if self._custom_authenticator and (not self._custom_auth_require_tls or self.session and self.session.ssl):
|
||||
return super()._get_auth_methods()
|
||||
return []
|
||||
|
||||
async def smtp_AUTH(self, arg):
|
||||
"""
|
||||
Override AUTH command to close connection after failed authentication.
|
||||
"""
|
||||
result = await super().smtp_AUTH(arg)
|
||||
# If authentication failed, close the connection immediately
|
||||
if isinstance(result, AuthResult) and not result.success:
|
||||
if hasattr(self, 'session') and hasattr(self.session, 'transport') and self.session.transport:
|
||||
self.session.transport.close()
|
||||
return result
|
||||
|
||||
class EnhancedCombinedAuthenticator:
|
||||
"""
|
||||
Enhanced combined authenticator with sender validation support.
|
||||
@@ -353,7 +373,9 @@ class EnhancedCustomSMTPHandler:
|
||||
return '250 OK'
|
||||
|
||||
class TLSController(Controller):
|
||||
"""Custom controller with TLS support - modeled after the working original."""
|
||||
"""
|
||||
Custom controller for direct TLS (SMTPS, port 465) support.
|
||||
"""
|
||||
|
||||
def __init__(self, handler, ssl_context, hostname='localhost', port=40587):
|
||||
logger.debug(f"TLSController __init__: ssl_context={ssl_context is not None}")
|
||||
@@ -365,10 +387,11 @@ class TLSController(Controller):
|
||||
logger.debug(f"TLSController factory: ssl_context={self._ssl_context is not None}")
|
||||
logger.debug(f"TLSController factory: ssl_context object={self._ssl_context}")
|
||||
logger.debug(f"TLSController factory: hostname={self.smtp_hostname}")
|
||||
# This is direct TLS (SMTPS, port 465 style)
|
||||
smtp_instance = CustomSMTP(
|
||||
self.handler,
|
||||
tls_context=self._ssl_context,
|
||||
require_starttls=False, # Don't require STARTTLS immediately, but make it available
|
||||
require_starttls=False, # Direct TLS: do not advertise or require STARTTLS
|
||||
auth_require_tls=True, # If auth is used, require TLS
|
||||
authenticator=self.handler.combined_authenticator,
|
||||
decode_data=True,
|
||||
@@ -378,17 +401,18 @@ class TLSController(Controller):
|
||||
return smtp_instance
|
||||
|
||||
class PlainController(Controller):
|
||||
"""Controller for plain SMTP with username/password and IP-based authentication."""
|
||||
"""Controller for plain SMTP with authentication and IP whitelist fallback."""
|
||||
|
||||
def __init__(self, handler, hostname='localhost', port=4025):
|
||||
self.smtp_hostname = hostname # Store for HELO identification
|
||||
super().__init__(handler, hostname='0.0.0.0', port=port) # Bind to all interfaces
|
||||
|
||||
def factory(self):
|
||||
# Pass authenticator and set auth_require_tls=False to enable AUTH on plain port
|
||||
return CustomSMTP(
|
||||
self.handler,
|
||||
authenticator=self.handler.combined_authenticator,
|
||||
auth_require_tls=False, # Allow AUTH over plain text (not recommended for production)
|
||||
auth_require_tls=False, # Allow AUTH on plain port
|
||||
decode_data=True,
|
||||
hostname=self.smtp_hostname # Use proper hostname for HELO
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user