RainLoop / rainloop-webmail

Simple, modern & fast web-based email client
http://rainloop.net
MIT License
4.12k stars 895 forks source link

Change password ISPConfig 3.1 #1298

Open andypl78 opened 7 years ago

andypl78 commented 7 years ago

RainLoop version, browser, OS: Latest

Please add posibility to change password via API ISPConfig 3.1

OrihuelaConde commented 7 years ago

I don't know how to make a new plugin or how to use the API of ISPConfig 3.1 so I edited the old one to support one Master and one Slave. Hope this help.

Index.php:

<?php

class IspconfigChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
    public function Init()
    {
        $this->addHook('main.fabrica', 'MainFabrica');
    }

    /**
     * @return string
     */
    public function Supported()
    {
        if (!extension_loaded('pdo') || !class_exists('PDO'))
        {
            return 'The PHP exention PDO (mysql) must be installed to use this plugin';
        }

        $aDrivers = \PDO::getAvailableDrivers();
        if (!is_array($aDrivers) || !in_array('mysql', $aDrivers))
        {
            return 'The PHP exention PDO (mysql) must be installed to use this plugin';
        }

        return '';
    }

    /**
     * @param string $sName
     * @param mixed $oProvider
     */
    public function MainFabrica($sName, &$oProvider)
    {
        switch ($sName)
        {
            case 'change-password':

                $sDsn = \trim($this->Config()->Get('plugin', 'pdo_dsn', ''));
                $sUser = (string) $this->Config()->Get('plugin', 'user', '');
                $sPassword = (string) $this->Config()->Get('plugin', 'password', '');
                $sDsn2 = \trim($this->Config()->Get('plugin', 'pdo_dsn2', ''));
                $sUser2 = (string) $this->Config()->Get('plugin', 'user2', '');
                $sPassword2 = (string) $this->Config()->Get('plugin', 'password2', '');

                if (!empty($sDsn) && 0 < \strlen($sUser) && 0 < \strlen($sPassword))
                {
                    include_once __DIR__.'/IspConfigChangePasswordDriver.php';

                    $oProvider = new IspConfigChangePasswordDriver();
                    $oProvider->SetLogger($this->Manager()->Actions()->Logger());
                    $oProvider->SetConfig($sDsn, $sUser, $sPassword, $sDsn2, $sUser2, $sPassword2);
                    $oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
                }

                break;
        }
    }

    /**
     * @return array
     */
    public function configMapping()
    {
        return array(
            \RainLoop\Plugins\Property::NewInstance('pdo_dsn')->SetLabel('Master ISPConfig PDO dsn')
                ->SetDefaultValue('mysql:host=127.0.0.1;dbname=dbispconfig'),
            \RainLoop\Plugins\Property::NewInstance('user')->SetLabel('Master DB User')
                ->SetDefaultValue('root'),
            \RainLoop\Plugins\Property::NewInstance('password')->SetLabel('Master DB Password')
                ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
                ->SetDefaultValue(''),
            \RainLoop\Plugins\Property::NewInstance('pdo_dsn2')->SetLabel('Slave ISPConfig PDO dsn')
                ->SetDefaultValue('mysql:host=127.0.0.1;dbname=dbispconfig'),
            \RainLoop\Plugins\Property::NewInstance('user2')->SetLabel('Slave DB User')
                ->SetDefaultValue('root'),
            \RainLoop\Plugins\Property::NewInstance('password2')->SetLabel('Slave DB Password')
                ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD)
                ->SetDefaultValue(''),
            \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
                ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
                ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
                ->SetDefaultValue('*')
        );
    }
}

IspConfigChangePasswordDriver.php:

<?php

class IspConfigChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
    /**
     * @var string
     */
    private $sDsn = '';

    /**
     * @var string
     */
    private $sUser = '';

    /**
     * @var string
     */
    private $sPassword = '';

    /**
     * @var string
     */
    private $sDsn2 = '';

    /**
     * @var string
     */
    private $sUser2 = '';

    /**
     * @var string
     */
    private $sPassword2 = '';

    /**
     * @var string
     */
    private $sAllowedEmails = '';

    /**
     * @var \MailSo\Log\Logger
     */
    private $oLogger = null;

    /**
     * @param string $sDsn
     * @param string $sUser
     * @param string $sPassword
     * @param string $sDsn2
     * @param string $sUser2
     * @param string $sPassword2
     *
     * @return \IspConfigChangePasswordDriver
     */
    public function SetConfig($sDsn, $sUser, $sPassword, $sDsn2, $sUser2, $sPassword2)
    {
        $this->sDsn = $sDsn;
        $this->sUser = $sUser;
        $this->sPassword = $sPassword;
        $this->sDsn2 = $sDsn2;
        $this->sUser2 = $sUser2;
        $this->sPassword2 = $sPassword2;

        return $this;
    }

    /**
     * @param string $sAllowedEmails
     *
     * @return \IspConfigChangePasswordDriver
     */
    public function SetAllowedEmails($sAllowedEmails)
    {
        $this->sAllowedEmails = $sAllowedEmails;
        return $this;
    }

    /**
     * @param \MailSo\Log\Logger $oLogger
     *
     * @return \IspConfigChangePasswordDriver
     */
    public function SetLogger($oLogger)
    {
        if ($oLogger instanceof \MailSo\Log\Logger)
        {
            $this->oLogger = $oLogger;
        }

        return $this;
    }

    /**
     * @param \RainLoop\Model\Account $oAccount
     *
     * @return bool
     */
    public function PasswordChangePossibility($oAccount)
    {
        return $oAccount && $oAccount->Email() &&
            \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
    }

    /**
     * @param \RainLoop\Model\Account $oAccount
     * @param string $sPrevPassword
     * @param string $sNewPassword
     *
     * @return bool
     */
    public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
    {
        if ($this->oLogger)
        {
            $this->oLogger->Write('ISP: Try to change password for '.$oAccount->Email());
        }

        $bResult = false;
        $bResult2 = false;
        $sNewPass = $this->cryptPassword($sNewPassword);
        $sOldPass = '';
        if (!empty($this->sDsn) && !empty($this->sDsn2) && 0 < \strlen($this->sUser) && 0 < \strlen($this->sUser2) && 0 < \strlen($this->sPassword) && \strlen($this->sPassword2) && $oAccount)
        {
            try
            {
                $oPdo = new \PDO($this->sDsn, $this->sUser, $this->sPassword);
                $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

                $oStmt = $oPdo->prepare('SELECT password, mailuser_id FROM mail_user WHERE login = ? LIMIT 1');
                if ($oStmt->execute(array($oAccount->IncLogin())))
                {
                    $aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
                    if (\is_array($aFetchResult) && isset($aFetchResult[0]['password'], $aFetchResult[0]['mailuser_id']))
                    {
                        $sDbPassword = \stripslashes($aFetchResult[0]['password']);
                        $sOldPass = $sDbPassword;
                        $sDbSalt = '$1$'.\substr($sDbPassword, 3, 8).'$';

                        if (\crypt(\stripslashes($sPrevPassword), $sDbSalt) === $sDbPassword)
                        {
                            $oStmt = $oPdo->prepare('UPDATE mail_user SET password = ? WHERE mailuser_id = ?');
                            $bResult = (bool) $oStmt->execute(
                                array($sNewPass, $aFetchResult[0]['mailuser_id']));
                        }
                    }
                }

                $oPdo = new \PDO($this->sDsn2, $this->sUser2, $this->sPassword2);
                $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

                $oStmt = $oPdo->prepare('SELECT password, mailuser_id FROM mail_user WHERE login = ? LIMIT 1');
                if ($oStmt->execute(array($oAccount->IncLogin())))
                {
                    $aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
                    if (\is_array($aFetchResult) && isset($aFetchResult[0]['password'], $aFetchResult[0]['mailuser_id']))
                    {
                        $sDbPassword = \stripslashes($aFetchResult[0]['password']);
                        $sDbSalt = '$1$'.\substr($sDbPassword, 3, 8).'$';

                        if (\crypt(\stripslashes($sPrevPassword), $sDbSalt) === $sDbPassword)
                        {
                            $oStmt = $oPdo->prepare('UPDATE mail_user SET password = ? WHERE mailuser_id = ?');
                            $bResult2 = (bool) $oStmt->execute(
                                array($sNewPass, $aFetchResult[0]['mailuser_id']));
                        }
                    }
                }
            }
            catch (\Exception $oException)
            {
                if($bResult && !$bResult2)
                {
                    try
                    {
                        $oPdo = new \PDO($this->sDsn, $this->sUser, $this->sPassword);
                        $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

                        $oStmt = $oPdo->prepare('SELECT password, mailuser_id FROM mail_user WHERE login = ? LIMIT 1');
                        if ($oStmt->execute(array($oAccount->IncLogin())))
                        {
                            $aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
                            if (\is_array($aFetchResult) && isset($aFetchResult[0]['password'], $aFetchResult[0]['mailuser_id']))
                            {
                                $sDbPassword = \stripslashes($aFetchResult[0]['password']);
                                $sDbSalt = '$1$'.\substr($sDbPassword, 3, 8).'$';
                                $oStmt = $oPdo->prepare('UPDATE mail_user SET password = ? WHERE mailuser_id = ?');
                                $oStmt->execute(array($sOldPass, $aFetchResult[0]['mailuser_id']));
                            }
                        }
                    }
                    catch (\Exception $oException)
                    {
                        $this->oLogger->WriteException($oException);
                    }
                }
                if ($this->oLogger)
                {
                    $this->oLogger->WriteException($oException);
                }
            }
        }

        return $bResult2;
    }

    /**
     * @param string $sPassword
     * @return string
     */
    private function cryptPassword($sPassword)
    {
        $sSalt = '';
        $sBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

        for ($iIndex = 0; $iIndex < 8; $iIndex++)
        {
            $sSalt .= $sBase64[\rand(0, 63)];
        }

        return \crypt($sPassword, '$1$'.$sSalt.'$');
    }
}
goranculibrk commented 6 years ago

Hi, did anyone manage to get this to work? I can't get this plugin to work with ISPconfig 3.1

katiakweb commented 5 years ago

My God ... we are in September of 2019 and this problem still exist? What happened to the amazing developers of this plugin? Help us, please! This plugin doesn't work in multi servers!

katiakweb commented 5 years ago

Hi, did anyone manage to get this to work? I can't get this plugin to work with ISPconfig 3.1

Same problem here... I almost broke Google serching for a solution, but I don't found. I tried the @OrihuelaConde code also, but also not work for me. I am using ISPConfig 3.1.15 on 5 Debian 9, multi server, dedicated web, mail, etc...

gppixelworks commented 5 years ago

My God ... we are in September of 2019 and this problem still exist? What happened to the amazing developers of this plugin? Help us, please! This plugin doesn't work in multi servers!

I'm rather surprised as well.

The plugin works on one of the domains controlled by ISPConfig but not another. The broken domain simply returns, "Could not save password".

samuelpetrosyan commented 3 years ago

I deployed a fine solution

2095