mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2025-01-23 12:37:05 +00:00
feat: renamed PRIMARY_HOSTNAME to BOX_HOSTNAME
using "primary" to describe the domain of the box / mail server is confusing when working with multiple domains. Usually the box domain is different from the domain you want to host your mail for.
This commit is contained in:
parent
230a6dc7bf
commit
93d1055869
4
Vagrantfile
vendored
4
Vagrantfile
vendored
@ -8,7 +8,7 @@ Vagrant.configure("2") do |config|
|
||||
# to the public web. However, we currently don't want to expose SSH since
|
||||
# the machine's box will let anyone log into it. So instead we'll put the
|
||||
# machine on a private network.
|
||||
config.vm.hostname = "mailinabox.lan"
|
||||
config.vm.hostname = "box.mailinabox.lan"
|
||||
config.vm.network "private_network", ip: "192.168.56.4"
|
||||
|
||||
config.vm.provision :shell, :inline => <<-SH
|
||||
@ -18,7 +18,7 @@ Vagrant.configure("2") do |config|
|
||||
export NONINTERACTIVE=1
|
||||
export PUBLIC_IP=auto
|
||||
export PUBLIC_IPV6=auto
|
||||
export PRIMARY_HOSTNAME=auto
|
||||
export BOX_HOSTNAME=auto
|
||||
#export SKIP_NETWORK_CHECKS=1
|
||||
|
||||
# Start the setup script.
|
||||
|
@ -13,19 +13,19 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>CalDAVAccountDescription</key>
|
||||
<string>PRIMARY_HOSTNAME calendar</string>
|
||||
<string>BOX_HOSTNAME calendar</string>
|
||||
<key>CalDAVHostName</key>
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<string>BOX_HOSTNAME</string>
|
||||
<key>CalDAVPort</key>
|
||||
<real>443</real>
|
||||
<key>CalDAVUseSSL</key>
|
||||
<true/>
|
||||
<key>PayloadDescription</key>
|
||||
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<string>BOX_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>PRIMARY_HOSTNAME calendar</string>
|
||||
<string>BOX_HOSTNAME calendar</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.CalDAV</string>
|
||||
<string>email.mailinabox.mobileconfig.BOX_HOSTNAME.CalDAV</string>
|
||||
<key>PayloadOrganization</key>
|
||||
<string></string>
|
||||
<key>PayloadType</key>
|
||||
@ -37,13 +37,13 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>EmailAccountDescription</key>
|
||||
<string>PRIMARY_HOSTNAME mail</string>
|
||||
<string>BOX_HOSTNAME mail</string>
|
||||
<key>EmailAccountType</key>
|
||||
<string>EmailTypeIMAP</string>
|
||||
<key>IncomingMailServerAuthentication</key>
|
||||
<string>EmailAuthPassword</string>
|
||||
<key>IncomingMailServerHostName</key>
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<string>BOX_HOSTNAME</string>
|
||||
<key>IncomingMailServerPortNumber</key>
|
||||
<integer>993</integer>
|
||||
<key>IncomingMailServerUseSSL</key>
|
||||
@ -51,7 +51,7 @@
|
||||
<key>OutgoingMailServerAuthentication</key>
|
||||
<string>EmailAuthPassword</string>
|
||||
<key>OutgoingMailServerHostName</key>
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<string>BOX_HOSTNAME</string>
|
||||
<key>OutgoingMailServerPortNumber</key>
|
||||
<integer>465</integer>
|
||||
<key>OutgoingMailServerUseSSL</key>
|
||||
@ -59,11 +59,11 @@
|
||||
<key>OutgoingPasswordSameAsIncomingPassword</key>
|
||||
<true/>
|
||||
<key>PayloadDescription</key>
|
||||
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<string>BOX_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>PRIMARY_HOSTNAME mail</string>
|
||||
<string>BOX_HOSTNAME mail</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.E-Mail</string>
|
||||
<string>email.mailinabox.mobileconfig.BOX_HOSTNAME.E-Mail</string>
|
||||
<key>PayloadOrganization</key>
|
||||
<string></string>
|
||||
<key>PayloadType</key>
|
||||
@ -81,9 +81,9 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CardDAVAccountDescription</key>
|
||||
<string>PRIMARY_HOSTNAME contacts</string>
|
||||
<string>BOX_HOSTNAME contacts</string>
|
||||
<key>CardDAVHostName</key>
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<string>BOX_HOSTNAME</string>
|
||||
<key>CardDAVPort</key>
|
||||
<integer>443</integer>
|
||||
<key>CardDAVPrincipalURL</key>
|
||||
@ -91,11 +91,11 @@
|
||||
<key>CardDAVUseSSL</key>
|
||||
<true/>
|
||||
<key>PayloadDescription</key>
|
||||
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<string>BOX_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>PRIMARY_HOSTNAME contacts</string>
|
||||
<string>BOX_HOSTNAME contacts</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME.carddav</string>
|
||||
<string>email.mailinabox.mobileconfig.BOX_HOSTNAME.carddav</string>
|
||||
<key>PayloadOrganization</key>
|
||||
<string></string>
|
||||
<key>PayloadType</key>
|
||||
@ -107,11 +107,11 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>PayloadDescription</key>
|
||||
<string>PRIMARY_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<string>BOX_HOSTNAME (Mail-in-a-Box)</string>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>PRIMARY_HOSTNAME</string>
|
||||
<string>BOX_HOSTNAME</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>email.mailinabox.mobileconfig.PRIMARY_HOSTNAME</string>
|
||||
<string>email.mailinabox.mobileconfig.BOX_HOSTNAME</string>
|
||||
<key>PayloadOrganization</key>
|
||||
<string></string>
|
||||
<key>PayloadRemovalDisallowed</key>
|
||||
|
@ -1,13 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<clientConfig version="1.1">
|
||||
<emailProvider id="PRIMARY_HOSTNAME">
|
||||
<domain>PRIMARY_HOSTNAME</domain>
|
||||
<emailProvider id="BOX_HOSTNAME">
|
||||
<domain>BOX_HOSTNAME</domain>
|
||||
|
||||
<displayName>PRIMARY_HOSTNAME (Mail-in-a-Box)</displayName>
|
||||
<displayShortName>PRIMARY_HOSTNAME</displayShortName>
|
||||
<displayName>BOX_HOSTNAME (Mail-in-a-Box)</displayName>
|
||||
<displayShortName>BOX_HOSTNAME</displayShortName>
|
||||
|
||||
<incomingServer type="imap">
|
||||
<hostname>PRIMARY_HOSTNAME</hostname>
|
||||
<hostname>BOX_HOSTNAME</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
@ -15,7 +15,7 @@
|
||||
</incomingServer>
|
||||
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>PRIMARY_HOSTNAME</hostname>
|
||||
<hostname>BOX_HOSTNAME</hostname>
|
||||
<port>465</port>
|
||||
<socketType>SSL</socketType>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
@ -24,14 +24,14 @@
|
||||
<useGlobalPreferredServer>false</useGlobalPreferredServer>
|
||||
</outgoingServer>
|
||||
|
||||
<documentation url="https://PRIMARY_HOSTNAME/">
|
||||
<descr lang="en">PRIMARY_HOSTNAME website.</descr>
|
||||
<documentation url="https://BOX_HOSTNAME/">
|
||||
<descr lang="en">BOX_HOSTNAME website.</descr>
|
||||
</documentation>
|
||||
</emailProvider>
|
||||
|
||||
<webMail>
|
||||
<loginPage url="https://PRIMARY_HOSTNAME/mail/" />
|
||||
<loginPageInfo url="https://PRIMARY_HOSTNAME/mail/" >
|
||||
<loginPage url="https://BOX_HOSTNAME/mail/" />
|
||||
<loginPageInfo url="https://BOX_HOSTNAME/mail/" >
|
||||
<username>%EMAILADDRESS%</username>
|
||||
<usernameField id="rcmloginuser" name="_user" />
|
||||
<passwordField id="rcmloginpwd" name="_pass" />
|
||||
@ -39,6 +39,6 @@
|
||||
</loginPageInfo>
|
||||
</webMail>
|
||||
|
||||
<clientConfigUpdate url="https://PRIMARY_HOSTNAME/.well-known/autoconfig/mail/config-v1.1.xml" />
|
||||
<clientConfigUpdate url="https://BOX_HOSTNAME/.well-known/autoconfig/mail/config-v1.1.xml" />
|
||||
|
||||
</clientConfig>
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: STSv1
|
||||
mode: MODE
|
||||
mx: PRIMARY_HOSTNAME
|
||||
max_age: 604800
|
||||
version: STSv1
|
||||
mode: MODE
|
||||
mx: BOX_HOSTNAME
|
||||
max_age: 604800
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Remove the first line of the Received: header. Note that we cannot fully remove the Received: header
|
||||
# because OpenDKIM requires that a header be present when signing outbound mail. The first line is
|
||||
# where the user's home IP address would be.
|
||||
/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP])$1
|
||||
/^\s*Received:[^\n]*(.*)/ REPLACE Received: from authenticated-user (BOX_HOSTNAME [PUBLIC_IP])$1
|
||||
|
||||
# Remove other typically private information.
|
||||
/^\s*User-Agent:/ IGNORE
|
||||
|
@ -10,7 +10,7 @@ define('TIMEZONE', '');
|
||||
// Defines the base path on the server
|
||||
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
|
||||
|
||||
define('ZPUSH_HOST', 'PRIMARY_HOSTNAME');
|
||||
define('ZPUSH_HOST', 'BOX_HOSTNAME');
|
||||
|
||||
define('USE_FULLEMAIL_FOR_LOGIN', true);
|
||||
|
||||
|
@ -125,7 +125,7 @@ def index():
|
||||
|
||||
|
||||
return render_template('index.html',
|
||||
hostname=env['PRIMARY_HOSTNAME'],
|
||||
hostname=env['BOX_HOSTNAME'],
|
||||
storage_root=env['STORAGE_ROOT'],
|
||||
|
||||
no_users_exist=no_users_exist,
|
||||
|
@ -22,13 +22,13 @@ DOMAIN_RE = r"^(?!\-)(?:[*][.])?(?:[a-zA-Z\d\-_]{0,62}[a-zA-Z\d_]\.){1,126}(?!\d
|
||||
def get_dns_domains(env):
|
||||
# Add all domain names in use by email users and mail aliases, any
|
||||
# domains we serve web for (except www redirects because that would
|
||||
# lead to infinite recursion here) and ensure PRIMARY_HOSTNAME is in the list.
|
||||
# lead to infinite recursion here) and ensure BOX_HOSTNAME is in the list.
|
||||
from mailconfig import get_mail_domains
|
||||
from web_update import get_web_domains
|
||||
domains = set()
|
||||
domains |= set(get_mail_domains(env))
|
||||
domains |= set(get_web_domains(env, include_www_redirects=False))
|
||||
domains.add(env['PRIMARY_HOSTNAME'])
|
||||
domains.add(env['BOX_HOSTNAME'])
|
||||
return domains
|
||||
|
||||
def get_dns_zones(env):
|
||||
@ -144,10 +144,10 @@ def build_zones(env):
|
||||
auto_domains = web_domains - set(get_web_domains(env, include_auto=False))
|
||||
domains |= auto_domains # www redirects not included in the initial list, see above
|
||||
|
||||
# Add ns1/ns2+PRIMARY_HOSTNAME which must also have A/AAAA records
|
||||
# Add ns1/ns2+BOX_HOSTNAME which must also have A/AAAA records
|
||||
# when the box is acting as authoritative DNS server for its domains.
|
||||
for ns in ("ns1", "ns2"):
|
||||
d = ns + "." + env["PRIMARY_HOSTNAME"]
|
||||
d = ns + "." + env["BOX_HOSTNAME"]
|
||||
domains.add(d)
|
||||
auto_domains.add(d)
|
||||
|
||||
@ -161,9 +161,9 @@ def build_zones(env):
|
||||
for domain in domains
|
||||
}
|
||||
|
||||
# For MTA-STS, we'll need to check if the PRIMARY_HOSTNAME certificate is
|
||||
# For MTA-STS, we'll need to check if the BOX_HOSTNAME certificate is
|
||||
# singned and valid. Check that now rather than repeatedly for each domain.
|
||||
domains[env["PRIMARY_HOSTNAME"]]["certificate-is-valid"] = is_domain_cert_signed_and_valid(env["PRIMARY_HOSTNAME"], env)
|
||||
domains[env["BOX_HOSTNAME"]]["certificate-is-valid"] = is_domain_cert_signed_and_valid(env["BOX_HOSTNAME"], env)
|
||||
|
||||
# Load custom records to add to zones.
|
||||
additional_records = list(get_custom_dns_config(env))
|
||||
@ -186,19 +186,19 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
||||
# 'False' in the tuple indicates these records would not be used if the zone
|
||||
# is managed outside of the box.
|
||||
if is_zone:
|
||||
# Obligatory NS record to ns1.PRIMARY_HOSTNAME.
|
||||
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False))
|
||||
# Obligatory NS record to ns1.BOX_HOSTNAME.
|
||||
records.append((None, "NS", "ns1.%s." % env["BOX_HOSTNAME"], False))
|
||||
|
||||
# NS record to ns2.PRIMARY_HOSTNAME or whatever the user overrides.
|
||||
# NS record to ns2.BOX_HOSTNAME or whatever the user overrides.
|
||||
# User may provide one or more additional nameservers
|
||||
secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \
|
||||
or ["ns2." + env["PRIMARY_HOSTNAME"]]
|
||||
or ["ns2." + env["BOX_HOSTNAME"]]
|
||||
records.extend((None, "NS", secondary_ns+'.', False) for secondary_ns in secondary_ns_list)
|
||||
|
||||
|
||||
# In PRIMARY_HOSTNAME...
|
||||
if domain == env["PRIMARY_HOSTNAME"]:
|
||||
# Set the A/AAAA records. Do this early for the PRIMARY_HOSTNAME so that the user cannot override them
|
||||
# In BOX_HOSTNAME...
|
||||
if domain == env["BOX_HOSTNAME"]:
|
||||
# Set the A/AAAA records. Do this early for the BOX_HOSTNAME so that the user cannot override them
|
||||
# and we can provide different explanatory text.
|
||||
records.append((None, "A", env["PUBLIC_IP"], "Required. Sets the IP address of the box."))
|
||||
if env.get("PUBLIC_IPV6"): records.append((None, "AAAA", env["PUBLIC_IPV6"], "Required. Sets the IPv6 address of the box."))
|
||||
@ -281,7 +281,7 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
||||
if domain_properties[domain]["mail"]:
|
||||
# The MX record says where email for the domain should be delivered: Here!
|
||||
if not has_rec(None, "MX", prefix="10 "):
|
||||
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
|
||||
records.append((None, "MX", "10 %s." % env["BOX_HOSTNAME"], "Required. Specifies the hostname (and priority) of the machine that handles @%s mail." % domain))
|
||||
|
||||
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
|
||||
# the domain, and no one else.
|
||||
@ -304,14 +304,14 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
||||
records.append(("_dmarc", "TXT", 'v=DMARC1; p=quarantine;', "Recommended. Specifies that mail that does not originate from the box but claims to be from @%s or which does not have a valid DKIM signature is suspect and should be quarantined by the recipient's mail system." % domain))
|
||||
|
||||
if domain_properties[domain]["user"]:
|
||||
# Add CardDAV/CalDAV SRV records on the non-primary hostname that points to the primary hostname
|
||||
# Add CardDAV/CalDAV SRV records on the non-box hostname that points to the box hostname
|
||||
# for autoconfiguration of mail clients (so only domains hosting user accounts need it).
|
||||
# The SRV record format is priority (0, whatever), weight (0, whatever), port, service provider hostname (w/ trailing dot).
|
||||
if domain != env["PRIMARY_HOSTNAME"]:
|
||||
if domain != env["BOX_HOSTNAME"]:
|
||||
for dav in ("card", "cal"):
|
||||
qname = "_" + dav + "davs._tcp"
|
||||
if not has_rec(qname, "SRV"):
|
||||
records.append((qname, "SRV", "0 0 443 " + env["PRIMARY_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
|
||||
records.append((qname, "SRV", "0 0 443 " + env["BOX_HOSTNAME"] + ".", "Recommended. Specifies the hostname of the server that handles CardDAV/CalDAV services for email addresses on this domain."))
|
||||
|
||||
# If this is a domain name that there are email addresses configured for, i.e. "something@"
|
||||
# this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461)
|
||||
@ -324,7 +324,7 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
||||
#
|
||||
# The policy itself is served at the "mta-sts" (no underscore) subdomain over HTTPS. Therefore
|
||||
# the TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
|
||||
# domain name (PRIMARY_HOSTNAME) *and* the TLS certificate used by nginx for HTTPS on the mta-sts
|
||||
# domain name (BOX_HOSTNAME) *and* the TLS certificate used by nginx for HTTPS on the mta-sts
|
||||
# subdomain must be valid certificate for that domain. Do not set an MTA-STS policy if either
|
||||
# certificate in use is not valid (e.g. because it is self-signed and a valid certificate has not
|
||||
# yet been provisioned). Since we cannot provision a certificate without A/AAAA records, we
|
||||
@ -332,7 +332,7 @@ def build_zone(domain, domain_properties, additional_records, env, is_zone=True)
|
||||
# being valid certificates.
|
||||
mta_sts_records = [ ]
|
||||
if domain_properties[domain]["mail"] \
|
||||
and domain_properties[env["PRIMARY_HOSTNAME"]]["certificate-is-valid"] \
|
||||
and domain_properties[env["BOX_HOSTNAME"]]["certificate-is-valid"] \
|
||||
and is_domain_cert_signed_and_valid("mta-sts." + domain, env):
|
||||
# Compute an up-to-32-character hash of the policy file. We'll take a SHA-1 hash of the policy
|
||||
# file (20 bytes) and encode it as base-64 (28 bytes, using alphanumeric alternate characters
|
||||
@ -479,7 +479,7 @@ def write_nsd_zone(domain, zonefile, records, env, force):
|
||||
# ldns-signzone, however. It used to say '; default zone domain'.
|
||||
#
|
||||
# The SOA contact address for all of the domains on this system is hostmaster
|
||||
# @ the PRIMARY_HOSTNAME. Hopefully that's legit.
|
||||
# @ the BOX_HOSTNAME. Hopefully that's legit.
|
||||
#
|
||||
# For the refresh through TTL fields, a good reference is:
|
||||
# https://www.ripe.net/publications/docs/ripe-203
|
||||
@ -492,7 +492,7 @@ def write_nsd_zone(domain, zonefile, records, env, force):
|
||||
$ORIGIN {domain}.
|
||||
$TTL 86400 ; default time to live
|
||||
|
||||
@ IN SOA ns1.{primary_domain}. hostmaster.{primary_domain}. (
|
||||
@ IN SOA ns1.{box_domain}. hostmaster.{box_domain}. (
|
||||
__SERIAL__ ; serial number
|
||||
7200 ; Refresh (secondary nameserver update interval)
|
||||
3600 ; Retry (when refresh fails, how often to try again, should be lower than the refresh)
|
||||
@ -502,7 +502,7 @@ $TTL 86400 ; default time to live
|
||||
"""
|
||||
|
||||
# Replace replacement strings.
|
||||
zone = zone.format(domain=domain, primary_domain=env["PRIMARY_HOSTNAME"])
|
||||
zone = zone.format(domain=domain, box_domain=env["BOX_HOSTNAME"])
|
||||
|
||||
# Add records.
|
||||
for subdomain, querytype, value, _explanation in records:
|
||||
|
@ -22,7 +22,7 @@ env = load_environment()
|
||||
subject = sys.argv[1]
|
||||
|
||||
# Administrator's email address.
|
||||
admin_addr = "administrator@" + env['PRIMARY_HOSTNAME']
|
||||
admin_addr = "administrator@" + env['BOX_HOSTNAME']
|
||||
|
||||
# Read in STDIN.
|
||||
content = sys.stdin.read().strip()
|
||||
@ -37,9 +37,9 @@ msg = MIMEMultipart('alternative')
|
||||
# In Python 3.6:
|
||||
#msg = Message()
|
||||
|
||||
msg['From'] = '"{}" <{}>'.format(env['PRIMARY_HOSTNAME'], admin_addr)
|
||||
msg['From'] = '"{}" <{}>'.format(env['BOX_HOSTNAME'], admin_addr)
|
||||
msg['To'] = admin_addr
|
||||
msg['Subject'] = "[{}] {}".format(env['PRIMARY_HOSTNAME'], subject)
|
||||
msg['Subject'] = "[{}] {}".format(env['BOX_HOSTNAME'], subject)
|
||||
|
||||
content_html = f'<html><body><pre style="overflow-x: scroll; white-space: pre;">{html.escape(content)}</pre></body></html>'
|
||||
|
||||
|
@ -517,7 +517,7 @@ def add_auto_aliases(aliases, env):
|
||||
conn.commit()
|
||||
|
||||
def get_system_administrator(env):
|
||||
return "administrator@" + env['PRIMARY_HOSTNAME']
|
||||
return "administrator@" + env['BOX_HOSTNAME']
|
||||
|
||||
def get_required_aliases(env):
|
||||
# These are the aliases that must exist.
|
||||
@ -527,7 +527,7 @@ def get_required_aliases(env):
|
||||
aliases.add(get_system_administrator(env))
|
||||
|
||||
# The hostmaster alias is exposed in the DNS SOA for each zone.
|
||||
aliases.add("hostmaster@" + env['PRIMARY_HOSTNAME'])
|
||||
aliases.add("hostmaster@" + env['BOX_HOSTNAME'])
|
||||
|
||||
# Get a list of domains we serve mail for, except ones for which the only
|
||||
# email on that domain are the required aliases or a catch-all/domain-forwarder.
|
||||
|
@ -83,7 +83,7 @@ def provision_totp(email, env):
|
||||
# Make a URI that we encode within a QR code.
|
||||
uri = pyotp.TOTP(secret).provisioning_uri(
|
||||
name=email,
|
||||
issuer_name=env["PRIMARY_HOSTNAME"] + " Mail-in-a-Box Control Panel"
|
||||
issuer_name=env["BOX_HOSTNAME"] + " Mail-in-a-Box Control Panel"
|
||||
)
|
||||
|
||||
# Generate a QR code as a base64-encode PNG image.
|
||||
|
@ -28,7 +28,7 @@ def get_ssl_certificates(env):
|
||||
if fn == 'ssl_certificate.pem':
|
||||
# This is always a symbolic link
|
||||
# to the certificate to use for
|
||||
# PRIMARY_HOSTNAME. Don't let it
|
||||
# BOX_HOSTNAME. Don't let it
|
||||
# be eligible for use because we
|
||||
# could end up creating a symlink
|
||||
# to itself --- we want to find
|
||||
@ -73,8 +73,8 @@ def get_ssl_certificates(env):
|
||||
domains = { }
|
||||
for cert in certificates:
|
||||
# What domains is this certificate good for?
|
||||
cert_domains, primary_domain = get_certificate_domains(cert["cert"])
|
||||
cert["primary_domain"] = primary_domain
|
||||
cert_domains, common_name = get_certificate_domains(cert["cert"])
|
||||
cert["common_name"] = common_name
|
||||
|
||||
# Is there a private key file for this certificate?
|
||||
private_key = private_keys.get(cert["cert"].public_key().public_numbers())
|
||||
@ -84,9 +84,9 @@ def get_ssl_certificates(env):
|
||||
|
||||
# Add this cert to the list of certs usable for the domains.
|
||||
for domain in cert_domains:
|
||||
# The primary hostname can only use a certificate mapped
|
||||
# The box hostname can only use a certificate mapped
|
||||
# to the system private key.
|
||||
if domain == env['PRIMARY_HOSTNAME'] and cert["private_key"]["filename"] != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
|
||||
if domain == env['BOX_HOSTNAME'] and cert["private_key"]["filename"] != os.path.join(env['STORAGE_ROOT'], 'ssl', 'ssl_private_key.pem'):
|
||||
continue
|
||||
|
||||
domains.setdefault(domain, []).append(cert)
|
||||
@ -134,7 +134,7 @@ def get_ssl_certificates(env):
|
||||
ret[domain] = {
|
||||
"private-key": cert["private_key"]["filename"],
|
||||
"certificate": cert["filename"],
|
||||
"primary-domain": cert["primary_domain"],
|
||||
"common-name": cert["common_name"],
|
||||
"certificate_object": cert["cert"],
|
||||
}
|
||||
|
||||
@ -148,12 +148,12 @@ def get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=False
|
||||
system_certificate = {
|
||||
"private-key": ssl_private_key,
|
||||
"certificate": ssl_certificate,
|
||||
"primary-domain": env['PRIMARY_HOSTNAME'],
|
||||
"common-name": env['BOX_HOSTNAME'],
|
||||
"certificate_object": load_pem(load_cert_chain(ssl_certificate)[0]),
|
||||
}
|
||||
|
||||
if use_main_cert and domain == env['PRIMARY_HOSTNAME']:
|
||||
# The primary domain must use the server certificate because
|
||||
if use_main_cert and domain == env['BOX_HOSTNAME']:
|
||||
# The box domain must use the server certificate because
|
||||
# it is hard-coded in some service configuration files.
|
||||
return system_certificate
|
||||
|
||||
@ -263,7 +263,7 @@ def provision_certificates(env, limit_domains):
|
||||
# we'll create a list of lists of domains where the inner lists have
|
||||
# at most 100 items. By sorting we also get the DNS zone domain as the first
|
||||
# entry in each list (unless we overflow beyond 100) which ends up as the
|
||||
# primary domain listed in each certificate.
|
||||
# first domain listed in each certificate.
|
||||
from dns_update import get_dns_zones
|
||||
certs = { }
|
||||
for zone, _zonefile in get_dns_zones(env):
|
||||
@ -467,21 +467,21 @@ def install_cert_copy_file(fn, env):
|
||||
def post_install_func(env):
|
||||
ret = []
|
||||
|
||||
# Get the certificate to use for PRIMARY_HOSTNAME.
|
||||
# Get the certificate to use for BOX_HOSTNAME.
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
cert = get_domain_ssl_files(env['PRIMARY_HOSTNAME'], ssl_certificates, env, use_main_cert=False)
|
||||
cert = get_domain_ssl_files(env['BOX_HOSTNAME'], ssl_certificates, env, use_main_cert=False)
|
||||
if not cert:
|
||||
# Ruh-row, we don't have any certificate usable
|
||||
# for the primary hostname.
|
||||
ret.append("there is no valid certificate for " + env['PRIMARY_HOSTNAME'])
|
||||
# for the box hostname.
|
||||
ret.append("there is no valid certificate for " + env['BOX_HOSTNAME'])
|
||||
|
||||
# Symlink the best cert for PRIMARY_HOSTNAME to the system
|
||||
# Symlink the best cert for BOX_HOSTNAME to the system
|
||||
# certificate path, which is hard-coded for various purposes, and then
|
||||
# restart postfix and dovecot.
|
||||
system_ssl_certificate = os.path.join(os.path.join(env["STORAGE_ROOT"], 'ssl', 'ssl_certificate.pem'))
|
||||
if cert and os.readlink(system_ssl_certificate) != cert['certificate']:
|
||||
# Update symlink.
|
||||
ret.append("updating primary certificate")
|
||||
ret.append("updating box certificate")
|
||||
ssl_certificate = cert['certificate']
|
||||
os.unlink(system_ssl_certificate)
|
||||
os.symlink(ssl_certificate, system_ssl_certificate)
|
||||
@ -523,7 +523,7 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
|
||||
# First check that the domain name is one of the names allowed by
|
||||
# the certificate.
|
||||
if domain is not None:
|
||||
certificate_names, _cert_primary_name = get_certificate_domains(cert)
|
||||
certificate_names, _cn = get_certificate_domains(cert)
|
||||
|
||||
# Check that the domain appears among the acceptable names, or a wildcard
|
||||
# form of the domain name (which is a stricter check than the specs but
|
||||
@ -558,9 +558,9 @@ def check_certificate(domain, ssl_certificate, ssl_private_key, warn_if_expiring
|
||||
if cert.issuer == cert.subject:
|
||||
return ("SELF-SIGNED", None)
|
||||
|
||||
# When selecting which certificate to use for non-primary domains, we check if the primary
|
||||
# certificate or a www-parent-domain certificate is good for the domain. There's no need
|
||||
# to run extra checks beyond this point.
|
||||
# When selecting which certificate to use for non-registrable domains, we check if the
|
||||
# registrable domain certificate or a www-parent-domain certificate is good for the domain.
|
||||
# There's no need to run extra checks beyond this point.
|
||||
if just_check_domain:
|
||||
return ("OK", None)
|
||||
|
||||
|
@ -216,7 +216,7 @@ def check_software_updates(env, output):
|
||||
def check_system_aliases(env, output):
|
||||
# Check that the administrator alias exists since that's where all
|
||||
# admin email is automatically directed.
|
||||
check_alias_exists("System administrator address", "administrator@" + env['PRIMARY_HOSTNAME'], env, output)
|
||||
check_alias_exists("System administrator address", "administrator@" + env['BOX_HOSTNAME'], env, output)
|
||||
|
||||
def check_free_disk_space(rounded_values, env, output):
|
||||
# Check free disk space.
|
||||
@ -382,8 +382,8 @@ def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zone
|
||||
output.add_heading(domain)
|
||||
output.print_error("Domain name is invalid: " + str(e))
|
||||
|
||||
if domain == env["PRIMARY_HOSTNAME"]:
|
||||
check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles)
|
||||
if domain == env["BOX_HOSTNAME"]:
|
||||
check_box_hostname_dns(domain, env, output, dns_domains, dns_zonefiles)
|
||||
|
||||
if domain in dns_domains:
|
||||
check_dns_zone(domain, env, output, dns_zonefiles)
|
||||
@ -419,13 +419,13 @@ def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zone
|
||||
|
||||
return (domain, output)
|
||||
|
||||
def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
def check_box_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
# If a DS record is set on the zone containing this domain, check DNSSEC now.
|
||||
has_dnssec = False
|
||||
for zone in dns_domains:
|
||||
if (zone == domain or domain.endswith("." + zone)) and query_dns(zone, "DS", nxdomain=None) is not None:
|
||||
has_dnssec = True
|
||||
check_dnssec(zone, env, output, dns_zonefiles, is_checking_primary=True)
|
||||
check_dnssec(zone, env, output, dns_zonefiles, is_checking_box_domain=True)
|
||||
|
||||
ip = query_dns(domain, "A")
|
||||
ns_ips = query_dns("ns1." + domain, "A") + '/' + query_dns("ns2." + domain, "A")
|
||||
@ -437,35 +437,35 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles):
|
||||
# the nameserver, are reporting the right info --- but if the glue is incorrect this
|
||||
# will probably fail.
|
||||
if ns_ips == env['PUBLIC_IP'] + '/' + env['PUBLIC_IP']:
|
||||
output.print_ok("Nameserver glue records are correct at registrar. [ns1/ns2.{} ↦ {}]".format(env['PRIMARY_HOSTNAME'], env['PUBLIC_IP']))
|
||||
output.print_ok("Nameserver glue records are correct at registrar. [ns1/ns2.{} ↦ {}]".format(env['BOX_HOSTNAME'], env['PUBLIC_IP']))
|
||||
|
||||
elif ip == env['PUBLIC_IP']:
|
||||
# The NS records are not what we expect, but the domain resolves correctly, so
|
||||
# the user may have set up external DNS. List this discrepancy as a warning.
|
||||
output.print_warning("""Nameserver glue records (ns1.{} and ns2.{}) should be configured at your domain name
|
||||
registrar as having the IP address of this box ({}). They currently report addresses of {}. If you have set up External DNS, this may be OK.""".format(env['PRIMARY_HOSTNAME'], env['PRIMARY_HOSTNAME'], env['PUBLIC_IP'], ns_ips))
|
||||
registrar as having the IP address of this box ({}). They currently report addresses of {}. If you have set up External DNS, this may be OK.""".format(env['BOX_HOSTNAME'], env['BOX_HOSTNAME'], env['PUBLIC_IP'], ns_ips))
|
||||
|
||||
else:
|
||||
output.print_error("""Nameserver glue records are incorrect. The ns1.{} and ns2.{} nameservers must be configured at your domain name
|
||||
registrar as having the IP address {}. They currently report addresses of {}. It may take several hours for
|
||||
public DNS to update after a change.""".format(env['PRIMARY_HOSTNAME'], env['PRIMARY_HOSTNAME'], env['PUBLIC_IP'], ns_ips))
|
||||
public DNS to update after a change.""".format(env['BOX_HOSTNAME'], env['BOX_HOSTNAME'], env['PUBLIC_IP'], ns_ips))
|
||||
|
||||
# Check that PRIMARY_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
||||
# Check that BOX_HOSTNAME resolves to PUBLIC_IP[V6] in public DNS.
|
||||
ipv6 = query_dns(domain, "AAAA") if env.get("PUBLIC_IPV6") else None
|
||||
if ip == env['PUBLIC_IP'] and not (ipv6 and env['PUBLIC_IPV6'] and ipv6 != normalize_ip(env['PUBLIC_IPV6'])):
|
||||
output.print_ok("Domain resolves to box's IP address. [{} ↦ {}]".format(env['PRIMARY_HOSTNAME'], my_ips))
|
||||
output.print_ok("Domain resolves to box's IP address. [{} ↦ {}]".format(env['BOX_HOSTNAME'], my_ips))
|
||||
else:
|
||||
output.print_error("""This domain must resolve to this box's IP address ({}) in public DNS but it currently resolves
|
||||
to {}. It may take several hours for public DNS to update after a change. This problem may result from other
|
||||
issues listed above.""".format(my_ips, ip + ((" / " + ipv6) if ipv6 is not None else "")))
|
||||
|
||||
|
||||
# Check reverse DNS matches the PRIMARY_HOSTNAME. Note that it might not be
|
||||
# Check reverse DNS matches the BOX_HOSTNAME. Note that it might not be
|
||||
# a DNS zone if it is a subdomain of another domain we have a zone for.
|
||||
existing_rdns_v4 = query_dns(dns.reversename.from_address(env['PUBLIC_IP']), "PTR")
|
||||
existing_rdns_v6 = query_dns(dns.reversename.from_address(env['PUBLIC_IPV6']), "PTR") if env.get("PUBLIC_IPV6") else None
|
||||
if existing_rdns_v4 == domain and existing_rdns_v6 in {None, domain}:
|
||||
output.print_ok("Reverse DNS is set correctly at ISP. [{} ↦ {}]".format(my_ips, env['PRIMARY_HOSTNAME']))
|
||||
output.print_ok("Reverse DNS is set correctly at ISP. [{} ↦ {}]".format(my_ips, env['BOX_HOSTNAME']))
|
||||
elif existing_rdns_v4 == existing_rdns_v6 or existing_rdns_v6 is None:
|
||||
output.print_error(f"""This box's reverse DNS is currently {existing_rdns_v4}, but it should be {domain}. Your ISP or cloud provider will have instructions
|
||||
on setting up reverse DNS for this box.""" )
|
||||
@ -518,10 +518,10 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
|
||||
custom_dns_records = list(get_custom_dns_config(env)) # generator => list so we can reuse it
|
||||
correct_ip = "; ".join(sorted(get_custom_dns_records(custom_dns_records, domain, "A"))) or env['PUBLIC_IP']
|
||||
custom_secondary_ns = get_secondary_dns(custom_dns_records, mode="NS")
|
||||
secondary_ns = custom_secondary_ns or ["ns2." + env['PRIMARY_HOSTNAME']]
|
||||
secondary_ns = custom_secondary_ns or ["ns2." + env['BOX_HOSTNAME']]
|
||||
|
||||
existing_ns = query_dns(domain, "NS")
|
||||
correct_ns = "; ".join(sorted(["ns1." + env["PRIMARY_HOSTNAME"], *secondary_ns]))
|
||||
correct_ns = "; ".join(sorted(["ns1." + env["BOX_HOSTNAME"], *secondary_ns]))
|
||||
ip = query_dns(domain, "A")
|
||||
|
||||
probably_external_dns = False
|
||||
@ -595,7 +595,7 @@ def check_dns_zone_suggestions(domain, env, output, dns_zonefiles, domains_with_
|
||||
check_dnssec(domain, env, output, dns_zonefiles)
|
||||
|
||||
|
||||
def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
|
||||
def check_dnssec(domain, env, output, dns_zonefiles, is_checking_box_domain=False):
|
||||
# See if the domain has a DS record set at the registrar. The DS record must
|
||||
# match one of the keys that we've used to sign the zone. It may use one of
|
||||
# several hashing algorithms. We've pre-generated all possible valid DS
|
||||
@ -661,7 +661,7 @@ def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
|
||||
IMPORTANT: Do not delete existing DNSSEC 'DS' records for this domain until confirmation that the new DNSSEC 'DS' record
|
||||
for this domain is valid.""")
|
||||
else:
|
||||
if is_checking_primary:
|
||||
if is_checking_box_domain:
|
||||
output.print_error("""The DNSSEC 'DS' record for %s is incorrect. See further details below.""" % domain)
|
||||
return
|
||||
output.print_error("""This domain's DNSSEC DS record is incorrect. The chain of trust is broken between the public DNS system
|
||||
@ -702,7 +702,7 @@ def check_dnssec(domain, env, output, dns_zonefiles, is_checking_primary=False):
|
||||
def check_mail_domain(domain, env, output):
|
||||
# Check the MX record.
|
||||
|
||||
recommended_mx = "10 " + env['PRIMARY_HOSTNAME']
|
||||
recommended_mx = "10 " + env['BOX_HOSTNAME']
|
||||
mx = query_dns(domain, "MX", nxdomain=None)
|
||||
|
||||
if mx is None or mx == "[timeout]":
|
||||
@ -713,26 +713,26 @@ def check_mail_domain(domain, env, output):
|
||||
mxhost = mx.split('; ')[0].split(' ')[1]
|
||||
|
||||
if mxhost is None:
|
||||
# A missing MX record is okay on the primary hostname because
|
||||
# the primary hostname's A record (the MX fallback) is... itself,
|
||||
# A missing MX record is okay on the box hostname because
|
||||
# the box hostname's A record (the MX fallback) is... itself,
|
||||
# which is what we want the MX to be.
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
if domain == env['BOX_HOSTNAME']:
|
||||
output.print_ok(f"Domain's email is directed to this domain. [{domain} has no MX record, which is ok]")
|
||||
|
||||
# And a missing MX record is okay on other domains if the A record
|
||||
# matches the A record of the PRIMARY_HOSTNAME. Actually this will
|
||||
# matches the A record of the BOX_HOSTNAME. Actually this will
|
||||
# probably confuse DANE TLSA, but we'll let that slide for now.
|
||||
else:
|
||||
domain_a = query_dns(domain, "A", nxdomain=None)
|
||||
primary_a = query_dns(env['PRIMARY_HOSTNAME'], "A", nxdomain=None)
|
||||
if domain_a is not None and domain_a == primary_a:
|
||||
box_a = query_dns(env['BOX_HOSTNAME'], "A", nxdomain=None)
|
||||
if domain_a is not None and domain_a == box_a:
|
||||
output.print_ok(f"Domain's email is directed to this domain. [{domain} has no MX record but its A record is OK]")
|
||||
else:
|
||||
output.print_error(f"""This domain's DNS MX record is not set. It should be '{recommended_mx}'. Mail will not
|
||||
be delivered to this box. It may take several hours for public DNS to update after a
|
||||
change. This problem may result from other issues listed here.""")
|
||||
|
||||
elif mxhost == env['PRIMARY_HOSTNAME']:
|
||||
elif mxhost == env['BOX_HOSTNAME']:
|
||||
good_news = f"Domain's email is directed to this domain. [{domain} ↦ {mx}]"
|
||||
if mx != recommended_mx:
|
||||
good_news += f" This configuration is non-standard. The recommended configuration is '{recommended_mx}'."
|
||||
@ -743,7 +743,7 @@ def check_mail_domain(domain, env, output):
|
||||
sts_resolver = postfix_mta_sts_resolver.resolver.STSResolver(loop=loop)
|
||||
valid, policy = loop.run_until_complete(sts_resolver.resolve(domain))
|
||||
if valid == postfix_mta_sts_resolver.resolver.STSFetchResult.VALID:
|
||||
if policy[1].get("mx") == [env['PRIMARY_HOSTNAME']] and policy[1].get("mode") == "enforce": # policy[0] is the policyid
|
||||
if policy[1].get("mx") == [env['BOX_HOSTNAME']] and policy[1].get("mode") == "enforce": # policy[0] is the policyid
|
||||
output.print_ok("MTA-STS policy is present.")
|
||||
else:
|
||||
output.print_error(f"MTA-STS policy is present but has unexpected settings. [{policy[1]}]")
|
||||
@ -763,7 +763,7 @@ def check_mail_domain(domain, env, output):
|
||||
# Stop if the domain is listed in the Spamhaus Domain Block List.
|
||||
# The user might have chosen a domain that was previously in use by a spammer
|
||||
# and will not be able to reliably send mail.
|
||||
|
||||
|
||||
# See https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now. for
|
||||
# information on spamhaus return codes
|
||||
dbl = query_dns(domain+'.dbl.spamhaus.org', "A", nxdomain=None)
|
||||
@ -786,9 +786,9 @@ def check_mail_domain(domain, env, output):
|
||||
|
||||
def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
|
||||
# See if the domain's A record resolves to our PUBLIC_IP. This is already checked
|
||||
# for PRIMARY_HOSTNAME, for which it is required for mail specifically. For it and
|
||||
# for BOX_HOSTNAME, for which it is required for mail specifically. For it and
|
||||
# other domains, it is required to access its website.
|
||||
if domain != env['PRIMARY_HOSTNAME']:
|
||||
if domain != env['BOX_HOSTNAME']:
|
||||
ok_values = []
|
||||
for (rtype, expected) in (("A", env['PUBLIC_IP']), ("AAAA", env.get('PUBLIC_IPV6'))):
|
||||
if not expected: continue # IPv6 is not configured
|
||||
@ -805,7 +805,7 @@ def check_web_domain(domain, rounded_time, ssl_certificates, env, output):
|
||||
output.print_ok("Domain resolves to this box's IP address. [{} ↦ {}]".format(domain, '; '.join(ok_values)))
|
||||
|
||||
|
||||
# We need a TLS certificate for PRIMARY_HOSTNAME because that's where the
|
||||
# We need a TLS certificate for BOX_HOSTNAME because that's where the
|
||||
# user will log in with IMAP or webmail. Any other domain we serve a
|
||||
# website for also needs a signed certificate.
|
||||
check_ssl_cert(domain, rounded_time, ssl_certificates, env, output)
|
||||
@ -886,7 +886,7 @@ def check_ssl_cert(domain, rounded_time, ssl_certificates, env, output):
|
||||
|
||||
elif cert_status == "SELF-SIGNED":
|
||||
# Offer instructions for purchasing a signed certificate.
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
if domain == env['BOX_HOSTNAME']:
|
||||
output.print_error("""The TLS (SSL) certificate for this domain is currently self-signed. You will get a security
|
||||
warning when you check or send email and when visiting this domain in a web browser (for webmail or
|
||||
static site hosting).""")
|
||||
@ -1140,9 +1140,9 @@ if __name__ == "__main__":
|
||||
with multiprocessing.pool.Pool(processes=10) as pool:
|
||||
run_and_output_changes(env, pool)
|
||||
|
||||
elif sys.argv[1] == "--check-primary-hostname":
|
||||
# See if the primary hostname appears resolvable and has a signed certificate.
|
||||
domain = env['PRIMARY_HOSTNAME']
|
||||
elif sys.argv[1] == "--check-box-hostname":
|
||||
# See if the box hostname appears resolvable and has a signed certificate.
|
||||
domain = env['BOX_HOSTNAME']
|
||||
if query_dns(domain, "A") != env['PUBLIC_IP']:
|
||||
sys.exit(1)
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
|
@ -73,8 +73,8 @@ def sort_domains(domain_names, env):
|
||||
# Sort the zones.
|
||||
zone_domains = sorted(zones.values(),
|
||||
key = lambda d : (
|
||||
# PRIMARY_HOSTNAME or the zone that contains it is always first.
|
||||
not (d == env['PRIMARY_HOSTNAME'] or env['PRIMARY_HOSTNAME'].endswith("." + d)),
|
||||
# BOX_HOSTNAME or the zone that contains it is always first.
|
||||
not (d == env['BOX_HOSTNAME'] or env['BOX_HOSTNAME'].endswith("." + d)),
|
||||
|
||||
# Then just dumb lexicographically.
|
||||
d,
|
||||
@ -86,11 +86,11 @@ def sort_domains(domain_names, env):
|
||||
# First by zone.
|
||||
zone_domains.index(zones[d]),
|
||||
|
||||
# PRIMARY_HOSTNAME is always first within the zone that contains it.
|
||||
d != env['PRIMARY_HOSTNAME'],
|
||||
# BOX_HOSTNAME is always first within the zone that contains it.
|
||||
d != env['BOX_HOSTNAME'],
|
||||
|
||||
# Followed by any of its subdomains.
|
||||
not d.endswith("." + env['PRIMARY_HOSTNAME']),
|
||||
not d.endswith("." + env['BOX_HOSTNAME']),
|
||||
|
||||
# Then in right-to-left lexicographic order of the .-separated parts of the name.
|
||||
list(reversed(d.split("."))),
|
||||
|
@ -39,10 +39,10 @@ def get_web_domains(env, include_www_redirects=True, include_auto=True, exclude_
|
||||
# IP address than this box. Remove those domains from our list.
|
||||
domains -= get_domains_with_a_records(env)
|
||||
|
||||
# Ensure the PRIMARY_HOSTNAME is in the list so we can serve webmail
|
||||
# Ensure the BOX_HOSTNAME is in the list so we can serve webmail
|
||||
# as well as Z-Push for Exchange ActiveSync. This can't be removed
|
||||
# by a custom A/AAAA record and is never a 'www.' redirect.
|
||||
domains.add(env['PRIMARY_HOSTNAME'])
|
||||
domains.add(env['BOX_HOSTNAME'])
|
||||
|
||||
# Sort the list so the nginx conf gets written in a stable order.
|
||||
return sort_domains(domains, env)
|
||||
@ -86,18 +86,18 @@ def do_web_update(env):
|
||||
# Load the templates.
|
||||
template0 = read_conf("nginx.conf")
|
||||
template1 = read_conf("nginx-alldomains.conf")
|
||||
template2 = read_conf("nginx-primaryonly.conf")
|
||||
template2 = read_conf("nginx-boxonly.conf")
|
||||
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
|
||||
|
||||
# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
|
||||
nginx_conf += make_domain_config(env['PRIMARY_HOSTNAME'], [template0, template1, template2], ssl_certificates, env)
|
||||
# Add the BOX_HOSTNAME configuration first so it becomes nginx's default server.
|
||||
nginx_conf += make_domain_config(env['BOX_HOSTNAME'], [template0, template1, template2], ssl_certificates, env)
|
||||
|
||||
# Add configuration all other web domains.
|
||||
has_root_proxy_or_redirect = get_web_domains_with_root_overrides(env)
|
||||
web_domains_not_redirect = get_web_domains(env, include_www_redirects=False)
|
||||
for domain in get_web_domains(env):
|
||||
if domain == env['PRIMARY_HOSTNAME']:
|
||||
# PRIMARY_HOSTNAME is handled above.
|
||||
if domain == env['BOX_HOSTNAME']:
|
||||
# BOX_HOSTNAME is handled above.
|
||||
continue
|
||||
if domain in web_domains_not_redirect:
|
||||
# This is a regular domain.
|
||||
@ -250,7 +250,7 @@ def get_web_domains_info(env):
|
||||
def check_cert(domain):
|
||||
try:
|
||||
tls_cert = get_domain_ssl_files(domain, ssl_certificates, env, allow_missing_cert=True)
|
||||
except OSError: # PRIMARY_HOSTNAME cert is missing
|
||||
except OSError: # BOX_HOSTNAME cert is missing
|
||||
tls_cert = None
|
||||
if tls_cert is None: return ("danger", "No certificate installed.")
|
||||
cert_status, cert_status_details = check_certificate(domain, tls_cert["certificate"], tls_cert["private-key"])
|
||||
|
@ -34,8 +34,8 @@ if [ -z "$(management/cli.py user)" ]; then
|
||||
# But in a non-interactive shell, just make something up.
|
||||
# This is normally for testing.
|
||||
else
|
||||
# Use me@PRIMARY_HOSTNAME
|
||||
EMAIL_ADDR=me@$PRIMARY_HOSTNAME
|
||||
# Use me@BOX_HOSTNAME
|
||||
EMAIL_ADDR=me@$BOX_HOSTNAME
|
||||
EMAIL_PW=12345678
|
||||
echo
|
||||
echo "Creating a new administrative mail account for $EMAIL_ADDR with password $EMAIL_PW."
|
||||
@ -54,5 +54,5 @@ if [ -z "$(management/cli.py user)" ]; then
|
||||
hide_output management/cli.py user make-admin "$EMAIL_ADDR"
|
||||
|
||||
# Create an alias to which we'll direct all automatically-created administrative aliases.
|
||||
management/cli.py alias add "administrator@$PRIMARY_HOSTNAME" "$EMAIL_ADDR" > /dev/null
|
||||
management/cli.py alias add "administrator@$BOX_HOSTNAME" "$EMAIL_ADDR" > /dev/null
|
||||
fi
|
||||
|
@ -152,7 +152,7 @@ EOF
|
||||
# Setting a `postmaster_address` is required or LMTP won't start. An alias
|
||||
# will be created automatically by our management daemon.
|
||||
tools/editconf.py /etc/dovecot/conf.d/15-lda.conf \
|
||||
"postmaster_address=postmaster@$PRIMARY_HOSTNAME"
|
||||
"postmaster_address=postmaster@$BOX_HOSTNAME"
|
||||
|
||||
# ### Sieve
|
||||
|
||||
|
@ -57,7 +57,7 @@ tools/editconf.py /etc/postfix/main.cf \
|
||||
inet_interfaces=all \
|
||||
smtp_bind_address="$PRIVATE_IP" \
|
||||
smtp_bind_address6="$PRIVATE_IPV6" \
|
||||
myhostname="$PRIMARY_HOSTNAME"\
|
||||
myhostname="$BOX_HOSTNAME"\
|
||||
smtpd_banner="\$myhostname ESMTP Hi, I'm a Mail-in-a-Box (Ubuntu/Postfix; see https://mailinabox.email/)" \
|
||||
mydestination=localhost
|
||||
|
||||
@ -121,7 +121,7 @@ cp conf/postfix_outgoing_mail_header_filters /etc/postfix/outgoing_mail_header_f
|
||||
# Modify the `outgoing_mail_header_filters` file to use the local machine name and ip
|
||||
# on the first received header line. This may help reduce the spam score of email by
|
||||
# removing the 127.0.0.1 reference.
|
||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /etc/postfix/outgoing_mail_header_filters
|
||||
sed -i "s/BOX_HOSTNAME/$BOX_HOSTNAME/" /etc/postfix/outgoing_mail_header_filters
|
||||
sed -i "s/PUBLIC_IP/$PUBLIC_IP/" /etc/postfix/outgoing_mail_header_filters
|
||||
|
||||
# Enable TLS on incoming connections. It is not required on port 25, allowing for opportunistic
|
||||
|
@ -149,36 +149,36 @@ def migration_11(env):
|
||||
|
||||
def migration_12(env):
|
||||
# Upgrading to Carddav Roundcube plugin to version 3+, it requires the carddav_*
|
||||
# tables to be dropped.
|
||||
# Checking that the roundcube database already exists.
|
||||
if os.path.exists(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite")):
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
# Get a list of all the tables that begin with 'carddav_'
|
||||
c.execute("SELECT name FROM sqlite_master WHERE type = ? AND name LIKE ?", ('table', 'carddav_%'))
|
||||
carddav_tables = c.fetchall()
|
||||
# If there were tables that begin with 'carddav_', drop them
|
||||
if carddav_tables:
|
||||
for table in carddav_tables:
|
||||
try:
|
||||
table = table[0]
|
||||
c = conn.cursor()
|
||||
dropcmd = "DROP TABLE %s" % table
|
||||
c.execute(dropcmd)
|
||||
except:
|
||||
print("Failed to drop table", table)
|
||||
# Save.
|
||||
conn.commit()
|
||||
conn.close()
|
||||
# tables to be dropped.
|
||||
# Checking that the roundcube database already exists.
|
||||
if os.path.exists(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite")):
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
# Get a list of all the tables that begin with 'carddav_'
|
||||
c.execute("SELECT name FROM sqlite_master WHERE type = ? AND name LIKE ?", ('table', 'carddav_%'))
|
||||
carddav_tables = c.fetchall()
|
||||
# If there were tables that begin with 'carddav_', drop them
|
||||
if carddav_tables:
|
||||
for table in carddav_tables:
|
||||
try:
|
||||
table = table[0]
|
||||
c = conn.cursor()
|
||||
dropcmd = "DROP TABLE %s" % table
|
||||
c.execute(dropcmd)
|
||||
except:
|
||||
print("Failed to drop table", table)
|
||||
# Save.
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Delete all sessions, requiring users to login again to recreate carddav_*
|
||||
# databases
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
c.execute("delete from session;")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
# Delete all sessions, requiring users to login again to recreate carddav_*
|
||||
# databases
|
||||
conn = sqlite3.connect(os.path.join(env["STORAGE_ROOT"], "mail/roundcube/roundcube.sqlite"))
|
||||
c = conn.cursor()
|
||||
c.execute("delete from session;")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def migration_13(env):
|
||||
# Add the "mfa" table for configuring MFA for login to the control panel.
|
||||
@ -190,6 +190,13 @@ def migration_14(env):
|
||||
db = os.path.join(env["STORAGE_ROOT"], 'mail/users.sqlite')
|
||||
shell("check_call", ["sqlite3", db, "CREATE TABLE auto_aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL UNIQUE, destination TEXT NOT NULL, permitted_senders TEXT);"])
|
||||
|
||||
def migration_15(env):
|
||||
# Replace PRIMARY_HOSTNAME with BOX_HOSTNAME in mailinabox.conf
|
||||
shell("check_call", ["sed", "-i", "s/PRIMARY_HOSTNAME/BOX_HOSTNAME/g", "/etc/mailinabox.conf"])
|
||||
env["BOX_HOSTNAME"] = env.get("PRIMARY_HOSTNAME", env.get("BOX_HOSTNAME"))
|
||||
env["PRIMARY_HOSTNAME"] = None
|
||||
del env["PRIMARY_HOSTNAME"]
|
||||
|
||||
###########################################################
|
||||
|
||||
def get_current_migration():
|
||||
|
@ -24,12 +24,12 @@ includedir /etc/munin/munin-conf.d
|
||||
cgiurl_graph /admin/munin/cgi-graph
|
||||
|
||||
# a simple host tree
|
||||
[$PRIMARY_HOSTNAME]
|
||||
[$BOX_HOSTNAME]
|
||||
address 127.0.0.1
|
||||
|
||||
# send alerts to the following address
|
||||
contacts admin
|
||||
contact.admin.command mail -s "Munin notification \${var:host}" administrator@$PRIMARY_HOSTNAME
|
||||
contact.admin.command mail -s "Munin notification \${var:host}" administrator@$BOX_HOSTNAME
|
||||
contact.admin.always_send warning critical
|
||||
EOF
|
||||
|
||||
@ -40,7 +40,7 @@ chown munin /var/log/munin/munin-cgi-graph.log
|
||||
# ensure munin-node knows the name of this machine
|
||||
# and reduce logging level to warning
|
||||
tools/editconf.py /etc/munin/munin-node.conf -s \
|
||||
host_name="$PRIMARY_HOSTNAME" \
|
||||
host_name="$BOX_HOSTNAME" \
|
||||
log_level=1
|
||||
|
||||
# Update the activated plugins through munin's autoconfiguration.
|
||||
|
@ -3,15 +3,15 @@
|
||||
# the rest of the system setup so we may not yet have things installed.
|
||||
apt_get_quiet install bind9-host sed netcat-openbsd
|
||||
|
||||
# Stop if the PRIMARY_HOSTNAME is listed in the Spamhaus Domain Block List.
|
||||
# Stop if the BOX_HOSTNAME is listed in the Spamhaus Domain Block List.
|
||||
# The user might have chosen a name that was previously in use by a spammer
|
||||
# and will not be able to reliably send mail. Do this after any automatic
|
||||
# choices made above.
|
||||
if host "$PRIMARY_HOSTNAME.dbl.spamhaus.org" > /dev/null; then
|
||||
if host "$BOX_HOSTNAME.dbl.spamhaus.org" > /dev/null; then
|
||||
echo
|
||||
echo "The hostname you chose '$PRIMARY_HOSTNAME' is listed in the"
|
||||
echo "The hostname you chose '$BOX_HOSTNAME' is listed in the"
|
||||
echo "Spamhaus Domain Block List. See http://www.spamhaus.org/dbl/"
|
||||
echo "and http://www.spamhaus.org/query/domain/$PRIMARY_HOSTNAME."
|
||||
echo "and http://www.spamhaus.org/query/domain/$BOX_HOSTNAME."
|
||||
echo
|
||||
echo "You will not be able to send mail using this domain name, so"
|
||||
echo "setup cannot continue."
|
||||
|
@ -253,7 +253,7 @@ if [ ! -f "$STORAGE_ROOT/owncloud/owncloud.db" ]; then
|
||||
mkdir -p "$STORAGE_ROOT/owncloud"
|
||||
|
||||
# Create an initial configuration file.
|
||||
instanceid=oc$(echo "$PRIMARY_HOSTNAME" | sha1sum | fold -w 10 | head -n 1)
|
||||
instanceid=oc$(echo "$BOX_HOSTNAME" | sha1sum | fold -w 10 | head -n 1)
|
||||
cat > "$STORAGE_ROOT/owncloud/config.php" <<EOF;
|
||||
<?php
|
||||
\$CONFIG = array (
|
||||
@ -308,7 +308,7 @@ fi
|
||||
|
||||
# Update config.php.
|
||||
# * trusted_domains is reset to localhost by autoconfig starting with ownCloud 8.1.1,
|
||||
# so set it here. It also can change if the box's PRIMARY_HOSTNAME changes, so
|
||||
# so set it here. It also can change if the box's BOX_HOSTNAME changes, so
|
||||
# this will make sure it has the right value.
|
||||
# * Some settings weren't included in previous versions of Mail-in-a-Box.
|
||||
# * We need to set the timezone to the system timezone to allow fail2ban to ban
|
||||
@ -325,10 +325,10 @@ include("$STORAGE_ROOT/owncloud/config.php");
|
||||
|
||||
\$CONFIG['config_is_read_only'] = false;
|
||||
|
||||
\$CONFIG['trusted_domains'] = array('$PRIMARY_HOSTNAME');
|
||||
\$CONFIG['trusted_domains'] = array('$BOX_HOSTNAME');
|
||||
|
||||
\$CONFIG['memcache.local'] = '\OC\Memcache\APCu';
|
||||
\$CONFIG['overwrite.cli.url'] = 'https://${PRIMARY_HOSTNAME}/cloud';
|
||||
\$CONFIG['overwrite.cli.url'] = 'https://${BOX_HOSTNAME}/cloud';
|
||||
|
||||
\$CONFIG['logtimezone'] = '$TIMEZONE';
|
||||
\$CONFIG['logdateformat'] = 'Y-m-d H:i:s';
|
||||
@ -342,8 +342,8 @@ include("$STORAGE_ROOT/owncloud/config.php");
|
||||
),
|
||||
);
|
||||
|
||||
\$CONFIG['mail_domain'] = '$PRIMARY_HOSTNAME';
|
||||
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches the required administrator alias on mail_domain/$PRIMARY_HOSTNAME
|
||||
\$CONFIG['mail_domain'] = '$BOX_HOSTNAME';
|
||||
\$CONFIG['mail_from_address'] = 'administrator'; # just the local part, matches the required administrator alias on mail_domain/$BOX_HOSTNAME
|
||||
\$CONFIG['mail_smtpmode'] = 'sendmail';
|
||||
\$CONFIG['mail_smtpauth'] = true; # if smtpmode is smtp
|
||||
\$CONFIG['mail_smtphost'] = '127.0.0.1'; # if smtpmode is smtp
|
||||
|
@ -26,8 +26,8 @@ if [ -z "${NONINTERACTIVE:-}" ]; then
|
||||
fi
|
||||
|
||||
# The box needs a name.
|
||||
if [ -z "${PRIMARY_HOSTNAME:-}" ]; then
|
||||
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
|
||||
if [ -z "${BOX_HOSTNAME:-}" ]; then
|
||||
if [ -z "${DEFAULT_BOX_HOSTNAME:-}" ]; then
|
||||
# We recommend to use box.example.com as this hosts name. The
|
||||
# domain the user possibly wants to use is example.com then.
|
||||
# We strip the string "box." from the hostname to get the mail
|
||||
@ -66,19 +66,19 @@ you really want.
|
||||
|
||||
# Take the part after the @-sign as the user's domain name, and add
|
||||
# 'box.' to the beginning to create a default hostname for this machine.
|
||||
DEFAULT_PRIMARY_HOSTNAME=box.$(echo "$EMAIL_ADDR" | sed 's/.*@//')
|
||||
DEFAULT_BOX_HOSTNAME=box.$(echo "$EMAIL_ADDR" | sed 's/.*@//')
|
||||
fi
|
||||
|
||||
input_box "Hostname" \
|
||||
"This box needs a name, called a 'hostname'. The name will form a part of the box's web address.
|
||||
\n\nWe recommend that the name be a subdomain of the domain in your email
|
||||
address, so we're suggesting $DEFAULT_PRIMARY_HOSTNAME.
|
||||
address, so we're suggesting $DEFAULT_BOX_HOSTNAME.
|
||||
\n\nYou can change it, but we recommend you don't.
|
||||
\n\nHostname:" \
|
||||
"$DEFAULT_PRIMARY_HOSTNAME" \
|
||||
PRIMARY_HOSTNAME
|
||||
"$DEFAULT_BOX_HOSTNAME" \
|
||||
BOX_HOSTNAME
|
||||
|
||||
if [ -z "$PRIMARY_HOSTNAME" ]; then
|
||||
if [ -z "$BOX_HOSTNAME" ]; then
|
||||
# user hit ESC/cancel
|
||||
exit
|
||||
fi
|
||||
@ -181,8 +181,8 @@ if [ "$PUBLIC_IPV6" = "auto" ]; then
|
||||
# Use a public API to get our public IPv6 address, or fall back to local network configuration.
|
||||
PUBLIC_IPV6=$(get_publicip_from_web_service 6 || get_default_privateip 6)
|
||||
fi
|
||||
if [ "$PRIMARY_HOSTNAME" = "auto" ]; then
|
||||
PRIMARY_HOSTNAME=$(get_default_hostname)
|
||||
if [ "$BOX_HOSTNAME" = "auto" ]; then
|
||||
BOX_HOSTNAME=$(get_default_hostname)
|
||||
fi
|
||||
|
||||
# Set STORAGE_USER and STORAGE_ROOT to default values (user-data and /home/user-data), unless
|
||||
@ -196,7 +196,7 @@ fi
|
||||
|
||||
# Show the configuration, since the user may have not entered it manually.
|
||||
echo
|
||||
echo "Primary Hostname: $PRIMARY_HOSTNAME"
|
||||
echo "Box Hostname: $BOX_HOSTNAME"
|
||||
echo "Public IP Address: $PUBLIC_IP"
|
||||
if [ -n "$PUBLIC_IPV6" ]; then
|
||||
echo "Public IPv6 Address: $PUBLIC_IPV6"
|
||||
|
@ -77,42 +77,42 @@ tools/editconf.py /etc/spamassassin/local.cf -s \
|
||||
# Our custom rules are added to their own file so that an update to the deb package config
|
||||
# does not remove our changes.
|
||||
#
|
||||
# We need to escape period's in $PRIMARY_HOSTNAME since spamassassin config uses regex.
|
||||
# We need to escape period's in $BOX_HOSTNAME since spamassassin config uses regex.
|
||||
|
||||
escapedprimaryhostname="${PRIMARY_HOSTNAME//./\\.}"
|
||||
escapedboxhostname="${BOX_HOSTNAME//./\\.}"
|
||||
|
||||
cat > /etc/spamassassin/miab_spf_dmarc.cf << EOF
|
||||
# Evaluate DMARC Authentication-Results
|
||||
header DMARC_PASS Authentication-Results =~ /$escapedprimaryhostname; dmarc=pass/
|
||||
header DMARC_PASS Authentication-Results =~ /$escapedboxhostname; dmarc=pass/
|
||||
describe DMARC_PASS DMARC check passed
|
||||
score DMARC_PASS -0.1
|
||||
|
||||
header DMARC_NONE Authentication-Results =~ /$escapedprimaryhostname; dmarc=none/
|
||||
header DMARC_NONE Authentication-Results =~ /$escapedboxhostname; dmarc=none/
|
||||
describe DMARC_NONE DMARC record not found
|
||||
score DMARC_NONE 0.1
|
||||
|
||||
header DMARC_FAIL_NONE Authentication-Results =~ /$escapedprimaryhostname; dmarc=fail \(p=none/
|
||||
header DMARC_FAIL_NONE Authentication-Results =~ /$escapedboxhostname; dmarc=fail \(p=none/
|
||||
describe DMARC_FAIL_NONE DMARC check failed (p=none)
|
||||
score DMARC_FAIL_NONE 2.0
|
||||
|
||||
header DMARC_FAIL_QUARANTINE Authentication-Results =~ /$escapedprimaryhostname; dmarc=fail \(p=quarantine/
|
||||
header DMARC_FAIL_QUARANTINE Authentication-Results =~ /$escapedboxhostname; dmarc=fail \(p=quarantine/
|
||||
describe DMARC_FAIL_QUARANTINE DMARC check failed (p=quarantine)
|
||||
score DMARC_FAIL_QUARANTINE 5.0
|
||||
|
||||
header DMARC_FAIL_REJECT Authentication-Results =~ /$escapedprimaryhostname; dmarc=fail \(p=reject/
|
||||
header DMARC_FAIL_REJECT Authentication-Results =~ /$escapedboxhostname; dmarc=fail \(p=reject/
|
||||
describe DMARC_FAIL_REJECT DMARC check failed (p=reject)
|
||||
score DMARC_FAIL_REJECT 10.0
|
||||
|
||||
# Evaluate SPF Authentication-Results
|
||||
header SPF_PASS Authentication-Results =~ /$escapedprimaryhostname; spf=pass/
|
||||
header SPF_PASS Authentication-Results =~ /$escapedboxhostname; spf=pass/
|
||||
describe SPF_PASS SPF check passed
|
||||
score SPF_PASS -0.1
|
||||
|
||||
header SPF_NONE Authentication-Results =~ /$escapedprimaryhostname; spf=none/
|
||||
header SPF_NONE Authentication-Results =~ /$escapedboxhostname; spf=none/
|
||||
describe SPF_NONE SPF record not found
|
||||
score SPF_NONE 2.0
|
||||
|
||||
header SPF_FAIL Authentication-Results =~ /$escapedprimaryhostname; spf=fail/
|
||||
header SPF_FAIL Authentication-Results =~ /$escapedboxhostname; spf=fail/
|
||||
describe SPF_FAIL SPF check failed
|
||||
score SPF_FAIL 5.0
|
||||
EOF
|
||||
|
@ -13,7 +13,7 @@
|
||||
# * SMTP (opportunistic TLS for port 25 and submission on ports 465/587)
|
||||
# * HTTPS
|
||||
#
|
||||
# The certificate is created with its CN set to the PRIMARY_HOSTNAME. It is
|
||||
# The certificate is created with its CN set to the BOX_HOSTNAME. It is
|
||||
# also used for other domains served over HTTPS until the user installs a
|
||||
# better certificate for those domains.
|
||||
#
|
||||
@ -74,10 +74,10 @@ if [ ! -f "$STORAGE_ROOT/ssl/ssl_certificate.pem" ]; then
|
||||
CSR=/tmp/ssl_cert_sign_req-$$.csr
|
||||
hide_output \
|
||||
openssl req -new -key "$STORAGE_ROOT/ssl/ssl_private_key.pem" -out $CSR \
|
||||
-sha256 -subj "/CN=$PRIMARY_HOSTNAME"
|
||||
-sha256 -subj "/CN=$BOX_HOSTNAME"
|
||||
|
||||
# Generate the self-signed certificate.
|
||||
CERT=$STORAGE_ROOT/ssl/$PRIMARY_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
||||
CERT=$STORAGE_ROOT/ssl/$BOX_HOSTNAME-selfsigned-$(date --rfc-3339=date | sed s/-//g).pem
|
||||
hide_output \
|
||||
openssl x509 -req -days 365 \
|
||||
-in $CSR -signkey "$STORAGE_ROOT/ssl/ssl_private_key.pem" -out "$CERT"
|
||||
|
@ -51,7 +51,7 @@ source setup/start.sh
|
||||
EOF
|
||||
chmod +x /usr/local/bin/mailinabox
|
||||
|
||||
# Ask the user for the PRIMARY_HOSTNAME, PUBLIC_IP, and PUBLIC_IPV6,
|
||||
# Ask the user for the BOX_HOSTNAME, PUBLIC_IP, and PUBLIC_IPV6,
|
||||
# if values have not already been set in environment variables. When running
|
||||
# non-interactively, be sure to set values for all! Also sets STORAGE_USER and
|
||||
# STORAGE_ROOT.
|
||||
@ -60,7 +60,7 @@ source setup/questions.sh
|
||||
# Run some network checks to make sure setup on this machine makes sense.
|
||||
# Skip on existing installs since we don't want this to block the ability to
|
||||
# upgrade, and these checks are also in the control panel status checks.
|
||||
if [ -z "${DEFAULT_PRIMARY_HOSTNAME:-}" ]; then
|
||||
if [ -z "${DEFAULT_BOX_HOSTNAME:-}" ]; then
|
||||
if [ -z "${SKIP_NETWORK_CHECKS:-}" ]; then
|
||||
source setup/network-checks.sh
|
||||
fi
|
||||
@ -95,7 +95,7 @@ fi
|
||||
cat > /etc/mailinabox.conf << EOF;
|
||||
STORAGE_USER=$STORAGE_USER
|
||||
STORAGE_ROOT=$STORAGE_ROOT
|
||||
PRIMARY_HOSTNAME=$PRIMARY_HOSTNAME
|
||||
BOX_HOSTNAME=$BOX_HOSTNAME
|
||||
PUBLIC_IP=$PUBLIC_IP
|
||||
PUBLIC_IPV6=$PUBLIC_IPV6
|
||||
PRIVATE_IP=$PRIVATE_IP
|
||||
@ -160,9 +160,9 @@ echo "Your Mail-in-a-Box is running."
|
||||
echo
|
||||
echo "Please log in to the control panel for further instructions at:"
|
||||
echo
|
||||
if management/status_checks.py --check-primary-hostname; then
|
||||
if management/status_checks.py --check-box-hostname; then
|
||||
# Show the nice URL if it appears to be resolving and has a valid certificate.
|
||||
echo "https://$PRIMARY_HOSTNAME/admin"
|
||||
echo "https://$BOX_HOSTNAME/admin"
|
||||
echo
|
||||
echo "If you have a DNS problem put the box's IP address in the URL"
|
||||
echo "(https://$PUBLIC_IP/admin) but then check the TLS fingerprint:"
|
||||
|
@ -12,8 +12,8 @@ source setup/functions.sh # load our functions
|
||||
#
|
||||
# First set the hostname in the configuration file, then activate the setting
|
||||
|
||||
echo "$PRIMARY_HOSTNAME" > /etc/hostname
|
||||
hostname "$PRIMARY_HOSTNAME"
|
||||
echo "$BOX_HOSTNAME" > /etc/hostname
|
||||
hostname "$BOX_HOSTNAME"
|
||||
|
||||
# ### Fix permissions
|
||||
|
||||
|
@ -104,7 +104,7 @@ fi
|
||||
mkdir -p /var/lib/mailinabox
|
||||
chmod a+rx /var/lib/mailinabox
|
||||
cat conf/ios-profile.xml \
|
||||
| sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" \
|
||||
| sed "s/BOX_HOSTNAME/$BOX_HOSTNAME/" \
|
||||
| sed "s/UUID1/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||
| sed "s/UUID2/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||
| sed "s/UUID3/$(cat /proc/sys/kernel/random/uuid)/" \
|
||||
@ -118,7 +118,7 @@ chmod a+r /var/lib/mailinabox/mobileconfig.xml
|
||||
# https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||
# and https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration/FileFormat/HowTo.
|
||||
cat conf/mozilla-autoconfig.xml \
|
||||
| sed "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" \
|
||||
| sed "s/BOX_HOSTNAME/$BOX_HOSTNAME/" \
|
||||
> /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||
chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||
|
||||
@ -130,10 +130,10 @@ chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
|
||||
# "MTA_STS_MODE=testing" which means "Messages will be delivered
|
||||
# as though there was no failure but a report will be sent if
|
||||
# TLS-RPT is configured" if you are not sure you want this yet. Or "none".
|
||||
PUNY_PRIMARY_HOSTNAME=$(echo "$PRIMARY_HOSTNAME" | idn2)
|
||||
PUNY_BOX_HOSTNAME=$(echo "$BOX_HOSTNAME" | idn2)
|
||||
cat conf/mta-sts.txt \
|
||||
| sed "s/MODE/${MTA_STS_MODE}/" \
|
||||
| sed "s/PRIMARY_HOSTNAME/$PUNY_PRIMARY_HOSTNAME/" \
|
||||
| sed "s/BOX_HOSTNAME/$PUNY_BOX_HOSTNAME/" \
|
||||
> /var/lib/mailinabox/mta-sts.txt
|
||||
chmod a+r /var/lib/mailinabox/mta-sts.txt
|
||||
|
||||
|
@ -132,7 +132,7 @@ cat > $RCM_CONFIG <<EOF;
|
||||
),
|
||||
);
|
||||
\$config['support_url'] = 'https://mailinabox.email/';
|
||||
\$config['product_name'] = '$PRIMARY_HOSTNAME Webmail';
|
||||
\$config['product_name'] = '$BOX_HOSTNAME Webmail';
|
||||
\$config['cipher_method'] = 'AES-256-CBC'; # persistent login cookie and potentially other things
|
||||
\$config['des_key'] = '$SECRET_KEY'; # 37 characters -> ~256 bits for AES-256, see above
|
||||
\$config['plugins'] = array('html5_notifier', 'archive', 'zipdownload', 'password', 'managesieve', 'jqueryui', 'persistent_login', 'carddav');
|
||||
@ -158,7 +158,7 @@ cat > ${RCM_PLUGIN_DIR}/carddav/config.inc.php <<EOF;
|
||||
'name' => 'ownCloud',
|
||||
'username' => '%u', // login username
|
||||
'password' => '%p', // login password
|
||||
'url' => 'https://${PRIMARY_HOSTNAME}/cloud/remote.php/dav/addressbooks/users/%u/contacts/',
|
||||
'url' => 'https://${BOX_HOSTNAME}/cloud/remote.php/dav/addressbooks/users/%u/contacts/',
|
||||
'active' => true,
|
||||
'readonly' => false,
|
||||
'refresh_time' => '02:00:00',
|
||||
|
@ -41,7 +41,7 @@ if [ $needs_update == 1 ]; then
|
||||
mv /tmp/z-push/*/src /usr/local/lib/z-push
|
||||
rm -rf /tmp/z-push.zip /tmp/z-push
|
||||
|
||||
# Create admin and top scripts with PHP_VER
|
||||
# Create admin and top scripts with PHP_VER
|
||||
rm -f /usr/sbin/z-push-{admin,top}
|
||||
echo '#!/bin/bash' > /usr/sbin/z-push-admin
|
||||
echo php"$PHP_VER" /usr/local/lib/z-push/z-push-admin.php '"$@"' >> /usr/sbin/z-push-admin
|
||||
@ -49,7 +49,7 @@ if [ $needs_update == 1 ]; then
|
||||
echo '#!/bin/bash' > /usr/sbin/z-push-top
|
||||
echo php"$PHP_VER" /usr/local/lib/z-push/z-push-top.php '"$@"' >> /usr/sbin/z-push-top
|
||||
chmod 755 /usr/sbin/z-push-top
|
||||
|
||||
|
||||
echo $VERSION > /usr/local/lib/z-push/version
|
||||
fi
|
||||
|
||||
@ -79,7 +79,7 @@ cp conf/zpush/backend_caldav.php /usr/local/lib/z-push/backend/caldav/config.php
|
||||
# Configure Autodiscover
|
||||
rm -f /usr/local/lib/z-push/autodiscover/config.php
|
||||
cp conf/zpush/autodiscover_config.php /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s/PRIMARY_HOSTNAME/$PRIMARY_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s/BOX_HOSTNAME/$BOX_HOSTNAME/" /usr/local/lib/z-push/autodiscover/config.php
|
||||
sed -i "s^define('TIMEZONE', .*^define('TIMEZONE', '$(cat /etc/timezone)');^" /usr/local/lib/z-push/autodiscover/config.php
|
||||
|
||||
# Some directories it will use.
|
||||
|
@ -11,22 +11,22 @@ import sys, re
|
||||
import dns.reversename, dns.resolver
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: tests/dns.py ipaddress hostname [primary hostname]")
|
||||
print("Usage: tests/dns.py ipaddress hostname [box hostname]")
|
||||
sys.exit(1)
|
||||
|
||||
ipaddr, hostname = sys.argv[1:3]
|
||||
primary_hostname = hostname
|
||||
box_hostname = hostname
|
||||
if len(sys.argv) == 4:
|
||||
primary_hostname = sys.argv[3]
|
||||
box_hostname = sys.argv[3]
|
||||
|
||||
def test(server, description):
|
||||
tests = [
|
||||
(hostname, "A", ipaddr),
|
||||
#(hostname, "NS", "ns1.%s.;ns2.%s." % (primary_hostname, primary_hostname)),
|
||||
("ns1." + primary_hostname, "A", ipaddr),
|
||||
("ns2." + primary_hostname, "A", ipaddr),
|
||||
#(hostname, "NS", "ns1.%s.;ns2.%s." % (box_hostname, box_hostname)),
|
||||
("ns1." + box_hostname, "A", ipaddr),
|
||||
("ns2." + box_hostname, "A", ipaddr),
|
||||
("www." + hostname, "A", ipaddr),
|
||||
(hostname, "MX", "10 " + primary_hostname + "."),
|
||||
(hostname, "MX", "10 " + box_hostname + "."),
|
||||
(hostname, "TXT", '"v=spf1 mx -all"'),
|
||||
("mail._domainkey." + hostname, "TXT", '"v=DKIM1; k=rsa; s=email; " "p=__KEY__"'),
|
||||
#("_adsp._domainkey." + hostname, "TXT", "\"dkim=all\""),
|
||||
|
@ -462,7 +462,7 @@ class BashScript(Grammar):
|
||||
v = v.replace("</pre>\n<pre class='shell'>", "")
|
||||
v = re.sub("<pre>([\w\W]*?)</pre>", lambda m : "<pre>" + strip_indent(m.group(1)) + "</pre>", v)
|
||||
|
||||
v = re.sub(r"(\$?)PRIMARY_HOSTNAME", r"<b>box.yourdomain.com</b>", v)
|
||||
v = re.sub(r"(\$?)BOX_HOSTNAME", r"<b>box.yourdomain.com</b>", v)
|
||||
v = re.sub(r"\$STORAGE_ROOT", r"<b>$STORE</b>", v)
|
||||
v = v.replace("`pwd`", "<code><b>/path/to/mailinabox</b></code>")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user