From c6ae3ea2f3921c1a682315d1205bec42aaa32035 Mon Sep 17 00:00:00 2001 From: BotoX Date: Mon, 11 Jan 2021 20:25:30 +0100 Subject: [PATCH] Suport for dovecot mail-crypt-plugin via new mailbox_postpassword_script hook. Uses doveadm mailbox cryptokey on create user / password change to adjust user encryption key. https://doc.dovecot.org/configuration_manual/mail_crypt_plugin/ --- .../postfixadmin-mailbox-postpassword.sh | 13 ++++ config.inc.php | 10 +++ languages/en.lang | 1 + model/Login.php | 23 +++++++ model/MailboxHandler.php | 66 +++++++++++++------ public/users/login.php | 2 +- public/users/password-change.php | 4 +- public/users/password-recover.php | 4 +- 8 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 ADDITIONS/postfixadmin-mailbox-postpassword.sh diff --git a/ADDITIONS/postfixadmin-mailbox-postpassword.sh b/ADDITIONS/postfixadmin-mailbox-postpassword.sh new file mode 100644 index 00000000..f7a86131 --- /dev/null +++ b/ADDITIONS/postfixadmin-mailbox-postpassword.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Example script for dovecot mail-crypt-plugin +# https://doc.dovecot.org/configuration_manual/mail_crypt_plugin/ + +# New user +if [ -z "$3" ]; then + doveadm -o plugin/mail_crypt_private_password="$4" mailbox cryptokey generate -u "$1" -U + exit 0 +fi + +# Password change +doveadm mailbox cryptokey password -u "$1" -o "$3" -n "$4" diff --git a/config.inc.php b/config.inc.php index a00e599e..3a6d6d57 100644 --- a/config.inc.php +++ b/config.inc.php @@ -589,6 +589,16 @@ $CONF['mailbox_postedit_script'] = ''; // $CONF['mailbox_postdeletion_script']='sudo -u courier /usr/local/bin/postfixadmin-mailbox-postdeletion.sh'; $CONF['mailbox_postdeletion_script'] = ''; +// Optional: +// Script to run after setting a mailbox password. (New mailbox [$4 = empty] or change existing password) +// Disables changing password without entering old password. +// Note that this may fail if PHP is run in "safe mode", or if +// operating system features (such as SELinux) or limitations +// prevent the web-server from executing external scripts. +// Parameters: (1) username (2) domain (3) old password (4) new password +// $CONF['mailbox_postpassword_script']='/usr/local/bin/postfixadmin-mailbox-postpassword.sh'; +$CONF['mailbox_postpassword_script'] = ''; + // Optional: // Script to run after creation of domains. // Note that this may fail if PHP is run in "safe mode", or if diff --git a/languages/en.lang b/languages/en.lang index 4cddf274..dd834032 100644 --- a/languages/en.lang +++ b/languages/en.lang @@ -279,6 +279,7 @@ $PALANG['domain_postcreate_failed'] = 'The domain postcreate script failed, chec $PALANG['mailbox_postdel_failed'] = 'The mailbox postdeletion script failed, check the error log for details!'; $PALANG['mailbox_postedit_failed'] = 'The mailbox postedit script failed, check the error log for details!'; $PALANG['mailbox_postcreate_failed'] = 'The mailbox postcreate script failed, check the error log for details!'; +$PALANG['mailbox_postpassword_failed'] = 'The mailbox postpassword script failed, check the error log for details!'; $PALANG['pAdminDelete_alias_domain_error'] = 'Unable to remove domain alias!'; $PALANG['domain_conflict_vacation_domain'] = 'You can\'t use the vacation domain as mail domain!'; diff --git a/model/Login.php b/model/Login.php index 96bbe21d..f1739655 100644 --- a/model/Login.php +++ b/model/Login.php @@ -103,6 +103,29 @@ class Login { } db_log($domain, 'edit_password', $username); + + $cmd_pw = Config::read('mailbox_postpassword_script'); + $warnmsg_pw = Config::Lang('mailbox_postpassword_failed'); + + if (empty($cmd_pw)) { + return true; + } # nothing to do + + $cmdarg1=escapeshellarg($this->id); + $cmdarg2=escapeshellarg($domain); + $cmdarg3=escapeshellarg($old_password); + $cmdarg4=escapeshellarg($new_password); + $command= "$cmd_pw $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4"; + $retval=0; + $output=array(); + $firstline=''; + $firstline=exec($command, $output, $retval); + if (0!=$retval) { + error_log("Running $command yielded return value=$retval, first line of output=$firstline"); + $this->errormsg[] = $warnmsg_pw; + return false; + } + return true; } } diff --git a/model/MailboxHandler.php b/model/MailboxHandler.php index f7c89540..61b6c282 100644 --- a/model/MailboxHandler.php +++ b/model/MailboxHandler.php @@ -12,11 +12,15 @@ class MailboxHandler extends PFAHandler { # init $this->struct, $this->db_table and $this->id_field protected function initStruct() { - $passwordReset = (int) Config::bool('forgotten_user_password_reset'); + $passwordReset = (int) Config::bool('forgotten_user_password_reset') && !Config::read('mailbox_postpassword_script'); $reset_by_sms = 0; if ($passwordReset && Config::read_string('sms_send_function')) { $reset_by_sms = 1; } + $editPw = 1; + if (!$this->new && Config::read('mailbox_postpassword_script')) { + $editPw = 0; + } $this->struct = array( @@ -29,8 +33,8 @@ class MailboxHandler extends PFAHandler { # TODO: maildir: display in list is needed to include maildir in SQL result (for post_edit hook) # TODO: (not a perfect solution, but works for now - maybe we need a separate "include in SELECT query" field?) 'maildir' => pacol($this->new, 0, 1, 'text', '' , '' , '' ), - 'password' => pacol(1, 1, 0, 'pass', 'password' , 'pCreate_mailbox_password_text' , '' ), - 'password2' => pacol(1, 1, 0, 'pass', 'password_again' , '' , '', + 'password' => pacol($editPw, $editPw,0, 'pass', 'password' , 'pCreate_mailbox_password_text' , '' ), + 'password2' => pacol($editPw, $editPw,0, 'pass', 'password_again' , '' , '', /*options*/ array(), /*not_in_db*/ 0, /*dont_write_to_db*/ 1, @@ -577,7 +581,12 @@ class MailboxHandler extends PFAHandler { $warnmsg = Config::Lang('mailbox_postedit_failed'); } - if (empty($cmd)) { + if ($this->new) { + $cmd_pw = Config::read('mailbox_postpassword_script'); + $warnmsg_pw = Config::Lang('mailbox_postpassword_failed'); + } + + if (empty($cmd) && empty($cmd_pw)) { return true; } # nothing to do @@ -591,23 +600,42 @@ class MailboxHandler extends PFAHandler { $cmdarg1=escapeshellarg($this->id); $cmdarg2=escapeshellarg($domain); - $cmdarg3=escapeshellarg($this->values['maildir']); - if ($quota <= 0) { - $quota = 0; - } # TODO: check if this is correct behaviour - $cmdarg4 = escapeshellarg("" . $quota); - $command= "$cmd $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4"; - $retval=0; - $output=array(); - $firstline=''; - $firstline=exec($command, $output, $retval); - if (0!=$retval) { - error_log("Running $command yielded return value=$retval, first line of output=$firstline"); - $this->errormsg[] = $warnmsg; - return false; + $status = true; + + if (!empty($cmd)) { + $cmdarg3=escapeshellarg($this->values['maildir']); + if ($quota <= 0) { + $quota = 0; + } # TODO: check if this is correct behaviour + $cmdarg4 = escapeshellarg("" . $quota); + $command= "$cmd $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4"; + $retval=0; + $output=array(); + $firstline=''; + $firstline=exec($command, $output, $retval); + if (0!=$retval) { + error_log("Running $command yielded return value=$retval, first line of output=$firstline"); + $this->errormsg[] .= $warnmsg; + $status = false; + } } - return true; + if (!empty($cmd_pw)) { + $cmdarg3='""'; + $cmdarg4=escapeshellarg($this->values['password']); + $command= "$cmd_pw $cmdarg1 $cmdarg2 $cmdarg3 $cmdarg4"; + $retval=0; + $output=array(); + $firstline=''; + $firstline=exec($command, $output, $retval); + if (0!=$retval) { + error_log("Running $command yielded return value=$retval, first line of output=$firstline"); + $this->errormsg[] .= $warnmsg_pw; + $status = false; + } + } + + return $status; } /** diff --git a/public/users/login.php b/public/users/login.php index 85898f8c..6ab17fc5 100644 --- a/public/users/login.php +++ b/public/users/login.php @@ -71,7 +71,7 @@ $_SESSION['PFA_token'] = md5(uniqid('pfa' . rand(), true)); $smarty->assign('language_selector', language_selector(), false); $smarty->assign('smarty_template', 'login'); $smarty->assign('logintype', 'user'); -$smarty->assign('forgotten_password_reset', Config::read('forgotten_user_password_reset')); +$smarty->assign('forgotten_password_reset', Config::read('forgotten_user_password_reset') && !Config::read('mailbox_postpassword_script')); $smarty->display('index.tpl'); /* vim: set expandtab softtabstop=3 tabstop=3 shiftwidth=3: */ diff --git a/public/users/password-change.php b/public/users/password-change.php index 4c5479cc..46aba78d 100644 --- a/public/users/password-change.php +++ b/public/users/password-change.php @@ -40,8 +40,8 @@ $CONF = Config::getInstance()->getAll(); $smarty->configureTheme($rel_path); -if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && !Config::read('forgotten_user_password_reset')) { - die('Password reset is disabled by configuration option: forgotten_admin_password_reset'); +if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && (!Config::read('forgotten_user_password_reset') || Config::read('mailbox_postpassword_script')) { + die('Password reset is disabled by configuration option: forgotten_admin_password_reset or mailbox_postpassword_script'); } if ($_SERVER['REQUEST_METHOD'] === 'GET') { diff --git a/public/users/password-recover.php b/public/users/password-recover.php index c4d33983..04bc73aa 100644 --- a/public/users/password-recover.php +++ b/public/users/password-recover.php @@ -39,8 +39,8 @@ require_once($rel_path . 'common.php'); -if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && !Config::read('forgotten_user_password_reset')) { - die('Password reset is disabled by configuration option: forgotten_admin_password_reset'); +if ($context === 'admin' && !Config::read('forgotten_admin_password_reset') || $context === 'users' && (!Config::read('forgotten_user_password_reset') || Config::read('mailbox_postpassword_script'))) { + die('Password reset is disabled by configuration option: forgotten_admin_password_reset or mailbox_postpassword_script'); } function sendCodebyEmail($to, $username, $code) {