diff --git a/install/email-configs-one/dovecot.conf b/install/email-configs-one/dovecot.conf index 9ec4b1a1c..682e5764d 100644 --- a/install/email-configs-one/dovecot.conf +++ b/install/email-configs-one/dovecot.conf @@ -53,6 +53,15 @@ protocol imap { mail_plugins = $mail_plugins zlib imap_zlib } +auth_master_user_separator = * + +passdb { + driver = passwd-file + master = yes + args = /etc/dovecot/master-users + result_success = continue +} + passdb { driver = sql args = /etc/dovecot/dovecot-sql.conf.ext diff --git a/install/email-configs/dovecot.conf b/install/email-configs/dovecot.conf index a7694b6ed..5f7188139 100644 --- a/install/email-configs/dovecot.conf +++ b/install/email-configs/dovecot.conf @@ -53,6 +53,15 @@ protocol imap { mail_plugins = $mail_plugins zlib imap_zlib } +auth_master_user_separator = * + +passdb { + driver = passwd-file + master = yes + args = /etc/dovecot/master-users + result_success = continue +} + passdb { driver = sql args = /etc/dovecot/dovecot-sql.conf.ext diff --git a/install/installCyberPanel.py b/install/installCyberPanel.py index 88a454496..90ef6a5ea 100644 --- a/install/installCyberPanel.py +++ b/install/installCyberPanel.py @@ -705,6 +705,50 @@ module cyberpanel_ols { logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [installSieve]") return 0 + def setupWebmail(self): + """Set up Dovecot master user and webmail config for SSO""" + try: + InstallCyberPanel.stdOut("Setting up webmail master user for SSO...", 1) + + from plogical.randomPassword import generate_pass + + master_password = generate_pass(32) + + # Hash the password using doveadm + result = subprocess.run( + ['doveadm', 'pw', '-s', 'SHA512-CRYPT', '-p', master_password], + capture_output=True, text=True + ) + if result.returncode != 0: + logging.InstallLog.writeToFile('[ERROR] doveadm pw failed: ' + result.stderr + " [setupWebmail]") + return 0 + + password_hash = result.stdout.strip() + + # Write /etc/dovecot/master-users + with open('/etc/dovecot/master-users', 'w') as f: + f.write('cyberpanel_master:' + password_hash + '\n') + os.chmod('/etc/dovecot/master-users', 0o600) + subprocess.call(['chown', 'dovecot:dovecot', '/etc/dovecot/master-users']) + + # Write /etc/cyberpanel/webmail.conf + import json as json_module + webmail_conf = { + 'master_user': 'cyberpanel_master', + 'master_password': master_password + } + with open('/etc/cyberpanel/webmail.conf', 'w') as f: + json_module.dump(webmail_conf, f) + os.chmod('/etc/cyberpanel/webmail.conf', 0o600) + subprocess.call(['chown', 'nobody:nobody', '/etc/cyberpanel/webmail.conf']) + + InstallCyberPanel.stdOut("Webmail master user setup complete!", 1) + return 1 + + except BaseException as msg: + logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [setupWebmail]") + return 0 + def installMySQL(self, mysql): ############## Install mariadb ###################### @@ -1320,6 +1364,9 @@ def Main(cwd, mysql, distro, ent, serial=None, port="8090", ftp=None, dns=None, logging.InstallLog.writeToFile('Installing Sieve for email filtering..,55') installer.installSieve() + logging.InstallLog.writeToFile('Setting up webmail master user..,57') + installer.setupWebmail() + logging.InstallLog.writeToFile('Installing MySQL,60') installer.installMySQL(mysql) installer.changeMYSQLRootPassword() diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 20a0e9f3d..64b550628 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -2801,6 +2801,95 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL except: pass + @staticmethod + def setupWebmail(): + """Set up Dovecot master user and webmail config for SSO (idempotent)""" + try: + # Skip if already configured + if os.path.exists('/etc/cyberpanel/webmail.conf'): + Upgrade.stdOut("Webmail master user already configured, skipping.", 0) + return + + # Skip if no mail server installed + if not os.path.exists('/etc/dovecot/dovecot.conf'): + Upgrade.stdOut("Dovecot not installed, skipping webmail setup.", 0) + return + + Upgrade.stdOut("Setting up webmail master user for SSO...", 0) + + from plogical.randomPassword import generate_pass + + master_password = generate_pass(32) + + # Hash the password using doveadm + result = subprocess.run( + ['doveadm', 'pw', '-s', 'SHA512-CRYPT', '-p', master_password], + capture_output=True, text=True + ) + if result.returncode != 0: + Upgrade.stdOut("doveadm pw failed: " + result.stderr, 0) + return + + password_hash = result.stdout.strip() + + # Write /etc/dovecot/master-users + with open('/etc/dovecot/master-users', 'w') as f: + f.write('cyberpanel_master:' + password_hash + '\n') + os.chmod('/etc/dovecot/master-users', 0o600) + subprocess.call(['chown', 'dovecot:dovecot', '/etc/dovecot/master-users']) + + # Write /etc/cyberpanel/webmail.conf + webmail_conf = { + 'master_user': 'cyberpanel_master', + 'master_password': master_password + } + with open('/etc/cyberpanel/webmail.conf', 'w') as f: + json.dump(webmail_conf, f) + os.chmod('/etc/cyberpanel/webmail.conf', 0o600) + subprocess.call(['chown', 'nobody:nobody', '/etc/cyberpanel/webmail.conf']) + + # Patch dovecot.conf if master user config not present + dovecot_conf_path = '/etc/dovecot/dovecot.conf' + with open(dovecot_conf_path, 'r') as f: + dovecot_content = f.read() + + if 'auth_master_user_separator' not in dovecot_content: + master_block = """auth_master_user_separator = * + +passdb { + driver = passwd-file + master = yes + args = /etc/dovecot/master-users + result_success = continue +} + +""" + dovecot_content = dovecot_content.replace( + 'passdb {', + master_block + 'passdb {', + 1 # Only replace the first occurrence + ) + with open(dovecot_conf_path, 'w') as f: + f.write(dovecot_content) + + # Run webmail migrations + Upgrade.executioner( + 'python /usr/local/CyberCP/manage.py makemigrations webmail', + 'Webmail makemigrations', shell=True + ) + Upgrade.executioner( + 'python /usr/local/CyberCP/manage.py migrate', + 'Webmail migrate', shell=True + ) + + # Restart Dovecot + subprocess.call(['systemctl', 'restart', 'dovecot']) + + Upgrade.stdOut("Webmail master user setup complete!", 0) + + except BaseException as msg: + Upgrade.stdOut("setupWebmail error: " + str(msg), 0) + @staticmethod def manageServiceMigrations(): try: @@ -4725,6 +4814,7 @@ pm.max_spare_servers = 3 Upgrade.s3BackupMigrations() Upgrade.containerMigrations() Upgrade.manageServiceMigrations() + Upgrade.setupWebmail() Upgrade.enableServices() Upgrade.installPHP73()