email headers fixing
This commit is contained in:
@@ -129,15 +129,26 @@ class EmailLog(Base):
|
||||
__tablename__ = 'esrv_email_logs'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
from_address = Column(String, nullable=False)
|
||||
to_address = Column(String, nullable=False)
|
||||
subject = Column(Text)
|
||||
|
||||
# Legacy columns (from original schema)
|
||||
message_id = Column(String, unique=True, nullable=False)
|
||||
timestamp = Column(DateTime, nullable=False)
|
||||
peer = Column(String, nullable=False)
|
||||
mail_from = Column(String, nullable=False)
|
||||
rcpt_tos = Column(String, nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
status = Column(String, nullable=False)
|
||||
message = Column(Text)
|
||||
dkim_signed = Column(Boolean, default=False)
|
||||
|
||||
# New columns (added later)
|
||||
from_address = Column(String, nullable=False, server_default='unknown')
|
||||
to_address = Column(String, nullable=False, server_default='unknown')
|
||||
subject = Column(Text, nullable=True)
|
||||
message = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EmailLog(id={self.id}, from='{self.from_address}', to='{self.to_address}', status='{self.status}')>"
|
||||
return f"<EmailLog(id={self.id}, message_id='{self.message_id}', from='{self.mail_from}', to='{self.rcpt_tos}', status='{self.status}')>"
|
||||
|
||||
class AuthLog(Base):
|
||||
"""Authentication log model for security auditing."""
|
||||
|
||||
@@ -9,6 +9,7 @@ Security Features:
|
||||
"""
|
||||
|
||||
import uuid
|
||||
import email.utils
|
||||
from datetime import datetime
|
||||
from aiosmtpd.smtp import SMTP as AIOSMTP, AuthResult
|
||||
from aiosmtpd.controller import Controller
|
||||
@@ -61,85 +62,47 @@ class EnhancedCustomSMTPHandler:
|
||||
self.auth_methods = ['LOGIN', 'PLAIN']
|
||||
|
||||
def _ensure_required_headers(self, content: str, envelope, message_id: str) -> str:
|
||||
"""Ensure all required email headers are present.
|
||||
|
||||
"""Ensure all required email headers are present and properly formatted.
|
||||
|
||||
Args:
|
||||
content (str): Email content.
|
||||
envelope: SMTP envelope.
|
||||
message_id (str): Generated message ID.
|
||||
|
||||
|
||||
Returns:
|
||||
str: Email content with all required headers.
|
||||
"""
|
||||
import email.utils
|
||||
from datetime import datetime
|
||||
|
||||
# Parse existing headers
|
||||
lines = content.split('\n')
|
||||
headers = {}
|
||||
body_start = 0
|
||||
|
||||
# Find where headers end and body begins
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == '':
|
||||
body_start = i + 1
|
||||
break
|
||||
if ':' in line and not line.startswith(' ') and not line.startswith('\t'):
|
||||
header_name, header_value = line.split(':', 1)
|
||||
headers[header_name.strip().lower()] = header_value.strip()
|
||||
|
||||
# Extract body
|
||||
body = '\n'.join(lines[body_start:]) if body_start < len(lines) else ''
|
||||
|
||||
# Build required headers
|
||||
required_headers = []
|
||||
|
||||
# Add Message-ID if missing
|
||||
if 'message-id' not in headers:
|
||||
required_headers.append(f"Message-ID: <{message_id}@{envelope.mail_from.split('@')[1] if '@' in envelope.mail_from else 'localhost'}>")
|
||||
|
||||
# Add Date if missing
|
||||
if 'date' not in headers:
|
||||
required_headers.append(f"Date: {email.utils.formatdate(localtime=True)}")
|
||||
|
||||
# Add From if missing
|
||||
if 'from' not in headers:
|
||||
required_headers.append(f"From: {envelope.mail_from}")
|
||||
|
||||
# Add To if missing
|
||||
if 'to' not in headers:
|
||||
to_list = ', '.join(envelope.rcpt_tos)
|
||||
required_headers.append(f"To: {to_list}")
|
||||
|
||||
# Add MIME-Version if missing
|
||||
if 'mime-version' not in headers:
|
||||
required_headers.append("MIME-Version: 1.0")
|
||||
|
||||
# Add Content-Type if missing
|
||||
if 'content-type' not in headers:
|
||||
required_headers.append("Content-Type: text/plain; charset=utf-8")
|
||||
|
||||
# Rebuild the email with required headers first, then existing headers, then body
|
||||
new_content_lines = required_headers
|
||||
|
||||
# Add existing headers (excluding the ones we just added)
|
||||
for i in range(body_start):
|
||||
line = lines[i]
|
||||
if ':' in line and not line.startswith(' ') and not line.startswith('\t'):
|
||||
header_name = line.split(':', 1)[0].strip().lower()
|
||||
if header_name not in ['message-id', 'date', 'from', 'to', 'mime-version', 'content-type']:
|
||||
new_content_lines.append(line)
|
||||
elif line.startswith(' ') or line.startswith('\t'):
|
||||
# Continuation of previous header
|
||||
new_content_lines.append(line)
|
||||
|
||||
# Add empty line between headers and body
|
||||
new_content_lines.append('')
|
||||
|
||||
# Add body
|
||||
new_content_lines.append(body)
|
||||
|
||||
return '\r\n'.join(new_content_lines)
|
||||
import email
|
||||
from email.parser import Parser
|
||||
from email.policy import default
|
||||
|
||||
# Parse the message using the email library
|
||||
msg = Parser(policy=default).parsestr(content)
|
||||
|
||||
# Set or add required headers if missing
|
||||
if not msg.get('Message-ID'):
|
||||
msg['Message-ID'] = f"<{message_id}@{envelope.mail_from.split('@')[1] if '@' in envelope.mail_from else 'localhost'}>"
|
||||
if not msg.get('Date'):
|
||||
msg['Date'] = email.utils.formatdate(localtime=True)
|
||||
if not msg.get('From'):
|
||||
msg['From'] = envelope.mail_from
|
||||
if not msg.get('To'):
|
||||
msg['To'] = ', '.join(envelope.rcpt_tos)
|
||||
if not msg.get('MIME-Version'):
|
||||
msg['MIME-Version'] = '1.0'
|
||||
if not msg.get('Content-Type'):
|
||||
msg['Content-Type'] = 'text/plain; charset=utf-8'
|
||||
if not msg.get('Subject'):
|
||||
msg['Subject'] = '(No Subject)'
|
||||
if not msg.get('Content-Transfer-Encoding'):
|
||||
msg['Content-Transfer-Encoding'] = '7bit'
|
||||
|
||||
# Ensure exactly one blank line between headers and body
|
||||
# The email library will handle this when flattening
|
||||
from io import StringIO
|
||||
out = StringIO()
|
||||
out.write(msg.as_string())
|
||||
return out.getvalue()
|
||||
|
||||
async def handle_DATA(self, server, session, envelope):
|
||||
"""Handle incoming email data."""
|
||||
|
||||
Reference in New Issue
Block a user