attachmentsystem overhaul and cid replacement
This commit is contained in:
parent
2e55c2ba1f
commit
b821441580
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}"
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -89,6 +78,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:
|
||||||
|
@ -99,12 +104,32 @@ 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:
|
||||||
json.dump(savedata, outfile)
|
json.dump(savedata, outfile)
|
||||||
|
|
||||||
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):
|
||||||
|
@ -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")
|
||||||
|
|
|
@ -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--
|
||||||
.
|
.
|
||||||
|
|
Loading…
Reference in a new issue