diff --git a/conf/nginx-alldomains.conf b/conf/nginx-alldomains.conf new file mode 100644 index 0000000..896e9b0 --- /dev/null +++ b/conf/nginx-alldomains.conf @@ -0,0 +1,79 @@ + # Expose this directory as static files. + root $ROOT; + index index.html index.htm; + + location = /robots.txt { + log_not_found off; + access_log off; + } + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /mailinabox.mobileconfig { + alias /var/lib/mailinabox/mobileconfig.xml; + } + location = /.well-known/autoconfig/mail/config-v1.1.xml { + alias /var/lib/mailinabox/mozilla-autoconfig.xml; + } + + # Roundcube Webmail configuration. + rewrite ^/mail$ /mail/ redirect; + rewrite ^/mail/$ /mail/index.php; + location /mail/ { + index index.php; + alias /usr/local/lib/roundcubemail/; + } + location ~ /mail/config/.* { + # A ~-style location is needed to give this precedence over the next block. + return 403; + } + location ~ /mail/.*\.php { + # note: ~ has precendence over a regular location block + include fastcgi_params; + fastcgi_split_path_info ^/mail(/.*)()$; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name; + fastcgi_pass php-fpm; + + # Outgoing mail also goes through this endpoint, so increase the maximum + # file upload limit to match the corresponding Postfix limit. + client_max_body_size 128M; + } + + # Z-Push (Microsoft Exchange ActiveSync) + location /Microsoft-Server-ActiveSync { + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php; + fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; + fastcgi_read_timeout 630; + fastcgi_pass php-fpm; + + # Outgoing mail also goes through this endpoint, so increase the maximum + # file upload limit to match the corresponding Postfix limit. + client_max_body_size 128M; + } + location /autodiscover/autodiscover.xml { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php; + fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; + fastcgi_pass php-fpm; + } + + + # ADDITIONAL DIRECTIVES HERE + + # Disable viewing dotfiles (.htaccess, .svn, .git, etc.) + # This block is placed at the end. Nginx's precedence rules means this block + # takes precedence over all non-regex matches and only regex matches that + # come after it (i.e. none of those, since this is the last one.) That means + # we're blocking dotfiles in the static hosted sites but not the FastCGI- + # handled locations for ownCloud (which serves user-uploaded files that might + # have this pattern, see #414) or some of the other services. + location ~ /\.(ht|svn|git|hg|bzr) { + log_not_found off; + access_log off; + deny all; + } diff --git a/conf/nginx-primaryonly.conf b/conf/nginx-primaryonly.conf index 2ecd071..f46a3d4 100644 --- a/conf/nginx-primaryonly.conf +++ b/conf/nginx-primaryonly.conf @@ -58,3 +58,4 @@ rewrite ^/.well-known/carddav /cloud/remote.php/carddav/ redirect; rewrite ^/.well-known/caldav /cloud/remote.php/caldav/ redirect; + # ADDITIONAL DIRECTIVES HERE diff --git a/conf/nginx.conf b/conf/nginx.conf index 8c480ec..03d0737 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -33,84 +33,5 @@ server { ssl_certificate_key $SSL_KEY; include /etc/nginx/nginx-ssl.conf; - # Expose this directory as static files. - root $ROOT; - index index.html index.htm; - - location = /robots.txt { - log_not_found off; - access_log off; - } - - location = /favicon.ico { - log_not_found off; - access_log off; - } - - location = /mailinabox.mobileconfig { - alias /var/lib/mailinabox/mobileconfig.xml; - } - location = /.well-known/autoconfig/mail/config-v1.1.xml { - alias /var/lib/mailinabox/mozilla-autoconfig.xml; - } - - # Roundcube Webmail configuration. - rewrite ^/mail$ /mail/ redirect; - rewrite ^/mail/$ /mail/index.php; - location /mail/ { - index index.php; - alias /usr/local/lib/roundcubemail/; - } - location ~ /mail/config/.* { - # A ~-style location is needed to give this precedence over the next block. - return 403; - } - location ~ /mail/.*\.php { - # note: ~ has precendence over a regular location block - include fastcgi_params; - fastcgi_split_path_info ^/mail(/.*)()$; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/roundcubemail/$fastcgi_script_name; - fastcgi_pass php-fpm; - - # Outgoing mail also goes through this endpoint, so increase the maximum - # file upload limit to match the corresponding Postfix limit. - client_max_body_size 128M; - } - - # Z-Push (Microsoft Exchange ActiveSync) - location /Microsoft-Server-ActiveSync { - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/index.php; - fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; - fastcgi_read_timeout 630; - fastcgi_pass php-fpm; - - # Outgoing mail also goes through this endpoint, so increase the maximum - # file upload limit to match the corresponding Postfix limit. - client_max_body_size 128M; - } - location /autodiscover/autodiscover.xml { - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME /usr/local/lib/z-push/autodiscover/autodiscover.php; - fastcgi_param PHP_VALUE "include_path=.:/usr/share/php:/usr/share/pear:/usr/share/awl/inc"; - fastcgi_pass php-fpm; - } - - # ADDITIONAL DIRECTIVES HERE - - # Disable viewing dotfiles (.htaccess, .svn, .git, etc.) - # This block is placed at the end. Nginx's precedence rules means this block - # takes precedence over all non-regex matches and only regex matches that - # come after it (i.e. none of those, since this is the last one.) That means - # we're blocking dotfiles in the static hosted sites but not the FastCGI- - # handled locations for ownCloud (which serves user-uploaded files that might - # have this pattern, see #414) or some of the other services. - location ~ /\.(ht|svn|git|hg|bzr) { - log_not_found off; - access_log off; - deny all; - } - } diff --git a/management/dns_update.py b/management/dns_update.py index cad204d..5530be2 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -57,13 +57,15 @@ def do_dns_update(env, force=False): # Custom records to add to zones. additional_records = list(get_custom_dns_config(env)) + from web_update import get_default_www_redirects + www_redirect_domains = get_default_www_redirects(env) # Write zone files. os.makedirs('/etc/nsd/zones', exist_ok=True) updated_domains = [] for i, (domain, zonefile) in enumerate(zonefiles): # Build the records to put in the zone. - records = build_zone(domain, domains, additional_records, env) + records = build_zone(domain, domains, additional_records, www_redirect_domains, env) # See if the zone has changed, and if so update the serial number # and write the zone file. @@ -126,7 +128,7 @@ def do_dns_update(env, force=False): ######################################################################## -def build_zone(domain, all_domains, additional_records, env, is_zone=True): +def build_zone(domain, all_domains, additional_records, www_redirect_domains, env, is_zone=True): records = [] # For top-level zones, define the authoritative name servers. @@ -177,7 +179,7 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True): subdomains = [d for d in all_domains if d.endswith("." + domain)] for subdomain in subdomains: subdomain_qname = subdomain[0:-len("." + domain)] - subzone = build_zone(subdomain, [], additional_records, env, is_zone=False) + subzone = build_zone(subdomain, [], additional_records, www_redirect_domains, env, is_zone=False) for child_qname, child_rtype, child_value, child_explanation in subzone: if child_qname == None: child_qname = subdomain_qname @@ -215,10 +217,13 @@ def build_zone(domain, all_domains, additional_records, env, is_zone=True): has_rec_base = records defaults = [ (None, "A", env["PUBLIC_IP"], "Required. May have a different value. Sets the IP address that %s resolves to for web hosting and other services besides mail. The A record must be present but its value does not affect mail delivery." % domain), - ("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to, e.g. for web hosting." % domain), (None, "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that %s resolves to, e.g. for web hosting. (It is not necessary for receiving mail on this domain.)" % domain), - ("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to, e.g. for web hosting." % domain), ] + if "www." + domain in www_redirect_domains: + defaults += [ + ("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain), + ("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to so that the box can provide a redirect to the parent domain." % domain), + ] for qname, rtype, value, explanation in defaults: if value is None or value.strip() == "": continue # skip IPV6 if not set if not is_zone and qname == "www": continue # don't create any default 'www' subdomains on what are themselves subdomains @@ -847,8 +852,10 @@ def build_recommended_dns(env): domains = get_dns_domains(env) zonefiles = get_dns_zones(env) additional_records = list(get_custom_dns_config(env)) + from web_update import get_default_www_redirects + www_redirect_domains = get_default_www_redirects(env) for domain, zonefile in zonefiles: - records = build_zone(domain, domains, additional_records, env) + records = build_zone(domain, domains, additional_records, www_redirect_domains, env) # remove records that we don't dislay records = [r for r in records if r[3] is not False] diff --git a/management/status_checks.py b/management/status_checks.py index 27fd26a..cb74043 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -12,7 +12,7 @@ import dns.reversename, dns.resolver import dateutil.parser, dateutil.tz from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns -from web_update import get_web_domains, get_domain_ssl_files +from web_update import get_web_domains, get_default_www_redirects, get_domain_ssl_files from mailconfig import get_mail_domains, get_mail_aliases from utils import shell, sort_domains, load_env_vars_from_file @@ -227,7 +227,7 @@ def run_domain_checks(rounded_time, env, output, pool): dns_domains = set(dns_zonefiles) # Get the list of domains we serve HTTPS for. - web_domains = set(get_web_domains(env)) + web_domains = set(get_web_domains(env) + get_default_www_redirects(env)) domains_to_check = mail_domains | dns_domains | web_domains diff --git a/management/web_update.py b/management/web_update.py index 28ad6b3..05439c4 100644 --- a/management/web_update.py +++ b/management/web_update.py @@ -5,7 +5,7 @@ import os, os.path, shutil, re, tempfile, rtyaml from mailconfig import get_mail_domains -from dns_update import get_custom_dns_config, do_dns_update +from dns_update import get_custom_dns_config, do_dns_update, get_dns_zones from utils import shell, safe_domain_name, sort_domains def get_web_domains(env): @@ -36,21 +36,35 @@ def get_domains_with_a_records(env): domains.add(domain) return domains +def get_default_www_redirects(env): + # Returns a list of www subdomains that we want to provide default redirects + # for, i.e. any www's that aren't domains the user has actually configured + # to serve for real. Which would be unusual. + web_domains = set(get_web_domains(env)) + www_domains = set('www.' + zone for zone, zonefile in get_dns_zones(env)) + return sort_domains(www_domains - web_domains - get_domains_with_a_records(env), env) + def do_web_update(env): # Build an nginx configuration file. nginx_conf = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-top.conf")).read() # Load the templates. - template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read() + template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read() + template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-alldomains.conf")).read() template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read() + template3 = "\trewrite / https://$REDIRECT_DOMAIN permanent;\n" # Add the PRIMARY_HOST configuration first so it becomes nginx's default server. - nginx_conf += make_domain_config(env['PRIMARY_HOSTNAME'], [template1, template2], env) + nginx_conf += make_domain_config(env['PRIMARY_HOSTNAME'], [template0, template1, template2], env) # Add configuration all other web domains. for domain in get_web_domains(env): if domain == env['PRIMARY_HOSTNAME']: continue # handled above - nginx_conf += make_domain_config(domain, [template1], env) + nginx_conf += make_domain_config(domain, [template0, template1], env) + + # Add default www redirects. + for domain in get_default_www_redirects(env): + nginx_conf += make_domain_config(domain, [template0, template3], env) # Did the file change? If not, don't bother writing & restarting nginx. nginx_conf_fn = "/etc/nginx/conf.d/local.conf" @@ -300,4 +314,12 @@ def get_web_domains_info(env): "static_enabled": not has_root_proxy_or_redirect(domain), } for domain in get_web_domains(env) + ] + \ + [ + { + "domain": domain, + "ssl_certificate": check_cert(domain), + "static_enabled": False, + } + for domain in get_default_www_redirects(env) ]