v1.2.0 closes #63
This commit is contained in:
parent
bb7613359a
commit
87404cc3d7
|
@ -1,7 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## V1.1.6
|
||||
- Reworked the navbar header to look better on smaller screens
|
||||
## V1.2.0
|
||||
- Implemented IP/Subnet filter using the config option `ALLOWED_IPS`
|
||||
- Implemented Password authentication of the site and API using config option `PASSWORD`
|
||||
- Implemented max attachment size as mentioned in [#63](https://github.com/HaschekSolutions/opentrashmail/issues/63)
|
||||
- Reworked the navbar header to look better on smaller screens
|
||||
|
||||
## V1.1.5
|
||||
- Added support for plaintext file attachments
|
||||
|
|
17
README.md
17
README.md
|
@ -35,7 +35,7 @@
|
|||
- Web interface to manage emails
|
||||
- Generates random email addresses
|
||||
- 100% file based, no database needed
|
||||
- Can be used as Email Honeypot
|
||||
- Can be used as Email Honeypot or to programmatically solve 2fa emails
|
||||
|
||||
# General API calls and functions
|
||||
|
||||
|
@ -64,6 +64,9 @@ Just edit the `config.ini` You can use the following settings
|
|||
- `MAILPORT`-> The port the Python-powered SMTP server will listen on. `Default: 25`
|
||||
- `ADMIN` -> An email address (doesn't have to exist, just has to be valid) that will list all emails of all addresses the server has received. Kind of a catch-all
|
||||
- `DATEFORMAT` -> How should timestamps be shown on the web interface ([moment.js syntax](https://momentjs.com/docs/#/displaying/))
|
||||
- `PASSWORD` -> If configured, site and API can't be used without providing it via form, POST/GET variable `password` or http header `PWD` (eg: `curl -H "PWD: 123456" http://localhost:8080/json...`)
|
||||
- `ALLOWED_IPS` -> Comma separated list of IPv4 or IPv6 CIDR addresses that are allowed to use the web UI or API
|
||||
- `ATTACHMENTS_MAX_SIZE` -> Max size for each individual attachment of an email in Bytes
|
||||
|
||||
## Docker env vars
|
||||
In Docker you can use the following environment variables:
|
||||
|
@ -77,7 +80,9 @@ In Docker you can use the following environment variables:
|
|||
| ADMIN | If set to a valid email address and this address is entered in the API or webinterface, will show all emails of all accounts. Kind-of catch-all | test@test.com
|
||||
| DATEFORMAT | Will format the received date in the web interface based on [moment.js](https://momentjs.com/) syntax | "MMMM Do YYYY, h:mm:ss a" |
|
||||
| SKIP_FILEPERMISSIONS | If set to `true`, won't fix file permissions for the code data folder in the container. Useful for local dev. Default `false` | true,false |
|
||||
|
||||
| PASSWORD | If configured, site and API can't be used without providing it via form, POST/GET variable `password` or http header `PWD` | yousrstrongpassword |
|
||||
| ALLOWED_IPS | Comma separated list of IPv4 or IPv6 CIDR addresses that are allowed to use the web UI or API | `192.168.5.0/24,2a02:ab:cd:ef::/60,172.16.0.0/16` |
|
||||
| ATTACHMENTS_MAX_SIZE | Max size for each individual attachment of an email in Bytes | `2000000` = 2MB |
|
||||
|
||||
# Roadmap
|
||||
- [x] Mail server
|
||||
|
@ -97,13 +102,13 @@ In Docker you can use the following environment variables:
|
|||
- [x] Make better theme
|
||||
- [x] Secure HTML, so no malicious things can be loaded
|
||||
- [x] Display embedded images inline using Content-ID
|
||||
- [ ] Configurable settings
|
||||
- [x] Configurable settings
|
||||
- [x] Choose domains for random generation
|
||||
- [x] Choose if out-of-scope emails are discarded
|
||||
- [x] Automated cleanup of old mails
|
||||
- [ ] Honeypot mode where all emails are also saved for a catchall account
|
||||
- [ ] Optionally secure whole site with a password
|
||||
- [ ] Optionally allow site to be seen only from specific IP Range
|
||||
- [x] Optionally secure whole site with a password
|
||||
- [x] Optionally allow site to be seen only from specific IP Range
|
||||
- [x] Honeypot mode where all emails are also saved for a catchall account (implemented with the ADMIN setting)
|
||||
|
||||
# Quick start
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ services:
|
|||
- DISCARD_UNKNOWN=false
|
||||
- SHOW_ACCOUNT_LIST=true
|
||||
- SHOW_LOGS=true
|
||||
# - PASSWORD=123456
|
||||
# - ALLOWED_IPS=192.168.0.0/16,2a02:ab:cd:ef::/60
|
||||
# - ATTACHMENTS_MAX_SIZE=10000000
|
||||
|
||||
ports:
|
||||
- '2525:25'
|
||||
|
|
|
@ -11,6 +11,9 @@ services:
|
|||
- DATEFORMAT=D.M.YYYY HH:mm
|
||||
- SKIP_FILEPERMISSIONS=true
|
||||
- DISCARD_UNKNOWN=false
|
||||
# - PASSWORD=123456
|
||||
# - ALLOWED_IPS=192.168.0.0/16,2a02:ab:cd:ef::/60
|
||||
# - ATTACHMENTS_MAX_SIZE=10000000
|
||||
|
||||
ports:
|
||||
- '2525:25'
|
||||
|
|
|
@ -33,10 +33,13 @@ _buildConfig() {
|
|||
echo "SHOW_ACCOUNT_LIST=${SHOW_ACCOUNT_LIST:-false}"
|
||||
echo "ADMIN=${ADMIN:-}"
|
||||
echo "SHOW_LOGS=${SHOW_LOGS:-false}"
|
||||
echo "PASSWORD=${PASSWORD:-}"
|
||||
echo "ALLOWED_IPS=${ALLOWED_IPS:-}"
|
||||
|
||||
echo "[MAILSERVER]"
|
||||
echo "MAILPORT=${MAILPORT:-25}"
|
||||
echo "DISCARD_UNKNOWN=${DISCARD_UNKNOWN:-true}"
|
||||
echo "ATTACHMENTS_MAX_SIZE=${ATTACHMENTS_MAX_SIZE:-0}"
|
||||
|
||||
echo "[DATETIME]"
|
||||
echo "DATEFORMAT=${DATEFORMAT:-D.M.YYYY HH:mm}"
|
||||
|
|
|
@ -19,6 +19,14 @@ URL="http://localhost:8080"
|
|||
; Enable to show logs on the website
|
||||
;SHOW_LOGS=false
|
||||
|
||||
; Password authentication for Web UI and API
|
||||
; Passwords have to be sent via the HTTP header "PWD" or as a GET/Post parameter "password"
|
||||
;PASSWORD=mystrongpassword
|
||||
|
||||
; If configured, only these IPs will be allowed to access the Web UI and API (but with no further authentication)
|
||||
; Comma separated if multiple, can be IPv4 or IPv6
|
||||
;ALLOWED_IPS=192.168.0.0/16,2a02:ab:cd:ef::/60
|
||||
|
||||
[MAILSERVER]
|
||||
; Port that the Mailserver will run on (default 25 but that needs root)
|
||||
MAILPORT=25
|
||||
|
@ -27,6 +35,9 @@ MAILPORT=25
|
|||
; this greatly reduces the amount of spam you will receive
|
||||
DISCARD_UNKNOWN=true
|
||||
|
||||
; Limits the size of each attachment in bytes. Leave empty to disable
|
||||
;ATTACHMENTS_MAX_SIZE=2000000 ; 2MB
|
||||
|
||||
; Port number of the !! HIGHLY EXPERIMENTAL !! POP3 server
|
||||
;POP3PORT=110
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
|
|||
# globals for settings
|
||||
DISCARD_UNKNOWN = False
|
||||
DELETE_OLDER_THAN_DAYS = False
|
||||
ATTACHMENTS_MAX_SIZE = 0
|
||||
DOMAINS = []
|
||||
LAST_CLEANUP = 0
|
||||
URL = ""
|
||||
|
@ -50,13 +51,19 @@ class CustomHandler:
|
|||
if part.get_content_type() == 'text/plain':
|
||||
#if it's a file
|
||||
if part.get_filename() is not None:
|
||||
attachments['file%d' % len(attachments)] = self.handleAttachment(part)
|
||||
att = self.handleAttachment(part)
|
||||
if(att == False):
|
||||
return '500 Attachment too large. Max size: ' + str(ATTACHMENTS_MAX_SIZE/1000000)+"MB"
|
||||
attachments['file%d' % len(attachments)] = att
|
||||
else:
|
||||
plaintext += part.get_payload()
|
||||
elif part.get_content_type() == 'text/html':
|
||||
html += part.get_payload()
|
||||
else:
|
||||
attachments['file%d' % len(attachments)] = self.handleAttachment(part)
|
||||
att = self.handleAttachment(part)
|
||||
if(att == False):
|
||||
return '500 Attachment too large. Max size: ' + str(ATTACHMENTS_MAX_SIZE/1000000)+"MB"
|
||||
attachments['file%d' % len(attachments)] = att
|
||||
|
||||
for em in rcpts:
|
||||
em = em.lower()
|
||||
|
@ -132,6 +139,10 @@ class CustomHandler:
|
|||
fid = hashlib.md5(filename.encode('utf-8')).hexdigest()+filename
|
||||
logger.debug('Handling attachment: "%s" (ID: "%s") of type "%s" with CID "%s"',filename, fid,part.get_content_type(), cid)
|
||||
|
||||
if(ATTACHMENTS_MAX_SIZE > 0 and len(part.get_payload(decode=True)) > ATTACHMENTS_MAX_SIZE):
|
||||
logger.info("Attachment too large: " + filename)
|
||||
return False
|
||||
|
||||
return (filename,part.get_payload(decode=True),cid,fid)
|
||||
|
||||
def replace_cid_with_attachment_id(self, html_content, attachments,filenamebase,email):
|
||||
|
@ -192,12 +203,15 @@ if __name__ == '__main__':
|
|||
DISCARD_UNKNOWN = (Config.get("MAILSERVER", "DISCARD_UNKNOWN").lower() == "true")
|
||||
DOMAINS = Config.get("GENERAL", "DOMAINS").lower().split(",")
|
||||
URL = Config.get("GENERAL", "URL")
|
||||
if("attachments_max_size" in Config.options("MAILSERVER")):
|
||||
ATTACHMENTS_MAX_SIZE = int(Config.get("MAILSERVER", "ATTACHMENTS_MAX_SIZE"))
|
||||
|
||||
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")
|
||||
|
||||
logger.info("[i] Starting Mailserver on port " + str(port))
|
||||
logger.info("[i] Discard unknown domains: " + str(DISCARD_UNKNOWN))
|
||||
logger.info("[i] Max size of attachments: " + str(ATTACHMENTS_MAX_SIZE))
|
||||
logger.info("[i] Listening for domains: " + str(DOMAINS))
|
||||
|
||||
asyncio.run(run(port))
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -9,6 +9,40 @@ $url = array_filter(explode('/',ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL
|
|||
|
||||
$backend = new OpenTrashmailBackend($url);
|
||||
|
||||
$settings = loadSettings();
|
||||
|
||||
if($settings['ALLOWED_IPS'])
|
||||
{
|
||||
$ip = getUserIP();
|
||||
if(!isIPInRange( $ip, $settings['ALLOWED_IPS'] ))
|
||||
exit("Your IP ($ip) is not allowed to access this site.");
|
||||
}
|
||||
|
||||
if($settings['PASSWORD']) //site requires a password
|
||||
{
|
||||
session_start();
|
||||
$pw = $settings['PASSWORD'];
|
||||
$auth = false;
|
||||
//first check for auth header or POST/GET variable
|
||||
if(isset($_SERVER['HTTP_PWD']) && $_SERVER['HTTP_PWD'] == $pw)
|
||||
$auth = true;
|
||||
else if(isset($_REQUEST['password']) && $_REQUEST['password'] == $pw)
|
||||
$auth = true;
|
||||
// if not, check for session
|
||||
else if(isset($_SESSION['authenticated']) && $_SESSION['authenticated'] == true)
|
||||
$auth = true;
|
||||
// if user sent a pw but it's wrong, show error
|
||||
else if($_REQUEST['password'] != $settings['PASSWORD'])
|
||||
exit($backend->renderTemplate('password.html',[
|
||||
'error'=>'Wrong password',
|
||||
]));
|
||||
|
||||
if($auth===true)
|
||||
$_SESSION['authenticated'] = true;
|
||||
else
|
||||
exit($backend->renderTemplate('password.html'));
|
||||
}
|
||||
|
||||
if($_SERVER['HTTP_HX_REQUEST']!='true')
|
||||
{
|
||||
if(count($url)==0 || !file_exists(ROOT.DS.implode('/', $url)))
|
||||
|
|
18
web/templates/password.html.php
Normal file
18
web/templates/password.html.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Password Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Enter Password</h1>
|
||||
<form action="/" method="POST">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<h2><?=$error?></h2>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue