From 101fa7b69d2b056d01a0ece8c7dad49b0dbb558c Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 27 Nov 2023 01:38:43 +0100 Subject: [PATCH] add /ws/2/notify/ socket --- core/lmtp/protocol.py | 50 ------------------ core/{lmtp => mail}/__init__.py | 0 core/mail/migrations/__init__.py | 0 core/mail/protocol.py | 69 ++++++++++++++++++++++++ core/{lmtp => mail}/socket.py | 0 core/mail/tests/__init__.py | 0 core/mail/tests/v2/__init__.py | 0 core/server.py | 5 +- web/src/App.vue | 91 ++++++++++++++++++++++---------- web/src/store/index.js | 4 +- 10 files changed, 138 insertions(+), 81 deletions(-) delete mode 100644 core/lmtp/protocol.py rename core/{lmtp => mail}/__init__.py (100%) create mode 100644 core/mail/migrations/__init__.py create mode 100644 core/mail/protocol.py rename core/{lmtp => mail}/socket.py (100%) create mode 100644 core/mail/tests/__init__.py create mode 100644 core/mail/tests/v2/__init__.py diff --git a/core/lmtp/protocol.py b/core/lmtp/protocol.py deleted file mode 100644 index c504ecb..0000000 --- a/core/lmtp/protocol.py +++ /dev/null @@ -1,50 +0,0 @@ -import asyncio -import logging - -from helper import create_task - - -def make_reply(message, to, subject): - from email.message import EmailMessage - from core.settings import MAIL_DOMAIN - - reply = EmailMessage() - reply["From"] = "noreply@" + MAIL_DOMAIN - reply["To"] = to - reply["Subject"] = subject - reply.set_content(message) - - return reply - - -async def send_smtp(message): - await asyncio.sleep(30) - import aiosmtplib - log = logging.getLogger('mail.log') - log.info('Sending message to %s' % message['To']) - - await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False) - - -class LMTPHandler: - async def handle_RCPT(self, server, session, envelope, address, rcpt_options): - from core.settings import MAIL_DOMAIN - if not address.endswith('@' + MAIL_DOMAIN): - return '550 not relaying to that domain' - envelope.rcpt_tos.append(address) - return '250 OK' - - async def handle_DATA(self, server, session, envelope): - log = logging.getLogger('mail.log') - log.info('Message from %s' % envelope.mail_from) - log.info('Message for %s' % envelope.rcpt_tos) - log.info('Message data:\n') - for ln in envelope.content.decode('utf8', errors='replace').splitlines(): - log.info(f'> {ln}'.strip()) - log.info('End of message') - - create_task(send_smtp(make_reply("Thank you for your message.", envelope.mail_from, 'Message received'))) - - # asyncio.create_task(send_reply()) - - return '250 Message accepted for delivery' diff --git a/core/lmtp/__init__.py b/core/mail/__init__.py similarity index 100% rename from core/lmtp/__init__.py rename to core/mail/__init__.py diff --git a/core/mail/migrations/__init__.py b/core/mail/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/mail/protocol.py b/core/mail/protocol.py new file mode 100644 index 0000000..d466f51 --- /dev/null +++ b/core/mail/protocol.py @@ -0,0 +1,69 @@ +import logging +import aiosmtplib + + +def make_reply(message, to, subject): + from email.message import EmailMessage + from core.settings import MAIL_DOMAIN + + reply = EmailMessage() + reply["From"] = "noreply@" + MAIL_DOMAIN + reply["To"] = to + reply["Subject"] = subject + reply.set_content(message) + + return reply + + +async def send_smtp(message, log): + log.info('Sending message to %s' % message['To']) + await aiosmtplib.send(message, hostname="127.0.0.1", port=25, use_tls=False, start_tls=False) + + +class LMTPHandler: + async def handle_RCPT(self, server, session, envelope, address, rcpt_options): + from core.settings import MAIL_DOMAIN + if not address.endswith('@' + MAIL_DOMAIN): + return '550 not relaying to that domain' + envelope.rcpt_tos.append(address) + return '250 OK' + + async def handle_DATA(self, server, session, envelope): + import email + log = logging.getLogger('mail.log') + log.info('Message from %s' % envelope.mail_from) + log.info('Message for %s' % envelope.rcpt_tos) + log.info('Message data:\n') + + try: + parsed = email.message_from_bytes(envelope.content) + body = "" + if parsed.is_multipart(): + for part in parsed.walk(): + ctype = part.get_content_type() + cdispo = str(part.get('Content-Disposition')) + + # skip any text/plain (txt) attachments + if ctype == 'text/plain' and 'attachment' not in cdispo: + body = part.get_payload(decode=True) + else: + log.info("Attachment", ctype, cdispo) + else: + body = parsed.get_payload(decode=True) + log.info(body) + + header_from = parsed.get('From') + header_to = parsed.get('To') + + if header_from != envelope.mail_from: + log.warning("Header from does not match envelope from") + + if header_to != envelope.rcpt_tos[0]: + log.warning("Header to does not match envelope to") + + await send_smtp(make_reply("Thank you for your message.", envelope.mail_from, 'Message received'), log) + log.info("Sent reply") + return '250 Message accepted for delivery' + except Exception as e: + log.error(e) + return '550 Message rejected' diff --git a/core/lmtp/socket.py b/core/mail/socket.py similarity index 100% rename from core/lmtp/socket.py rename to core/mail/socket.py diff --git a/core/mail/tests/__init__.py b/core/mail/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/mail/tests/v2/__init__.py b/core/mail/tests/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/server.py b/core/server.py index 621e6fd..5cd764c 100644 --- a/core/server.py +++ b/core/server.py @@ -10,8 +10,9 @@ import uvicorn django.setup() from helper import init_loop -from lmtp.protocol import LMTPHandler -from lmtp.socket import UnixSocketLMTPController +from mail.protocol import LMTPHandler +from mail.socket import UnixSocketLMTPController + # async def handle_echo(reader, writer): diff --git a/web/src/App.vue b/web/src/App.vue index 1fb66de..99a78d8 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -24,7 +24,9 @@ export default { components: {Toast, Navbar, AddItemModal}, computed: mapState(['loadedItems', 'layout', 'toasts']), data: () => ({ - addModalOpen: false + addModalOpen: false, + notify_socket: null, + socket_toast: null, }), methods: { ...mapMutations(['removeToast', 'createToast']), @@ -33,35 +35,68 @@ export default { }, closeAddModal() { this.addModalOpen = false; - } + }, + tryConnect() { + if (!this.notify_socket || this.notify_socket.readyState !== WebSocket.OPEN) { + if (this.socket_toast) { + this.removeToast(this.socket_toast.key); + this.socket_toast = null; + } + this.socket_toast = this.createToast({ + title: "Connecting...", + message: "Connecting to websocket...", + color: "warning" + }); + this.notify_socket = new WebSocket('wss://' + window.location.host + '/ws/2/notify/'); + this.notify_socket.onopen = (e) => { + if (this.socket_toast) { + this.removeToast(this.socket_toast.key); + this.socket_toast = null; + } + this.socket_toast = this.createToast({ + title: "Connection established", + message: JSON.stringify(e), + color: "success" + }); + }; + this.notify_socket.onclose = (e) => { + if (this.socket_toast) { + this.removeToast(this.socket_toast.key); + this.socket_toast = null; + } + this.socket_toast = this.createToast({ + title: "Connection closed", + message: JSON.stringify(e), + color: "danger" + }); + setTimeout(() => { + this.tryConnect(); + }, 1000); + }; + this.notify_socket.onerror = (e) => { + if (this.socket_toast) { + this.removeToast(this.socket_toast.key); + this.socket_toast = null; + } + this.socket_toast = this.createToast({ + title: "Connection error", + message: JSON.stringify(e), + color: "danger" + }); + setTimeout(() => { + this.tryConnect(); + }, 1000); + }; + this.notify_socket.onmessage = (e) => { + let data = JSON.parse(e.data); + console.log(data); + + } + } + }, }, created: function () { - this.notify_socket = new WebSocket('wss://' + window.location.host + '/ws/notify/'); - this.notify_socket.onmessage = (e) => { - let data = JSON.parse(e.data); - console.log(data, e.data); - }; - this.notify_socket.onopen = (e) => { - this.createToast({ - title: "Connection established", - message: JSON.stringify(e), - color: "success" - }); - }; - this.notify_socket.onclose = (e) => { - this.createToast({ - title: "Connection closed", - message: JSON.stringify(e), - color: "danger" - }); - }; - this.notify_socket.onerror = (e) => { - this.createToast({ - title: "Connection error", - message: JSON.stringify(e), - color: "danger" - }); - }; + this.tryConnect(); } }; diff --git a/web/src/store/index.js b/web/src/store/index.js index 6d33992..1fb6474 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -84,8 +84,10 @@ const store = new Vuex.Store({ state.loadedItems.push(item); }, createToast(state, {title, message, color}) { - state.toasts.push({title, message, color, key: state.keyIncrement}); + var toast = {title, message, color, key: state.keyIncrement} + state.toasts.push(toast); state.keyIncrement += 1; + return toast; }, removeToast(state, key) { state.toasts = state.toasts.filter(toast => toast.key !== key);