While someone else recently posted that they got the existing password plugin to work with PLESK / Qmail - I was unable to do this.
I have a working solution that I would like to share and if anyone would like to discuss this, here is my question that I don't have time to figure out (laughing):
Am I wrong to say that PLESK needs to have both the 'accounts' table in database 'psa' updated -- as well as poppassd run?
If I'm wrong, there's a bit of redundancy below -- I used the code from the poppassd for RC v0.2.1 that was on here before to handle the poppassd aspect of this.
/.../plugins/password/config.inc.php:
$rcmail_config['password_driver'] = 'plesk';
$rcmail_config['plesk_db_host'] = 'localhost';
$rcmail_config['plesk_db_admin'] = 'admin';
$rcmail_config['plesk_db_pass'] = '[your plesk admin password here]';
/.../plugins/password/drivers/plesk.php:
/**
* PLESK SQL+POPDPASSWD Password Driver
* poppassd code lifted from Crusher's Non-API RoundCube PLESK Password Plugin
*/
function password_save($curpass, $passwd) {
$rcmail = rcmail::get_instance();
$link = mysql_connect($rcmail->config->get('plesk_db_host'), $rcmail->config->get('plesk_db_admin'), $rcmail->config->get('plesk_db_pass'));
if (!$link)
return PASSWORD_ERROR;
mysql_selectdb("psa");
$sql = "SELECT a.id,a.password,m.mail_name,d.name
FROM domains d, mail m, accounts a
WHERE
d.name='" . $rcmail->user->get_username('domain') . "' AND
m.mail_name='" . $rcmail->user->get_username('local'). "' AND
m.dom_id=d.id AND a.id=m.account_id";
$res = mysql_query($sql);
if ($row = mysql_fetch_assoc($res)) {
$driver = new Passwd_Driver_poppassd(array('host'=>'localhost', 'port'=> '106'));
$return = $driver->changePassword($_SESSION['username'], $curpass, $passwd);
if ($return[1] == "Success")
if ($row["password"] != $curpass)
return PASSWORD_ERROR;
elseif (mysql_query("UPDATE accounts set password='" . $passwd . "' where id='" . $row["id"] . "' LIMIT 1"))
return PASSWORD_SUCCESS;
else
return PASSWORD_ERROR;
else
return PASSWORD_ERROR;
}
else
return PASSWORD_ERROR;
}
class Passwd_Driver_poppassd{
var $_fp;
function Passwd_Driver_poppassd($params = array())
{
$this->_params['host'] = array_key_exists('host', $params) ? $params['host'] : 'localhost';
$this->_params['port'] = array_key_exists('port', $params) ? $params['port'] : 106;
}
function _connect()
{
$this->_fp = fsockopen($this->_params['host'], $this->_params['port'], $errno, $errstr, 30);
if (!$this->_fp) {
return array(false, $errstr);
} else {
$res = $this->_getPrompt();
return $res;
}
}
function _disconnect()
{
if (isset($this->_fp)) {
fputs($this->_fp, "quit\n");
fclose($this->_fp);
}
}
function _getPrompt()
{
$prompt = fgets($this->_fp, 4096);
if (!$prompt) {
return array(false, "No prompt returned from server.");
}
if (preg_match('/^[1-5][0-9][0-9]/', $prompt)) {
$rc = substr($prompt, 0, 3);
/* This should probably be a regex match for 2?0 or 3?0, no? */
if ($rc == '200' || $rc == '220' || $rc == '250' || $rc == '300' ) {
return array(true, "Success");
} else {
return array(false, $prompt);
}
} else {
return array(true, "Success");
}
}
function _sendCommand($cmd, $arg)
{
$line = $cmd . ' ' . $arg . "\n";
$res_fputs = fputs($this->_fp, $line);
if (!$res_fputs) {
return array(false, "Cannot send command to server.");
}
$res = $this->_getPrompt();
return $res;
}
function changePassword($username, $old_password, $new_password)
{
$res = $this->_connect();
if (!$res[0]) {
return $res;
}
$res = $this->_sendCommand('user', $username);
if (!$res[0]) {
$this->_disconnect();
return array(false, "User not found");
}
$res = $this->_sendCommand('pass', $old_password);
if (!$res[0]) {
$this->_disconnect();
return array(false, "Incorrect Password");
}
$res = $this->_sendCommand('newpass', $new_password);
$this->_disconnect();
if (!$res[0]) {
return $res;
}
return array(true, "Success");
}
}
?>
I think that would only work if you have access to username and password for the email database and as i don't that would work for me.
However i used this settings and it worked for me
<?php
$rcmail_config['password_driver'] = 'poppassd';
$rcmail_config['password_confirm_current'] = true;
$rcmail_config['password_minimum_length'] = 5;
$rcmail_config['password_require_nonalpha'] = false;
$rcmail_config['password_pop_host'] = 'localhost';
$rcmail_config['password_pop_port'] = 106;
?>
Funny. I overwrote my password driver that I forgot that I wrote (as a fork, of course). Searched for the solution for PLESK and Passwords and discovered that I'd posted the driver.
projectmyst -- I wanted to caution you to allowing password changing if Roundcube does not have access to the PLESK Password Database. If you change the password directly via POPPASSD, PLESK will overwrite the change when you run mchk, or upgrade PLESK or do something that causes PLESK to do an integrity check.
The 'driver' that I wrote is a fork of poppassd that also makes the needed PLESK DB changes simultaneously. For me, it's a great solution as I run PLESK servers and have root access to make this a global application.
I can understand why this does not work well for you.
Antioch, thanks for the PLESK driver! However, I can't seem to get it to work. I simply get an error that it can't save the new password.
I'm using the default config.inc for the password plugin, with your configuration added to it and I've created and uploaded the PLESK driver that you created.
I do see this in the logs:
PHP Error: Password plugin: Broken driver plesk in /var/www/vhosts/roundcube.local/httpdocs/plugins/password/password.php on line 258 (POST /?_task=settings&_action=plugin.password-save?_task=&_action=)
Any suggestions?
The base password plugin has been re-written so the password driver needs to be updated to the new style.
Quote from: SKaero on May 22, 2013, 12:57:11 PM
The base password plugin has been re-written so the password driver needs to be updated to the new style.
Thanks for the quick reply SKaero.
Based on the plugin that is in this thread, can you offer any suggestions on what needs to be changed in order for it to work with the new password plugin?
TIA
I haven't played with the driver system very much so I can't give to much insight into what needs to be changed, try comparing it to the other password drivers to see whats different.
Here is a working solution for Plesk (should work for plesk 10 and 11). it uses the version of the plugin specified above, but adapted to work with the new driver style.
create a file at /plugins/password/drivers/plesk.php
<?php
class rcube_plesk_password
{
function __construct($params = array())
{
$this->_params['host'] = 'localhost';
$this->_params['port'] = 106;
}
function format_error_result($code, $line)
{
if (preg_match('/^\d\d\d\s+(\S.*)\s*$/', $line, $matches)) {
return array('code' => $code, 'message' => $matches[1]);
} else {
return $code;
}
}
function save($curpass, $passwd) {
$rcmail = rcmail::get_instance();
$link = mysql_connect($rcmail->config->get('plesk_db_host'), $rcmail->config->get('plesk_db_admin'), $rcmail->config->get('plesk_db_pass'));
if (!$link)
return PASSWORD_ERROR;
mysql_select_db("psa");
$sql = "SELECT a.id,a.password,m.mail_name,d.name
FROM domains d, mail m, accounts a
WHERE
d.name='" . $rcmail->user->get_username('domain') . "' AND
m.mail_name='" . $rcmail->user->get_username('local'). "' AND
m.dom_id=d.id AND a.id=m.account_id";
$res = mysql_query($sql);
if ($row = mysql_fetch_assoc($res)) {
//$driver = new Passwd_Driver_poppassd(array('host'=>'localhost', 'port'=> '106'));
$return = $this->changePassword($_SESSION['username'], $curpass, $passwd);
if ($return[1] == "Success")
if ($row["password"] != $curpass)
return PASSWORD_ERROR;
elseif (mysql_query("UPDATE accounts set password='" . $passwd . "' where id='" . $row["id"] . "' LIMIT 1"))
return PASSWORD_SUCCESS;
else
return PASSWORD_ERROR;
else
return PASSWORD_ERROR;
}
else
return PASSWORD_ERROR;
}
function _connect()
{
$this->_fp = fsockopen($this->_params['host'], $this->_params['port'], $errno, $errstr, 30);
if (!$this->_fp) {
return array(false, $errstr);
} else {
$res = $this->_getPrompt();
return $res;
}
}
function _disconnect()
{
if (isset($this->_fp)) {
fputs($this->_fp, "quit\n");
fclose($this->_fp);
}
}
function _getPrompt()
{
$prompt = fgets($this->_fp, 4096);
if (!$prompt) {
return array(false, "No prompt returned from server.");
}
if (preg_match('/^[1-5][0-9][0-9]/', $prompt)) {
$rc = substr($prompt, 0, 3);
/* This should probably be a regex match for 2?0 or 3?0, no? */
if ($rc == '200' || $rc == '220' || $rc == '250' || $rc == '300' ) {
return array(true, "Success");
} else {
return array(false, $prompt);
}
} else {
return array(true, "Success");
}
}
function _sendCommand($cmd, $arg)
{
$line = $cmd . ' ' . $arg . "\n";
$res_fputs = fputs($this->_fp, $line);
if (!$res_fputs) {
return array(false, "Cannot send command to server.");
}
$res = $this->_getPrompt();
return $res;
}
function changePassword($username, $old_password, $new_password)
{
$res = $this->_connect();
if (!$res[0]) {
return $res;
}
$res = $this->_sendCommand('user', $username);
if (!$res[0]) {
$this->_disconnect();
return array(false, "User not found");
}
$res = $this->_sendCommand('pass', $old_password);
if (!$res[0]) {
$this->_disconnect();
return array(false, "Incorrect Password");
}
$res = $this->_sendCommand('newpass', $new_password);
$this->_disconnect();
if (!$res[0]) {
return $res;
}
return array(true, "Success");
}
}
Then in your config/main.inc.php add these lines:
<?php
// Password Plugin options
// -----------------------
// A driver to use for password change. Default: "sql".
// See README file for list of supported driver names.
$rcmail_config['password_driver'] = 'plesk';
// Determine whether current password is required to change password.
// Default: false.
$rcmail_config['password_confirm_current'] = true;
// Require the new password to be a certain length.
// set to blank to allow passwords of any length
$rcmail_config['password_minimum_length'] = 5;
// Require the new password to contain a letter and punctuation character
// Change to false to remove this check.
$rcmail_config['password_require_nonalpha'] = false;
// Enables logging of password changes into logs/password
$rcmail_config['password_log'] = false;
// Comma-separated list of login exceptions for which password change
// will be not available (no Password tab in Settings)
$rcmail_config['password_login_exceptions'] = null;
// Array of hosts that support password changing. Default is NULL.
// Listed hosts will feature a Password option in Settings; others will not.
// Example:
//$rcmail_config['password_hosts'] = array('mail.example.com', 'mail2.example.org');
$rcmail_config['password_hosts'] = null;
// Enables saving the new password even if it matches the old password. Useful
// for upgrading the stored passwords after the encryption scheme has changed.
$rcmail_config['password_force_save'] = false;
//Plesk DB
$rcmail_config['plesk_db_host'] = 'localhost'; //psa database host
$rcmail_config['plesk_db_admin'] = ''; //psa database user
$rcmail_config['plesk_db_pass'] = ''; //psa db user password
Lastly remember to activate the password plugin in the config, look for this code and insert 'password' into the array:
// ----------------------------------
// PLUGINS
// ----------------------------------
// List of active plugins (in plugins/ directory)
$rcmail_config['plugins'] = array('password');
Hi activeindex, i have tried this put can't get it to work. Is this for qmail or postfix? If added the plesk.php and copied the code to it, added the extra config!
I think this is for postfix.
Have you remembered to fill in your database admin username and password.
Also check that your $rcmail_config['password_driver'] declaration exists only once in the page.
@activeindex
Thanks for updating this thread, however, I'm having issues with this driver. I've put everything in where it should go and added the db creds.
One thing I wasn't sure of, was which db creds to use...the ones found in config/db.inc.php or if I should add dedicated creds for roundcube to the 'psa' database. I did both and I end up with the error: Could not save password.
Any ideas?
The solution:
- Set "plesk.php" as password driver
- Add a user who is authorized to write in the database "psa". i.e. the plesk admin user.
config/main.inc.php:
$rcmail_config['password_driver'] = 'plesk';
$rcmail_config['plesk_db_host'] = 'localhost';
$rcmail_config['plesk_db_admin'] = 'admin';
$rcmail_config['plesk_db_pass'] = '[your plesk admin password here]';
plugins/password/drivers/plesk.php:
<?php
//debug
//ini_set('display_errors','On');
class rcube_plesk_password
{
function __construct($params = array())
{
$this->_params['host'] = 'localhost';
$this->_params['port'] = 106;
}
function format_error_result($code, $line)
{
if (preg_match('/^\d\d\d\s+(\S.*)\s*$/', $line, $matches)) {
return array('code' => $code, 'message' => $matches[1]);
} else {
return $code;
}
}
function save($curpass, $passwd) {
$rcmail = rcmail::get_instance();
$link = mysql_connect($rcmail->config->get('plesk_db_host'), $rcmail->config->get('plesk_db_admin'), $rcmail->config->get('plesk_db_pass'));
if (!$link)
return PASSWORD_ERROR;
mysql_select_db("psa");
$sql = "SELECT a.id,a.password,m.mail_name,d.name
FROM domains d, mail m, accounts a
WHERE
d.name='" . $rcmail->user->get_username('domain') . "' AND
m.mail_name='" . $rcmail->user->get_username('local'). "' AND
m.dom_id=d.id AND a.id=m.account_id";
$res = mysql_query($sql);
if ($row = mysql_fetch_assoc($res)) {
//$driver = new Passwd_Driver_poppassd(array('host'=>'localhost', 'port'=> '106'));
$return = $this->changePassword($_SESSION['username'], $curpass, $passwd);
$decryptedPasswordFromDatabase = $this->aes_decrypt($row["password"]);
$encryptedPasswordForDatabase = $this->aes_encrypt($passwd);
if ($return[1] == "Success") {
if ($decryptedPasswordFromDatabase != $curpass) {
return PASSWORD_ERROR;
} elseif (mysql_query("UPDATE accounts set password='" . $encryptedPasswordForDatabase . "' where id='" . $row["id"] . "' LIMIT 1")) {
return PASSWORD_SUCCESS;
} else {
return PASSWORD_ERROR;
}
} else {
return PASSWORD_ERROR;
}
} else {
return PASSWORD_ERROR;
}
}
function _connect()
{
$this->_fp = fsockopen($this->_params['host'], $this->_params['port'], $errno, $errstr, 30);
if (!$this->_fp) {
return array(false, $errstr);
} else {
$res = $this->_getPrompt();
return $res;
}
}
function _disconnect()
{
if (isset($this->_fp)) {
fputs($this->_fp, "quit\n");
fclose($this->_fp);
}
}
function _getPrompt()
{
$prompt = fgets($this->_fp, 4096);
if (!$prompt) {
return array(false, "No prompt returned from server.");
}
if (preg_match('/^[1-5][0-9][0-9]/', $prompt)) {
$rc = substr($prompt, 0, 3);
/* This should probably be a regex match for 2?0 or 3?0, no? */
if ($rc == '200' || $rc == '220' || $rc == '250' || $rc == '300' ) {
return array(true, "Success");
} else {
return array(false, $prompt);
}
} else {
return array(true, "Success");
}
}
function _sendCommand($cmd, $arg)
{
$line = $cmd . ' ' . $arg . "\n";
$res_fputs = fputs($this->_fp, $line);
if (!$res_fputs) {
return array(false, "Cannot send command to server.");
}
$res = $this->_getPrompt();
return $res;
}
function changePassword($username, $old_password, $new_password)
{
$res = $this->_connect();
if (!$res[0]) {
return $res;
}
$res = $this->_sendCommand('user', $username);
if (!$res[0]) {
$this->_disconnect();
return array(false, "User not found");
}
$res = $this->_sendCommand('pass', $old_password);
if (!$res[0]) {
$this->_disconnect();
return array(false, "Incorrect Password");
}
$res = $this->_sendCommand('newpass', $new_password);
$this->_disconnect();
if (!$res[0]) {
return $res;
}
return array(true, "Success");
}
function aes_encrypt($passwd) {
$plaintext = $passwd;
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
//$key = file_get_contents("/etc/psa/private/secret_key");
$key = base64_decode('INSERT-YOUR-VALUE'); // INSERT-YOUR-VALUE is the output from "base64 /etc/psa/private/secret_key" on linux console
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, $iv);
$ciphertext = $ciphertext;
$ciphertext_base64 = base64_encode($ciphertext);
$ivtext_base64 = base64_encode($iv);
return '$AES-128-CBC$'.$ivtext_base64. '$' .$ciphertext_base64;
}
function aes_decrypt($passwd) {
$encryptedPass = $passwd;
$key = base64_decode('INSERT-YOUR-VALUE'); // INSERT-YOUR-VALUE is the output from "base64 /etc/psa/private/secret_key" on linux console
$hash = explode('$', $encryptedPass);
$i = base64_decode($hash[2]);
$k = base64_decode($hash[3]);
return str_replace("\0", "", mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $k , MCRYPT_MODE_CBC, $i));
}
}
The value 'INSERT-YOUR-VALUE' is the base64-encoded data of the file /etc/psa/private/secret_key. This is necessary because the file is not and should not readable to other users exept root.
My forked driver is tested with Plesk version 11.5.30-debian7.0.build115130819.13.
Greetz Planzo