1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2026-03-05 15:57:23 +01:00
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:
downtownallday
2021-10-01 17:43:48 -04:00
30 changed files with 1326 additions and 458 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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"])))

View File

@@ -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++)

View File

@@ -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>

View 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>