diff --git a/README.md b/README.md index 27416d7..8614ac7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,32 @@ -# Open Trashmail -Open source / selfhostable trashmail solution +

+ + PictShare logo +

+ +

Open Trashmail

+ +
+ +![](https://img.shields.io/badge/php-7.1%2B-brightgreen.svg) +![](https://img.shields.io/badge/python-2.7%2B-brightgreen.svg) +[![Apache License](https://img.shields.io/badge/license-Apache-blue.svg?style=flat)](https://github.com/HaschekSolutions/opentrashmail/blob/master/LICENSE) +[![HitCount](http://hits.dwyl.io/HaschekSolutions/opentrashmail.svg)](http://hits.dwyl.io/HaschekSolutions/opentrashmail) +[![](https://img.shields.io/github/stars/HaschekSolutions/opentrashmail.svg?label=Stars&style=social)](https://github.com/HaschekSolutions/opentrashmail) + +#### Host your own `trashmail` solution to use with your own domains or subdomains + +
+ +# Roadmap +- [x] Mailserver + - [x] Storing received mails in JSON + - [x] Storing file attachments +- [ ] Web interface + - [ ] Choose email + - [ ] Get random email address + - [ ] Download attachments in a safe way +- [ ] Customization options +- [ ] Docker files and config # Features - Python powered mail server that works out of the box for any domain diff --git a/python/mailserver.py b/python/mailserver.py index e6fb2d8..3b75aa0 100644 --- a/python/mailserver.py +++ b/python/mailserver.py @@ -1,24 +1,118 @@ import smtpd import asyncore -import uuid +import logging +import email +from email.header import decode_header +from email.Utils import parseaddr +import requests import time import os, sys import json -class CustomSMTPServer(smtpd.SMTPServer): - def process_message(self, peer, mailfrom, rcpttos, data): - print 'Receiving message from:', peer - print 'Message addressed from:', mailfrom - print 'Message addressed to :', rcpttos - print 'Message length :', len(data) - print "----------" - for email in rcpttos: - if not os.path.exists("../data/"+email): - os.mkdir( "../data/"+email, 0755 ) - with open("../data/"+email+"/"+str(int(round(time.time() * 1000)))+".json", "w") as outfile: - json.dump({'sender_ip':peer[0],'from':mailfrom,'rcpts':rcpttos,'data':data}, outfile) - - return -server = CustomSMTPServer(('0.0.0.0', 25), None) +logger = logging.getLogger(__name__) -asyncore.loop() \ No newline at end of file +class CustomSMTPServer(smtpd.SMTPServer): + def process_message(self, peer, mailfrom, rcpttos, data): + try: + mailfrom = parseaddr(mailfrom)[1] + logger.debug('Receiving message from: %s:%d' % peer) + logger.debug('Message addressed from: %s' % mailfrom) + logger.debug('Message addressed to: %s' % str(rcpttos)) + + + + msg = email.message_from_string(data) + subject = '' + for encoded_string, charset in decode_header(msg.get('Subject')): + try: + if charset is not None: + subject += encoded_string.decode(charset) + else: + subject += encoded_string + except: + logger.exception('Error reading part of subject: %s charset %s' % + (encoded_string, charset)) + + logger.debug('Subject: %s' % subject) + + text_parts = [] + attachments = {} + + #logger.debug('Headers: %s' % msg.items()) + + # YOU CAN DO SOME SECURITY CONTROLS HERE + #if (not mailfrom.endswith("@hankenfeld.at") or + # not msg.get('Mail-Header') == 'expected value'): + # raise Exception("Email not trusted") + + # loop on the email parts + for part in msg.walk(): + if part.get_content_maintype() == 'multipart': + continue + + c_type = part.get_content_type() + c_disp = part.get('Content-Disposition') + + # text parts will be appended to text_parts + if c_type == 'text/plain' and c_disp == None: + text_parts.append(part.get_payload(decode=True).strip()) + # ignore html part + elif c_type == 'text/html': + continue + # attachments will be sent as files in the POST request + else: + filename = part.get_filename() + filecontent = part.get_payload(decode=True) + if filecontent is not None: + if filename is None: + filename = 'untitled' + attachments['file%d' % len(attachments)] = (filename, + filecontent) + + body = '\n'.join(text_parts) + + except: + logger.exception('Error reading incoming email') + else: + # this data will be sent as POST data + edata = { + 'subject': subject, + 'body': body, + 'from': mailfrom, + 'attachments':[] + } + savedata = {'sender_ip':peer[0],'from':mailfrom,'rcpts':rcpttos,'raw':data,'parsed':edata} + + filenamebase = str(int(round(time.time() * 1000))) + + for em in rcpttos: + if not os.path.exists("../data/"+em): + os.mkdir( "../data/"+em, 0755 ) + + #same attachments if any + for att in attachments: + if not os.path.exists("../data/"+em+"/attachments"): + os.mkdir( "../data/"+em+"/attachments", 0755 ) + attd = attachments[att] + file = open("../data/"+em+"/attachments/"+filenamebase+"-"+attd[0], 'wb') + file.write(attd[1]) + file.close() + edata["attachments"].append(filenamebase+"-"+attd[0]) + + # save actual json data + with open("../data/"+em+"/"+filenamebase+".json", "w") as outfile: + json.dump(savedata, outfile) + + #print edata + return + +if __name__ == '__main__': + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(formatter) + logger.setLevel(logging.DEBUG) + logger.addHandler(ch) + + server = CustomSMTPServer(('0.0.0.0', 2525), None) # use your public IP here + asyncore.loop() \ No newline at end of file