diff --git a/management/mailconfig.py b/management/mailconfig.py index f5993459..bc5df48d 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -88,7 +88,7 @@ def add_mail_user(email, pw, env): if not os.path.exists(user_mail_dir): os.makedirs(user_mail_dir) os.chown(user_mail_dir, maildirstat.st_uid, maildirstat.st_gid) - shutil.copyfile(env["CONF_DIR"] + "/dovecot_sieve.txt", user_mail_dir + "/.dovecot.sieve") + shutil.copyfile(utils.CONF_DIR + "/dovecot_sieve.txt", user_mail_dir + "/.dovecot.sieve") os.chown(user_mail_dir + "/.dovecot.sieve", maildirstat.st_uid, maildirstat.st_gid) # Update DNS in case any new domains are added. diff --git a/management/utils.py b/management/utils.py index a7d53b34..9dd05416 100644 --- a/management/utils.py +++ b/management/utils.py @@ -1,16 +1,23 @@ +import os.path + +CONF_DIR = os.path.join(os.path.dirname(__file__), "../conf") + def load_environment(): # Load settings from /etc/mailinabox.conf. - import os.path - env = load_env_vars_from_file("/etc/mailinabox.conf") - env["CONF_DIR"] = os.path.join(os.path.dirname(__file__), "../conf") - return env + return load_env_vars_from_file("/etc/mailinabox.conf") def load_env_vars_from_file(fn): # Load settings from a KEY=VALUE file. - env = { } + import collections + env = collections.OrderedDict() for line in open(fn): env.setdefault(*line.strip().split("=", 1)) return env +def save_environment(env): + with open("/etc/mailinabox.conf", "w") as f: + for k, v in env.items(): + f.write("%s=%s\n" % (k, v)) + def safe_domain_name(name): # Sanitize a domain name so it is safe to use as a file name on disk. import urllib.parse diff --git a/management/web_update.py b/management/web_update.py index 276f2e25..d68a279a 100644 --- a/management/web_update.py +++ b/management/web_update.py @@ -75,7 +75,7 @@ def get_domain_ssl_files(domain, env): # Don't allow the user to override the key for PRIMARY_HOSTNAME because # that's what's in the main file. ssl_key = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_private_key.pem') - alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_private_key.pem' % safe_domain_name(domain)) + alt_key = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/private_key.pem' % safe_domain_name(domain)) if domain != env['PRIMARY_HOSTNAME'] and os.path.exists(alt_key): ssl_key = alt_key @@ -85,14 +85,14 @@ def get_domain_ssl_files(domain, env): if domain == env['PRIMARY_HOSTNAME']: ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_certificate.pem') else: - ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_certifiate.pem' % safe_domain_name(domain)) + ssl_certificate = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/ssl_certificate.pem' % safe_domain_name(domain)) # Where would the CSR go? As with the SSL cert itself, the CSR must be # different for each domain name. if domain == env['PRIMARY_HOSTNAME']: csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/ssl_cert_sign_req.csr') else: - csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/domains/%s_cert_sign_req.csr' % safe_domain_name(domain)) + csr_path = os.path.join(env["STORAGE_ROOT"], 'ssl/%s/certificate_signing_request.csr' % safe_domain_name(domain)) return ssl_key, ssl_certificate, csr_path diff --git a/setup/migrate.py b/setup/migrate.py new file mode 100755 index 00000000..1ddbd441 --- /dev/null +++ b/setup/migrate.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 + +# Migrates any file structures, database schemas, etc. between versions of Mail-in-a-Box. + +# We have to be careful here that any dependencies are already installed in the previous +# version since this script runs before all other aspects of the setup script. + +import sys, os, os.path, glob, re, shutil + +sys.path.insert(0, 'management') +from utils import load_environment, save_environment, safe_domain_name + +def migration_1(env): + # Re-arrange where we store SSL certificates. There was a typo also. + + def move_file(fn, domain_name_escaped, filename): + # Moves an SSL-related file into the right place. + fn1 = os.path.join( env["STORAGE_ROOT"], 'ssl', domain_name_escaped, file_type) + os.makedirs(os.path.dirname(fn1), exist_ok=True) + shutil.move(fn, fn1) + + # Migrate the 'domains' directory. + for sslfn in glob.glob(os.path.join( env["STORAGE_ROOT"], 'ssl/domains/*' )): + fn = os.path.basename(sslfn) + m = re.match("(.*)_(certifiate.pem|cert_sign_req.csr|private_key.pem)$", fn) + if m: + # get the new name for the file + domain_name, file_type = m.groups() + if file_type == "certifiate.pem": file_type = "ssl_certificate.pem" # typo + if file_type == "cert_sign_req.csr": file_type = "certificate_signing_request.csr" # nicer + move_file(sslfn, domain_name, file_type) + + # Move the old domains directory if it is now empty. + try: + os.rmdir(os.path.join( env["STORAGE_ROOT"], 'ssl/domains')) + except: + pass + +if __name__ == "__main__": + if not os.access("/etc/mailinabox.conf", os.W_OK, effective_ids=True): + print("This script must be run as root.", file=sys.stderr) + sys.exit(1) + + env = load_environment() + + ourver = int(env.get("MIGRATIONID", "0")) + + while True: + next_ver = (ourver + 1) + migration_func = globals().get("migration_%d" % next_ver) + + if not migration_func: + # No more migrations to run. + break + + print("Running migration to Mail-in-a-Box #%d..." % next_ver) + + try: + migration_func(env) + except Exception as e: + print() + print("Error running the migration script:") + print() + print(e) + print() + print("Your system may be in an inconsistent state now. We're terribly sorry. A re-install from a backup might be the best way to continue.") + sys.exit(1) + + ourver = next_ver + + # Write out our current version now. Do this sooner rather than later + # in case of any problems. + env["MIGRATIONID"] = ourver + save_environment(env) + + # iterate and try next version... + diff --git a/setup/start.sh b/setup/start.sh index d57a914f..2d6ac1a8 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -28,6 +28,11 @@ fi # Recall the last settings used if we're running this a second time. if [ -f /etc/mailinabox.conf ]; then + # Run any system migrations before proceeding. Since this is a second run, + # we assume we have Python already installed. + setup/migrate.py + + # Okay now load the old .conf file to get existing configuration options. cat /etc/mailinabox.conf | sed s/^/DEFAULT_/ > /tmp/mailinabox.prev.conf source /tmp/mailinabox.prev.conf fi