diff --git a/installer.cfg b/installer.cfg index 8d0f255..77e75eb 100644 --- a/installer.cfg +++ b/installer.cfg @@ -1,7 +1,3 @@ -[general] -tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key -tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem - [database] # Select database engine : postgres or mysql engine = postgres diff --git a/modoboa_installer/scripts/amavis.py b/modoboa_installer/scripts/amavis.py index 0d6dea1..272816a 100644 --- a/modoboa_installer/scripts/amavis.py +++ b/modoboa_installer/scripts/amavis.py @@ -25,6 +25,12 @@ class Amavis(base.Installer): return "/etc/amavisd" return "/etc/amavis" + def get_daemon_name(self): + """Return appropriate daemon name.""" + if package.backend.FORMAT == "rpm": + return "amavisd" + return "amavis" + def get_config_files(self): """Return appropriate config files.""" if package.backend.FORMAT == "deb": diff --git a/modoboa_installer/scripts/clamav.py b/modoboa_installer/scripts/clamav.py index 38c906e..c1d3283 100644 --- a/modoboa_installer/scripts/clamav.py +++ b/modoboa_installer/scripts/clamav.py @@ -12,7 +12,6 @@ class Clamav(base.Installer): """ClamAV installer.""" appname = "clamav" - daemon_name = "clamav-daemon" packages = { "deb": ["clamav-daemon"], "rpm": [ @@ -55,17 +54,23 @@ class Clamav(base.Installer): user = "clamupdate" utils.exec_cmd( "perl -pi -e 's/^Example/#Example/' /etc/freshclam.conf") - utils.exec_cmd( - """cat <> /usr/lib/systemd/system/clamd@.service + # Check if not present before + path = "/usr/lib/systemd/system/clamd@.service" + code, output = utils.exec_cmd( + "grep 'WantedBy=multi-user.target' {}".format(path)) + if code: + utils.exec_cmd( + """cat <> {} + [Install] WantedBy=multi-user.target EOM -""") +""".format(path)) - if utils.dist_name == "ubuntu": + if utils.dist_name() == "ubuntu": # Stop freshclam daemon to allow manual download utils.exec_cmd("service clamav-freshclam stop") utils.exec_cmd("freshclam", sudo_user=user) utils.exec_cmd("service clamav-freshclam start") else: - utils.exec_cmd("freshclam", sudo_user=user) + utils.exec_cmd("freshclam", sudo_user=user, login=False) diff --git a/modoboa_installer/scripts/dovecot.py b/modoboa_installer/scripts/dovecot.py index e1df7fb..f753b24 100644 --- a/modoboa_installer/scripts/dovecot.py +++ b/modoboa_installer/scripts/dovecot.py @@ -52,6 +52,14 @@ class Dovecot(base.Installer): """Additional variables.""" context = super(Dovecot, self).get_template_context() pw = pwd.getpwnam(self.user) + if "centos" in utils.dist_name(): + protocols = "protocols = imap lmtp sieve" + extra_protocols = self.config.get("dovecot", "extra_protocols") + if extra_protocols: + protocols += " {}".format(extra_protocols) + else: + # Protocols are automatically guessed on debian/ubuntu + protocols = "" context.update({ "db_driver": self.db_driver, "mailboxes_owner_uid": pw[2], @@ -59,6 +67,7 @@ class Dovecot(base.Installer): "modoboa_dbname": self.config.get("modoboa", "dbname"), "modoboa_dbuser": self.config.get("modoboa", "dbuser"), "modoboa_dbpassword": self.config.get("modoboa", "dbpassword"), + "protocols": protocols }) return context diff --git a/modoboa_installer/scripts/files/dovecot/dovecot.conf.tpl b/modoboa_installer/scripts/files/dovecot/dovecot.conf.tpl index e72d94e..0ebb94b 100644 --- a/modoboa_installer/scripts/files/dovecot/dovecot.conf.tpl +++ b/modoboa_installer/scripts/files/dovecot/dovecot.conf.tpl @@ -18,6 +18,7 @@ # Enable installed protocols !include_try /usr/share/dovecot/protocols.d/*.protocol +%protocols # A comma separated list of IPs or hosts where to listen in for connections. # "*" listens in all IPv4 interfaces, "::" listens in all IPv6 interfaces. diff --git a/modoboa_installer/scripts/files/uwsgi/uwsgi.ini.tpl b/modoboa_installer/scripts/files/uwsgi/uwsgi.ini.tpl index 87e06c2..a013085 100644 --- a/modoboa_installer/scripts/files/uwsgi/uwsgi.ini.tpl +++ b/modoboa_installer/scripts/files/uwsgi/uwsgi.ini.tpl @@ -6,8 +6,9 @@ home = %modoboa_venv_path chdir = %modoboa_instance_path module = instance.wsgi:application master = true -harakiri = 60 processes = %nb_processes vhost = true no-default-app = true -socket = %uwsgi_socket_path \ No newline at end of file +socket = %uwsgi_socket_path +chmod-socket = 660 +vacuum = true diff --git a/modoboa_installer/scripts/modoboa.py b/modoboa_installer/scripts/modoboa.py index 2d9d508..85244e1 100644 --- a/modoboa_installer/scripts/modoboa.py +++ b/modoboa_installer/scripts/modoboa.py @@ -108,7 +108,7 @@ class Modoboa(base.Installer): context.update({ "dovecot_mailboxes_owner": ( self.config.get("dovecot", "mailboxes_owner")), - "radicale_enabled": "#" if "modoboa-radicale" in extensions else "" + "radicale_enabled": "" if "modoboa-radicale" in extensions else "#" }) return context @@ -128,6 +128,9 @@ class Modoboa(base.Installer): "modoboa_stats.RRD_ROOTDIR": rrd_root_dir, "modoboa_pdfcredentials.STORAGE_DIR": pdf_storage_dir, } + for path in ["/var/log/maillog", "/var/log/mail.log"]: + if os.path.exists(path): + settings["modoboa_stats.LOGFILE"] = path for name, value in settings.items(): query = ( diff --git a/modoboa_installer/scripts/nginx.py b/modoboa_installer/scripts/nginx.py index b1d0ae0..40ccb32 100644 --- a/modoboa_installer/scripts/nginx.py +++ b/modoboa_installer/scripts/nginx.py @@ -44,11 +44,12 @@ class Nginx(base.Installer): if os.path.exists(link): return os.symlink(dst, link) - group = "www-data" + group = self.config.get("modoboa", "user") + user = "www-data" else: dst = os.path.join( self.config_dir, "conf.d", "{}.conf".format(hostname)) utils.copy_from_template(src, dst, context) - group = "nginx" - system.add_user_to_group( - group, self.config.get("modoboa", "user")) + group = "uwsgi" + user = "nginx" + system.add_user_to_group(user, group) diff --git a/modoboa_installer/scripts/postfix.py b/modoboa_installer/scripts/postfix.py index 890c83b..73b6b02 100644 --- a/modoboa_installer/scripts/postfix.py +++ b/modoboa_installer/scripts/postfix.py @@ -1,5 +1,9 @@ """Postfix related tools.""" +try: + import configparser +except ImportError: + import ConfigParser as configparser import os from .. import package @@ -29,6 +33,17 @@ class Postfix(base.Installer): def install_packages(self): """Preconfigure postfix package installation.""" + if "centos" in utils.dist_name(): + config = configparser.SafeConfigParser() + with open("/etc/yum.repos.d/CentOS-Base.repo") as fp: + config.readfp(fp) + config.set("centosplus", "enabled", "1") + config.set("centosplus", "includepkgs", "postfix-*") + config.set("base", "exclude", "postfix-*") + config.set("updates", "exclude", "postfix-*") + with open("/etc/yum.repos.d/CentOS-Base.repo", "w") as fp: + config.write(fp) + package.backend.preconfigure( "postfix", "main_mailer_type", "select", "No configuration") super(Postfix, self).install_packages() @@ -71,6 +86,16 @@ class Postfix(base.Installer): " ".join(extensions), db_url, self.config_dir)) utils.exec_cmd(cmd) + # Check chroot directory + chroot_dir = "/var/spool/postfix/etc" + chroot_files = ["services", "resolv.conf"] + if not os.path.exists(chroot_dir): + os.mkdir(chroot_dir) + for f in chroot_files: + path = os.path.join(chroot_dir, f) + if not os.path.exists(path): + utils.copy_file(os.path.join("/etc", f), path) + # Generate EDH parameters if not os.path.exists("{}/dh2048.pem".format(self.config_dir)): cmd = "openssl dhparam -out dh2048.pem 2048" diff --git a/modoboa_installer/scripts/razor.py b/modoboa_installer/scripts/razor.py index 06b1426..7f5f965 100644 --- a/modoboa_installer/scripts/razor.py +++ b/modoboa_installer/scripts/razor.py @@ -36,7 +36,7 @@ class Razor(base.Installer): utils.copy_file( os.path.join(path, "razor-agent.conf"), self.config_dir) utils.exec_cmd("razor-admin -home {} -discover".format(path), - sudo_user=user) + sudo_user=user, login=False) utils.exec_cmd("razor-admin -home {} -register".format(path), - sudo_user=user) + sudo_user=user, login=False) # FIXME: move log file to /var/log ? diff --git a/modoboa_installer/scripts/spamassassin.py b/modoboa_installer/scripts/spamassassin.py index a510170..8f44877 100644 --- a/modoboa_installer/scripts/spamassassin.py +++ b/modoboa_installer/scripts/spamassassin.py @@ -55,5 +55,7 @@ class Spamassassin(base.Installer): def post_run(self): """Additional tasks.""" utils.exec_cmd( - "pyzor discover", sudo_user=self.config.get("amavis", "user")) + "pyzor discover", sudo_user=self.config.get("amavis", "user"), + login=False + ) install("razor", self.config) diff --git a/modoboa_installer/scripts/uwsgi.py b/modoboa_installer/scripts/uwsgi.py index 8e00cd4..4da9c1a 100644 --- a/modoboa_installer/scripts/uwsgi.py +++ b/modoboa_installer/scripts/uwsgi.py @@ -34,7 +34,7 @@ class Uwsgi(base.Installer): "modoboa_venv_path": self.config.get("modoboa", "venv_path"), "modoboa_instance_path": ( self.config.get("modoboa", "instance_path")), - "uwsgi_socket_path": self.socket_path + "uwsgi_socket_path": self.socket_path, }) return context @@ -58,9 +58,11 @@ class Uwsgi(base.Installer): os.symlink(dst, link) else: system.add_user_to_group( - "uwsgi", self.config.get("modoboa", "user")) + self.config.get("modoboa", "user"), "uwsgi") + utils.exec_cmd("chmod -R g+w {}/media".format( + self.config.get("modoboa", "instance_path"))) pattern = ( - "s/emperor-tyrant = true/emperor-tyrant false/") + "s/emperor-tyrant = true/emperor-tyrant = false/") utils.exec_cmd( "perl -pi -e '{}' /etc/uwsgi.ini".format(pattern)) diff --git a/modoboa_installer/ssl.py b/modoboa_installer/ssl.py new file mode 100644 index 0000000..3b996b9 --- /dev/null +++ b/modoboa_installer/ssl.py @@ -0,0 +1,49 @@ +"""SSL tools.""" + +import os + +from . import utils + + +class CertificateBackend(object): + """Base class.""" + + def __init__(self, config): + """Set path to certificates.""" + self.config = config + for base_dir in ["/etc/pki/tls", "/etc/ssl"]: + if os.path.exists(base_dir): + self.config.set( + "general", "tls_key_file", + "{}/private/%(hostname)s.key".format(base_dir)) + self.config.set( + "general", "tls_cert_file", + "{}/certs/%(hostname)s.cert".format(base_dir)) + return + raise RuntimeError("Cannot find a directory to store certificate") + + +class SelfSignedCertificate(CertificateBackend): + """Create a self signed certificate.""" + + def create(self): + """Create a certificate.""" + if os.path.exists(self.config.get("general", "tls_key_file")): + answer = utils.user_input( + "Overwrite the existing SSL certificate? (y/N) ") + if not answer.lower().startswith("y"): + return + utils.printcolor( + "Generating new self-signed certificate", utils.YELLOW) + utils.exec_cmd( + "openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 " + "-subj '/CN={}' -keyout {} -out {}".format( + self.config.get("general", "hostname"), + self.config.get("general", "tls_key_file"), + self.config.get("general", "tls_cert_file")) + ) + + +def get_backend(config): + """Return the appropriate backend.""" + return SelfSignedCertificate(config) diff --git a/modoboa_installer/utils.py b/modoboa_installer/utils.py index bc790a8..0550f5e 100644 --- a/modoboa_installer/utils.py +++ b/modoboa_installer/utils.py @@ -33,7 +33,7 @@ def user_input(message): return answer -def exec_cmd(cmd, sudo_user=None, pinput=None, **kwargs): +def exec_cmd(cmd, sudo_user=None, pinput=None, login=True, **kwargs): """Execute a shell command. Run a command using the current user. Set :keyword:`sudo_user` if you need different privileges. @@ -45,7 +45,7 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, **kwargs): """ sudo_user = ENV.get("sudo_user", sudo_user) if sudo_user is not None: - cmd = "sudo -i -u %s %s" % (sudo_user, cmd) + cmd = "sudo {}-u {} {}".format("-i " if login else "", sudo_user, cmd) if "shell" not in kwargs: kwargs["shell"] = True if pinput is not None: diff --git a/run.py b/run.py index 191ec10..756cc30 100755 --- a/run.py +++ b/run.py @@ -11,6 +11,7 @@ except ImportError: from modoboa_installer import scripts from modoboa_installer import utils from modoboa_installer import package +from modoboa_installer import ssl def main(): @@ -30,6 +31,8 @@ def main(): config = configparser.SafeConfigParser() with open("installer.cfg") as fp: config.readfp(fp) + if not config.has_section("general"): + config.add_section("general") config.set("general", "hostname", args.hostname) utils.printcolor( "Your mail server {} will be installed with the following components:" @@ -52,6 +55,7 @@ def main(): "and come back later ;)", utils.BLUE) utils.printcolor("Starting...", utils.GREEN) package.backend.install("sudo") + ssl.get_backend(config).create() scripts.install("modoboa", config) scripts.install("postfix", config) scripts.install("amavis", config)