| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 本地SMTP中继服务器 - 将邮件转发到外部邮箱
- 支持两种方式:
- 1. 直接投递(会被拒收): python smtp_relay_server.py --direct
- 2. 通过QQ邮箱中继(推荐): python smtp_relay_server.py --relay
- """
- import asyncio
- import smtplib
- import sys
- from datetime import datetime
- from aiosmtpd.controller import Controller
- from aiosmtpd.handlers import Proxy
- from email import message_from_bytes
- # ========== 配置你的QQ邮箱中继 ==========
- RELAY_HOST = "smtp.qq.com"
- RELAY_PORT = 587
- RELAY_USER = "380880504@qq.com" # QQ邮箱
- RELAY_PASS = "your_auth_code" # QQ邮箱授权码
- # =======================================
- class SmartRelayHandler:
- """
- 智能中继处理器
- - 将收到的邮件通过真实SMTP服务器转发
- """
- def __init__(self, relay_host=None, relay_port=None, relay_user=None, relay_pass=None):
- self.relay_host = relay_host
- self.relay_port = relay_port
- self.relay_user = relay_user
- self.relay_pass = relay_pass
- self.use_relay = all([relay_host, relay_port, relay_user, relay_pass])
- self.email_count = 0
- async def handle_DATA(self, server, session, envelope):
- """处理收到的邮件并转发"""
- self.email_count += 1
- mail_from = envelope.mail_from
- rcpt_tos = envelope.rcpt_tos
- data = envelope.content
- print(f"\n{'='*70}")
- print(f"[RECEIVE] 收到邮件 #{self.email_count}")
- print(f"{'='*70}")
- print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
- print(f"发件人: {mail_from}")
- print(f"收件人: {', '.join(rcpt_tos)}")
- print(f"大小: {len(data)} bytes")
- # 解析邮件主题
- try:
- msg = message_from_bytes(data)
- subject = msg.get('Subject', 'No Subject')
- print(f"主题: {subject}")
- except:
- pass
- # 转发邮件
- if self.use_relay:
- success = self._relay_email(mail_from, rcpt_tos, data)
- if success:
- print(f"[RELAY] 邮件已通过 {self.relay_host} 转发成功")
- else:
- print(f"[FAIL] 转发失败,邮件已保存到本地")
- self._save_local(mail_from, rcpt_tos, data)
- else:
- print(f"[MODE] 本地模式 - 未配置中继,仅保存到文件")
- self._save_local(mail_from, rcpt_tos, data)
- return '250 OK'
- def _relay_email(self, mail_from, rcpt_tos, data):
- """通过真实SMTP服务器转发邮件"""
- try:
- print(f"[RELAY] 正在连接 {self.relay_host}:{self.relay_port}...")
- with smtplib.SMTP(self.relay_host, self.relay_port, timeout=30) as server:
- server.starttls()
- print(f"[RELAY] 正在登录 {self.relay_user}...")
- server.login(self.relay_user, self.relay_pass)
- print(f"[RELAY] 正在发送给 {', '.join(rcpt_tos)}...")
- server.sendmail(self.relay_user, rcpt_tos, data)
- return True
- except Exception as e:
- print(f"[RELAY ERROR] {e}")
- return False
- def _save_local(self, mail_from, rcpt_tos, data):
- """保存到本地文件"""
- try:
- with open("received_emails.log", 'a', encoding='utf-8') as f:
- f.write(f"\n{'='*70}\n")
- f.write(f"Email #{self.email_count} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
- f.write(f"From: {mail_from}\n")
- f.write(f"To: {', '.join(rcpt_tos)}\n")
- f.write(f"{'-'*70}\n")
- f.write(data.decode('utf-8', errors='replace'))
- f.write(f"\n{'='*70}\n")
- print(f"[SAVE] 已保存到 received_emails.log")
- except Exception as e:
- print(f"[SAVE ERROR] {e}")
- class DirectDeliveryHandler:
- """
- 直接投递处理器
- - 尝试直接连接到收件人域名的MX记录投递
- - 大概率会被拒收(因为没有SPF/DKIM)
- """
- def __init__(self):
- self.email_count = 0
- async def handle_DATA(self, server, session, envelope):
- self.email_count += 1
- mail_from = envelope.mail_from
- rcpt_tos = envelope.rcpt_tos
- data = envelope.content
- print(f"\n{'='*70}")
- print(f"[DIRECT] 收到邮件 #{self.email_count}")
- print(f"{'='*70}")
- print(f"警告: 直接投递模式,大概率会被拒收!")
- print(f"发件人: {mail_from}")
- print(f"收件人: {', '.join(rcpt_tos)}")
- # 尝试直接投递
- for rcpt in rcpt_tos:
- domain = rcpt.split('@')[1]
- print(f"[DIRECT] 尝试投递到 {domain}...")
- try:
- # 这里简化处理,实际需要查询MX记录
- self._try_deliver(domain, mail_from, rcpt, data)
- except Exception as e:
- print(f"[DIRECT FAIL] {e}")
- # 保存备份
- with open("received_emails.log", 'a', encoding='utf-8') as f:
- f.write(f"\n[Direct Attempt] {datetime.now()}\n")
- f.write(data.decode('utf-8', errors='replace'))
- return '250 OK'
- def _try_deliver(self, domain, mail_from, rcpt_to, data):
- """尝试直接连接到目标域的邮件服务器"""
- import dns.resolver
- try:
- # 查询MX记录
- answers = dns.resolver.resolve(domain, 'MX')
- mx_host = str(answers[0].exchange)
- print(f"[DIRECT] MX记录: {mx_host}")
- # 连接并投递
- with smtplib.SMTP(mx_host, 25, timeout=10) as server:
- server.sendmail(mail_from, [rcpt_to], data)
- print(f"[DIRECT] 投递成功!")
- except Exception as e:
- print(f"[DIRECT] 投递失败: {e}")
- def main():
- import argparse
- parser = argparse.ArgumentParser(description='本地SMTP中继服务器')
- parser.add_argument('--port', type=int, default=25,
- help='本地监听端口 (默认: 25)')
- parser.add_argument('--host', type=str, default='localhost',
- help='监听地址 (默认: localhost)')
- parser.add_argument('--relay', action='store_true',
- help='使用中继模式 (通过QQ邮箱转发)')
- parser.add_argument('--direct', action='store_true',
- help='使用直接投递模式 (会被拒收)')
- args = parser.parse_args()
- # 选择处理器
- if args.direct:
- handler = DirectDeliveryHandler()
- mode = "直接投递 (大概率被拒收)"
- elif args.relay:
- if RELAY_PASS == "your_auth_code":
- print("[ERROR] 请先配置QQ邮箱授权码!")
- print(f"编辑 {__file__},修改 RELAY_PASS = '你的授权码'")
- sys.exit(1)
- handler = SmartRelayHandler(RELAY_HOST, RELAY_PORT, RELAY_USER, RELAY_PASS)
- mode = f"中继模式 (via {RELAY_HOST})"
- else:
- handler = SmartRelayHandler() # 本地模式
- mode = "本地保存 (未配置中继)"
- # 启动服务器
- controller = Controller(handler, hostname=args.host, port=args.port)
- print("="*70)
- print(f"[SMTP Relay Server] 启动成功")
- print("="*70)
- print(f"模式: {mode}")
- print(f"监听: {args.host}:{args.port}")
- print(f"测试: python send_simple_test_email.py")
- print("-"*70)
- if args.relay:
- print(f"中继账号: {RELAY_USER}")
- print("邮件将转发到收件人邮箱")
- else:
- print("邮件仅保存到本地: received_emails.log")
- print("要启用转发,请运行: python smtp_relay_server.py --relay")
- print(f"\n按 Ctrl+C 停止服务器")
- print("="*70)
- controller.start()
- try:
- while True:
- import time
- time.sleep(1)
- except KeyboardInterrupt:
- print("\n\n正在停止服务器...")
- controller.stop()
- print("[OK] 服务器已停止")
- if __name__ == '__main__':
- main()
|