attachmentsystem overhaul and cid replacement

This commit is contained in:
Chris 2023-11-19 22:13:19 +01:00
parent 2e55c2ba1f
commit b821441580
5 changed files with 62 additions and 24 deletions

View file

@ -1,5 +1,13 @@
# Changelog # Changelog
## Nov 19th 2023 - V1.1.3
- Switched SMTP server to Python3 and aiosmptd
- Switched PHP backend to PHP8.1
- Implemented content-id replacement with smart link to API so embedded images will now work
- Updated JSON to include details about attachments (filename,size in bytes,id,cid and a download URL)
- Removed quotes from ini settings
- Made docker start script more neat
## Nov 13th 2023 - V1.0.0 ## Nov 13th 2023 - V1.0.0
- Launch of V1.0.0 - Launch of V1.0.0
- Complete rewrite of the GUI - Complete rewrite of the GUI

View file

@ -84,7 +84,7 @@ In Docker you can use the following environment variables:
- [x] Storing received mails in JSON - [x] Storing received mails in JSON
- [x] Storing file attachments - [x] Storing file attachments
- [x] Docker files and configs - [x] Docker files and configs
- [ ] Web interface - [x] Web interface
- [x] Choose email - [x] Choose email
- [x] Get random email address - [x] Get random email address
- [x] Download attachments safely - [x] Download attachments safely
@ -96,7 +96,7 @@ In Docker you can use the following environment variables:
- [x] Delete messages - [x] Delete messages
- [x] Make better theme - [x] Make better theme
- [x] Secure HTML, so no malicious things can be loaded - [x] Secure HTML, so no malicious things can be loaded
- [ ] Display embedded images inline using Content-ID - [x] Display embedded images inline using Content-ID
- [ ] Configurable settings - [ ] Configurable settings
- [x] Choose domains for random generation - [x] Choose domains for random generation
- [x] Choose if out-of-scope emails are discarded - [x] Choose if out-of-scope emails are discarded

View file

@ -29,7 +29,7 @@ echo ' [+] Setting up config.ini'
_buildConfig() { _buildConfig() {
echo "[GENERAL]" echo "[GENERAL]"
echo "DOMAINS=${DOMAINS:-localhost}" echo "DOMAINS=${DOMAINS:-localhost}"
echo "URL='${URL:-http://localhost:8080}'" echo "URL=${URL:-http://localhost:8080}"
echo "SHOW_ACCOUNT_LIST=${SHOW_ACCOUNT_LIST:-false}" echo "SHOW_ACCOUNT_LIST=${SHOW_ACCOUNT_LIST:-false}"
echo "ADMIN=${ADMIN:-}" echo "ADMIN=${ADMIN:-}"
echo "SHOW_LOGS=${SHOW_LOGS:-false}" echo "SHOW_LOGS=${SHOW_LOGS:-false}"
@ -39,7 +39,7 @@ _buildConfig() {
echo "DISCARD_UNKNOWN=${DISCARD_UNKNOWN:-true}" echo "DISCARD_UNKNOWN=${DISCARD_UNKNOWN:-true}"
echo "[DATETIME]" echo "[DATETIME]"
echo "DATEFORMAT='${DATEFORMAT:-D.M.YYYY HH:mm}'" echo "DATEFORMAT=${DATEFORMAT:-D.M.YYYY HH:mm}"
echo "[CLEANUP]" echo "[CLEANUP]"
echo "DELETE_OLDER_THAN_DAYS=${DELETE_OLDER_THAN_DAYS:-false}" echo "DELETE_OLDER_THAN_DAYS=${DELETE_OLDER_THAN_DAYS:-false}"

View file

@ -19,6 +19,7 @@ DISCARD_UNKNOWN = False
DELETE_OLDER_THAN_DAYS = False DELETE_OLDER_THAN_DAYS = False
DOMAINS = [] DOMAINS = []
LAST_CLEANUP = 0 LAST_CLEANUP = 0
URL = ""
class CustomHandler: class CustomHandler:
async def handle_DATA(self, server, session, envelope): async def handle_DATA(self, server, session, envelope):
@ -31,6 +32,8 @@ class CustomHandler:
logger.debug('Message addressed from: %s' % envelope.mail_from) logger.debug('Message addressed from: %s' % envelope.mail_from)
logger.debug('Message addressed to: %s' % str(rcpts)) logger.debug('Message addressed to: %s' % str(rcpts))
filenamebase = str(int(round(time.time() * 1000)))
# Get the raw email data # Get the raw email data
raw_email = envelope.content.decode('utf-8') raw_email = envelope.content.decode('utf-8')
@ -50,25 +53,11 @@ class CustomHandler:
html += part.get_payload() html += part.get_payload()
else: else:
filename = part.get_filename() filename = part.get_filename()
cid = part.get('Content-ID')
logger.debug('Handling attachment: "%s" of type "%s" with CID "%s"',filename, part.get_content_type(), cid)
if filename is None: if filename is None:
filename = 'untitled' filename = 'untitled'
attachments['file%d' % len(attachments)] = (filename,part.get_payload(decode=True)) attachments['file%d' % len(attachments)] = (filename,part.get_payload(decode=True),cid)
edata = {
'subject': message['subject'],
'body': plaintext,
'htmlbody': html,
'from': message['from'],
'attachments':[]
}
savedata = {'sender_ip':peer[0],
'from':message['from'],
'rcpts':rcpts,
'raw':raw_email,
'parsed':edata
}
filenamebase = str(int(round(time.time() * 1000)))
for em in rcpts: for em in rcpts:
em = em.lower() em = em.lower()
@ -90,6 +79,22 @@ class CustomHandler:
if not os.path.exists("../data/"+em): if not os.path.exists("../data/"+em):
os.mkdir( "../data/"+em, 0o755 ) os.mkdir( "../data/"+em, 0o755 )
edata = {
'subject': message['subject'],
'body': plaintext,
'htmlbody': self.replace_cid_with_attachment_id(html, attachments,filenamebase,em),
'from': message['from'],
'attachments':[],
'attachments_details':[]
}
savedata = {'sender_ip':peer[0],
'from':message['from'],
'rcpts':rcpts,
'raw':raw_email,
'parsed':edata
}
#same attachments if any #same attachments if any
for att in attachments: for att in attachments:
if not os.path.exists("../data/"+em+"/attachments"): if not os.path.exists("../data/"+em+"/attachments"):
@ -99,6 +104,13 @@ class CustomHandler:
file.write(attd[1]) file.write(attd[1])
file.close() file.close()
edata["attachments"].append(filenamebase+"-"+attd[0]) edata["attachments"].append(filenamebase+"-"+attd[0])
edata["attachments_details"].append({
"filename":attd[0],
"cid":attd[2][1:-1],
"id":filenamebase+"-"+attd[0],
"download_url":URL+"/api/attachment/"+em+"/"+filenamebase+"-"+attd[0],
"size":len(attd[1])
})
# save actual json data # save actual json data
with open("../data/"+em+"/"+filenamebase+".json", "w") as outfile: with open("../data/"+em+"/"+filenamebase+".json", "w") as outfile:
@ -106,6 +118,19 @@ class CustomHandler:
return '250 OK' return '250 OK'
def replace_cid_with_attachment_id(self, html_content, attachments,filenamebase,email):
# Replace cid references with attachment filename
for attachment_id in attachments:
attachment = attachments[attachment_id]
filename = attachment[0]
cid = attachment[2]
if cid is None:
continue
cid = cid[1:-1]
if cid is not None:
html_content = html_content.replace('cid:' + cid, "/api/attachment/"+email+"/"+filenamebase+"-"+filename)
return html_content
def cleanup(): def cleanup():
if(DELETE_OLDER_THAN_DAYS == False or time.time() - LAST_CLEANUP < 86400): if(DELETE_OLDER_THAN_DAYS == False or time.time() - LAST_CLEANUP < 86400):
return return
@ -150,6 +175,7 @@ if __name__ == '__main__':
if("discard_unknown" in Config.options("MAILSERVER")): if("discard_unknown" in Config.options("MAILSERVER")):
DISCARD_UNKNOWN = (Config.get("MAILSERVER", "DISCARD_UNKNOWN").lower() == "true") DISCARD_UNKNOWN = (Config.get("MAILSERVER", "DISCARD_UNKNOWN").lower() == "true")
DOMAINS = Config.get("GENERAL", "DOMAINS").lower().split(",") DOMAINS = Config.get("GENERAL", "DOMAINS").lower().split(",")
URL = Config.get("GENERAL", "URL")
if("CLEANUP" in Config.sections() and "delete_older_than_days" in Config.options("CLEANUP")): if("CLEANUP" in Config.sections() and "delete_older_than_days" in Config.options("CLEANUP")):
DELETE_OLDER_THAN_DAYS = (Config.get("CLEANUP", "DELETE_OLDER_THAN_DAYS").lower() == "true") DELETE_OLDER_THAN_DAYS = (Config.get("CLEANUP", "DELETE_OLDER_THAN_DAYS").lower() == "true")

View file

@ -21,6 +21,9 @@ Content-Type: text/html
<html> <html>
<body> <body>
<p><img src="cid:part1.wUxVdgTp.JyT3JNov@localhost.dev"
moz-do-not-send="false"></p>
<p><br>
<p>This is the HTML part of the email.</p> <p>This is the HTML part of the email.</p>
</body> </body>
</html> </html>
@ -28,10 +31,11 @@ Content-Type: text/html
--alternative-boundary-- --alternative-boundary--
--boundary-string --boundary-string
Content-Type: application/octet-stream Content-Type: image/svg+xml
Content-Disposition: attachment; filename="example.txt" Content-Id: <part1.wUxVdgTp.JyT3JNov@localhost.dev>
Content-Disposition: attachment; filename="42.svg"
(Attach the contents of your file here) <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>42</title><path d="M24 12.42l-4.428 4.415H24zm-4.428-4.417l-4.414 4.418v4.414h4.414V12.42L24 8.003V3.575h-4.428zm-4.414 0l4.414-4.428h-4.414zM0 15.996h8.842v4.43h4.412V12.42H4.428l8.826-8.846H8.842L0 12.421z"/></svg>
--boundary-string-- --boundary-string--
. .