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
## 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
- Launch of V1.0.0
- 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 file attachments
- [x] Docker files and configs
- [ ] Web interface
- [x] Web interface
- [x] Choose email
- [x] Get random email address
- [x] Download attachments safely
@ -96,7 +96,7 @@ In Docker you can use the following environment variables:
- [x] Delete messages
- [x] Make better theme
- [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
- [x] Choose domains for random generation
- [x] Choose if out-of-scope emails are discarded

View file

@ -29,7 +29,7 @@ echo ' [+] Setting up config.ini'
_buildConfig() {
echo "[GENERAL]"
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 "ADMIN=${ADMIN:-}"
echo "SHOW_LOGS=${SHOW_LOGS:-false}"
@ -39,7 +39,7 @@ _buildConfig() {
echo "DISCARD_UNKNOWN=${DISCARD_UNKNOWN:-true}"
echo "[DATETIME]"
echo "DATEFORMAT='${DATEFORMAT:-D.M.YYYY HH:mm}'"
echo "DATEFORMAT=${DATEFORMAT:-D.M.YYYY HH:mm}"
echo "[CLEANUP]"
echo "DELETE_OLDER_THAN_DAYS=${DELETE_OLDER_THAN_DAYS:-false}"

View file

@ -19,6 +19,7 @@ DISCARD_UNKNOWN = False
DELETE_OLDER_THAN_DAYS = False
DOMAINS = []
LAST_CLEANUP = 0
URL = ""
class CustomHandler:
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 to: %s' % str(rcpts))
filenamebase = str(int(round(time.time() * 1000)))
# Get the raw email data
raw_email = envelope.content.decode('utf-8')
@ -50,25 +53,11 @@ class CustomHandler:
html += part.get_payload()
else:
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:
filename = 'untitled'
attachments['file%d' % len(attachments)] = (filename,part.get_payload(decode=True))
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)))
attachments['file%d' % len(attachments)] = (filename,part.get_payload(decode=True),cid)
for em in rcpts:
em = em.lower()
@ -89,6 +78,22 @@ class CustomHandler:
if not os.path.exists("../data/"+em):
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
for att in attachments:
@ -99,12 +104,32 @@ class CustomHandler:
file.write(attd[1])
file.close()
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
with open("../data/"+em+"/"+filenamebase+".json", "w") as outfile:
json.dump(savedata, outfile)
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():
if(DELETE_OLDER_THAN_DAYS == False or time.time() - LAST_CLEANUP < 86400):
@ -150,6 +175,7 @@ if __name__ == '__main__':
if("discard_unknown" in Config.options("MAILSERVER")):
DISCARD_UNKNOWN = (Config.get("MAILSERVER", "DISCARD_UNKNOWN").lower() == "true")
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")):
DELETE_OLDER_THAN_DAYS = (Config.get("CLEANUP", "DELETE_OLDER_THAN_DAYS").lower() == "true")

View file

@ -21,6 +21,9 @@ Content-Type: text/html
<html>
<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>
</body>
</html>
@ -28,10 +31,11 @@ Content-Type: text/html
--alternative-boundary--
--boundary-string
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="example.txt"
Content-Type: image/svg+xml
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--
.