Merge branch 'refactor-nginx'
This commit is contained in:
@@ -18,7 +18,8 @@ VERSION=stable
|
||||
SECRET_KEY=ChangeMeChangeMe
|
||||
|
||||
# Address where listening ports should bind
|
||||
BIND_ADDRESS=127.0.0.1
|
||||
BIND_ADDRESS4=127.0.0.1
|
||||
BIND_ADDRESS6=::1
|
||||
|
||||
# Main mail domain
|
||||
DOMAIN=mailu.io
|
||||
@@ -94,4 +95,3 @@ COMPOSE_PROJECT_NAME=mailu
|
||||
# Default password scheme used for newly created accounts and changed passwords
|
||||
# (value: SHA512-CRYPT, SHA256-CRYPT, MD5-CRYPT, CRYPT)
|
||||
PASSWORD_SCHEME=SHA512-CRYPT
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -1,16 +1,3 @@
|
||||
:warning: Warning
|
||||
==================
|
||||
|
||||
**Be very careful when using `master`**, especially if you are currently running
|
||||
`1.4`, development of version `1.5` includes refactoring the frontend and
|
||||
authentication mechanisms. At best your server will stop working, at worst you
|
||||
could expose your data to malicious attackers!
|
||||
|
||||
**Do not start using `traefik`** as a frontend server. Traefik was first tested
|
||||
to replace nginx because certificate generation was a nightmare. As we are in the
|
||||
process of completely rewriting the frontend and authentication interface, it will
|
||||
probably be deprecated before `1.5` is out.
|
||||
|
||||

|
||||
|
||||
[Join us and chat about the project.](https://riot.im/app/#/room/#mailu:tedomum.net)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from mailu import db, models
|
||||
|
||||
import socket
|
||||
import urllib
|
||||
|
||||
|
||||
SUPPORTED_AUTH_METHODS = ["none", "plain"]
|
||||
|
||||
|
||||
STATUSES = {
|
||||
"authentication": ("Authentication credentials invalid", {
|
||||
"imap": "AUTHENTICATIONFAILED",
|
||||
@@ -14,21 +16,15 @@ STATUSES = {
|
||||
}
|
||||
|
||||
|
||||
SERVER_MAP = {
|
||||
"imap": ("imap", 143),
|
||||
"smtp": ("smtp", 25)
|
||||
}
|
||||
|
||||
|
||||
def handle_authentication(headers):
|
||||
""" Handle an HTTP nginx authentication request
|
||||
See: http://nginx.org/en/docs/mail/ngx_mail_auth_http_module.html#protocol
|
||||
"""
|
||||
method = headers["Auth-Method"]
|
||||
protocol = headers["Auth-Protocol"]
|
||||
server, port = get_server(headers["Auth-Protocol"])
|
||||
# Incoming mail, no authentication
|
||||
if method == "none" and protocol == "smtp":
|
||||
server, port = get_server(headers["Auth-Protocol"], False)
|
||||
return {
|
||||
"Auth-Status": "OK",
|
||||
"Auth-Server": server,
|
||||
@@ -36,8 +32,9 @@ def handle_authentication(headers):
|
||||
}
|
||||
# Authenticated user
|
||||
elif method == "plain":
|
||||
user_email = headers["Auth-User"]
|
||||
password = headers["Auth-Pass"]
|
||||
server, port = get_server(headers["Auth-Protocol"], True)
|
||||
user_email = urllib.parse.unquote(headers["Auth-User"])
|
||||
password = urllib.parse.unquote(headers["Auth-Pass"])
|
||||
user = models.User.query.get(user_email)
|
||||
if user and user.check_password(password):
|
||||
return {
|
||||
@@ -64,7 +61,13 @@ def get_status(protocol, status):
|
||||
return status, codes[protocol]
|
||||
|
||||
|
||||
def get_server(protocol):
|
||||
hostname, port = SERVER_MAP[protocol]
|
||||
def get_server(protocol, authenticated=False):
|
||||
if protocol == "imap":
|
||||
hostname, port = "imap", 143
|
||||
elif protocol == "pop3":
|
||||
hostname, port = "imap", 110
|
||||
elif protocol == "smtp":
|
||||
hostname = "smtp"
|
||||
port = 10025 if authenticated else 25
|
||||
address = socket.gethostbyname(hostname)
|
||||
return address, port
|
||||
|
||||
@@ -4,8 +4,10 @@ from mailu.internal import internal, nginx
|
||||
import flask
|
||||
|
||||
|
||||
@internal.route("/nginx")
|
||||
@internal.route("/auth/email")
|
||||
def nginx_authentication():
|
||||
""" Main authentication endpoint for Nginx email server
|
||||
"""
|
||||
headers = nginx.handle_authentication(flask.request.headers)
|
||||
response = flask.Response()
|
||||
for key, value in headers.items():
|
||||
|
||||
@@ -8,15 +8,24 @@ services:
|
||||
restart: always
|
||||
env_file: .env
|
||||
ports:
|
||||
- "$BIND_ADDRESS:80:80"
|
||||
- "$BIND_ADDRESS:443:443"
|
||||
- "$BIND_ADDRESS:110:110"
|
||||
- "$BIND_ADDRESS:143:143"
|
||||
- "$BIND_ADDRESS:993:993"
|
||||
- "$BIND_ADDRESS:995:995"
|
||||
- "$BIND_ADDRESS:25:25"
|
||||
- "$BIND_ADDRESS:465:465"
|
||||
- "$BIND_ADDRESS:587:587"
|
||||
- "$BIND_ADDRESS4:80:80"
|
||||
- "$BIND_ADDRESS4:443:443"
|
||||
- "$BIND_ADDRESS4:110:110"
|
||||
- "$BIND_ADDRESS4:143:143"
|
||||
- "$BIND_ADDRESS4:993:993"
|
||||
- "$BIND_ADDRESS4:995:995"
|
||||
- "$BIND_ADDRESS4:25:25"
|
||||
- "$BIND_ADDRESS4:465:465"
|
||||
- "$BIND_ADDRESS4:587:587"
|
||||
- "$BIND_ADDRESS6:80:80"
|
||||
- "$BIND_ADDRESS6:443:443"
|
||||
- "$BIND_ADDRESS6:110:110"
|
||||
- "$BIND_ADDRESS6:143:143"
|
||||
- "$BIND_ADDRESS6:993:993"
|
||||
- "$BIND_ADDRESS6:995:995"
|
||||
- "$BIND_ADDRESS6:25:25"
|
||||
- "$BIND_ADDRESS6:465:465"
|
||||
- "$BIND_ADDRESS6:587:587"
|
||||
volumes:
|
||||
- "$ROOT/certs:/certs"
|
||||
|
||||
|
||||
@@ -19,12 +19,17 @@ http {
|
||||
server_tokens off;
|
||||
absolute_redirect off;
|
||||
|
||||
# Main HTTP server
|
||||
server {
|
||||
# Always listen over HTTP
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
# TLS configuration
|
||||
# Only enable HTTPS if TLS is enabled with no error
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
include /etc/nginx/tls.conf;
|
||||
ssl_session_cache shared:SSLHTTP:50m;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
@@ -34,18 +39,21 @@ http {
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
# In any case, enable the proxy for certbot if the flavor is letsencrypt
|
||||
{% if TLS_FLAVOR == 'letsencrypt' %}
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
proxy_pass http://localhost:8000;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
# Actual logic
|
||||
# If TLS is failing, prevent access to anything except certbot
|
||||
{% if TLS_ERROR %}
|
||||
location / {
|
||||
return 403
|
||||
return 403;
|
||||
}
|
||||
{% else %}
|
||||
|
||||
# Actual logic
|
||||
{% if WEBMAIL != 'none' %}
|
||||
location / {
|
||||
return 301 $scheme://$host/webmail/;
|
||||
@@ -76,11 +84,20 @@ http {
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
# Forwarding authentication server
|
||||
server {
|
||||
listen 127.0.0.1:8000;
|
||||
|
||||
location / {
|
||||
proxy_pass http://admin/internal/;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mail {
|
||||
server_name {{ HOSTNAMES.split(",")[0] }};
|
||||
auth_http http://{{ ADMIN_ADDRESS }}/internal/nginx;
|
||||
auth_http http://127.0.0.1:8000/auth/email;
|
||||
proxy_pass_error_message on;
|
||||
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
@@ -88,18 +105,36 @@ mail {
|
||||
ssl_session_cache shared:SSLMAIL:50m;
|
||||
{% endif %}
|
||||
|
||||
# Default SMTP server for the webmail (no encryption, but authentication)
|
||||
server {
|
||||
listen 10025;
|
||||
protocol smtp;
|
||||
smtp_auth plain;
|
||||
}
|
||||
|
||||
# Default IMAP server for the webmail (no encryption, but authentication)
|
||||
server {
|
||||
listen 10143;
|
||||
protocol imap;
|
||||
smtp_auth plain;
|
||||
}
|
||||
|
||||
# SMTP is always enabled, to avoid losing emails when TLS is failing
|
||||
server {
|
||||
listen 25;
|
||||
{% if TLS_FLAVOR != 'notls' %}
|
||||
listen [::]:25;
|
||||
{% if TLS and not TLS_ERROR %}
|
||||
starttls on;
|
||||
{% endif %}
|
||||
protocol smtp;
|
||||
smtp_auth none;
|
||||
}
|
||||
|
||||
# All other protocols are disabled if TLS is failing
|
||||
{% if not TLS_ERROR %}
|
||||
server {
|
||||
listen 143;
|
||||
listen [::]:143;
|
||||
{% if TLS %}
|
||||
starttls only;
|
||||
{% endif %}
|
||||
@@ -107,22 +142,27 @@ mail {
|
||||
imap_auth plain;
|
||||
}
|
||||
|
||||
{% if TLS %}
|
||||
server {
|
||||
listen 465 ssl;
|
||||
listen 587;
|
||||
listen [::]:587;
|
||||
{% if TLS %}
|
||||
starttls only;
|
||||
{% endif %}
|
||||
protocol smtp;
|
||||
smtp_auth plain;
|
||||
}
|
||||
|
||||
{% if TLS %}
|
||||
server {
|
||||
listen 597;
|
||||
starttls only;
|
||||
listen 465 ssl;
|
||||
listen [::]:465 ssl;
|
||||
protocol smtp;
|
||||
smtp_auth plain;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 993 ssl;
|
||||
listen [::]:993 ssl;
|
||||
protocol imap;
|
||||
imap_auth plain;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
import socket
|
||||
|
||||
convert = lambda src, dst, args: open(dst, "w").write(jinja2.Template(open(src).read()).render(**args))
|
||||
|
||||
args = os.environ.copy()
|
||||
|
||||
if "ADMIN_ADDRESS" not in os.environ:
|
||||
args["ADMIN_ADDRESS"] = socket.gethostbyname("admin")
|
||||
|
||||
args["TLS"] = {
|
||||
"cert": ("/certs/cert.pem", "/certs/key.pem"),
|
||||
"letsencrypt": ("/certs/letsencrypt/live/mailu/fullchain.pem",
|
||||
|
||||
@@ -31,7 +31,7 @@ relayhost = {{ RELAYHOST }}
|
||||
# Recipient delimiter for extended addresses
|
||||
recipient_delimiter = {{ RECIPIENT_DELIMITER }}
|
||||
|
||||
# XClient for connection from the frontend
|
||||
# Only the front server is allowed to perform xclient
|
||||
smtpd_authorized_xclient_hosts={{ FRONT_ADDRESS }}
|
||||
|
||||
###############
|
||||
@@ -78,23 +78,14 @@ smtpd_delay_reject = yes
|
||||
# Allowed senders are: the user or one of the alias destinations
|
||||
smtpd_sender_login_maps = $virtual_alias_maps
|
||||
|
||||
# Helo restrictions are specified for smtp only in master.cf
|
||||
# Restrictions for incoming SMTP, other restrictions are applied in master.cf
|
||||
smtpd_helo_required = yes
|
||||
|
||||
# Sender restrictions
|
||||
smtpd_sender_restrictions =
|
||||
permit_mynetworks,
|
||||
reject_non_fqdn_sender,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unlisted_sender,
|
||||
reject_sender_login_mismatch,
|
||||
permit
|
||||
|
||||
# Recipient restrictions:
|
||||
smtpd_recipient_restrictions =
|
||||
permit_mynetworks,
|
||||
reject_unauth_pipelining,
|
||||
reject_non_fqdn_recipient,
|
||||
check_sender_access ${sql}sqlite-reject-spoofed.cf,
|
||||
reject_non_fqdn_sender,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unknown_recipient_domain,
|
||||
permit
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# service type private unpriv chroot wakeup maxproc command + args
|
||||
# (yes) (yes) (yes) (never) (100)
|
||||
|
||||
# Exposed SMTP services
|
||||
# Exposed SMTP service
|
||||
smtp inet n - n - - smtpd
|
||||
-o cleanup_service_name=outclean
|
||||
|
||||
# Additional services
|
||||
# Internal SMTP service
|
||||
10025 inet n - n - - smtpd
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_recipient_restrictions=reject_unlisted_sender,reject_sender_login_mismatch,permit
|
||||
-o cleanup_service_name=outclean
|
||||
outclean unix n - n - 0 cleanup
|
||||
-o header_checks=pcre:/etc/postfix/outclean_header_filter
|
||||
-o header_checks=pcre:/etc/postfix/outclean_header_filter.cf
|
||||
|
||||
# Internal postfix services
|
||||
pickup unix n - n 60 1 pickup
|
||||
|
||||
5
postfix/conf/sqlite-reject-spoofed.cf
Normal file
5
postfix/conf/sqlite-reject-spoofed.cf
Normal file
@@ -0,0 +1,5 @@
|
||||
dbpath = /data/main.db
|
||||
query =
|
||||
SELECT 'REJECT' FROM domain WHERE name='%s'
|
||||
UNION
|
||||
SELECT 'REJECT' FROM alternative WHERE name='%s'
|
||||
@@ -14,9 +14,9 @@ stock = utf-8
|
||||
|
||||
[auth]
|
||||
type = IMAP
|
||||
imap_hostname = imap
|
||||
imap_port = 993
|
||||
imap_ssl = True
|
||||
imap_hostname = front
|
||||
imap_port = 10143
|
||||
imap_ssl = False
|
||||
|
||||
[git]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
imap_host = "front"
|
||||
imap_port = 143
|
||||
imap_port = 10143
|
||||
imap_secure = "None"
|
||||
imap_short_login = Off
|
||||
sieve_use = On
|
||||
@@ -8,7 +8,7 @@ sieve_host = "imap"
|
||||
sieve_port = 4190
|
||||
sieve_secure = "TLS"
|
||||
smtp_host = "front"
|
||||
smtp_port = 25
|
||||
smtp_port = 10025
|
||||
smtp_secure = "None"
|
||||
smtp_short_login = Off
|
||||
smtp_auth = On
|
||||
|
||||
@@ -18,9 +18,9 @@ $config['plugins'] = array(
|
||||
|
||||
// Mail servers
|
||||
$config['default_host'] = 'front';
|
||||
$config['default_port'] = 143;
|
||||
$config['default_port'] = 10143;
|
||||
$config['smtp_server'] = 'front';
|
||||
$config['smtp_port'] = 25;
|
||||
$config['smtp_port'] = 10025;
|
||||
$config['smtp_user'] = '%u';
|
||||
$config['smtp_pass'] = '%p';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user