mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-05 15:57:23 +01:00
Merge branch 'main' of https://github.com/mail-in-a-box/mailinabox
Upstream is adding handling for utf8 domains by creating a domain alias @utf8 -> @idna. I'm deviating from this approach by setting multiple email address (idna and utf8) per user and alias where a domain contains non-ascii characters. The maildrop (mailbox) remains the same - all mail goes to the user's mailbox regardless of which email address was used. This is more in line with how other systems (eg. active directory), handle multiple email addresses for a single user. # Conflicts: # README.md # management/mailconfig.py # management/templates/index.html # setup/dns.sh # setup/mail-users.sh
This commit is contained in:
@@ -73,14 +73,9 @@ class AuthService:
|
||||
return (None, ["admin"])
|
||||
|
||||
# If the password corresponds with a session token for the user, grant access for that user.
|
||||
if password in self.sessions and self.sessions[password]["email"] == username and not login_only:
|
||||
if self.get_session(username, password, "login", env) and not login_only:
|
||||
sessionid = password
|
||||
session = self.sessions[sessionid]
|
||||
if session["password_token"] != self.create_user_password_state_token(username, env):
|
||||
# This session is invalid because the user's password/MFA state changed
|
||||
# after the session was created.
|
||||
del self.sessions[sessionid]
|
||||
raise ValueError("Session expired.")
|
||||
if logout:
|
||||
# Clear the session.
|
||||
del self.sessions[sessionid]
|
||||
@@ -144,5 +139,14 @@ class AuthService:
|
||||
self.sessions[token] = {
|
||||
"email": username,
|
||||
"password_token": self.create_user_password_state_token(username, env),
|
||||
"type": type,
|
||||
}
|
||||
return token
|
||||
|
||||
def get_session(self, user_email, session_key, session_type, env):
|
||||
if session_key not in self.sessions: return None
|
||||
session = self.sessions[session_key]
|
||||
if session_type == "login" and session["email"] != user_email: return None
|
||||
if session["type"] != session_type: return None
|
||||
if session["password_token"] != self.create_user_password_state_token(session["email"], env): return None
|
||||
return session
|
||||
|
||||
@@ -244,7 +244,7 @@ def mail_aliases():
|
||||
if request.args.get("format", "") == "json":
|
||||
return json_response(get_mail_aliases_ex(env))
|
||||
else:
|
||||
return "".join(address+"\t"+receivers+"\t"+(senders or "")+"\n" for address, receivers, senders in get_mail_aliases(env))
|
||||
return "".join(address+"\t"+receivers+"\t"+(senders or "")+"\n" for address, receivers, senders, auto in get_mail_aliases(env))
|
||||
|
||||
@app.route('/mail/aliases/add', methods=['POST'])
|
||||
@authorized_personnel_only
|
||||
@@ -691,16 +691,42 @@ def postgrey_whitelist_handler():
|
||||
# MUNIN
|
||||
|
||||
@app.route('/munin/')
|
||||
@app.route('/munin/<path:filename>')
|
||||
@authorized_personnel_only
|
||||
def munin(filename=""):
|
||||
# Checks administrative access (@authorized_personnel_only) and then just proxies
|
||||
# the request to static files.
|
||||
def munin_start():
|
||||
# Munin pages, static images, and dynamically generated images are served
|
||||
# outside of the AJAX API. We'll start with a 'start' API that sets a cookie
|
||||
# that subsequent requests will read for authorization. (We don't use cookies
|
||||
# for the API to avoid CSRF vulnerabilities.)
|
||||
response = make_response("OK")
|
||||
response.set_cookie("session", auth_service.create_session_key(request.user_email, env, type='cookie'),
|
||||
max_age=60*30, secure=True, httponly=True, samesite="Strict") # 30 minute duration
|
||||
return response
|
||||
|
||||
def check_request_cookie_for_admin_access():
|
||||
session = auth_service.get_session(None, request.cookies.get("session", ""), "cookie", env)
|
||||
if not session: return False
|
||||
privs = get_mail_user_privileges(session["email"], env)
|
||||
if not isinstance(privs, list): return False
|
||||
if "admin" not in privs: return False
|
||||
return True
|
||||
|
||||
def authorized_personnel_only_via_cookie(f):
|
||||
@wraps(f)
|
||||
def g(*args, **kwargs):
|
||||
if not check_request_cookie_for_admin_access():
|
||||
return Response("Unauthorized", status=403, mimetype='text/plain', headers={})
|
||||
return f(*args, **kwargs)
|
||||
return g
|
||||
|
||||
@app.route('/munin/<path:filename>')
|
||||
@authorized_personnel_only_via_cookie
|
||||
def munin_static_file(filename=""):
|
||||
# Proxy the request to static files.
|
||||
if filename == "": filename = "index.html"
|
||||
return send_from_directory("/var/cache/munin/www", filename)
|
||||
|
||||
@app.route('/munin/cgi-graph/<path:filename>')
|
||||
@authorized_personnel_only
|
||||
@authorized_personnel_only_via_cookie
|
||||
def munin_cgi(filename):
|
||||
""" Relay munin cgi dynazoom requests
|
||||
/usr/lib/munin/cgi/munin-cgi-graph is a perl cgi script in the munin package
|
||||
|
||||
@@ -604,7 +604,7 @@ def get_dns_zonefile(zone, env):
|
||||
|
||||
def write_nsd_conf(zonefiles, additional_records, env):
|
||||
# Write the list of zones to a configuration file.
|
||||
nsd_conf_file = "/etc/nsd/zones.conf"
|
||||
nsd_conf_file = "/etc/nsd/nsd.conf.d/zones.conf"
|
||||
nsdconf = ""
|
||||
|
||||
# Append the zones.
|
||||
|
||||
@@ -114,8 +114,8 @@ def scan_mail_log(env):
|
||||
|
||||
try:
|
||||
import mailconfig
|
||||
users = mailconfig.get_mail_users(env, as_map=True)
|
||||
aliases = mailconfig.get_mail_aliases(env, as_map=True)
|
||||
users = mailconfig.get_mail_users(env, as_map=True, map_by="mail")
|
||||
aliases = mailconfig.get_mail_aliases(env, as_map=True, map_by="mail")
|
||||
collector["known_addresses"] = (set(users.keys()) |
|
||||
set(aliases.keys()))
|
||||
except ImportError:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -476,7 +476,7 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
check_alias_exists("Hostmaster contact address", "hostmaster@" + domain, env, output)
|
||||
|
||||
def check_alias_exists(alias_name, alias, env, output):
|
||||
mail_aliases = get_mail_aliases(env, as_map=True)
|
||||
mail_aliases = get_mail_aliases(env, as_map=True, map_by="mail")
|
||||
if alias in mail_aliases:
|
||||
if mail_aliases[alias]["forward_tos"]:
|
||||
output.print_ok("%s exists as a mail alias. [%s ↦ %s]" % (alias_name, alias, ",".join(mail_aliases[alias]["forward_tos"])))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<style>
|
||||
#alias_table .actions > * { padding-right: 3px; }
|
||||
#alias_table .alias-required .remove { display: none }
|
||||
#alias_table .alias-auto .actions > * { display: none }
|
||||
</style>
|
||||
|
||||
<h2>Aliases</h2>
|
||||
@@ -174,7 +174,7 @@ function show_aliases() {
|
||||
var n = $("#alias-template").clone();
|
||||
n.attr('id', '');
|
||||
|
||||
if (alias.required) n.addClass('alias-required');
|
||||
if (alias.auto) n.addClass('alias-auto');
|
||||
n.attr('data-address', alias.address_display); // this is decoded from IDNA, but will get re-coded to IDNA on the backend
|
||||
n.find('td.address').text(alias.address_display)
|
||||
for (var j = 0; j < alias.forwards_to.length; j++)
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
<li class="dropdown-header">Advanced Pages</li>
|
||||
<li><a href="#custom_dns" onclick="return show_panel(this);">Custom DNS</a></li>
|
||||
<li><a href="#external_dns" onclick="return show_panel(this);">External DNS</a></li>
|
||||
<li><a href="/admin/munin" target="_blank">Munin Monitoring</a></li>
|
||||
<li><a href="#munin" onclick="return show_panel(this);">Munin Monitoring</a></li>
|
||||
<li><a href="#postgrey_whitelist" onclick="return show_panel(this);">Postgrey Whitelist</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -208,6 +208,10 @@
|
||||
{% include "ssl.html" %}
|
||||
</div>
|
||||
|
||||
<div id="panel_munin" class="admin_panel">
|
||||
{% include "munin.html" %}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
|
||||
20
management/templates/munin.html
Normal file
20
management/templates/munin.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<h2>Munin Monitoring</h2>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<p>Opening munin in a new tab... You may need to allow pop-ups for this site.</p>
|
||||
|
||||
<script>
|
||||
function show_munin() {
|
||||
// Set the cookie.
|
||||
api(
|
||||
"/munin",
|
||||
"GET",
|
||||
{ },
|
||||
function(r) {
|
||||
// Redirect.
|
||||
window.open("/admin/munin/index.html", "_blank");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user