aaaa
This commit is contained in:
121
2025/lake/web/web-magicauth/mta/server.py
Normal file
121
2025/lake/web/web-magicauth/mta/server.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from email import message_from_bytes
|
||||
|
||||
from aiosmtpd.controller import Controller
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LISTEN_HOST = "0.0.0.0"
|
||||
LISTEN_PORT = 25
|
||||
FORWARD_HOST = "smtp2http"
|
||||
FORWARD_PORT = 25
|
||||
|
||||
class MTAHandler:
|
||||
async def handle_DATA(self, server, session, envelope):
|
||||
try:
|
||||
peer = session.peer
|
||||
client_ip = peer[0] if peer else "unknown"
|
||||
|
||||
logger.info(f"Received email from {client_ip}: {envelope.mail_from} -> {envelope.rcpt_tos}")
|
||||
|
||||
message_data = envelope.content
|
||||
|
||||
msg = message_from_bytes(message_data)
|
||||
|
||||
# Security: Remove any existing Received headers to prevent spoofing
|
||||
# Only the last MTA's Received header should be trusted
|
||||
if 'Received' in msg:
|
||||
del msg['Received']
|
||||
|
||||
msg['Received'] = client_ip
|
||||
|
||||
await self.forward_email(
|
||||
msg,
|
||||
envelope.mail_from,
|
||||
envelope.rcpt_tos
|
||||
)
|
||||
|
||||
return '250 OK: Message accepted for delivery'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing email: {e}", exc_info=True)
|
||||
return '451 Requested action aborted: error in processing'
|
||||
|
||||
async def forward_email(self, msg, mail_from, rcpt_tos):
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(FORWARD_HOST, FORWARD_PORT)
|
||||
|
||||
# Read banner
|
||||
banner = await reader.readline()
|
||||
logger.debug(f"smtp2http banner: {banner.decode().strip()}")
|
||||
|
||||
# Send EHLO
|
||||
writer.write(b"EHLO mta\r\n")
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"EHLO response: {response.decode().strip()}")
|
||||
|
||||
# Send MAIL FROM
|
||||
writer.write(f"MAIL FROM:<{mail_from}>\r\n".encode())
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"MAIL FROM response: {response.decode().strip()}")
|
||||
|
||||
# Send RCPT TO for each recipient
|
||||
for recipient in rcpt_tos:
|
||||
writer.write(f"RCPT TO:<{recipient}>\r\n".encode())
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"RCPT TO response: {response.decode().strip()}")
|
||||
|
||||
# Send DATA command
|
||||
writer.write(b"DATA\r\n")
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"DATA response: {response.decode().strip()}")
|
||||
|
||||
email_content = msg.as_string().encode('utf-8')
|
||||
writer.write(email_content)
|
||||
|
||||
if not email_content.endswith(b"\r\n"):
|
||||
writer.write(b"\r\n")
|
||||
writer.write(b".\r\n")
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"Message response: {response.decode().strip()}")
|
||||
|
||||
# Send QUIT
|
||||
writer.write(b"QUIT\r\n")
|
||||
await writer.drain()
|
||||
response = await reader.readline()
|
||||
logger.debug(f"QUIT response: {response.decode().strip()}")
|
||||
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
logger.info(f"Successfully forwarded email from {mail_from} to {rcpt_tos}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to forward email: {e}", exc_info=True)
|
||||
|
||||
async def main():
|
||||
handler = MTAHandler()
|
||||
|
||||
controller = Controller(
|
||||
handler,
|
||||
hostname=LISTEN_HOST,
|
||||
port=LISTEN_PORT
|
||||
)
|
||||
|
||||
controller.start()
|
||||
logger.info(f"MTA listening on {LISTEN_HOST}:{LISTEN_PORT}")
|
||||
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
controller.stop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user