Merge pull request #36 from gnh1201/smtp

SMTP fix #35 / SMTP 서버 수정
This commit is contained in:
Namhyeon Go 2024-07-13 21:03:55 +09:00 committed by GitHub
commit feb7cff398
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 58 additions and 43 deletions

14
base.py
View File

@ -8,9 +8,8 @@
# Euiseo Cha (Wonkwang University) <zeroday0619_dev@outlook.com> # Euiseo Cha (Wonkwang University) <zeroday0619_dev@outlook.com>
# https://github.com/gnh1201/caterpillar # https://github.com/gnh1201/caterpillar
# Created at: 2024-05-20 # Created at: 2024-05-20
# Updated at: 2024-07-11 # Updated at: 2024-07-12
# #
import logging import logging
import hashlib import hashlib
import json import json
@ -48,10 +47,18 @@ def jsonrpc2_create_id(data):
def jsonrpc2_encode(method, params=None): def jsonrpc2_encode(method, params=None):
data = {"jsonrpc": "2.0", "method": method, "params": params} data = {"jsonrpc": "2.0", "method": method, "params": params}
id = jsonrpc2_create_id(data) id = jsonrpc2_create_id(data)
data["id"] = id id = data.get('id')
return (id, json.dumps(data)) return (id, json.dumps(data))
def jsonrpc2_decode(text):
data = json.loads(text)
type = 'error' if 'error' in data else 'result' if 'result' in data else None
id = data.get('id')
rpcdata = data.get(type) if type else None
return type, id, rpcdata
def jsonrpc2_result_encode(result, id=""): def jsonrpc2_result_encode(result, id=""):
data = {"jsonrpc": "2.0", "result": result, "id": id} data = {"jsonrpc": "2.0", "result": result, "id": id}
return json.dumps(data) return json.dumps(data)
@ -61,7 +68,6 @@ def jsonrpc2_error_encode(error, id=""):
data = {"jsonrpc": "2.0", "error": error, "id": id} data = {"jsonrpc": "2.0", "error": error, "id": id}
return json.dumps(data) return json.dumps(data)
def find_openssl_binpath(): def find_openssl_binpath():
system = platform.system() system = platform.system()

View File

@ -1,2 +1,3 @@
python-decouple python-decouple
requests requests
aiosmtpd

86
smtp.py
View File

@ -1,23 +1,23 @@
#!/usr/bin/python3 #!/usr/bin/python3
# #
# smtp.py # smtp.py
# SMTP over HTTP gateway # SMTP mail sender over HTTP/S
# #
# Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy) # Caterpillar Proxy - The simple web debugging proxy (formerly, php-httpproxy)
# Namyheon Go (Catswords Research) <gnh1201@gmail.com> # Namyheon Go (Catswords Research) <gnh1201@gmail.com>
# https://github.com/gnh1201/caterpillar # https://github.com/gnh1201/caterpillar
# Created at: 2024-03-01 # Created at: 2024-03-01
# Updated at: 2024-05-20 # Updated at: 2024-07-12
# #
import asyncio
import asyncore from aiosmtpd.controller import Controller
from smtpd import SMTPServer from aiosmtpd.handlers import Message
from email.message import EmailMessage
import re import re
import sys import sys
import json import json
import requests import requests
from platform import python_version
from decouple import config from decouple import config
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from base import ( from base import (
@ -45,28 +45,22 @@ auth = None
if _username: if _username:
auth = HTTPBasicAuth(_username, _password) auth = HTTPBasicAuth(_username, _password)
class CaterpillarSMTPHandler:
def __init__(self):
self.smtpd_hostname = "CaterpillarSMTPServer"
self.smtp_version = "0.1.6"
class CaterpillarSMTPServer(SMTPServer): async def handle_DATA(self, server, session, envelope):
def __init__(self, localaddr, remoteaddr): mailfrom = envelope.mail_from
self.__class__.smtpd_hostname = "CaterpillarSMTPServer" rcpttos = envelope.rcpt_tos
self.__class__.smtp_version = "0.1.6" data = envelope.content
super().__init__(localaddr, remoteaddr)
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): message = EmailMessage()
message_lines = data.decode("utf-8").split("\n") message.set_content(data)
subject = ""
to = "" subject = message.get('Subject', '')
for line in message_lines: to = message.get('To', '')
pos = line.find(":")
if pos > -1:
k = line[0:pos]
v = line[pos + 1 :]
if k == "Subject":
subject = v
elif k == "To":
to = v
# build a data
proxy_data = { proxy_data = {
"headers": { "headers": {
"User-Agent": "php-httpproxy/0.1.6 (Client; Python " "User-Agent": "php-httpproxy/0.1.6 (Client; Python "
@ -82,27 +76,41 @@ class CaterpillarSMTPServer(SMTPServer):
} }
_, raw_data = jsonrpc2_encode("relay_sendmail", proxy_data["data"]) _, raw_data = jsonrpc2_encode("relay_sendmail", proxy_data["data"])
# send HTTP POST request
try: try:
response = requests.post( response = await asyncio.to_thread(
server_url, headers=proxy_data["headers"], data=raw_data, auth=auth requests.post,
server_url,
headers=proxy_data['headers'],
data=raw_data,
auth=auth
) )
if response.status_code == 200: if response.status_code == 200:
type, id, method, rpcdata = jsonrpc2_decode(response.text) type, id, rpcdata = jsonrpc2_decode(response.text)
if rpcdata["success"]: if rpcdata['success']:
logger.info("[*] Email sent successfully.") logger.info("[*] Email sent successfully.")
else: else:
raise Exception( raise Exception(f"({rpcdata['code']}) {rpcdata['message']}")
"(%s) %s" % (str(rpcdata["code"]), rpcdata["message"])
)
else: else:
raise Exception("Status %s" % (str(response.status_code))) raise Exception(f"Status {response.status_code}")
except Exception as e: except Exception as e:
logger.error("[*] Failed to send email", exc_info=e) logger.error("[*] Failed to send email", exc_info=e)
return '500 Could not process your message. ' + str(e)
return '250 OK'
# Start SMTP server # https://aiosmtpd-pepoluan.readthedocs.io/en/latest/migrating.html
smtp_server = CaterpillarSMTPServer((smtp_host, smtp_port), None) def main():
handler = CaterpillarSMTPHandler()
controller = Controller(handler, hostname=smtp_host, port=smtp_port)
# Run the event loop in a separate thread.
controller.start()
# Wait for the user to press Return.
input('SMTP server running. Press Return to stop server and exit.')
controller.stop()
logger.warning("[*] User has requested an interrupt")
logger.warning("[*] Application Exiting.....")
sys.exit()
# Start asynchronous event loop if __name__ == "__main__":
asyncore.loop() main()