diff --git a/core/admin/start.py b/core/admin/start.py index 0d7f2110..f92d845e 100755 --- a/core/admin/start.py +++ b/core/admin/start.py @@ -52,15 +52,21 @@ def test_DNS(): test_DNS() -start_command=" ".join([ - "gunicorn", - f"--threads {str(os.cpu_count())}", - "-b :80", +cmdline = [ + "gunicorn", + "--threads", f"{os.cpu_count()}", + # If SUBNET6 is defined, gunicorn must listen on IPv6 as well as IPv4 + "-b", f"{'[::]' if os.environ.get('SUBNET6') else ''}:80", "--logger-class mailu.Logger", "--worker-tmp-dir /dev/shm", - "--access-logfile -" if (log.root.level<=log.INFO) else "", - "--error-logfile -", - "--preload", - "'mailu:create_app()'"]) + "--error-logfile", "-", + "--preload" +] -os.system(start_command) +# logging +if log.root.level <= log.INFO: + cmdline.extend(["--access-logfile", "-"]) + +cmdline.append("'mailu:create_app()'") + +os.system(" ".join(cmdline)) diff --git a/core/nginx/conf/nginx.conf b/core/nginx/conf/nginx.conf index db161862..38cf7871 100644 --- a/core/nginx/conf/nginx.conf +++ b/core/nginx/conf/nginx.conf @@ -59,7 +59,9 @@ http { server { # Listen over HTTP listen 80; +{% if SUBNET6 %} listen [::]:80; +{% endif %} {% if TLS_FLAVOR == 'letsencrypt' %} location ^~ /.well-known/acme-challenge/ { proxy_pass http://127.0.0.1:8008; @@ -91,13 +93,17 @@ http { # Listen on HTTP only in kubernetes or behind reverse proxy {% if KUBERNETES_INGRESS == 'true' or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %} listen 80; +{% if SUBNET6 %} listen [::]:80; +{% endif %} {% endif %} # Only enable HTTPS if TLS is enabled with no error and not on kubernetes {% if KUBERNETES_INGRESS != 'true' and TLS and not TLS_ERROR %} listen 443 ssl http2; +{% if SUBNET6 %} listen [::]:443 ssl http2; +{% endif %} include /etc/nginx/tls.conf; ssl_stapling on; @@ -341,7 +347,9 @@ mail { # SMTP is always enabled, to avoid losing emails when TLS is failing server { listen 25; +{% if SUBNET6 %} listen [::]:25; +{% endif %} {% if TLS and not TLS_ERROR %} {% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %} ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem; @@ -363,7 +371,9 @@ mail { {% if not TLS_ERROR %} server { listen 143; +{% if SUBNET6 %} listen [::]:143; +{% endif %} {% if TLS %} starttls only; {% endif %} @@ -376,7 +386,9 @@ mail { server { listen 110; +{% if SUBNET6 %} listen [::]:110; +{% endif %} {% if TLS %} starttls only; {% endif %} @@ -389,7 +401,9 @@ mail { server { listen 587; +{% if SUBNET6 %} listen [::]:587; +{% endif %} {% if TLS %} starttls only; {% endif %} @@ -401,7 +415,9 @@ mail { {% if TLS %} server { listen 465 ssl; +{% if SUBNET6 %} listen [::]:465 ssl; +{% endif %} protocol smtp; smtp_auth plain login; auth_http_header Auth-Port 465; @@ -409,7 +425,9 @@ mail { server { listen 993 ssl; +{% if SUBNET6 %} listen [::]:993 ssl; +{% endif %} protocol imap; imap_auth plain; auth_http_header Auth-Port 993; @@ -419,7 +437,9 @@ mail { server { listen 995 ssl; +{% if SUBNET6 %} listen [::]:995 ssl; +{% endif %} protocol pop3; pop3_auth plain; auth_http_header Auth-Port 995; diff --git a/core/oletools/Dockerfile b/core/oletools/Dockerfile index 8526e506..39fd0e18 100644 --- a/core/oletools/Dockerfile +++ b/core/oletools/Dockerfile @@ -19,7 +19,7 @@ HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG" USER nobody:nobody ENV \ - OLEFY_BINDADDRESS="0.0.0.0" \ + OLEFY_BINDADDRESS="" \ OLEFY_BINDPORT="11343" \ OLEFY_OLEVBA_PATH="/app/venv/bin/olevba" \ OLEFY_PYTHON_PATH="/app/venv/bin/python3" \ diff --git a/core/postfix/conf/main.cf b/core/postfix/conf/main.cf index 0f6fd392..2e038f61 100644 --- a/core/postfix/conf/main.cf +++ b/core/postfix/conf/main.cf @@ -14,7 +14,7 @@ queue_directory = /queue message_size_limit = {{ MESSAGE_SIZE_LIMIT }} # Relayed networks -mynetworks = 127.0.0.1/32 [::1]/128 {{ SUBNET }} {% if SUBNET6 %}{{ "[{}]/{}".format(*SUBNET6.split("/")) }}{% endif %} {% if RELAYNETS %}{{ RELAYNETS.split(",") | join(" ") }}{% endif %} +mynetworks = 127.0.0.1/32 {{ SUBNET }} {% if SUBNET6 %}[::1]/128 {{ "[{}]/{}".format(*SUBNET6.translate({91: None, 93: None}).split("/")) }}{% endif %} {% if RELAYNETS %}{{ RELAYNETS.split(",") | join(" ") }}{% endif %} # Empty alias list to override the configuration variable and disable NIS alias_maps = @@ -121,7 +121,7 @@ smtpd_relay_restrictions = unverified_recipient_reject_reason = Address lookup failure -smtpd_authorized_xclient_hosts={{ SUBNET }}{% if SUBNET6 %},[{{ SUBNET6 }}]{% endif %} +smtpd_authorized_xclient_hosts={{ SUBNET }}{% if SUBNET6 %},{{ "[{}]/{}".format(*SUBNET6.translate({91: None, 93: None}).split("/")) }}{% endif %} ############### # Milter diff --git a/docs/faq.rst b/docs/faq.rst index 3f16fc6d..c73304b0 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -145,51 +145,43 @@ Your mail service will be reachable for IMAP, POP3, SMTP and Webmail at the addr How to make IPv6 work? `````````````````````` -Docker currently does not expose the IPv6 ports properly, as it does not interface with ``ip6tables``. -Lets start with quoting everything that's wrong: +Docker IPv6 interfacing with ``ip6tables``, which is required for proper IPv6 support, is currently considered experimental. - Unfortunately, initially Docker was not created with IPv6 in mind. - It was added later and, while it has come a long way, is still not as usable as one would want. - Much discussion is still going on as to how IPv6 should be used in a containerized world; - See the various GitHub issues linked below: +Although the supposed way to enable IPv6 would be to give each container a publicly routable address, docker's IPv6 support +uses NAT to pass outside connections to the containers. - - Giving each container a publicly routable address means all ports (even unexposed / unpublished ports) are suddenly - reachable by everyone, if no additional filtering is done - (`docker/docker#21614 `_) - - By default, each container gets a random IPv6, making it impossible to do properly do DNS; - the alternative is to assign a specific IPv6 address to each container, - still an administrative hassle (`docker/docker#13481 `_) - - Published ports won't work on IPv6, unless you have the userland proxy enabled - (which, for now, is enabled by default in Docker) - - The userland proxy, however, seems to be on its way out - (`docker/docker#14856 `_) and has various issues, like: +Currently we recommend to use `docker-ipv6nat` by `Robert Klarenbeek ` instead of docker's +experimental support. - - It can use a lot of RAM (`docker/docker#11185 `_) - - Source IP addresses are rewritten, making it completely unusable for many purposes, e.g. mail servers - (`docker/docker#17666 `_), - (`docker/libnetwork#1099 `_). +Before enabling IPv6 you **MUST** disable the userland-proxy in your ``/etc/docker/daemon.json`` to not create an Open Relay! - -- `Robbert Klarenbeek `_ (docker-ipv6nat author) +.. code-block:: json -Okay, but I still want to use IPv6! Can I just use the installers IPv6 checkbox? **NO, YOU SHOULD NOT DO THAT!** Why you ask? -Mailu has its own trusted IPv4 network, every container inside this network can use e.g. the SMTP container without further -authentication. If you enabled IPv6 inside the setup assistant (and fixed the ports to also be exposed on IPv6) Docker will -still rewrite any incoming IPv6 requests to an IPv4 address, *which is located inside the trusted network*. Therefore any -incoming connection to the SMTP container will bypass the authentication stage by the front container regardless of your -settings and causes an Open Relay. And you really don't want this! + { + "userland-proxy": false + } -So, how to make it work? Well, by using `docker-ipv6nat`_! This nifty container will set up ``ip6tables``, -just as Docker would do for IPv4. We know that NAT-ing is not advised in IPv6, -however exposing all containers to public network neither. The choice is ultimately yous. +You can enable `docker-ipv6nat` like this: -Mailu `setup utility`_ generates a safe IPv6 ULA subnet by default. So when you run the following command, -Mailu will start to function on IPv6: + docker run -d --name ipv6nat --privileged --network host --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock:ro -v /lib/modules:/lib/modules:ro robbertkl/ipv6nat -.. code-block:: bash +If you want to try docker's experimental IPv6 support, it can be enabled like this: - docker run -d --restart=always -v /var/run/docker.sock:/var/run/docker.sock:ro --privileged --net=host robbertkl/ipv6nat +.. code-block:: json + + { + "userland-proxy": false, + "ipv6": true, + "experimental": true, + "fixed-cidr-v6": "fd00:1234:abcd::/48", + "ip6tables": true + } + +and enabling the IPv6 checkbox in the `setup utility`_. + +This setup however is not officially supported, and might result in unforeseen issues. +With bad misconfiguration you might even cause your instance to become an Open Relay, you have been warned! -.. _`docker-ipv6nat`: https://github.com/robbertkl/docker-ipv6nat .. _`setup utility`: https://setup.mailu.io How does Mailu scale up? diff --git a/optional/radicale/radicale.conf b/optional/radicale/radicale.conf index 6c99d8e0..fb1304d1 100644 --- a/optional/radicale/radicale.conf +++ b/optional/radicale/radicale.conf @@ -1,5 +1,5 @@ [server] -hosts = 0.0.0.0:5232, [::]:5232 +hosts = :5232 ssl = False [encoding] diff --git a/setup/flavors/compose/docker-compose.yml b/setup/flavors/compose/docker-compose.yml index 87b4183b..3ca8236a 100644 --- a/setup/flavors/compose/docker-compose.yml +++ b/setup/flavors/compose/docker-compose.yml @@ -33,7 +33,7 @@ services: - "{{ bind4 }}:{{ port }}:{{ port }}" {% endif %} {% if ipv6_enabled and bind6 %} - - "{{ bind6 }}:{{ port }}:{{ port }}" + - "[{{ bind6 }}]:{{ port }}:{{ port }}" {% endif %} {% endfor %} networks: diff --git a/towncrier/2630.feature b/towncrier/2630.feature new file mode 100644 index 00000000..29865199 --- /dev/null +++ b/towncrier/2630.feature @@ -0,0 +1 @@ +Improved IPv6 support diff --git a/webmails/nginx-webmail.conf b/webmails/nginx-webmail.conf index fde7adfd..8772c8c8 100644 --- a/webmails/nginx-webmail.conf +++ b/webmails/nginx-webmail.conf @@ -1,6 +1,8 @@ server { listen 80 default_server; +{% if SUBNET6 %} listen [::]:80 default_server; +{% endif %} resolver {{ RESOLVER }} valid=30s; {% if WEBMAIL == 'roundcube' %}