This commit is contained in:
Namhyeon Go 2024-07-12 10:14:39 +09:00
parent ce3c6e7623
commit 1d39e8a3b6
3 changed files with 60 additions and 40 deletions

12
base.py
View File

@ -8,7 +8,7 @@
# 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-09 # Updated at: 2024-07-12
# #
import logging import logging
@ -48,9 +48,16 @@ def jsonrpc2_encode(method, params=None):
"params": params "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 = { data = {
@ -69,7 +76,6 @@ def jsonrpc2_error_encode(error, id=''):
} }
return json.dumps(data) return json.dumps(data)
class Extension(): class Extension():
extensions = [] extensions = []
protocols = [] protocols = []

View File

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

87
smtp.py
View File

@ -1,26 +1,26 @@
#!/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 extract_credentials, jsonrpc2_create_id, jsonrpc2_encode, jsonrpc2_result_encode, Logger from base import extract_credentials, jsonrpc2_create_id, jsonrpc2_encode, jsonrpc2_decode, jsonrpc2_result_encode, Logger
logger = Logger(name="smtp") logger = Logger(name="smtp")
@ -37,30 +37,25 @@ auth = None
if _username: if _username:
auth = HTTPBasicAuth(_username, _password) auth = HTTPBasicAuth(_username, _password)
class CaterpillarSMTPServer(SMTPServer): class CaterpillarSMTPHandler:
def __init__(self, localaddr, remoteaddr): def __init__(self):
self.__class__.smtpd_hostname = "CaterpillarSMTPServer" self.smtpd_hostname = "CaterpillarSMTPServer"
self.__class__.smtp_version = "0.1.6" self.smtp_version = "0.1.6"
super().__init__(localaddr, remoteaddr)
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): async def handle_DATA(self, server, session, envelope):
message_lines = data.decode('utf-8').split('\n') mailfrom = envelope.mail_from
subject = '' rcpttos = envelope.rcpt_tos
to = '' data = envelope.content
for line in message_lines:
pos = line.find(':') message = EmailMessage()
if pos > -1: message.set_content(data)
k = line[0:pos]
v = line[pos+1:] subject = message.get('Subject', '')
if k == 'Subject': to = message.get('To', '')
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 " + python_version() + "; Caterpillar; abuse@catswords.net)", "User-Agent": f"php-httpproxy/0.1.6 (Client; Python {python_version()}; Caterpillar; abuse@catswords.net)",
}, },
'data': { 'data': {
"to": to, "to": to,
@ -71,22 +66,40 @@ 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(server_url, headers=proxy_data['headers'], data=raw_data, auth=auth) response = await asyncio.to_thread(
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("(%s) %s" % (str(rpcdata['code']), rpcdata['message'])) raise Exception(f"({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)
# Start SMTP server return '250 OK'
smtp_server = CaterpillarSMTPServer((smtp_host, smtp_port), None)
# Start asynchronous event loop # https://aiosmtpd-pepoluan.readthedocs.io/en/latest/migrating.html
asyncore.loop() 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()
if __name__ == "__main__":
main()