Merge branch 'refactor-nginx'

master
kaiyou 7 years ago
commit 166aa02b01

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

@ -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.
![Logo](logo.png)
[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

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

Loading…
Cancel
Save