diff --git a/Dockerfile b/Dockerfile index 5c6093c..586cf32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Base on latest (edge) alpine image -FROM harbor.ervine.dev/public/x86_64/alpine:v3.12 +FROM harbor.ervine.dev/public/x86_64/alpine:v3.13 LABEL maintainer="Jonathan Ervine " # Install updates diff --git a/Jenkinsfile b/Jenkinsfile index ec160a0..284f673 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,11 +27,11 @@ spec: stage('Build with Kaniko') { git url: 'ssh://git@git.ervine.org/jonny/x86_64-alpine-qnap2slack.git', credentialsId: 'jenkins-to-git' container('kaniko') { - sh '/kaniko/executor -f `pwd`/Dockerfile -c `pwd` --cache=true --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.12.1 --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.12' + sh '/kaniko/executor -f `pwd`/Dockerfile -c `pwd` --cache=true --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.13.0 --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.13' } } stage('Notify gchat') { - hangoutsNotify message: "QNAP to Slack Notifier Application on Alpine Linux 3.12.1 has built",token: "A2ET831pVslqXTqAx6ycu573r",threadByJob: false + hangoutsNotify message: "QNAP to Slack Notifier Application on Alpine Linux 3.13.0 has built",token: "A2ET831pVslqXTqAx6ycu573r",threadByJob: false } } } diff --git a/smtp2slack4qnap.py b/smtp2slack4qnap.py index 15bc300..dc1226d 100644 --- a/smtp2slack4qnap.py +++ b/smtp2slack4qnap.py @@ -39,10 +39,10 @@ WEBHOOK_URL = 'http://slack2chat.ipa.champion/AAAAcMVs3C4?key=AIzaSyDdI0hCZtE6vy # implemented LOGIN authentication (non-RFC compliant, works with QNAP-NAS) # overkill for running locally, but mandatory for remote class MyServer(Server): - print("[+] Connection made") authenticated = False @syntax('AUTH LOGIN') async def smtp_AUTH(self, arg): + print("[+] Connection made") if arg != 'LOGIN': await self.push('501 Syntax: AUTH LOGIN') return @@ -64,12 +64,15 @@ class MyServer(Server): # again, overkill for running locally, but mandatory for remote class MyController(Controller): def factory(self): - print("[+} Starting TLS SMTP service") + print("[+] Starting TLS SMTP service") context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + print("[+] Context created") context.load_cert_chain('cert.pem', 'key.pem') + print("[+] Certificate chain loaded") return MyServer(self.handler, tls_context=context, require_starttls=True) def email2text(data): + print("[+] Converting email to text") body = email.message_from_bytes(data).get_payload() h = html2text.HTML2Text() h.ignore_tables = True @@ -92,4 +95,4 @@ if __name__ == '__main__': handler = CustomHandler() controller = MyController(handler, hostname=LHOST, port=LPORT) controller.start() - input('SMTP server has started ...') + input('SMTP server has started ...\n') diff --git a/smtp2slack4qnap.py.old b/smtp2slack4qnap.py.old new file mode 100644 index 0000000..ce1e189 --- /dev/null +++ b/smtp2slack4qnap.py.old @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# +# Compact SMTP to HTTP Gateway +# -> targeting Slack for QNAP-NAS notifications +# + +# generate self-signed cert (better than nothing): +# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3650 -nodes -subj '/CN=localhost' + +import ssl +import asyncio +from aiosmtpd.controller import Controller +from aiosmtpd.smtp import SMTP as Server, syntax +from aiosmtpd.handlers import Debugging +from hashlib import sha256 +from base64 import b64encode, b64decode +import requests +import email +import json +import html2text +import re +import os + +### CONFIG DATA + +# for SMTP AUTH LOGIN (SECRET = sha256(password) avoiding storing plaintext) +USER = 'qnap-messenger' +#SECRET = '8fb12c5b36b2287fd66a433bbcb5f5beaa2645cc9e2d1d9f6fd1177426cbc7cd' +SECRET = '72aa07df56098db02abe681d2ed5bfcc81484e228752935fc4caf07fbe78f858' + +# SMTP listener (set to localhost if running on QNAP device) +LHOST, LPORT = '0.0.0.0', 1025 + +# target slack authenticated webhook url (keep confidential!) +WEBHOOK_URL = 'http://slack2chat.ipa.champion/AAAAcMVs3C4?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=IAoPWEptPtdR1TOS6XtUhqOWZgPwAhabl_sqSvwjtjk%3D' + +### END OF CONFIG DATA + +# implemented LOGIN authentication (non-RFC compliant, works with QNAP-NAS) +# overkill for running locally, but mandatory for remote +class MyServer(Server): + authenticated = False + print("[+] Connection made") + @syntax('AUTH LOGIN') + async def smtp_AUTH(self, arg): + if arg != 'LOGIN': + await self.push('501 Syntax: AUTH LOGIN') + return + await self.push('334 VXNlcm5hbWU=') # b64('Username') + username = await self._reader.readline() + username = b64decode(username.rstrip(b'\r\n')) + await self.push('334 UGFzc3dvcmQ=') # b64('Password') + password = await self._reader.readline() + password = b64decode(password.rstrip(b'\r\n')) + if username.decode() == USER and sha256(password).hexdigest() == SECRET: + self.authenticated = True + print("[+] Authenticated") + await self.push('235 2.7.0 Authentication successful') + else: + print("[-] Authentication Failure") + await self.push('535 Invalid credentials') + +# requires STARTTLS +# again, overkill for running locally, but mandatory for remote +class MyController(Controller): + def factory(self): + print("[+} Starting TLS SMTP service") + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain('cert.pem', 'key.pem') + return MyServer(self.handler, tls_context=context, require_starttls=True) + +def email2text(data): + body = email.message_from_bytes(data).get_payload() + h = html2text.HTML2Text() + h.ignore_tables = True + return re.sub(r'\n\s*\n', '\n\n', h.handle(body)) + +class CustomHandler: + async def handle_DATA(self, server, session, envelope): + if not server.authenticated: + return '500 Unauthenticated. Could not process your message' + mail_from = envelope.mail_from + data = envelope.content + text = email2text(data) + # tuned for slack, but can be anything else + requests.post(WEBHOOK_URL, data={'payload': json.dumps({'username': mail_from, 'text': text})}) + print("[+] Alert sent: {}".format(text.encode())) + return '250 OK' + +if __name__ == '__main__': + os.chdir(os.path.dirname(os.path.abspath(__file__))) + handler = CustomHandler() + controller = MyController(handler, hostname=LHOST, port=LPORT) + controller.start() + input('SMTP server has started ...\n')