No Global IP whitelist - fixing failed auth response hang, fixing sender authentication.

This commit is contained in:
nahakubuilde
2025-06-13 09:41:55 +01:00
parent f07b9c2150
commit 38672bea0b
11 changed files with 190 additions and 352 deletions
+3 -3
View File
@@ -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:
"""
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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,
+30 -6
View File
@@ -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
)