summaryrefslogtreecommitdiff
path: root/plugins/password/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/password/drivers')
-rw-r--r--plugins/password/drivers/chgsaslpasswd.c29
-rw-r--r--plugins/password/drivers/chgvirtualminpasswd.c28
-rw-r--r--plugins/password/drivers/chpass-wrapper.py32
-rw-r--r--plugins/password/drivers/chpasswd.php39
-rw-r--r--plugins/password/drivers/cpanel.php120
-rw-r--r--plugins/password/drivers/directadmin.php489
-rw-r--r--plugins/password/drivers/hmail.php63
-rw-r--r--plugins/password/drivers/ldap.php319
-rw-r--r--plugins/password/drivers/ldap_simple.php276
-rw-r--r--plugins/password/drivers/pam.php42
-rw-r--r--plugins/password/drivers/poppassd.php67
-rw-r--r--plugins/password/drivers/sasl.php45
-rw-r--r--plugins/password/drivers/sql.php175
-rw-r--r--plugins/password/drivers/virtualmin.php76
-rw-r--r--plugins/password/drivers/vpopmaild.php53
-rw-r--r--plugins/password/drivers/ximss.php76
-rw-r--r--plugins/password/drivers/xmail.php106
17 files changed, 2035 insertions, 0 deletions
diff --git a/plugins/password/drivers/chgsaslpasswd.c b/plugins/password/drivers/chgsaslpasswd.c
new file mode 100644
index 000000000..bcdcb2e0d
--- /dev/null
+++ b/plugins/password/drivers/chgsaslpasswd.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <unistd.h>
+
+// set the UID this script will run as (cyrus user)
+#define UID 96
+// set the path to saslpasswd or saslpasswd2
+#define CMD "/usr/sbin/saslpasswd2"
+
+/* INSTALLING:
+ gcc -o chgsaslpasswd chgsaslpasswd.c
+ chown cyrus.apache chgsaslpasswd
+ strip chgsaslpasswd
+ chmod 4550 chgsaslpasswd
+*/
+
+main(int argc, char *argv[])
+{
+ int rc,cc;
+
+ cc = setuid(UID);
+ rc = execvp(CMD, argv);
+ if ((rc != 0) || (cc != 0))
+ {
+ fprintf(stderr, "__ %s: failed %d %d\n", argv[0], rc, cc);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/plugins/password/drivers/chgvirtualminpasswd.c b/plugins/password/drivers/chgvirtualminpasswd.c
new file mode 100644
index 000000000..4e2299c66
--- /dev/null
+++ b/plugins/password/drivers/chgvirtualminpasswd.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <unistd.h>
+
+// set the UID this script will run as (root user)
+#define UID 0
+#define CMD "/usr/sbin/virtualmin"
+
+/* INSTALLING:
+ gcc -o chgvirtualminpasswd chgvirtualminpasswd.c
+ chown root.apache chgvirtualminpasswd
+ strip chgvirtualminpasswd
+ chmod 4550 chgvirtualminpasswd
+*/
+
+main(int argc, char *argv[])
+{
+ int rc,cc;
+
+ cc = setuid(UID);
+ rc = execvp(CMD, argv);
+ if ((rc != 0) || (cc != 0))
+ {
+ fprintf(stderr, "__ %s: failed %d %d\n", argv[0], rc, cc);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/plugins/password/drivers/chpass-wrapper.py b/plugins/password/drivers/chpass-wrapper.py
new file mode 100644
index 000000000..61bba849e
--- /dev/null
+++ b/plugins/password/drivers/chpass-wrapper.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+import sys
+import pwd
+import subprocess
+
+BLACKLIST = (
+ # add blacklisted users here
+ #'user1',
+)
+
+try:
+ username, password = sys.stdin.readline().split(':', 1)
+except ValueError, e:
+ sys.exit('Malformed input')
+
+try:
+ user = pwd.getpwnam(username)
+except KeyError, e:
+ sys.exit('No such user: %s' % username)
+
+if user.pw_uid < 1000:
+ sys.exit('Changing the password for user id < 1000 is forbidden')
+
+if username in BLACKLIST:
+ sys.exit('Changing password for user %s is forbidden (user blacklisted)' %
+ username)
+
+handle = subprocess.Popen('/usr/sbin/chpasswd', stdin = subprocess.PIPE)
+handle.communicate('%s:%s' % (username, password))
+
+sys.exit(handle.returncode)
diff --git a/plugins/password/drivers/chpasswd.php b/plugins/password/drivers/chpasswd.php
new file mode 100644
index 000000000..cce7c1f42
--- /dev/null
+++ b/plugins/password/drivers/chpasswd.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * chpasswd Driver
+ *
+ * Driver that adds functionality to change the systems user password via
+ * the 'chpasswd' command.
+ *
+ * For installation instructions please read the README file.
+ *
+ * @version 2.0
+ * @author Alex Cartwright <acartwright@mutinydesign.co.uk)
+ */
+
+class rcube_chpasswd_password
+{
+ public function save($currpass, $newpass)
+ {
+ $cmd = rcmail::get_instance()->config->get('password_chpasswd_cmd');
+ $username = $_SESSION['username'];
+
+ $handle = popen($cmd, "w");
+ fwrite($handle, "$username:$newpass\n");
+
+ if (pclose($handle) == 0) {
+ return PASSWORD_SUCCESS;
+ }
+ else {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: Unable to execute $cmd"
+ ), true, false);
+ }
+
+ return PASSWORD_ERROR;
+ }
+}
diff --git a/plugins/password/drivers/cpanel.php b/plugins/password/drivers/cpanel.php
new file mode 100644
index 000000000..58351143b
--- /dev/null
+++ b/plugins/password/drivers/cpanel.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * cPanel Password Driver
+ *
+ * Driver that adds functionality to change the users cPanel password.
+ * The cPanel PHP API code has been taken from: http://www.phpclasses.org/browse/package/3534.html
+ *
+ * This driver has been tested with Hostmonster hosting and seems to work fine.
+ *
+ * @version 2.0
+ * @author Fulvio Venturelli <fulvio@venturelli.org>
+ */
+
+class rcube_cpanel_password
+{
+ public function save($curpas, $newpass)
+ {
+ $rcmail = rcmail::get_instance();
+
+ // Create a cPanel email object
+ $cPanel = new emailAccount($rcmail->config->get('password_cpanel_host'),
+ $rcmail->config->get('password_cpanel_username'),
+ $rcmail->config->get('password_cpanel_password'),
+ $rcmail->config->get('password_cpanel_port'),
+ $rcmail->config->get('password_cpanel_ssl'),
+ $rcmail->config->get('password_cpanel_theme'),
+ $_SESSION['username'] );
+
+ if ($cPanel->setPassword($newpass)){
+ return PASSWORD_SUCCESS;
+ }
+ else {
+ return PASSWORD_ERROR;
+ }
+ }
+}
+
+
+class HTTP
+{
+ function HTTP($host, $username, $password, $port, $ssl, $theme)
+ {
+ $this->ssl = $ssl ? 'ssl://' : '';
+ $this->username = $username;
+ $this->password = $password;
+ $this->theme = $theme;
+ $this->auth = base64_encode($username . ':' . $password);
+ $this->port = $port;
+ $this->host = $host;
+ $this->path = '/frontend/' . $theme . '/';
+ }
+
+ function getData($url, $data = '')
+ {
+ $url = $this->path . $url;
+ if(is_array($data))
+ {
+ $url = $url . '?';
+ foreach($data as $key=>$value)
+ {
+ $url .= urlencode($key) . '=' . urlencode($value) . '&';
+ }
+ $url = substr($url, 0, -1);
+ }
+ $response = '';
+ $fp = fsockopen($this->ssl . $this->host, $this->port);
+ if(!$fp)
+ {
+ return false;
+ }
+ $out = 'GET ' . $url . ' HTTP/1.0' . "\r\n";
+ $out .= 'Authorization: Basic ' . $this->auth . "\r\n";
+ $out .= 'Connection: Close' . "\r\n\r\n";
+ fwrite($fp, $out);
+ while (!feof($fp))
+ {
+ $response .= @fgets($fp);
+ }
+ fclose($fp);
+ return $response;
+ }
+}
+
+
+class emailAccount
+{
+ function emailAccount($host, $username, $password, $port, $ssl, $theme, $address)
+ {
+ $this->HTTP = new HTTP($host, $username, $password, $port, $ssl, $theme);
+ if(strpos($address, '@'))
+ {
+ list($this->email, $this->domain) = explode('@', $address);
+ }
+ else
+ {
+ list($this->email, $this->domain) = array($address, '');
+ }
+ }
+
+ /**
+ * Change email account password
+ *
+ * Returns true on success or false on failure.
+ * @param string $password email account password
+ * @return bool
+ */
+ function setPassword($password)
+ {
+ $data['email'] = $this->email;
+ $data['domain'] = $this->domain;
+ $data['password'] = $password;
+ $response = $this->HTTP->getData('mail/dopasswdpop.html', $data);
+ if(strpos($response, 'success') && !strpos($response, 'failure'))
+ {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/plugins/password/drivers/directadmin.php b/plugins/password/drivers/directadmin.php
new file mode 100644
index 000000000..1be14f6e8
--- /dev/null
+++ b/plugins/password/drivers/directadmin.php
@@ -0,0 +1,489 @@
+<?php
+
+/**
+ * DirectAdmin Password Driver
+ *
+ * Driver to change passwords via DirectAdmin Control Panel
+ *
+ * @version 2.0
+ * @author Victor Benincasa <vbenincasa@gmail.com>
+ *
+ */
+
+class rcube_directadmin_password
+{
+ public function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+ $Socket = new HTTPSocket;
+
+ $da_user = $_SESSION['username'];
+ $da_curpass = $curpass;
+ $da_newpass = $passwd;
+ $da_host = $rcmail->config->get('password_directadmin_host');
+ $da_port = $rcmail->config->get('password_directadmin_port');
+
+ if (strpos($da_user, '@') === false) {
+ return array('code' => PASSWORD_ERROR, 'message' => 'Change the SYSTEM user password through control panel!');
+ }
+
+ $da_host = str_replace('%h', $_SESSION['imap_host'], $da_host);
+ $da_host = str_replace('%d', $rcmail->user->get_username('domain'), $da_host);
+
+ $Socket->connect($da_host,$da_port);
+ $Socket->set_method('POST');
+ $Socket->query('/CMD_CHANGE_EMAIL_PASSWORD',
+ array(
+ 'email' => $da_user,
+ 'oldpassword' => $da_curpass,
+ 'password1' => $da_newpass,
+ 'password2' => $da_newpass,
+ 'api' => '1'
+ ));
+ $response = $Socket->fetch_parsed_body();
+
+ //DEBUG
+ //console("Password Plugin: [USER: $da_user] [HOST: $da_host] - Response: [SOCKET: ".$Socket->result_status_code."] [DA ERROR: ".strip_tags($response['error'])."] [TEXT: ".$response[text]."]");
+
+ if($Socket->result_status_code != 200)
+ return array('code' => PASSWORD_CONNECT_ERROR, 'message' => $Socket->error[0]);
+ elseif($response['error'] == 1)
+ return array('code' => PASSWORD_ERROR, 'message' => strip_tags($response['text']));
+ else
+ return PASSWORD_SUCCESS;
+ }
+}
+
+
+/**
+ * Socket communication class.
+ *
+ * Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need.
+ *
+ * Very, very basic usage:
+ * $Socket = new HTTPSocket;
+ * echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that');
+ *
+ * @author Phi1 'l0rdphi1' Stier <l0rdphi1@liquenox.net>
+ * @package HTTPSocket
+ * @version 2.7 (Updated by Victor Benincasa <vbenincasa@gmail.com>)
+ */
+class HTTPSocket {
+
+ var $version = '2.7';
+
+ /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */
+
+ var $method = 'GET';
+
+ var $remote_host;
+ var $remote_port;
+ var $remote_uname;
+ var $remote_passwd;
+
+ var $result;
+ var $result_header;
+ var $result_body;
+ var $result_status_code;
+
+ var $lastTransferSpeed;
+
+ var $bind_host;
+
+ var $error = array();
+ var $warn = array();
+ var $query_cache = array();
+
+ var $doFollowLocationHeader = TRUE;
+ var $redirectURL;
+
+ var $extra_headers = array();
+
+ /**
+ * Create server "connection".
+ *
+ */
+ function connect($host, $port = '' )
+ {
+ if (!is_numeric($port))
+ {
+ $port = 80;
+ }
+
+ $this->remote_host = $host;
+ $this->remote_port = $port;
+ }
+
+ function bind( $ip = '' )
+ {
+ if ( $ip == '' )
+ {
+ $ip = $_SERVER['SERVER_ADDR'];
+ }
+
+ $this->bind_host = $ip;
+ }
+
+ /**
+ * Change the method being used to communicate.
+ *
+ * @param string|null request method. supports GET, POST, and HEAD. default is GET
+ */
+ function set_method( $method = 'GET' )
+ {
+ $this->method = strtoupper($method);
+ }
+
+ /**
+ * Specify a username and password.
+ *
+ * @param string|null username. defualt is null
+ * @param string|null password. defualt is null
+ */
+ function set_login( $uname = '', $passwd = '' )
+ {
+ if ( strlen($uname) > 0 )
+ {
+ $this->remote_uname = $uname;
+ }
+
+ if ( strlen($passwd) > 0 )
+ {
+ $this->remote_passwd = $passwd;
+ }
+
+ }
+
+ /**
+ * Query the server
+ *
+ * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too.
+ * @param string|array query to pass to url
+ * @param int if connection KB/s drops below value here, will drop connection
+ */
+ function query( $request, $content = '', $doSpeedCheck = 0 )
+ {
+ $this->error = $this->warn = array();
+ $this->result_status_code = NULL;
+
+ // is our request a http:// ... ?
+ if (preg_match('!^http://!i',$request))
+ {
+ $location = parse_url($request);
+ $this->connect($location['host'],$location['port']);
+ $this->set_login($location['user'],$location['pass']);
+
+ $request = $location['path'];
+ $content = $location['query'];
+
+ if ( strlen($request) < 1 )
+ {
+ $request = '/';
+ }
+
+ }
+
+ $array_headers = array(
+ 'User-Agent' => "HTTPSocket/$this->version",
+ 'Host' => ( $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port" ),
+ 'Accept' => '*/*',
+ 'Connection' => 'Close' );
+
+ foreach ( $this->extra_headers as $key => $value )
+ {
+ $array_headers[$key] = $value;
+ }
+
+ $this->result = $this->result_header = $this->result_body = '';
+
+ // was content sent as an array? if so, turn it into a string
+ if (is_array($content))
+ {
+ $pairs = array();
+
+ foreach ( $content as $key => $value )
+ {
+ $pairs[] = "$key=".urlencode($value);
+ }
+
+ $content = join('&',$pairs);
+ unset($pairs);
+ }
+
+ $OK = TRUE;
+
+ // instance connection
+ if ($this->bind_host)
+ {
+ $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+ socket_bind($socket,$this->bind_host);
+
+ if (!@socket_connect($socket,$this->remote_host,$this->remote_port))
+ {
+ $OK = FALSE;
+ }
+
+ }
+ else
+ {
+ $socket = @fsockopen( $this->remote_host, $this->remote_port, $sock_errno, $sock_errstr, 10 );
+ }
+
+ if ( !$socket || !$OK )
+ {
+ $this->error[] = "Can't create socket connection to $this->remote_host:$this->remote_port.";
+ return 0;
+ }
+
+ // if we have a username and password, add the header
+ if ( isset($this->remote_uname) && isset($this->remote_passwd) )
+ {
+ $array_headers['Authorization'] = 'Basic '.base64_encode("$this->remote_uname:$this->remote_passwd");
+ }
+
+ // for DA skins: if $this->remote_passwd is NULL, try to use the login key system
+ if ( isset($this->remote_uname) && $this->remote_passwd == NULL )
+ {
+ $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}";
+ }
+
+ // if method is POST, add content length & type headers
+ if ( $this->method == 'POST' )
+ {
+ $array_headers['Content-type'] = 'application/x-www-form-urlencoded';
+ $array_headers['Content-length'] = strlen($content);
+ }
+ // else method is GET or HEAD. we don't support anything else right now.
+ else
+ {
+ if ($content)
+ {
+ $request .= "?$content";
+ }
+ }
+
+ // prepare query
+ $query = "$this->method $request HTTP/1.0\r\n";
+ foreach ( $array_headers as $key => $value )
+ {
+ $query .= "$key: $value\r\n";
+ }
+ $query .= "\r\n";
+
+ // if POST we need to append our content
+ if ( $this->method == 'POST' && $content )
+ {
+ $query .= "$content\r\n\r\n";
+ }
+
+ // query connection
+ if ($this->bind_host)
+ {
+ socket_write($socket,$query);
+
+ // now load results
+ while ( $out = socket_read($socket,2048) )
+ {
+ $this->result .= $out;
+ }
+ }
+ else
+ {
+ fwrite( $socket, $query, strlen($query) );
+
+ // now load results
+ $this->lastTransferSpeed = 0;
+ $status = socket_get_status($socket);
+ $startTime = time();
+ $length = 0;
+ $prevSecond = 0;
+ while ( !feof($socket) && !$status['timed_out'] )
+ {
+ $chunk = fgets($socket,1024);
+ $length += strlen($chunk);
+ $this->result .= $chunk;
+
+ $elapsedTime = time() - $startTime;
+
+ if ( $elapsedTime > 0 )
+ {
+ $this->lastTransferSpeed = ($length/1024)/$elapsedTime;
+ }
+
+ if ( $doSpeedCheck > 0 && $elapsedTime > 5 && $this->lastTransferSpeed < $doSpeedCheck )
+ {
+ $this->warn[] = "kB/s for last 5 seconds is below 50 kB/s (~".( ($length/1024)/$elapsedTime )."), dropping connection...";
+ $this->result_status_code = 503;
+ break;
+ }
+
+ }
+
+ if ( $this->lastTransferSpeed == 0 )
+ {
+ $this->lastTransferSpeed = $length/1024;
+ }
+
+ }
+
+ list($this->result_header,$this->result_body) = preg_split("/\r\n\r\n/",$this->result,2);
+
+ if ($this->bind_host)
+ {
+ socket_close($socket);
+ }
+ else
+ {
+ fclose($socket);
+ }
+
+ $this->query_cache[] = $query;
+
+
+ $headers = $this->fetch_header();
+
+ // what return status did we get?
+ if (!$this->result_status_code)
+ {
+ preg_match("#HTTP/1\.. (\d+)#",$headers[0],$matches);
+ $this->result_status_code = $matches[1];
+ }
+
+ // did we get the full file?
+ if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) )
+ {
+ $this->result_status_code = 206;
+ }
+
+ // now, if we're being passed a location header, should we follow it?
+ if ($this->doFollowLocationHeader)
+ {
+ if ($headers['location'])
+ {
+ $this->redirectURL = $headers['location'];
+ $this->query($headers['location']);
+ }
+ }
+ }
+
+ function getTransferSpeed()
+ {
+ return $this->lastTransferSpeed;
+ }
+
+ /**
+ * The quick way to get a URL's content :)
+ *
+ * @param string URL
+ * @param boolean return as array? (like PHP's file() command)
+ * @return string result body
+ */
+ function get($location, $asArray = FALSE )
+ {
+ $this->query($location);
+
+ if ( $this->get_status_code() == 200 )
+ {
+ if ($asArray)
+ {
+ return preg_split("/\n/",$this->fetch_body());
+ }
+
+ return $this->fetch_body();
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Returns the last status code.
+ * 200 = OK;
+ * 403 = FORBIDDEN;
+ * etc.
+ *
+ * @return int status code
+ */
+ function get_status_code()
+ {
+ return $this->result_status_code;
+ }
+
+ /**
+ * Adds a header, sent with the next query.
+ *
+ * @param string header name
+ * @param string header value
+ */
+ function add_header($key,$value)
+ {
+ $this->extra_headers[$key] = $value;
+ }
+
+ /**
+ * Clears any extra headers.
+ *
+ */
+ function clear_headers()
+ {
+ $this->extra_headers = array();
+ }
+
+ /**
+ * Return the result of a query.
+ *
+ * @return string result
+ */
+ function fetch_result()
+ {
+ return $this->result;
+ }
+
+ /**
+ * Return the header of result (stuff before body).
+ *
+ * @param string (optional) header to return
+ * @return array result header
+ */
+ function fetch_header( $header = '' )
+ {
+ $array_headers = preg_split("/\r\n/",$this->result_header);
+
+ $array_return = array( 0 => $array_headers[0] );
+ unset($array_headers[0]);
+
+ foreach ( $array_headers as $pair )
+ {
+ list($key,$value) = preg_split("/: /",$pair,2);
+ $array_return[strtolower($key)] = $value;
+ }
+
+ if ( $header != '' )
+ {
+ return $array_return[strtolower($header)];
+ }
+
+ return $array_return;
+ }
+
+ /**
+ * Return the body of result (stuff after header).
+ *
+ * @return string result body
+ */
+ function fetch_body()
+ {
+ return $this->result_body;
+ }
+
+ /**
+ * Return parsed body in array format.
+ *
+ * @return array result parsed
+ */
+ function fetch_parsed_body()
+ {
+ parse_str($this->result_body,$x);
+ return $x;
+ }
+
+}
diff --git a/plugins/password/drivers/hmail.php b/plugins/password/drivers/hmail.php
new file mode 100644
index 000000000..104c851ae
--- /dev/null
+++ b/plugins/password/drivers/hmail.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * hMailserver password driver
+ *
+ * @version 2.0
+ * @author Roland 'rosali' Liebl <myroundcube@mail4us.net>
+ *
+ */
+
+class rcube_hmail_password
+{
+ public function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+
+ if ($curpass == '' || $passwd == '') {
+ return PASSWORD_ERROR;
+ }
+
+ try {
+ $remote = $rcmail->config->get('hmailserver_remote_dcom', false);
+ if ($remote)
+ $obApp = new COM("hMailServer.Application", $rcmail->config->get('hmailserver_server'));
+ else
+ $obApp = new COM("hMailServer.Application");
+ }
+ catch (Exception $e) {
+ write_log('errors', "Plugin password (hmail driver): " . trim(strip_tags($e->getMessage())));
+ write_log('errors', "Plugin password (hmail driver): This problem is often caused by DCOM permissions not being set.");
+ return PASSWORD_ERROR;
+ }
+
+ $username = $rcmail->user->data['username'];
+ if (strstr($username,'@')){
+ $temparr = explode('@', $username);
+ $domain = $temparr[1];
+ }
+ else {
+ $domain = $rcmail->config->get('username_domain',false);
+ if (!$domain) {
+ write_log('errors','Plugin password (hmail driver): $rcmail_config[\'username_domain\'] is not defined.');
+ write_log('errors','Plugin password (hmail driver): Hint: Use hmail_login plugin (http://myroundcube.googlecode.com');
+ return PASSWORD_ERROR;
+ }
+ $username = $username . "@" . $domain;
+ }
+
+ $obApp->Authenticate($username, $curpass);
+ try {
+ $obDomain = $obApp->Domains->ItemByName($domain);
+ $obAccount = $obDomain->Accounts->ItemByAddress($username);
+ $obAccount->Password = $passwd;
+ $obAccount->Save();
+ return PASSWORD_SUCCESS;
+ }
+ catch (Exception $e) {
+ write_log('errors', "Plugin password (hmail driver): " . trim(strip_tags($e->getMessage())));
+ write_log('errors', "Plugin password (hmail driver): This problem is often caused by DCOM permissions not being set.");
+ return PASSWORD_ERROR;
+ }
+ }
+}
diff --git a/plugins/password/drivers/ldap.php b/plugins/password/drivers/ldap.php
new file mode 100644
index 000000000..def07a175
--- /dev/null
+++ b/plugins/password/drivers/ldap.php
@@ -0,0 +1,319 @@
+<?php
+
+/**
+ * LDAP Password Driver
+ *
+ * Driver for passwords stored in LDAP
+ * This driver use the PEAR Net_LDAP2 class (http://pear.php.net/package/Net_LDAP2).
+ *
+ * @version 2.0
+ * @author Edouard MOREAU <edouard.moreau@ensma.fr>
+ *
+ * method hashPassword based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
+ * method randomSalt based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
+ *
+ */
+
+class rcube_ldap_password
+{
+ public function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+ require_once 'Net/LDAP2.php';
+
+ // Building user DN
+ if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) {
+ $userDN = $this->substitute_vars($userDN);
+ } else {
+ $userDN = $this->search_userdn($rcmail);
+ }
+
+ if (empty($userDN)) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // Connection Method
+ switch($rcmail->config->get('password_ldap_method')) {
+ case 'admin':
+ $binddn = $rcmail->config->get('password_ldap_adminDN');
+ $bindpw = $rcmail->config->get('password_ldap_adminPW');
+ break;
+ case 'user':
+ default:
+ $binddn = $userDN;
+ $bindpw = $curpass;
+ break;
+ }
+
+ // Configuration array
+ $ldapConfig = array (
+ 'binddn' => $binddn,
+ 'bindpw' => $bindpw,
+ 'basedn' => $rcmail->config->get('password_ldap_basedn'),
+ 'host' => $rcmail->config->get('password_ldap_host'),
+ 'port' => $rcmail->config->get('password_ldap_port'),
+ 'starttls' => $rcmail->config->get('password_ldap_starttls'),
+ 'version' => $rcmail->config->get('password_ldap_version'),
+ );
+
+ // Connecting using the configuration array
+ $ldap = Net_LDAP2::connect($ldapConfig);
+
+ // Checking for connection error
+ if (PEAR::isError($ldap)) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ $crypted_pass = $this->hashPassword($passwd, $rcmail->config->get('password_ldap_encodage'));
+ $force = $rcmail->config->get('password_ldap_force_replace');
+ $pwattr = $rcmail->config->get('password_ldap_pwattr');
+ $lchattr = $rcmail->config->get('password_ldap_lchattr');
+ $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr');
+ $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr');
+ $samba = $rcmail->config->get('password_ldap_samba');
+
+ // Support password_ldap_samba option for backward compat.
+ if ($samba && !$smbpwattr) {
+ $smbpwattr = 'sambaNTPassword';
+ $smblchattr = 'sambaPwdLastSet';
+ }
+
+ // Crypt new password
+ if (!$crypted_pass) {
+ return PASSWORD_CRYPT_ERROR;
+ }
+
+ // Crypt new samba password
+ if ($smbpwattr && !($samba_pass = $this->hashPassword($passwd, 'samba'))) {
+ return PASSWORD_CRYPT_ERROR;
+ }
+
+ // Writing new crypted password to LDAP
+ $userEntry = $ldap->getEntry($userDN);
+ if (Net_LDAP2::isError($userEntry)) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ if (!$userEntry->replace(array($pwattr => $crypted_pass), $force)) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // Updating PasswordLastChange Attribute if desired
+ if ($lchattr) {
+ $current_day = (int)(time() / 86400);
+ if (!$userEntry->replace(array($lchattr => $current_day), $force)) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+ }
+
+ // Update Samba password and last change fields
+ if ($smbpwattr) {
+ $userEntry->replace(array($smbpwattr => $samba_pass), $force);
+ }
+ // Update Samba password last change field
+ if ($smblchattr) {
+ $userEntry->replace(array($smblchattr => time()), $force);
+ }
+
+ if (Net_LDAP2::isError($userEntry->update())) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // All done, no error
+ return PASSWORD_SUCCESS;
+ }
+
+ /**
+ * Bind with searchDN and searchPW and search for the user's DN.
+ * Use search_base and search_filter defined in config file.
+ * Return the found DN.
+ */
+ function search_userdn($rcmail)
+ {
+ $ldapConfig = array (
+ 'binddn' => $rcmail->config->get('password_ldap_searchDN'),
+ 'bindpw' => $rcmail->config->get('password_ldap_searchPW'),
+ 'basedn' => $rcmail->config->get('password_ldap_basedn'),
+ 'host' => $rcmail->config->get('password_ldap_host'),
+ 'port' => $rcmail->config->get('password_ldap_port'),
+ 'starttls' => $rcmail->config->get('password_ldap_starttls'),
+ 'version' => $rcmail->config->get('password_ldap_version'),
+ );
+
+ $ldap = Net_LDAP2::connect($ldapConfig);
+
+ if (PEAR::isError($ldap)) {
+ return '';
+ }
+
+ $base = $rcmail->config->get('password_ldap_search_base');
+ $filter = $this->substitute_vars($rcmail->config->get('password_ldap_search_filter'));
+ $options = array (
+ 'scope' => 'sub',
+ 'attributes' => array(),
+ );
+
+ $result = $ldap->search($base, $filter, $options);
+ $ldap->done();
+ if (PEAR::isError($result) || ($result->count() != 1)) {
+ return '';
+ }
+
+ return $result->current()->dn();
+ }
+
+ /**
+ * Substitute %login, %name, %domain, %dc in $str.
+ * See plugin config for details.
+ */
+ function substitute_vars($str)
+ {
+ $rcmail = rcmail::get_instance();
+ $domain = $rcmail->user->get_username('domain');
+ $dc = 'dc='.strtr($domain, array('.' => ',dc=')); // hierarchal domain string
+
+ $str = str_replace(array(
+ '%login',
+ '%name',
+ '%domain',
+ '%dc',
+ ), array(
+ $_SESSION['username'],
+ $rcmail->user->get_username('local'),
+ $domain,
+ $dc,
+ ), $str
+ );
+
+ return $str;
+ }
+
+ /**
+ * Code originaly from the phpLDAPadmin development team
+ * http://phpldapadmin.sourceforge.net/
+ *
+ * Hashes a password and returns the hash based on the specified enc_type.
+ *
+ * @param string $passwordClear The password to hash in clear text.
+ * @param string $encodageType Standard LDAP encryption type which must be one of
+ * crypt, ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear.
+ * @return string The hashed password.
+ *
+ */
+ function hashPassword( $passwordClear, $encodageType )
+ {
+ $encodageType = strtolower( $encodageType );
+ switch( $encodageType ) {
+ case 'crypt':
+ $cryptedPassword = '{CRYPT}' . crypt($passwordClear, $this->randomSalt(2));
+ break;
+
+ case 'ext_des':
+ // extended des crypt. see OpenBSD crypt man page.
+ if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {
+ // Your system crypt library does not support extended DES encryption.
+ return FALSE;
+ }
+ $cryptedPassword = '{CRYPT}' . crypt( $passwordClear, '_' . $this->randomSalt(8) );
+ break;
+
+ case 'md5crypt':
+ if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {
+ // Your system crypt library does not support md5crypt encryption.
+ return FALSE;
+ }
+ $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$1$' . $this->randomSalt(9) );
+ break;
+
+ case 'blowfish':
+ if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {
+ // Your system crypt library does not support blowfish encryption.
+ return FALSE;
+ }
+ // hardcoded to second blowfish version and set number of rounds
+ $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$2a$12$' . $this->randomSalt(13) );
+ break;
+
+ case 'md5':
+ $cryptedPassword = '{MD5}' . base64_encode( pack( 'H*' , md5( $passwordClear) ) );
+ break;
+
+ case 'sha':
+ if( function_exists('sha1') ) {
+ // use php 4.3.0+ sha1 function, if it is available.
+ $cryptedPassword = '{SHA}' . base64_encode( pack( 'H*' , sha1( $passwordClear) ) );
+ } elseif( function_exists( 'mhash' ) ) {
+ $cryptedPassword = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $passwordClear) );
+ } else {
+ return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
+ }
+ break;
+
+ case 'ssha':
+ if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
+ mt_srand( (double) microtime() * 1000000 );
+ $salt = mhash_keygen_s2k( MHASH_SHA1, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
+ $cryptedPassword = '{SSHA}'.base64_encode( mhash( MHASH_SHA1, $passwordClear.$salt ).$salt );
+ } else {
+ return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
+ }
+ break;
+
+ case 'smd5':
+ if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
+ mt_srand( (double) microtime() * 1000000 );
+ $salt = mhash_keygen_s2k( MHASH_MD5, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
+ $cryptedPassword = '{SMD5}'.base64_encode( mhash( MHASH_MD5, $passwordClear.$salt ).$salt );
+ } else {
+ return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
+ }
+ break;
+
+ case 'samba':
+ if (function_exists('hash')) {
+ $cryptedPassword = hash('md4', rcube_charset_convert($passwordClear, RCMAIL_CHARSET, 'UTF-16LE'));
+ $cryptedPassword = strtoupper($cryptedPassword);
+ } else {
+ /* Your PHP install does not have the hash() function */
+ return false;
+ }
+ break;
+
+ case 'clear':
+ default:
+ $cryptedPassword = $passwordClear;
+ }
+
+ return $cryptedPassword;
+ }
+
+ /**
+ * Code originaly from the phpLDAPadmin development team
+ * http://phpldapadmin.sourceforge.net/
+ *
+ * Used to generate a random salt for crypt-style passwords. Salt strings are used
+ * to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses
+ * not only the user's password but also a randomly generated string. The string is
+ * stored as the first N characters of the hash for reference of hashing algorithms later.
+ *
+ * --- added 20021125 by bayu irawan <bayuir@divnet.telkom.co.id> ---
+ * --- ammended 20030625 by S C Rigler <srigler@houston.rr.com> ---
+ *
+ * @param int $length The length of the salt string to generate.
+ * @return string The generated salt string.
+ */
+ function randomSalt( $length )
+ {
+ $possible = '0123456789'.
+ 'abcdefghijklmnopqrstuvwxyz'.
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
+ './';
+ $str = '';
+ // mt_srand((double)microtime() * 1000000);
+
+ while (strlen($str) < $length)
+ $str .= substr($possible, (rand() % strlen($possible)), 1);
+
+ return $str;
+ }
+}
diff --git a/plugins/password/drivers/ldap_simple.php b/plugins/password/drivers/ldap_simple.php
new file mode 100644
index 000000000..e1daed9f3
--- /dev/null
+++ b/plugins/password/drivers/ldap_simple.php
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * Simple LDAP Password Driver
+ *
+ * Driver for passwords stored in LDAP
+ * This driver is based on Edouard's LDAP Password Driver, but does not
+ * require PEAR's Net_LDAP2 to be installed
+ *
+ * @version 2.0
+ * @author Wout Decre <wout@canodus.be>
+ */
+
+class rcube_ldap_simple_password
+{
+ function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+
+ // Connect
+ if (!$ds = ldap_connect($rcmail->config->get('password_ldap_host'), $rcmail->config->get('password_ldap_port'))) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // Set protocol version
+ if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $rcmail->config->get('password_ldap_version'))) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // Start TLS
+ if ($rcmail->config->get('password_ldap_starttls')) {
+ if (!ldap_start_tls($ds)) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+ }
+
+ // Build user DN
+ if ($user_dn = $rcmail->config->get('password_ldap_userDN_mask')) {
+ $user_dn = $this->substitute_vars($user_dn);
+ }
+ else {
+ $user_dn = $this->search_userdn($rcmail, $ds);
+ }
+
+ if (empty($user_dn)) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // Connection method
+ switch ($rcmail->config->get('password_ldap_method')) {
+ case 'admin':
+ $binddn = $rcmail->config->get('password_ldap_adminDN');
+ $bindpw = $rcmail->config->get('password_ldap_adminPW');
+ break;
+ case 'user':
+ default:
+ $binddn = $user_dn;
+ $bindpw = $curpass;
+ break;
+ }
+
+ $crypted_pass = $this->hash_password($passwd, $rcmail->config->get('password_ldap_encodage'));
+ $lchattr = $rcmail->config->get('password_ldap_lchattr');
+ $pwattr = $rcmail->config->get('password_ldap_pwattr');
+ $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr');
+ $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr');
+ $samba = $rcmail->config->get('password_ldap_samba');
+
+ // Support password_ldap_samba option for backward compat.
+ if ($samba && !$smbpwattr) {
+ $smbpwattr = 'sambaNTPassword';
+ $smblchattr = 'sambaPwdLastSet';
+ }
+
+ // Crypt new password
+ if (!$crypted_pass) {
+ return PASSWORD_CRYPT_ERROR;
+ }
+
+ // Crypt new Samba password
+ if ($smbpwattr && !($samba_pass = $this->hash_password($passwd, 'samba'))) {
+ return PASSWORD_CRYPT_ERROR;
+ }
+
+ // Bind
+ if (!ldap_bind($ds, $binddn, $bindpw)) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ $entree[$pwattr] = $crypted_pass;
+
+ // Update PasswordLastChange Attribute if desired
+ if ($lchattr) {
+ $entree[$lchattr] = (int)(time() / 86400);
+ }
+
+ // Update Samba password
+ if ($smbpwattr) {
+ $entree[$smbpwattr] = $samba_pass;
+ }
+
+ // Update Samba password last change
+ if ($smblchattr) {
+ $entree[$smblchattr] = time();
+ }
+
+ if (!ldap_modify($ds, $user_dn, $entree)) {
+ ldap_unbind($ds);
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // All done, no error
+ ldap_unbind($ds);
+ return PASSWORD_SUCCESS;
+ }
+
+ /**
+ * Bind with searchDN and searchPW and search for the user's DN
+ * Use search_base and search_filter defined in config file
+ * Return the found DN
+ */
+ function search_userdn($rcmail, $ds)
+ {
+ /* Bind */
+ if (!ldap_bind($ds, $rcmail->config->get('password_ldap_searchDN'), $rcmail->config->get('password_ldap_searchPW'))) {
+ return false;
+ }
+
+ /* Search for the DN */
+ if (!$sr = ldap_search($ds, $rcmail->config->get('password_ldap_search_base'), $this->substitute_vars($rcmail->config->get('password_ldap_search_filter')))) {
+ return false;
+ }
+
+ /* If no or more entries were found, return false */
+ if (ldap_count_entries($ds, $sr) != 1) {
+ return false;
+ }
+
+ return ldap_get_dn($ds, ldap_first_entry($ds, $sr));
+ }
+
+ /**
+ * Substitute %login, %name, %domain, %dc in $str
+ * See plugin config for details
+ */
+ function substitute_vars($str)
+ {
+ $str = str_replace('%login', $_SESSION['username'], $str);
+ $str = str_replace('%l', $_SESSION['username'], $str);
+
+ $parts = explode('@', $_SESSION['username']);
+
+ if (count($parts) == 2) {
+ $dc = 'dc='.strtr($parts[1], array('.' => ',dc=')); // hierarchal domain string
+
+ $str = str_replace('%name', $parts[0], $str);
+ $str = str_replace('%n', $parts[0], $str);
+ $str = str_replace('%dc', $dc, $str);
+ $str = str_replace('%domain', $parts[1], $str);
+ $str = str_replace('%d', $parts[1], $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Code originaly from the phpLDAPadmin development team
+ * http://phpldapadmin.sourceforge.net/
+ *
+ * Hashes a password and returns the hash based on the specified enc_type
+ */
+ function hash_password($password_clear, $encodage_type)
+ {
+ $encodage_type = strtolower($encodage_type);
+ switch ($encodage_type) {
+ case 'crypt':
+ $crypted_password = '{CRYPT}' . crypt($password_clear, $this->random_salt(2));
+ break;
+ case 'ext_des':
+ /* Extended DES crypt. see OpenBSD crypt man page */
+ if (!defined('CRYPT_EXT_DES') || CRYPT_EXT_DES == 0) {
+ /* Your system crypt library does not support extended DES encryption */
+ return false;
+ }
+ $crypted_password = '{CRYPT}' . crypt($password_clear, '_' . $this->random_salt(8));
+ break;
+ case 'md5crypt':
+ if (!defined('CRYPT_MD5') || CRYPT_MD5 == 0) {
+ /* Your system crypt library does not support md5crypt encryption */
+ return false;
+ }
+ $crypted_password = '{CRYPT}' . crypt($password_clear, '$1$' . $this->random_salt(9));
+ break;
+ case 'blowfish':
+ if (!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH == 0) {
+ /* Your system crypt library does not support blowfish encryption */
+ return false;
+ }
+ /* Hardcoded to second blowfish version and set number of rounds */
+ $crypted_password = '{CRYPT}' . crypt($password_clear, '$2a$12$' . $this->random_salt(13));
+ break;
+ case 'md5':
+ $crypted_password = '{MD5}' . base64_encode(pack('H*', md5($password_clear)));
+ break;
+ case 'sha':
+ if (function_exists('sha1')) {
+ /* Use PHP 4.3.0+ sha1 function, if it is available */
+ $crypted_password = '{SHA}' . base64_encode(pack('H*', sha1($password_clear)));
+ } else if (function_exists('mhash')) {
+ $crypted_password = '{SHA}' . base64_encode(mhash(MHASH_SHA1, $password_clear));
+ } else {
+ /* Your PHP install does not have the mhash() function */
+ return false;
+ }
+ break;
+ case 'ssha':
+ if (function_exists('mhash') && function_exists('mhash_keygen_s2k')) {
+ mt_srand((double) microtime() * 1000000 );
+ $salt = mhash_keygen_s2k(MHASH_SHA1, $password_clear, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
+ $crypted_password = '{SSHA}' . base64_encode(mhash(MHASH_SHA1, $password_clear . $salt) . $salt);
+ } else {
+ /* Your PHP install does not have the mhash() function */
+ return false;
+ }
+ break;
+ case 'smd5':
+ if (function_exists('mhash') && function_exists('mhash_keygen_s2k')) {
+ mt_srand((double) microtime() * 1000000 );
+ $salt = mhash_keygen_s2k(MHASH_MD5, $password_clear, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
+ $crypted_password = '{SMD5}' . base64_encode(mhash(MHASH_MD5, $password_clear . $salt) . $salt);
+ } else {
+ /* Your PHP install does not have the mhash() function */
+ return false;
+ }
+ break;
+ case 'samba':
+ if (function_exists('hash')) {
+ $crypted_password = hash('md4', rcube_charset_convert($password_clear, RCMAIL_CHARSET, 'UTF-16LE'));
+ $crypted_password = strtoupper($crypted_password);
+ } else {
+ /* Your PHP install does not have the hash() function */
+ return false;
+ }
+ break;
+ case 'clear':
+ default:
+ $crypted_password = $password_clear;
+ }
+
+ return $crypted_password;
+ }
+
+ /**
+ * Code originaly from the phpLDAPadmin development team
+ * http://phpldapadmin.sourceforge.net/
+ *
+ * Used to generate a random salt for crypt-style passwords
+ */
+ function random_salt($length)
+ {
+ $possible = '0123456789' . 'abcdefghijklmnopqrstuvwxyz' . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . './';
+ $str = '';
+ // mt_srand((double)microtime() * 1000000);
+
+ while (strlen($str) < $length) {
+ $str .= substr($possible, (rand() % strlen($possible)), 1);
+ }
+
+ return $str;
+ }
+}
diff --git a/plugins/password/drivers/pam.php b/plugins/password/drivers/pam.php
new file mode 100644
index 000000000..ed60bd841
--- /dev/null
+++ b/plugins/password/drivers/pam.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * PAM Password Driver
+ *
+ * @version 2.0
+ * @author Aleksander Machniak
+ */
+
+class rcube_pam_password
+{
+ function save($currpass, $newpass)
+ {
+ $user = $_SESSION['username'];
+
+ if (extension_loaded('pam')) {
+ if (pam_auth($user, $currpass, $error, false)) {
+ if (pam_chpass($user, $currpass, $newpass)) {
+ return PASSWORD_SUCCESS;
+ }
+ }
+ else {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: PAM authentication failed for user $user: $error"
+ ), true, false);
+ }
+ }
+ else {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: PECL-PAM module not loaded"
+ ), true, false);
+ }
+
+ return PASSWORD_ERROR;
+ }
+}
diff --git a/plugins/password/drivers/poppassd.php b/plugins/password/drivers/poppassd.php
new file mode 100644
index 000000000..e18ec2660
--- /dev/null
+++ b/plugins/password/drivers/poppassd.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Poppassd Password Driver
+ *
+ * Driver to change passwords via Poppassd/Courierpassd
+ *
+ * @version 2.0
+ * @author Philip Weir
+ *
+ */
+
+class rcube_poppassd_password
+{
+ 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();
+// include('Net/Socket.php');
+ $poppassd = new Net_Socket();
+
+ $result = $poppassd->connect($rcmail->config->get('password_pop_host'), $rcmail->config->get('password_pop_port'), null);
+ if (PEAR::isError($result)) {
+ return $this->format_error_result(PASSWORD_CONNECT_ERROR, $result->getMessage());
+ }
+ else {
+ $result = $poppassd->readLine();
+ if(!preg_match('/^2\d\d/', $result)) {
+ $poppassd->disconnect();
+ return $this->format_error_result(PASSWORD_ERROR, $result);
+ }
+ else {
+ $poppassd->writeLine("user ". $_SESSION['username']);
+ $result = $poppassd->readLine();
+ if(!preg_match('/^[23]\d\d/', $result) ) {
+ $poppassd->disconnect();
+ return $this->format_error_result(PASSWORD_CONNECT_ERROR, $result);
+ }
+ else {
+ $poppassd->writeLine("pass ". $curpass);
+ $result = $poppassd->readLine();
+ if(!preg_match('/^[23]\d\d/', $result) ) {
+ $poppassd->disconnect();
+ return $this->format_error_result(PASSWORD_ERROR, $result);
+ }
+ else {
+ $poppassd->writeLine("newpass ". $passwd);
+ $result = $poppassd->readLine();
+ $poppassd->disconnect();
+ if (!preg_match('/^2\d\d/', $result))
+ return $this->format_error_result(PASSWORD_ERROR, $result);
+ else
+ return PASSWORD_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/password/drivers/sasl.php b/plugins/password/drivers/sasl.php
new file mode 100644
index 000000000..3e6fe1c8b
--- /dev/null
+++ b/plugins/password/drivers/sasl.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * SASL Password Driver
+ *
+ * Driver that adds functionality to change the users Cyrus/SASL password.
+ * The code is derrived from the Squirrelmail "Change SASL Password" Plugin
+ * by Galen Johnson.
+ *
+ * It only works with saslpasswd2 on the same host where Roundcube runs
+ * and requires shell access and gcc in order to compile the binary.
+ *
+ * For installation instructions please read the README file.
+ *
+ * @version 2.0
+ * @author Thomas Bruederli
+ */
+
+class rcube_sasl_password
+{
+ function save($currpass, $newpass)
+ {
+ $curdir = realpath(dirname(__FILE__));
+ $username = escapeshellcmd($_SESSION['username']);
+ $args = rcmail::get_instance()->config->get('password_saslpasswd_args', '');
+
+ if ($fh = popen("$curdir/chgsaslpasswd -p $args $username", 'w')) {
+ fwrite($fh, $newpass."\n");
+ $code = pclose($fh);
+
+ if ($code == 0)
+ return PASSWORD_SUCCESS;
+ }
+ else {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: Unable to execute $curdir/chgsaslpasswd"
+ ), true, false);
+ }
+
+ return PASSWORD_ERROR;
+ }
+}
diff --git a/plugins/password/drivers/sql.php b/plugins/password/drivers/sql.php
new file mode 100644
index 000000000..e9207300e
--- /dev/null
+++ b/plugins/password/drivers/sql.php
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * SQL Password Driver
+ *
+ * Driver for passwords stored in SQL database
+ *
+ * @version 2.0
+ * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
+ *
+ */
+
+class rcube_sql_password
+{
+ function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+
+ if (!($sql = $rcmail->config->get('password_query')))
+ $sql = 'SELECT update_passwd(%c, %u)';
+
+ if ($dsn = $rcmail->config->get('password_db_dsn')) {
+ // #1486067: enable new_link option
+ if (is_array($dsn) && empty($dsn['new_link']))
+ $dsn['new_link'] = true;
+ else if (!is_array($dsn) && !preg_match('/\?new_link=true/', $dsn))
+ $dsn .= '?new_link=true';
+
+ $db = new rcube_mdb2($dsn, '', FALSE);
+ $db->set_debug((bool)$rcmail->config->get('sql_debug'));
+ $db->db_connect('w');
+ }
+ else {
+ $db = $rcmail->get_dbh();
+ }
+
+ if ($err = $db->is_error())
+ return PASSWORD_ERROR;
+
+ // crypted password
+ if (strpos($sql, '%c') !== FALSE) {
+ $salt = '';
+ if (CRYPT_MD5) {
+ // Always use eight salt characters for MD5 (#1488136)
+ $len = 8;
+ } else if (CRYPT_STD_DES) {
+ $len = 2;
+ } else {
+ return PASSWORD_CRYPT_ERROR;
+ }
+
+ //Restrict the character set used as salt (#1488136)
+ $seedchars = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+ for ($i = 0; $i < $len ; $i++) {
+ $salt .= $seedchars[rand(0, 63)];
+ }
+
+ $sql = str_replace('%c', $db->quote(crypt($passwd, CRYPT_MD5 ? '$1$'.$salt.'$' : $salt)), $sql);
+ }
+
+ // dovecotpw
+ if (strpos($sql, '%D') !== FALSE) {
+ if (!($dovecotpw = $rcmail->config->get('password_dovecotpw')))
+ $dovecotpw = 'dovecotpw';
+ if (!($method = $rcmail->config->get('password_dovecotpw_method')))
+ $method = 'CRAM-MD5';
+
+ // use common temp dir
+ $tmp_dir = $rcmail->config->get('temp_dir');
+ $tmpfile = tempnam($tmp_dir, 'roundcube-');
+
+ $pipe = popen("$dovecotpw -s '$method' > '$tmpfile'", "w");
+ if (!$pipe) {
+ unlink($tmpfile);
+ return PASSWORD_CRYPT_ERROR;
+ }
+ else {
+ fwrite($pipe, $passwd . "\n", 1+strlen($passwd)); usleep(1000);
+ fwrite($pipe, $passwd . "\n", 1+strlen($passwd));
+ pclose($pipe);
+ $newpass = trim(file_get_contents($tmpfile), "\n");
+ if (!preg_match('/^\{' . $method . '\}/', $newpass)) {
+ return PASSWORD_CRYPT_ERROR;
+ }
+ if (!$rcmail->config->get('password_dovecotpw_with_method'))
+ $newpass = trim(str_replace('{' . $method . '}', '', $newpass));
+ unlink($tmpfile);
+ }
+ $sql = str_replace('%D', $db->quote($newpass), $sql);
+ }
+
+ // hashed passwords
+ if (preg_match('/%[n|q]/', $sql)) {
+ if (!extension_loaded('hash')) {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: 'hash' extension not loaded!"
+ ), true, false);
+
+ return PASSWORD_ERROR;
+ }
+
+ if (!($hash_algo = strtolower($rcmail->config->get('password_hash_algorithm'))))
+ $hash_algo = 'sha1';
+
+ $hash_passwd = hash($hash_algo, $passwd);
+ $hash_curpass = hash($hash_algo, $curpass);
+
+ if ($rcmail->config->get('password_hash_base64')) {
+ $hash_passwd = base64_encode(pack('H*', $hash_passwd));
+ $hash_curpass = base64_encode(pack('H*', $hash_curpass));
+ }
+
+ $sql = str_replace('%n', $db->quote($hash_passwd, 'text'), $sql);
+ $sql = str_replace('%q', $db->quote($hash_curpass, 'text'), $sql);
+ }
+
+ // Handle clear text passwords securely (#1487034)
+ $sql_vars = array();
+ if (preg_match_all('/%[p|o]/', $sql, $m)) {
+ foreach ($m[0] as $var) {
+ if ($var == '%p') {
+ $sql = preg_replace('/%p/', '?', $sql, 1);
+ $sql_vars[] = (string) $passwd;
+ }
+ else { // %o
+ $sql = preg_replace('/%o/', '?', $sql, 1);
+ $sql_vars[] = (string) $curpass;
+ }
+ }
+ }
+
+ $local_part = $rcmail->user->get_username('local');
+ $domain_part = $rcmail->user->get_username('domain');
+ $username = $_SESSION['username'];
+ $host = $_SESSION['imap_host'];
+
+ // convert domains to/from punnycode
+ if ($rcmail->config->get('password_idn_ascii')) {
+ $domain_part = rcube_idn_to_ascii($domain_part);
+ $username = rcube_idn_to_ascii($username);
+ $host = rcube_idn_to_ascii($host);
+ }
+ else {
+ $domain_part = rcube_idn_to_utf8($domain_part);
+ $username = rcube_idn_to_utf8($username);
+ $host = rcube_idn_to_utf8($host);
+ }
+
+ // at least we should always have the local part
+ $sql = str_replace('%l', $db->quote($local_part, 'text'), $sql);
+ $sql = str_replace('%d', $db->quote($domain_part, 'text'), $sql);
+ $sql = str_replace('%u', $db->quote($username, 'text'), $sql);
+ $sql = str_replace('%h', $db->quote($host, 'text'), $sql);
+
+ $res = $db->query($sql, $sql_vars);
+
+ if (!$db->is_error()) {
+ if (strtolower(substr(trim($query),0,6))=='select') {
+ if ($result = $db->fetch_array($res))
+ return PASSWORD_SUCCESS;
+ } else {
+ // This is the good case: 1 row updated
+ if ($db->affected_rows($res) == 1)
+ return PASSWORD_SUCCESS;
+ // @TODO: Some queries don't affect any rows
+ // Should we assume a success if there was no error?
+ }
+ }
+
+ return PASSWORD_ERROR;
+ }
+}
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
new file mode 100644
index 000000000..5a9d9c0ca
--- /dev/null
+++ b/plugins/password/drivers/virtualmin.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Virtualmin Password Driver
+ *
+ * Driver that adds functionality to change the users Virtualmin password.
+ * The code is derrived from the Squirrelmail "Change Cyrus/SASL Password" Plugin
+ * by Thomas Bruederli.
+ *
+ * It only works with virtualmin on the same host where Roundcube runs
+ * and requires shell access and gcc in order to compile the binary.
+ *
+ * @version 3.0
+ * @author Martijn de Munnik
+ */
+
+class rcube_virtualmin_password
+{
+ function save($currpass, $newpass)
+ {
+ $rcmail = rcmail::get_instance();
+
+ $format = $rcmail->config->get('password_virtualmin_format', 0);
+ $username = $_SESSION['username'];
+
+ switch ($format) {
+ case 1: // username%domain
+ $domain = substr(strrchr($username, "%"), 1);
+ break;
+ case 2: // username.domain (could be bogus)
+ $pieces = explode(".", $username);
+ $domain = $pieces[count($pieces)-2]. "." . end($pieces);
+ break;
+ case 3: // domain.username (could be bogus)
+ $pieces = explode(".", $username);
+ $domain = $pieces[0]. "." . $pieces[1];
+ break;
+ case 4: // username-domain
+ $domain = substr(strrchr($username, "-"), 1);
+ break;
+ case 5: // domain-username
+ $domain = str_replace(strrchr($username, "-"), "", $username);
+ break;
+ case 6: // username_domain
+ $domain = substr(strrchr($username, "_"), 1);
+ break;
+ case 7: // domain_username
+ $pieces = explode("_", $username);
+ $domain = $pieces[0];
+ break;
+ default: // username@domain
+ $domain = substr(strrchr($username, "@"), 1);
+ }
+
+ $username = escapeshellcmd($username);
+ $domain = escapeshellcmd($domain);
+ $newpass = escapeshellcmd($newpass);
+ $curdir = realpath(dirname(__FILE__));
+
+ exec("$curdir/chgvirtualminpasswd modify-user --domain $domain --user $username --pass $newpass", $output, $returnvalue);
+
+ if ($returnvalue == 0) {
+ return PASSWORD_SUCCESS;
+ }
+ else {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: Unable to execute $curdir/chgvirtualminpasswd"
+ ), true, false);
+ }
+
+ return PASSWORD_ERROR;
+ }
+}
diff --git a/plugins/password/drivers/vpopmaild.php b/plugins/password/drivers/vpopmaild.php
new file mode 100644
index 000000000..510cf3338
--- /dev/null
+++ b/plugins/password/drivers/vpopmaild.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * vpopmail Password Driver
+ *
+ * Driver to change passwords via vpopmaild
+ *
+ * @version 2.0
+ * @author Johannes Hessellund
+ *
+ */
+
+class rcube_vpopmaild_password
+{
+ function save($curpass, $passwd)
+ {
+ $rcmail = rcmail::get_instance();
+ // include('Net/Socket.php');
+ $vpopmaild = new Net_Socket();
+
+ if (PEAR::isError($vpopmaild->connect($rcmail->config->get('password_vpopmaild_host'),
+ $rcmail->config->get('password_vpopmaild_port'), null))) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ $result = $vpopmaild->readLine();
+ if(!preg_match('/^\+OK/', $result)) {
+ $vpopmaild->disconnect();
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ $vpopmaild->writeLine("slogin ". $_SESSION['username'] . " " . $curpass);
+ $result = $vpopmaild->readLine();
+
+ if(!preg_match('/^\+OK/', $result) ) {
+ $vpopmaild->writeLine("quit");
+ $vpopmaild->disconnect();
+ return PASSWORD_ERROR;
+ }
+
+ $vpopmaild->writeLine("mod_user ". $_SESSION['username']);
+ $vpopmaild->writeLine("clear_text_password ". $passwd);
+ $vpopmaild->writeLine(".");
+ $result = $vpopmaild->readLine();
+ $vpopmaild->writeLine("quit");
+ $vpopmaild->disconnect();
+
+ if (!preg_match('/^\+OK/', $result))
+ return PASSWORD_ERROR;
+
+ return PASSWORD_SUCCESS;
+ }
+}
diff --git a/plugins/password/drivers/ximss.php b/plugins/password/drivers/ximss.php
new file mode 100644
index 000000000..3b5286a27
--- /dev/null
+++ b/plugins/password/drivers/ximss.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Communigate driver for the Password Plugin for Roundcube
+ *
+ * Tested with Communigate Pro 5.1.2
+ *
+ * Configuration options:
+ * password_ximss_host - Host name of Communigate server
+ * password_ximss_port - XIMSS port on Communigate server
+ *
+ *
+ * References:
+ * http://www.communigate.com/WebGuide/XMLAPI.html
+ *
+ * @version 2.0
+ * @author Erik Meitner <erik wanderings.us>
+ */
+
+class rcube_ximss_password
+{
+ function save($pass, $newpass)
+ {
+ $rcmail = rcmail::get_instance();
+
+ $host = $rcmail->config->get('password_ximss_host');
+ $port = $rcmail->config->get('password_ximss_port');
+ $sock = stream_socket_client("tcp://$host:$port", $errno, $errstr, 30);
+
+ if ($sock === FALSE) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+
+ // send all requests at once(pipelined)
+ fwrite( $sock, '<login id="A001" authData="'.$_SESSION['username'].'" password="'.$pass.'" />'."\0");
+ fwrite( $sock, '<passwordModify id="A002" oldPassword="'.$pass.'" newPassword="'.$newpass.'" />'."\0");
+ fwrite( $sock, '<bye id="A003" />'."\0");
+
+ //example responses
+ // <session id="A001" urlID="4815-vN2Txjkggy7gjHRD10jw" userName="user@example.com"/>\0
+ // <response id="A001"/>\0
+ // <response id="A002"/>\0
+ // <response id="A003"/>\0
+ // or an error:
+ // <response id="A001" errorText="incorrect password or account name" errorNum="515"/>\0
+
+ $responseblob = '';
+ while (!feof($sock)) {
+ $responseblob .= fgets($sock, 1024);
+ }
+
+ fclose($sock);
+
+ foreach( explode( "\0",$responseblob) as $response ) {
+ $resp = simplexml_load_string("<xml>".$response."</xml>");
+
+ if( $resp->response[0]['id'] == 'A001' ) {
+ if( isset( $resp->response[0]['errorNum'] ) ) {
+ return PASSWORD_CONNECT_ERROR;
+ }
+ }
+ else if( $resp->response[0]['id'] == 'A002' ) {
+ if( isset( $resp->response[0]['errorNum'] )) {
+ return PASSWORD_ERROR;
+ }
+ }
+ else if( $resp->response[0]['id'] == 'A003' ) {
+ if( isset($resp->response[0]['errorNum'] )) {
+ //There was a problem during logout(This is probably harmless)
+ }
+ }
+ } //foreach
+
+ return PASSWORD_SUCCESS;
+
+ }
+}
diff --git a/plugins/password/drivers/xmail.php b/plugins/password/drivers/xmail.php
new file mode 100644
index 000000000..33a49ffe3
--- /dev/null
+++ b/plugins/password/drivers/xmail.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * XMail Password Driver
+ *
+ * Driver for XMail password
+ *
+ * @version 2.0
+ * @author Helio Cavichiolo Jr <helio@hcsistemas.com.br>
+ *
+ * Setup xmail_host, xmail_user, xmail_pass and xmail_port into
+ * config.inc.php of password plugin as follows:
+ *
+ * $rcmail_config['xmail_host'] = 'localhost';
+ * $rcmail_config['xmail_user'] = 'YourXmailControlUser';
+ * $rcmail_config['xmail_pass'] = 'YourXmailControlPass';
+ * $rcmail_config['xmail_port'] = 6017;
+ *
+ */
+
+class rcube_xmail_password
+{
+ function save($currpass, $newpass)
+ {
+ $rcmail = rcmail::get_instance();
+ list($user,$domain) = explode('@', $_SESSION['username']);
+
+ $xmail = new XMail;
+
+ $xmail->hostname = $rcmail->config->get('xmail_host');
+ $xmail->username = $rcmail->config->get('xmail_user');
+ $xmail->password = $rcmail->config->get('xmail_pass');
+ $xmail->port = $rcmail->config->get('xmail_port');
+
+ if (!$xmail->connect()) {
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: Unable to connect to mail server"
+ ), true, false);
+ return PASSWORD_CONNECT_ERROR;
+ }
+ else if (!$xmail->send("userpasswd\t".$domain."\t".$user."\t".$newpass."\n")) {
+ $xmail->close();
+ raise_error(array(
+ 'code' => 600,
+ 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Password plugin: Unable to change password"
+ ), true, false);
+ return PASSWORD_ERROR;
+ }
+ else {
+ $xmail->close();
+ return PASSWORD_SUCCESS;
+ }
+ }
+}
+
+class XMail {
+ var $socket;
+ var $hostname = 'localhost';
+ var $username = 'xmail';
+ var $password = '';
+ var $port = 6017;
+
+ function send($msg)
+ {
+ socket_write($this->socket,$msg);
+ if (substr($in = socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
+ return false;
+ }
+ return true;
+ }
+
+ function connect()
+ {
+ $this->socket = socket_create(AF_INET, SOCK_STREAM, 0);
+ if ($this->socket < 0)
+ return false;
+
+ $result = socket_connect($this->socket, $this->hostname, $this->port);
+ if ($result < 0) {
+ socket_close($this->socket);
+ return false;
+ }
+
+ if (substr($in = socket_read($this->socket, 512, PHP_BINARY_READ),0,1) != "+") {
+ socket_close($this->socket);
+ return false;
+ }
+
+ if (!$this->send("$this->username\t$this->password\n")) {
+ socket_close($this->socket);
+ return false;
+ }
+ return true;
+ }
+
+ function close()
+ {
+ $this->send("quit\n");
+ socket_close($this->socket);
+ }
+}
+