smtp_relay_server.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 本地SMTP中继服务器 - 将邮件转发到外部邮箱
  5. 支持两种方式:
  6. 1. 直接投递(会被拒收): python smtp_relay_server.py --direct
  7. 2. 通过QQ邮箱中继(推荐): python smtp_relay_server.py --relay
  8. """
  9. import asyncio
  10. import smtplib
  11. import sys
  12. from datetime import datetime
  13. from aiosmtpd.controller import Controller
  14. from aiosmtpd.handlers import Proxy
  15. from email import message_from_bytes
  16. # ========== 配置你的QQ邮箱中继 ==========
  17. RELAY_HOST = "smtp.qq.com"
  18. RELAY_PORT = 587
  19. RELAY_USER = "380880504@qq.com" # QQ邮箱
  20. RELAY_PASS = "your_auth_code" # QQ邮箱授权码
  21. # =======================================
  22. class SmartRelayHandler:
  23. """
  24. 智能中继处理器
  25. - 将收到的邮件通过真实SMTP服务器转发
  26. """
  27. def __init__(self, relay_host=None, relay_port=None, relay_user=None, relay_pass=None):
  28. self.relay_host = relay_host
  29. self.relay_port = relay_port
  30. self.relay_user = relay_user
  31. self.relay_pass = relay_pass
  32. self.use_relay = all([relay_host, relay_port, relay_user, relay_pass])
  33. self.email_count = 0
  34. async def handle_DATA(self, server, session, envelope):
  35. """处理收到的邮件并转发"""
  36. self.email_count += 1
  37. mail_from = envelope.mail_from
  38. rcpt_tos = envelope.rcpt_tos
  39. data = envelope.content
  40. print(f"\n{'='*70}")
  41. print(f"[RECEIVE] 收到邮件 #{self.email_count}")
  42. print(f"{'='*70}")
  43. print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
  44. print(f"发件人: {mail_from}")
  45. print(f"收件人: {', '.join(rcpt_tos)}")
  46. print(f"大小: {len(data)} bytes")
  47. # 解析邮件主题
  48. try:
  49. msg = message_from_bytes(data)
  50. subject = msg.get('Subject', 'No Subject')
  51. print(f"主题: {subject}")
  52. except:
  53. pass
  54. # 转发邮件
  55. if self.use_relay:
  56. success = self._relay_email(mail_from, rcpt_tos, data)
  57. if success:
  58. print(f"[RELAY] 邮件已通过 {self.relay_host} 转发成功")
  59. else:
  60. print(f"[FAIL] 转发失败,邮件已保存到本地")
  61. self._save_local(mail_from, rcpt_tos, data)
  62. else:
  63. print(f"[MODE] 本地模式 - 未配置中继,仅保存到文件")
  64. self._save_local(mail_from, rcpt_tos, data)
  65. return '250 OK'
  66. def _relay_email(self, mail_from, rcpt_tos, data):
  67. """通过真实SMTP服务器转发邮件"""
  68. try:
  69. print(f"[RELAY] 正在连接 {self.relay_host}:{self.relay_port}...")
  70. with smtplib.SMTP(self.relay_host, self.relay_port, timeout=30) as server:
  71. server.starttls()
  72. print(f"[RELAY] 正在登录 {self.relay_user}...")
  73. server.login(self.relay_user, self.relay_pass)
  74. print(f"[RELAY] 正在发送给 {', '.join(rcpt_tos)}...")
  75. server.sendmail(self.relay_user, rcpt_tos, data)
  76. return True
  77. except Exception as e:
  78. print(f"[RELAY ERROR] {e}")
  79. return False
  80. def _save_local(self, mail_from, rcpt_tos, data):
  81. """保存到本地文件"""
  82. try:
  83. with open("received_emails.log", 'a', encoding='utf-8') as f:
  84. f.write(f"\n{'='*70}\n")
  85. f.write(f"Email #{self.email_count} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
  86. f.write(f"From: {mail_from}\n")
  87. f.write(f"To: {', '.join(rcpt_tos)}\n")
  88. f.write(f"{'-'*70}\n")
  89. f.write(data.decode('utf-8', errors='replace'))
  90. f.write(f"\n{'='*70}\n")
  91. print(f"[SAVE] 已保存到 received_emails.log")
  92. except Exception as e:
  93. print(f"[SAVE ERROR] {e}")
  94. class DirectDeliveryHandler:
  95. """
  96. 直接投递处理器
  97. - 尝试直接连接到收件人域名的MX记录投递
  98. - 大概率会被拒收(因为没有SPF/DKIM)
  99. """
  100. def __init__(self):
  101. self.email_count = 0
  102. async def handle_DATA(self, server, session, envelope):
  103. self.email_count += 1
  104. mail_from = envelope.mail_from
  105. rcpt_tos = envelope.rcpt_tos
  106. data = envelope.content
  107. print(f"\n{'='*70}")
  108. print(f"[DIRECT] 收到邮件 #{self.email_count}")
  109. print(f"{'='*70}")
  110. print(f"警告: 直接投递模式,大概率会被拒收!")
  111. print(f"发件人: {mail_from}")
  112. print(f"收件人: {', '.join(rcpt_tos)}")
  113. # 尝试直接投递
  114. for rcpt in rcpt_tos:
  115. domain = rcpt.split('@')[1]
  116. print(f"[DIRECT] 尝试投递到 {domain}...")
  117. try:
  118. # 这里简化处理,实际需要查询MX记录
  119. self._try_deliver(domain, mail_from, rcpt, data)
  120. except Exception as e:
  121. print(f"[DIRECT FAIL] {e}")
  122. # 保存备份
  123. with open("received_emails.log", 'a', encoding='utf-8') as f:
  124. f.write(f"\n[Direct Attempt] {datetime.now()}\n")
  125. f.write(data.decode('utf-8', errors='replace'))
  126. return '250 OK'
  127. def _try_deliver(self, domain, mail_from, rcpt_to, data):
  128. """尝试直接连接到目标域的邮件服务器"""
  129. import dns.resolver
  130. try:
  131. # 查询MX记录
  132. answers = dns.resolver.resolve(domain, 'MX')
  133. mx_host = str(answers[0].exchange)
  134. print(f"[DIRECT] MX记录: {mx_host}")
  135. # 连接并投递
  136. with smtplib.SMTP(mx_host, 25, timeout=10) as server:
  137. server.sendmail(mail_from, [rcpt_to], data)
  138. print(f"[DIRECT] 投递成功!")
  139. except Exception as e:
  140. print(f"[DIRECT] 投递失败: {e}")
  141. def main():
  142. import argparse
  143. parser = argparse.ArgumentParser(description='本地SMTP中继服务器')
  144. parser.add_argument('--port', type=int, default=25,
  145. help='本地监听端口 (默认: 25)')
  146. parser.add_argument('--host', type=str, default='localhost',
  147. help='监听地址 (默认: localhost)')
  148. parser.add_argument('--relay', action='store_true',
  149. help='使用中继模式 (通过QQ邮箱转发)')
  150. parser.add_argument('--direct', action='store_true',
  151. help='使用直接投递模式 (会被拒收)')
  152. args = parser.parse_args()
  153. # 选择处理器
  154. if args.direct:
  155. handler = DirectDeliveryHandler()
  156. mode = "直接投递 (大概率被拒收)"
  157. elif args.relay:
  158. if RELAY_PASS == "your_auth_code":
  159. print("[ERROR] 请先配置QQ邮箱授权码!")
  160. print(f"编辑 {__file__},修改 RELAY_PASS = '你的授权码'")
  161. sys.exit(1)
  162. handler = SmartRelayHandler(RELAY_HOST, RELAY_PORT, RELAY_USER, RELAY_PASS)
  163. mode = f"中继模式 (via {RELAY_HOST})"
  164. else:
  165. handler = SmartRelayHandler() # 本地模式
  166. mode = "本地保存 (未配置中继)"
  167. # 启动服务器
  168. controller = Controller(handler, hostname=args.host, port=args.port)
  169. print("="*70)
  170. print(f"[SMTP Relay Server] 启动成功")
  171. print("="*70)
  172. print(f"模式: {mode}")
  173. print(f"监听: {args.host}:{args.port}")
  174. print(f"测试: python send_simple_test_email.py")
  175. print("-"*70)
  176. if args.relay:
  177. print(f"中继账号: {RELAY_USER}")
  178. print("邮件将转发到收件人邮箱")
  179. else:
  180. print("邮件仅保存到本地: received_emails.log")
  181. print("要启用转发,请运行: python smtp_relay_server.py --relay")
  182. print(f"\n按 Ctrl+C 停止服务器")
  183. print("="*70)
  184. controller.start()
  185. try:
  186. while True:
  187. import time
  188. time.sleep(1)
  189. except KeyboardInterrupt:
  190. print("\n\n正在停止服务器...")
  191. controller.stop()
  192. print("[OK] 服务器已停止")
  193. if __name__ == '__main__':
  194. main()