alecpl
2011-02-16 207cc0b9b3cfdfb29e4f02e83014320fd12eeb68
- Applied plugin changes since 0.5-stable release


3 files added
40 files modified
1236 ■■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
plugins/archive/archive.php 2 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/autologon/autologon.php 2 ●●●●● patch | view | raw | blame | history
plugins/help/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/help/localization/ru_RU.inc 2 ●●● patch | view | raw | blame | history
plugins/http_authentication/http_authentication.php 3 ●●●● patch | view | raw | blame | history
plugins/managesieve/Changelog 9 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/Net/Sieve.php 55 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/rcube_sieve.php 661 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/es_ES.inc 2 ●●● patch | view | raw | blame | history
plugins/managesieve/localization/fr_FR.inc 2 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/ru_RU.inc 12 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.js 2 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.php 4 ●●●● patch | view | raw | blame | history
plugins/managesieve/tests/Makefile 7 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser.phpt 103 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/tokenize.phpt 66 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/markasjunk/markasjunk.php 4 ●●● patch | view | raw | blame | history
plugins/markasjunk/package.xml 1 ●●●● patch | view | raw | blame | history
plugins/new_user_identity/new_user_identity.php 2 ●●● patch | view | raw | blame | history
plugins/password/README 2 ●●● patch | view | raw | blame | history
plugins/password/config.inc.php.dist 27 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/directadmin.php 12 ●●●● patch | view | raw | blame | history
plugins/password/drivers/ldap.php 57 ●●●● patch | view | raw | blame | history
plugins/password/drivers/ldap_simple.php 72 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/virtualmin.php 43 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/xmail.php 2 ●●● patch | view | raw | blame | history
plugins/password/localization/es_ES.inc 22 ●●●● patch | view | raw | blame | history
plugins/password/localization/ru_RU.inc 2 ●●● patch | view | raw | blame | history
plugins/password/package.xml 10 ●●●● patch | view | raw | blame | history
plugins/password/password.js 6 ●●●●● patch | view | raw | blame | history
plugins/password/password.php 10 ●●●●● patch | view | raw | blame | history
plugins/show_additional_headers/show_additional_headers.php 2 ●●● patch | view | raw | blame | history
plugins/squirrelmail_usercopy/squirrelmail_usercopy.php 4 ●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/userinfo/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/cs_CZ.inc 2 ●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/es_ES.inc 4 ●●●● patch | view | raw | blame | history
plugins/vcard_attachments/package.xml 1 ●●●● patch | view | raw | blame | history
plugins/virtuser_file/virtuser_file.php 2 ●●● patch | view | raw | blame | history
plugins/virtuser_query/virtuser_query.php 6 ●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Applied plugin changes since 0.5-stable release
- Fix SQL query in rcube_user::query() so it uses index on MySQL again
- Use only one from IMAP authentication methods to prevent login delays (1487784)
- Fix strftime format support in date_today option
plugins/archive/archive.php
@@ -30,6 +30,8 @@
            'command' => 'plugin.archive',
            'imagepas' => $skin_path.'/archive_pas.png',
            'imageact' => $skin_path.'/archive_act.png',
            'width' => 32,
            'height' => 32,
            'title' => 'buttontitle',
            'domain' => $this->ID,
        ),
plugins/archive/localization/cs_CZ.inc
@@ -6,7 +6,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube archive plugin                         |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2009, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/autologon/autologon.php
@@ -31,6 +31,8 @@
      $args['user'] = 'me';
      $args['pass'] = '******';
      $args['host'] = 'localhost';
      $args['cookiecheck'] = false;
      $args['valid'] = true;
    }
  
    return $args;
plugins/help/localization/cs_CZ.inc
@@ -6,7 +6,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube help plugin                            |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2009, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/help/localization/ru_RU.inc
@@ -6,7 +6,7 @@
| plugins/help/localization/ru_RU.inc                                   |
|                                                                       |
| Language file of the Roundcube help plugin                            |
| Copyright (C) 2005-2010, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2010, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/http_authentication/http_authentication.php
@@ -5,7 +5,7 @@
 *
 * Make use of an existing HTTP authentication and perform login with the existing user credentials
 *
 * @version 1.1
 * @version 1.2
 * @author Thomas Bruederli
 */
class http_authentication extends rcube_plugin
@@ -36,6 +36,7 @@
    }
    
    $args['cookiecheck'] = false;
    $args['valid'] = true;
  
    return $args;
  }
plugins/managesieve/Changelog
@@ -1,6 +1,15 @@
- Fix escaping of backslash character in quoted strings (#1487780)
- Fix STARTTLS for timsieved < 2.3.10
* version 3.0 [2011-02-01]
-----------------------------------------------------------
- Added support for SASL proxy authentication (#1486691)
- Fixed parsing of scripts with \r\n line separator
- Apply forgotten changes for form errors handling
- Fix multi-line strings parsing (#1487685)
- Added tests for script parser
- Rewritten script parser
- Fix double request when clicking on Filters tab using Firefox
* version 2.10 [2010-10-10]
-----------------------------------------------------------
plugins/managesieve/lib/Net/Sieve.php
@@ -763,7 +763,7 @@
            return $res;
        }
        return preg_replace('/{[0-9]+}\r\n/', '', $res);
        return preg_replace('/^{[0-9]+}\r\n/', '', $res);
    }
    /**
@@ -981,6 +981,28 @@
    }
    /**
     * Receives x bytes from the server.
     *
     * @param int $length  Number of bytes to read
     *
     * @return string  The server response.
     */
    function _recvBytes($length)
    {
        $response = '';
        $response_length = 0;
        while ($response_length < $length) {
            $response .= $this->_sock->read($length - $response_length);
            $response_length = $this->_getLineLength($response);
        }
        $this->_debug("S: " . rtrim($response));
        return $response;
    }
    /**
     * Send a command and retrieves a response from the server.
     *
     * @param string $cmd   The command to send.
@@ -1013,11 +1035,11 @@
                if ('NO' == substr($uc_line, 0, 2)) {
                    // Check for string literal error message.
                    if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) {
                        $line .= str_replace(
                            "\r\n", ' ', $this->_sock->read($matches[1] + 2)
                        );
                        $this->_debug("S: $line");
                    if (preg_match('/{([0-9]+)}$/i', $line, $matches)) {
                        $line = substr($line, 0, -(strlen($matches[1])+2))
                            . str_replace(
                                "\r\n", ' ', $this->_recvBytes($matches[1] + 2)
                            );
                    }
                    return PEAR::raiseError(trim($response . substr($line, 2)), 3);
                }
@@ -1052,16 +1074,9 @@
                    return PEAR::raiseError(trim($response . $line), 6);
                }
                if (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
                    // Matches String Responses.
                    $str_size = $matches[1] + 2;
                    $line = '';
                    $line_length = 0;
                    while ($line_length < $str_size) {
                        $line .= $this->_sock->read($str_size - $line_length);
                        $line_length = $this->_getLineLength($line);
                    }
                    $this->_debug("S: $line");
                if (preg_match('/^{([0-9]+)}/i', $line, $matches)) {
                    // Matches literal string responses.
                    $line = $this->_recvBytes($matches[1] + 2);
                    if (!$auth) {
                        // Receive the pending OK only if we aren't
@@ -1146,7 +1161,13 @@
        // The server should be sending a CAPABILITY response after
        // negotiating TLS. Read it, and ignore if it doesn't.
        $this->_doCmd();
        // Doesn't work with older timsieved versions
        $regexp = '/^CYRUS TIMSIEVED V([0-9.]+)/';
        if (!preg_match($regexp, $this->_capability['implementation'], $matches)
            || version_compare($matches[1], '2.3.10', '>=')
        ) {
            $this->_doCmd();
        }
        // RFC says we need to query the server capabilities again now that we
        // are under encryption.
plugins/managesieve/lib/rcube_sieve.php
@@ -1,6 +1,6 @@
<?php
/*
/**
  Classes for managesieve operations (using PEAR::Net_Sieve)
  Author: Aleksander Machniak <alec@alec.pl>
@@ -57,7 +57,7 @@
            $this->sieve->setDebug(true, array($this, 'debug_handler'));
        }
        if (PEAR::isError($this->sieve->connect($host, $port, NULL, $usetls))) {
        if (PEAR::isError($this->sieve->connect($host, $port, null, $usetls))) {
            return $this->_set_error(SIEVE_ERROR_CONNECTION);
        }
@@ -414,12 +414,17 @@
     * @param  string  Script's text content
     * @param  array   Disabled extensions
     */
    public function __construct($script, $disabled=NULL)
    public function __construct($script, $disabled=null)
    {
        if (!empty($disabled))
            foreach ($disabled as $ext)
                if (($idx = array_search($ext, $this->supported)) !== false)
        if (!empty($disabled)) {
            // we're working on lower-cased names
            $disabled = array_map('strtolower', (array) $disabled);
            foreach ($disabled as $ext) {
                if (($idx = array_search($ext, $this->supported)) !== false) {
                    unset($this->supported[$idx]);
                }
            }
        }
        $this->content = $this->_parse_text($script);
    }
@@ -513,14 +518,11 @@
                    $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
                    break;
                case 'true':
                    $tests[$i] .= ($test['not'] ? 'not true' : 'true');
                    $tests[$i] .= ($test['not'] ? 'false' : 'true');
                    break;
                case 'exists':
                    $tests[$i] .= ($test['not'] ? 'not ' : '');
                    if (is_array($test['arg']))
                        $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]';
                    else
                        $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"';
                    $tests[$i] .= 'exists ' . self::escape_string($test['arg']);
                    break;
                case 'header':
                    $tests[$i] .= ($test['not'] ? 'not ' : '');
@@ -533,33 +535,34 @@
                    }
                    else
                        $tests[$i] .= 'header :' . $test['type'];
                    if (is_array($test['arg1']))
                        $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]';
                    else
                        $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"';
                    if (is_array($test['arg2']))
                        $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]';
                    else
                        $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"';
                    $tests[$i] .= ' ' . self::escape_string($test['arg1']);
                    $tests[$i] .= ' ' . self::escape_string($test['arg2']);
                    break;
                }
                $i++;
            }
//          $script .= ($idx>0 ? 'els' : '').($rule['join'] ? 'if allof (' : 'if anyof (');
            // disabled rule: if false #....
            $script .= 'if' . ($rule['disabled'] ? ' false #' : '');
            $script .= $rule['join'] ? ' allof (' : ' anyof (';
            if (sizeof($tests) > 1)
                $script .= implode(", ", $tests);
            else if (sizeof($tests))
                $script .= $tests[0];
            else
                $script .= 'true';
            $script .= ")\n{\n";
            $script .= 'if ' . ($rule['disabled'] ? 'false # ' : '');
            if (empty($tests)) {
                $tests_str = 'true';
            }
            else if (count($tests) > 1) {
                $tests_str = implode(', ', $tests);
            }
            else {
                $tests_str = $tests[0];
            }
            if ($rule['join'] || count($tests) > 1) {
                $script .= sprintf('%s (%s)', $rule['join'] ? 'allof' : 'anyof', $tests_str);
            }
            else {
                $script .= $tests_str;
            }
            $script .= "\n{\n";
            // action(s)
            foreach ($rule['actions'] as $action) {
@@ -571,7 +574,7 @@
                        $script .= ':copy ';
                        array_push($exts, 'copy');
                    }
                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
                    $script .= self::escape_string($action['target']) . ";\n";
                    break;
                case 'redirect':
                    $script .= "\tredirect ";
@@ -579,15 +582,13 @@
                        $script .= ':copy ';
                        array_push($exts, 'copy');
                    }
                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
                    $script .= self::escape_string($action['target']) . ";\n";
                    break;
                case 'reject':
                case 'ereject':
                    array_push($exts, $action['type']);
                    if (strpos($action['target'], "\n")!==false)
                        $script .= "\t".$action['type']." text:\n" . $action['target'] . "\n.\n;\n";
                    else
                        $script .= "\t".$action['type']." \"" . $this->_escape_string($action['target']) . "\";\n";
                    $script .= "\t".$action['type']." "
                        . self::escape_string($action['target']) . ";\n";
                    break;
                case 'keep':
                case 'discard':
@@ -597,22 +598,19 @@
                case 'vacation':
                    array_push($exts, 'vacation');
                    $script .= "\tvacation";
                    if ($action['days'])
                    if (!empty($action['days']))
                        $script .= " :days " . $action['days'];
                    if ($action['addresses'])
                        $script .= " :addresses " . $this->_print_list($action['addresses']);
                    if ($action['subject'])
                        $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\"";
                    if ($action['handle'])
                        $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\"";
                    if ($action['from'])
                        $script .= " :from \"" . $this->_escape_string($action['from']) . "\"";
                    if ($action['mime'])
                    if (!empty($action['addresses']))
                        $script .= " :addresses " . self::escape_string($action['addresses']);
                    if (!empty($action['subject']))
                        $script .= " :subject " . self::escape_string($action['subject']);
                    if (!empty($action['handle']))
                        $script .= " :handle " . self::escape_string($action['handle']);
                    if (!empty($action['from']))
                        $script .= " :from " . self::escape_string($action['from']);
                    if (!empty($action['mime']))
                        $script .= " :mime";
                    if (strpos($action['reason'], "\n")!==false)
                        $script .= " text:\n" . $action['reason'] . "\n.\n;\n";
                    else
                        $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n";
                    $script .= " " . self::escape_string($action['reason']) . ";\n";
                    break;
                }
            }
@@ -656,9 +654,6 @@
        $i = 0;
        $content = array();
        // remove C comments
        $script = preg_replace('|/\*.*?\*/|sm', '', $script);
        // tokenize rules
        if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
            foreach($tokens as $token) {
@@ -686,31 +681,118 @@
     */
    private function _tokenize_rule($content)
    {
        $result = NULL;
        $cond = strtolower(self::tokenize($content, 1));
        if (preg_match('/^(if|elsif|else)\s+((true|false|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm',
            trim($content), $matches)) {
        if ($cond != 'if' && $cond != 'elsif' && $cond != 'else') {
            return null;
        }
            $tests = trim($matches[2]);
        $disabled = false;
        $join     = false;
            // disabled rule (false + comment): if false #.....
            if ($matches[3] == 'false') {
                $tests = preg_replace('/^false\s+#\s+/', '', $tests);
                $disabled = true;
        // disabled rule (false + comment): if false # .....
        if (preg_match('/^\s*false\s+#/i', $content)) {
            $content = preg_replace('/^\s*false\s+#\s*/i', '', $content);
            $disabled = true;
        }
        while (strlen($content)) {
            $tokens = self::tokenize($content, true);
            $separator = array_pop($tokens);
            if (!empty($tokens)) {
                $token = array_shift($tokens);
            }
            else
                $disabled = false;
            else {
                $token = $separator;
            }
            list($tests, $join) = $this->_parse_tests($tests);
            $actions = $this->_parse_actions(trim($matches[5]));
            $token = strtolower($token);
            if ($tests && $actions)
                $result = array(
                    'type'     => $matches[1],
                    'tests'    => $tests,
                    'actions'  => $actions,
                    'join'     => $join,
                    'disabled' => $disabled,
            if ($token == 'not') {
                $not = true;
                $token = strtolower(array_shift($tokens));
            }
            else {
                $not = false;
            }
            switch ($token) {
            case 'allof':
                $join = true;
                break;
            case 'anyof':
                break;
            case 'size':
                $size = array('test' => 'size', 'not'  => $not);
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (!is_array($tokens[$i])
                        && preg_match('/^:(under|over)$/i', $tokens[$i])
                    ) {
                        $size['type'] = strtolower(substr($tokens[$i], 1));
                    }
                    else {
                        $size['arg'] = $tokens[$i];
                    }
                }
                $tests[] = $size;
                break;
            case 'header':
                $header = array('test' => 'header', 'not' => $not, 'arg1' => '', 'arg2' => '');
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) {
                        $i++;
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(count|value)$/i', $tokens[$i])) {
                        $header['type'] = strtolower(substr($tokens[$i], 1)) . '-' . $tokens[++$i];
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches)$/i', $tokens[$i])) {
                        $header['type'] = strtolower(substr($tokens[$i], 1));
                    }
                    else {
                        $header['arg1'] = $header['arg2'];
                        $header['arg2'] = $tokens[$i];
                    }
                }
                $tests[] = $header;
                break;
            case 'exists':
                $tests[] = array('test' => 'exists', 'not'  => $not,
                    'arg'  => array_pop($tokens));
                break;
            case 'true':
                $tests[] = array('test' => 'true', 'not'  => $not);
                break;
            case 'false':
                $tests[] = array('test' => 'true', 'not'  => !$not);
                break;
            }
            // goto actions...
            if ($separator == '{') {
                break;
            }
        }
        // ...and actions block
        if ($tests) {
            $actions = $this->_parse_actions($content);
        }
        if ($tests && $actions) {
            $result = array(
                'type'     => $cond,
                'tests'    => $tests,
                'actions'  => $actions,
                'join'     => $join,
                'disabled' => $disabled,
            );
        }
@@ -725,94 +807,76 @@
     */
    private function _parse_actions($content)
    {
        $result = NULL;
        $result = null;
        // supported actions
        $patterns[] = '^\s*discard;';
        $patterns[] = '^\s*keep;';
        $patterns[] = '^\s*stop;';
        $patterns[] = '^\s*redirect\s+(.*?[^\\\]);';
        if (in_array('fileinto', $this->supported))
            $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);';
        if (in_array('reject', $this->supported)) {
            $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;';
            $patterns[] = '^\s*reject\s+(.*?[^\\\]);';
            $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;';
            $patterns[] = '^\s*ereject\s+(.*?[^\\\]);';
        }
        if (in_array('vacation', $this->supported))
            $patterns[] = '^\s*vacation\s+(.*?[^\\\]);';
        while (strlen($content)) {
            $tokens = self::tokenize($content, true);
            $separator = array_pop($tokens);
        $pattern = '/(' . implode('\s*$)|(', $patterns) . '$\s*)/ms';
            if (!empty($tokens)) {
                $token = array_shift($tokens);
            }
            else {
                $token = $separator;
            }
        // parse actions body
        if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) {
            foreach ($mm as $m) {
                $content = trim($m[0]);
            switch ($token) {
            case 'discard':
            case 'keep':
            case 'stop':
                $result[] = array('type' => $token);
                break;
                if(preg_match('/^(discard|keep|stop)/', $content, $matches)) {
                    $result[] = array('type' => $matches[1]);
                }
                else if(preg_match('/^fileinto/', $content)) {
                    $target = $m[sizeof($m)-1];
                    $copy = false;
                    if (preg_match('/^:copy\s+/', $target)) {
                        $target = preg_replace('/^:copy\s+/', '', $target);
            case 'fileinto':
            case 'redirect':
                $copy   = false;
                $target = '';
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (strtolower($tokens[$i]) == ':copy') {
                        $copy = true;
                    }
                    $result[] = array('type' => 'fileinto', 'copy' => $copy,
                        'target' => $this->_parse_string($target));
                    else {
                        $target = $tokens[$i];
                    }
                }
                else if(preg_match('/^redirect/', $content)) {
                    $target = $m[sizeof($m)-1];
                    $copy = false;
                    if (preg_match('/^:copy\s+/', $target)) {
                        $target = preg_replace('/^:copy\s+/', '', $target);
                        $copy = true;
                    }
                    $result[] = array('type' => 'redirect', 'copy' => $copy,
                        'target' => $this->_parse_string($target));
                }
                else if(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) {
                    $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2]));
                }
                else if(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) {
                    $vacation = array('type' => 'vacation');
                    if (preg_match('/:days\s+([0-9]+)/', $content, $vm)) {
                        $vacation['days'] = $vm[1];
                        $content = preg_replace('/:days\s+([0-9]+)/', '', $content);
                    }
                    if (preg_match('/:subject\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['subject'] = $vm[1];
                        $content = preg_replace('/:subject\s+"(.*?[^\\\])"/', '', $content);
                    }
                    if (preg_match('/:addresses\s+\[(.*?[^\\\])\]/', $content, $vm)) {
                        $vacation['addresses'] = $this->_parse_list($vm[1]);
                        $content = preg_replace('/:addresses\s+\[(.*?[^\\\])\]/', '', $content);
                    }
                    if (preg_match('/:handle\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['handle'] = $vm[1];
                        $content = preg_replace('/:handle\s+"(.*?[^\\\])"/', '', $content);
                    }
                    if (preg_match('/:from\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['from'] = $vm[1];
                        $content = preg_replace('/:from\s+"(.*?[^\\\])"/', '', $content);
                    }
                $result[] = array('type' => $token, 'copy' => $copy,
                    'target' => $target);
                break;
                    $content = preg_replace('/^vacation/', '', $content);
                    $content = preg_replace('/;$/', '', $content);
                    $content = trim($content);
            case 'reject':
            case 'ereject':
                $result[] = array('type' => $token, 'target' => array_pop($tokens));
                break;
                    if (preg_match('/^:mime/', $content, $vm)) {
            case 'vacation':
                $vacation = array('type' => 'vacation', 'reason' => array_pop($tokens));
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    $tok = strtolower($tokens[$i]);
                    if ($tok == ':days') {
                        $vacation['days'] = $tokens[++$i];
                    }
                    else if ($tok == ':subject') {
                        $vacation['subject'] = $tokens[++$i];
                    }
                    else if ($tok == ':addresses') {
                        $vacation['addresses'] = $tokens[++$i];
                    }
                    else if ($tok == ':handle') {
                        $vacation['handle'] = $tokens[++$i];
                    }
                    else if ($tok == ':from') {
                        $vacation['from'] = $tokens[++$i];
                    }
                    else if ($tok == ':mime') {
                        $vacation['mime'] = true;
                        $content = preg_replace('/^:mime/', '', $content);
                    }
                    $vacation['reason'] = $this->_parse_string($content);
                    $result[] = $vacation;
                }
                $result[] = $vacation;
                break;
            }
        }
@@ -820,171 +884,196 @@
    }
    /**
     * Parse test/conditions section
     * Escape special chars into quoted string value or multi-line string
     * or list of strings
     *
     * @param string Text
     * @param string $str Text or array (list) of strings
     *
     * @return string Result text
     */
    private function _parse_tests($content)
    static function escape_string($str)
    {
        $result = NULL;
        if (is_array($str) && count($str) > 1) {
            foreach($str as $idx => $val)
                $str[$idx] = self::escape_string($val);
        // lists
        if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches)) {
            $content = $matches[2];
            $join = $matches[1]=='allof' ? true : false;
            return '[' . implode(',', $str) . ']';
        }
        else
            $join = false;
        else if (is_array($str)) {
            $str = array_pop($str);
        }
        // supported tests regular expressions
        // TODO: comparators, envelope
        $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]';
        $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(true)';
        $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)((\s+))\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)((\s+))(".*?[^\\\]")\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)((\s+))\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)((\s+))(".*?[^\\\]")\s+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]';
        // multi-line string
        if (preg_match('/[\r\n\0]/', $str) || strlen($str) > 1024) {
            return sprintf("text:\n%s\n.\n", self::escape_multiline_string($str));
        }
        // quoted-string
        else {
            $replace = array('\\' => '\\\\', '"' => '\\"');
            $str = str_replace(array_keys($replace), array_values($replace), $str);
            return '"' . $str . '"';
        }
    }
        // join patterns...
        $pattern = '/(' . implode(')|(', $patterns) . ')/';
    /**
     * Escape special chars in multi-line string value
     *
     * @param string $str Text
     *
     * @return string Text
     */
    static function escape_multiline_string($str)
    {
        $str = preg_split('/(\r?\n)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
        // ...and parse tests list
        if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $size = sizeof($match);
                if (preg_match('/^(not\s+)?size/', $match[0])) {
                    $result[] = array(
                        'test' => 'size',
                        'not'  => $match[$size-4] ? true : false,
                        'type' => $match[$size-2], // under/over
                        'arg'  => $match[$size-1], // value
                    );
                }
                else if (preg_match('/^(not\s+)?header/', $match[0])) {
                    $type = $match[$size-5];
                    if (preg_match('/^(count|value)\s+"([gtleqn]{2})"/', $type, $m))
                        $type = $m[1] . '-' . $m[2];
                    $result[] = array(
                        'test' => 'header',
                        'type' => $type, // is/contains/matches
                        'not'  => $match[$size-7] ? true : false,
                        'arg1' => $this->_parse_list($match[$size-2]), // header(s)
                        'arg2' => $this->_parse_list($match[$size-1]), // string(s)
                    );
                }
                else if (preg_match('/^(not\s+)?exists/', $match[0])) {
                    $result[] = array(
                        'test' => 'exists',
                        'not'  => $match[$size-3] ? true : false,
                        'arg'  => $this->_parse_list($match[$size-1]), // header(s)
                    );
                }
                else if (preg_match('/^(not\s+)?true/', $match[0])) {
                    $result[] = array(
                        'test' => 'true',
                        'not'  => $match[$size-2] ? true : false,
                    );
                }
        foreach ($str as $idx => $line) {
            // dot-stuffing
            if (isset($line[0]) && $line[0] == '.') {
                $str[$idx] = '.' . $line;
            }
        }
        return array($result, $join);
        return implode($str);
    }
    /**
     * Parse string value
     * Splits script into string tokens
     *
     * @param string Text
     */
    private function _parse_string($content)
    {
        $text = '';
        $content = trim($content);
        if (preg_match('/^text:(.*)\.$/sm', $content, $matches))
            $text = trim($matches[1]);
        else if (preg_match('/^"(.*)"$/', $content, $matches))
            $text = str_replace('\"', '"', $matches[1]);
        return $text;
    }
    /**
     * Escape special chars in string value
     * @param string &$str    The script
     * @param mixed  $num     Number of tokens to return, 0 for all
     *                        or True for all tokens until separator is found.
     *                        Separator will be returned as last token.
     * @param int    $in_list Enable to called recursively inside a list
     *
     * @param string Text
     * @return mixed Tokens array or string if $num=1
     */
    private function _escape_string($content)
    {
        $replace['/"/'] = '\\"';
        if (is_array($content)) {
            for ($x=0, $y=sizeof($content); $x<$y; $x++)
                $content[$x] = preg_replace(array_keys($replace),
                    array_values($replace), $content[$x]);
            return $content;
        }
        else
            return preg_replace(array_keys($replace), array_values($replace), $content);
    }
    /**
     * Parse string or list of strings to string or array of strings
     *
     * @param string Text
     */
    private function _parse_list($content)
    static function tokenize(&$str, $num=0, $in_list=false)
    {
        $result = array();
        for ($x=0, $len=strlen($content); $x<$len; $x++) {
            switch ($content[$x]) {
            case '\\':
                $str .= $content[++$x];
                break;
        // remove spaces from the beginning of the string
        while (($str = ltrim($str)) !== ''
            && (!$num || $num === true || count($result) < $num)
        ) {
            switch ($str[0]) {
            // Quoted string
            case '"':
                if (isset($str)) {
                    $result[] = $str;
                    unset($str);
                $len = strlen($str);
                for ($pos=1; $pos<$len; $pos++) {
                    if ($str[$pos] == '"') {
                        break;
                    }
                    if ($str[$pos] == "\\") {
                        if ($str[$pos + 1] == '"' || $str[$pos + 1] == "\\") {
                            $pos++;
                        }
                    }
                }
                else
                    $str = '';
                if ($str[$pos] != '"') {
                    // error
                }
                // we need to strip slashes for a quoted string
                $result[] = stripslashes(substr($str, 1, $pos - 1));
                $str      = substr($str, $pos + 1);
                break;
            // Parenthesized list
            case '[':
                $str = substr($str, 1);
                $result[] = self::tokenize($str, 0, true);
                break;
            case ']':
                $str = substr($str, 1);
                return $result;
                break;
            // list/test separator
            case ',':
            // command separator
            case ';':
            // block/tests-list
            case '(':
            case ')':
            case '{':
            case '}':
                $sep = $str[0];
                $str = substr($str, 1);
                if ($num === true) {
                    $result[] = $sep;
                    break 2;
                }
                break;
            // bracket-comment
            case '/':
                if ($str[1] == '*') {
                    if ($end_pos = strpos($str, '*/')) {
                        $str = substr($str, $end_pos + 2);
                    }
                    else {
                        // error
                        $str = '';
                    }
                }
                break;
            // hash-comment
            case '#':
                if ($lf_pos = strpos($str, "\n")) {
                    $str = substr($str, $lf_pos);
                    break;
                }
                else {
                    $str = '';
                }
            // String atom
            default:
                if(isset($str))
                    $str .= $content[$x];
            break;
                // empty or one character
                if ($str === '') {
                    break 2;
                }
                if (strlen($str) < 2) {
                    $result[] = $str;
                    $str = '';
                    break;
                }
                // tag/identifier/number
                if (preg_match('/^([a-z0-9:_]+)/i', $str, $m)) {
                    $str = substr($str, strlen($m[1]));
                    if ($m[1] != 'text:') {
                        $result[] = $m[1];
                    }
                    // multiline string
                    else {
                        // possible hash-comment after "text:"
                        if (preg_match('/^( |\t)*(#[^\n]+)?\n/', $str, $m)) {
                            $str = substr($str, strlen($m[0]));
                        }
                        // get text until alone dot in a line
                        if (preg_match('/^(.*)\r?\n\.\r?\n/sU', $str, $m)) {
                            $text = $m[1];
                            // remove dot-stuffing
                            $text = str_replace("\n..", "\n.", $text);
                            $str = substr($str, strlen($m[0]));
                        }
                        else {
                            $text = '';
                        }
                        $result[] = $text;
                    }
                }
                break;
            }
        }
        if (sizeof($result)>1)
            return $result;
        else if (sizeof($result) == 1)
            return $result[0];
        else
            return NULL;
        return $num === 1 ? (isset($result[0]) ? $result[0] : null) : $result;
    }
    /**
     * Convert array of elements to list of strings
     *
     * @param string Text
     */
    private function _print_list($list)
    {
        $list = (array) $list;
        foreach($list as $idx => $val)
            $list[$idx] = $this->_escape_string($val);
        return '["' . implode('","', $list) . '"]';
    }
}
plugins/managesieve/localization/es_ES.inc
@@ -9,7 +9,7 @@
$labels['filterdel'] = 'Eliminar filtro';
$labels['moveup'] = 'Mover arriba';
$labels['movedown'] = 'Mover abajo';
$labels['filterallof'] = 'coinidir con todas las reglas siguientes';
$labels['filterallof'] = 'coincidir con todas las reglas siguientes';
$labels['filteranyof'] = 'coincidir con alguna de las reglas siguientes';
$labels['filterany'] = 'todos los mensajes';
$labels['filtercontains'] = 'contiene';
plugins/managesieve/localization/fr_FR.inc
@@ -27,6 +27,8 @@
$labels['messagedelete'] = 'Supprimer le message';
$labels['messagediscard'] = 'Rejeter avec le message';
$labels['messagecopyto'] = 'Copier le message vers';
$labels['messagesendcopy'] = 'Envoyer une copie du message Ã ';
$labels['messagecopyto'] = 'Copier le message vers';
$labels['messagesendcopy'] = 'Envoyer une copie du message à';
$labels['messagesrules'] = 'Pour les mails entrants:';
$labels['messagesactions'] = '...exécuter les actions suivantes:';
plugins/managesieve/localization/ru_RU.inc
@@ -76,6 +76,18 @@
$labels['countequals'] = 'количество равно';
$labels['countnotequals'] = 'количество не равно';
$labels['valueisgreaterthan'] = 'значение больше, чем';
$labels['countisgreaterthan'] = 'кПлОчествП Ð±ÐŸÐ»ÑŒÑˆÐµ, Ñ‡ÐµÐŒ';
$labels['countisgreaterthanequal'] = 'кПлОчествП Ð±ÐŸÐ»ÑŒÑˆÐµ ÐžÐ»Ðž Ñ€Ð°Ð²ÐœÐŸ';
$labels['countislessthan'] = 'кПлОчествП ÐŒÐµÐœÑŒÑˆÐµ, Ñ‡ÐµÐŒ';
$labels['countislessthanequal'] = 'кПлОчествП ÐŒÐµÐœÑŒÑˆÐµ ÐžÐ»Ðž Ñ€Ð°Ð²ÐœÐŸ';
$labels['countequals'] = 'кПлОчествП Ñ€Ð°Ð²ÐœÐŸ';
$labels['countnotequals'] = 'кПлОчествП ÐœÐµ Ñ€Ð°Ð²ÐœÐŸ';
$labels['valueisgreaterthan'] = 'зМачеМОе Ð±ÐŸÐ»ÑŒÑˆÐµ, Ñ‡ÐµÐŒ';
$labels['valueisgreaterthanequal'] = 'зМачеМОе Ð±ÐŸÐ»ÑŒÑˆÐµ ÐžÐ»Ðž Ñ€Ð°Ð²ÐœÐŸ';
$labels['valueislessthan'] = 'зМачеМОе ÐŒÐµÐœÑŒÑˆÐµ, Ñ‡ÐµÐŒ';
$labels['valueislessthanequal'] = 'зМачеМОе ÐŒÐµÐœÑŒÑˆÐµ ÐžÐ»Ðž Ñ€Ð°Ð²ÐœÐŸ';
$labels['valueequals'] = 'зМачеМОе Ñ€Ð°Ð²ÐœÐŸ';
$labels['valuenotequals'] = 'зМачеМОе ÐœÐµ Ñ€Ð°Ð²ÐœÐŸ';
$labels['valueisgreaterthanequal'] = 'значение больше или равно';
$labels['valueislessthan'] = 'значение меньше, чем';
$labels['valueislessthanequal'] = 'значение меньше или равно';
plugins/managesieve/managesieve.js
@@ -7,12 +7,10 @@
    var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.managesieve')
      .attr('title', rcmail.gettext('managesieve.managefilters'))
      .html(rcmail.gettext('managesieve.filters'))
      .bind('click', function(e){ return rcmail.command('plugin.managesieve', this) })
      .appendTo(tab);
    // add button and register commands
    rcmail.add_element(tab, 'tabs');
    rcmail.register_command('plugin.managesieve', function() { rcmail.goto_url('plugin.managesieve') }, true);
    rcmail.register_command('plugin.managesieve-save', function() { rcmail.managesieve_save() }, true);
    rcmail.register_command('plugin.managesieve-add', function() { rcmail.managesieve_add() }, true);
    rcmail.register_command('plugin.managesieve-del', function() { rcmail.managesieve_del() }, true);
plugins/managesieve/managesieve.php
@@ -7,7 +7,7 @@
 * It's clickable interface which operates on text scripts and communicates
 * with server using managesieve protocol. Adds Filters tab in Settings.
 *
 * @version 2.10
 * @version 3.0
 * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
 *
 * Configuration (see config.inc.php.dist)
@@ -66,7 +66,7 @@
        $host = rcube_parse_host($this->rc->config->get('managesieve_host', 'localhost'));
        $port = $this->rc->config->get('managesieve_port', 2000);
        $host = idn_to_ascii($host);
        $host = rcube_idn_to_ascii($host);
        // try to connect to managesieve server and to fetch the script
        $this->sieve = new rcube_sieve($_SESSION['username'],
plugins/managesieve/tests/Makefile
New file
@@ -0,0 +1,7 @@
clean:
    rm -f *.log *.php *.diff *.exp *.out
test:
    pear run-tests *.phpt
plugins/managesieve/tests/parser.phpt
New file
@@ -0,0 +1,103 @@
--TEST--
Main test of script parser
--SKIPIF--
--FILE--
<?php
include('../lib/rcube_sieve.php');
$txt = '
require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric"];
# rule:[spam]
if anyof (header :contains "X-DSPAM-Result" "Spam")
{
    fileinto "Spam";
    stop;
}
# rule:[test1]
if anyof (header :contains ["From","To"] "test@domain.tld")
{
    discard;
    stop;
}
# rule:[test2]
if anyof (not header :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[test-vacation]
if anyof (header :contains "Subject" "vacation")
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
# rule:[comments]
if anyof (true) /* comment
 * "comment" #comment */ {
    /* comment */ stop;
# comment
}
# rule:[reject]
if size :over 5000K {
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric"
    ["X-Spam-score"] ["14"] {redirect "test@test.tld";}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
?>
--EXPECT--
require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric"];
# rule:[spam]
if header :contains "X-DSPAM-Result" "Spam"
{
    fileinto "Spam";
    stop;
}
# rule:[test1]
if header :contains ["From","To"] "test@domain.tld"
{
    discard;
    stop;
}
# rule:[test2]
if anyof (not header :contains "Subject" "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[test-vacation]
if header :contains "Subject" "vacation"
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
# rule:[comments]
if true
{
    stop;
}
# rule:[reject]
if size :over 5000K
{
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
{
    redirect "test@test.tld";
}
plugins/managesieve/tests/tokenize.phpt
New file
@@ -0,0 +1,66 @@
--TEST--
Script parsing: tokenizer
--SKIPIF--
--FILE--
<?php
include('../lib/rcube_sieve.php');
$txt[1] = array(1, 'text: #test
This is test ; message;
Multi line
.
;
');
$txt[2] = array(0, '["test1","test2"]');
$txt[3] = array(1, '["test"]');
$txt[4] = array(1, '"te\\"st"');
$txt[5] = array(0, 'test #comment');
$txt[6] = array(0, 'text:
test
.
text:
test
.
');
$txt[7] = array(1, '"\\a\\\\\\"a"');
foreach ($txt as $idx => $t) {
    echo "[$idx]---------------\n";
    var_dump(rcube_sieve_script::tokenize($t[1], $t[0]));
}
?>
--EXPECT--
[1]---------------
string(34) "This is test ; message;
Multi line"
[2]---------------
array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(5) "test1"
    [1]=>
    string(5) "test2"
  }
}
[3]---------------
array(1) {
  [0]=>
  string(4) "test"
}
[4]---------------
string(5) "te"st"
[5]---------------
array(1) {
  [0]=>
  string(4) "test"
}
[6]---------------
array(2) {
  [0]=>
  string(4) "test"
  [1]=>
  string(4) "test"
}
[7]---------------
string(4) "a\"a"
plugins/markasjunk/localization/cs_CZ.inc
@@ -6,7 +6,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube markasjunk plugin                      |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2009, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/markasjunk/markasjunk.php
@@ -27,7 +27,9 @@
        'command' => 'plugin.markasjunk',
        'imagepas' => $skin_path.'/junk_pas.png',
        'imageact' => $skin_path.'/junk_act.png',
    'title' => 'markasjunk.buttontitle'), 'toolbar');
        'width' => 32,
        'height' => 32,
        'title' => 'markasjunk.buttontitle'), 'toolbar');
    }
  }
plugins/markasjunk/package.xml
@@ -42,6 +42,7 @@
            <file name="localization/es_AR.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/gl_ES.inc" role="data"></file>
            <file name="localization/ja_JP.inc" role="data"></file>
            <file name="localization/pl_PL.inc" role="data"></file>
            <file name="localization/ru_RU.inc" role="data"></file>
plugins/new_user_identity/new_user_identity.php
@@ -40,7 +40,7 @@
            if (count($results->records) == 1) {
                $args['user_name'] = $results->records[0]['name'];
                if (!$args['user_email'] && strpos($results->records[0]['email'], '@')) {
                    $args['user_email'] = idn_to_ascii($results->records[0]['email']);
                    $args['user_email'] = rcube_idn_to_ascii($results->records[0]['email']);
                }
            }
        }
plugins/password/README
@@ -201,7 +201,7 @@
 As in sasl driver this one allows to change password using shell
 utility called "virtualmin". See drivers/chgvirtualminpasswd.c for
 installation instructions.
 installation instructions. See also config.inc.php.dist file.
 2.9. hMailServer (hmail)
plugins/password/config.inc.php.dist
@@ -18,6 +18,9 @@
// 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;
// SQL Driver options
// ------------------
@@ -195,8 +198,15 @@
// Whenever the password is changed, the attribute will be updated if set (e.g. shadowLastChange)
$rcmail_config['password_ldap_lchattr'] = '';
// Also try to update Samba password attributes: sambaNTPassword and sambaPwdLastSet
$rcmail_config['password_ldap_samba'] = false;
// LDAP Samba password attribute, e.g. sambaNTPassword
// Name of the LDAP's Samba attribute used for storing user password
$rcmail_config['password_ldap_samba_pwattr'] = '';
// LDAP Samba Password Last Change Date attribute, e.g. sambaPwdLastSet
// Some places use an attribute to store the date of the last password change
// The date is meassured in "seconds since epoch" (an integer value)
// Whenever the password is changed, the attribute will be updated if set
$rcmail_config['password_ldap_samba_lchattr'] = '';
// DirectAdmin Driver options
@@ -275,3 +285,16 @@
    'Password' => 'password' // windows user password
);
// Virtualmin Driver options
// -------------------------
// Username format:
// 0: username@domain
// 1: username%domain
// 2: username.domain
// 3: domain.username
// 4: username-domain
// 5: domain-username
// 6: username_domain
// 7: domain_username
$rcmail_config['password_virtualmin_format'] = 0;
plugins/password/drivers/directadmin.php
@@ -316,8 +316,8 @@
            }
        }
        list($this->result_header,$this->result_body) = split("\r\n\r\n",$this->result,2);
        list($this->result_header, $this->result_body) = explode("\r\n\r\n", $this->result, 2);
        if ($this->bind_host)
        {
@@ -378,7 +378,7 @@
        {
            if ($asArray)
            {
                return split("\n",$this->fetch_body());
                return explode("\n", $this->fetch_body());
            }
            return $this->fetch_body();
@@ -438,14 +438,14 @@
     */
    function fetch_header( $header = '' )
    {
        $array_headers = split("\r\n",$this->result_header);
        $array_headers = explode("\r\n", $this->result_header);
        $array_return = array( 0 => $array_headers[0] );
        unset($array_headers[0]);
        foreach ( $array_headers as $pair )
        {
            list($key,$value) = split(": ",$pair,2);
            list($key,$value) = explode(": ", $pair, 2);
            $array_return[strtolower($key)] = $value;
        }
plugins/password/drivers/ldap.php
@@ -62,10 +62,28 @@
        return PASSWORD_CONNECT_ERROR;
    }
    // Crypting new password
    $newCryptedPassword = hashPassword($passwd, $rcmail->config->get('password_ldap_encodage'));
    if (!$newCryptedPassword) {
    $crypted_pass = 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 = hashPassword($passwd, 'samba'))) {
        return PASSWORD_CRYPT_ERROR;
    }
    // Writing new crypted password to LDAP
@@ -74,31 +92,29 @@
        return PASSWORD_CONNECT_ERROR;
    }
    $pwattr = $rcmail->config->get('password_ldap_pwattr');
    $force = $rcmail->config->get('password_ldap_force_replace');
    if (!$userEntry->replace(array($pwattr => $newCryptedPassword), $force)) {
    if (!$userEntry->replace(array($pwattr => $crypted_pass), $force)) {
        return PASSWORD_CONNECT_ERROR;
    }
    // Updating PasswordLastChange Attribute if desired
    if ($lchattr = $rcmail->config->get('password_ldap_lchattr')) {
    if ($lchattr) {
       $current_day = (int)(time() / 86400);
       if (!$userEntry->replace(array($lchattr => $current_day), $force)) {
           return PASSWORD_CONNECT_ERROR;
       }
    }
    if (Net_LDAP2::isError($userEntry->update())) {
        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);
    }
    // Update Samba password fields, ignore errors if attributes are not found
    if ($rcmail->config->get('password_ldap_samba')) {
        $sambaNTPassword = hash('md4', rcube_charset_convert($passwd, RCMAIL_CHARSET, 'UTF-16LE'));
        $userEntry->replace(array('sambaNTPassword' => $sambaNTPassword), $force);
        $userEntry->replace(array('sambaPwdLastSet' => time()), $force);
        $userEntry->update();
    if (Net_LDAP2::isError($userEntry->update())) {
        return PASSWORD_CONNECT_ERROR;
    }
    // All done, no error
@@ -253,6 +269,15 @@
            }
            break;
        case 'samba':
            if (function_exists('hash')) {
                $cryptedPassword = hash('md4', rcube_charset_convert($password_clear, RCMAIL_CHARSET, 'UTF-16LE'));
            } else {
                /* Your PHP install does not have the hash() function */
                return false;
            }
            break;
        case 'clear':
        default:
            $cryptedPassword = $passwordClear;
plugins/password/drivers/ldap_simple.php
@@ -14,19 +14,19 @@
{
    $rcmail = rcmail::get_instance();
    /* Connect */
    // 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 */
    // 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 */
    // Start TLS
    if ($rcmail->config->get('password_ldap_starttls')) {
        if (!ldap_start_tls($ds)) {
            ldap_unbind($ds);
@@ -34,7 +34,7 @@
        }
    }
    /* Build user DN */
    // Build user DN
    if ($user_dn = $rcmail->config->get('password_ldap_userDN_mask')) {
        $user_dn = ldap_simple_substitute_vars($user_dn);
    } else {
@@ -46,7 +46,7 @@
        return PASSWORD_CONNECT_ERROR;
    }
    /* Connection method */
    // Connection method
    switch ($rcmail->config->get('password_ldap_method')) {
        case 'admin':
            $binddn = $rcmail->config->get('password_ldap_adminDN');
@@ -59,31 +59,51 @@
            break;
    }
    /* Bind */
    $crypted_pass = ldap_simple_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 = ldap_simple_hash_password($passwd, 'samba'))) {
        return PASSWORD_CRYPT_ERROR;
    }
    // Bind
    if (!ldap_bind($ds, $binddn, $bindpw)) {
        ldap_unbind($ds);
        return PASSWORD_CONNECT_ERROR;
    }
    /* Crypting new password */
    $crypted_pass = ldap_simple_hash_password($passwd, $rcmail->config->get('password_ldap_encodage'));
    if (!$crypted_pass) {
        ldap_unbind($ds);
        return PASSWORD_CRYPT_ERROR;
    }
    $entree[$pwattr] = $crypted_pass;
    $entree[$rcmail->config->get('password_ldap_pwattr')] = $crypted_pass;
    /* Updating PasswordLastChange Attribute if desired */
    if ($lchattr = $rcmail->config->get('password_ldap_lchattr')) {
    // Update PasswordLastChange Attribute if desired
    if ($lchattr) {
        $entree[$lchattr] = (int)(time() / 86400);
    }
    /* Update Samba password fields */
    if ($smbattr = $rcmail->config->get('password_ldap_samba')) {
        $sambaNTPassword = hash('md4', rcube_charset_convert($passwd, RCMAIL_CHARSET, 'UTF-16LE'));
        $entree['sambaNTPassword'] = $sambaNTPassword;
        $entree['sambaPwdLastSet'] = time();
    // 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)) {
@@ -91,7 +111,7 @@
        return PASSWORD_CONNECT_ERROR;
    }
    /* All done, no error */
    // All done, no error
    ldap_unbind($ds);
    return PASSWORD_SUCCESS;
}
@@ -215,6 +235,14 @@
                return false;
            }
            break;
        case 'samba':
            if (function_exists('hash')) {
                $crypted_password = hash('md4', rcube_charset_convert($password_clear, RCMAIL_CHARSET, 'UTF-16LE'));
            } else {
                /* Your PHP install does not have the hash() function */
                return false;
            }
            break;
        case 'clear':
        default:
            $crypted_password = $password_clear;
plugins/password/drivers/virtualmin.php
@@ -10,15 +10,50 @@
 * 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 1.0
 * @version 2.0
 * @author Martijn de Munnik
 */
function password_save($currpass, $newpass)
{
    $curdir = realpath(dirname(__FILE__));
    $username = escapeshellcmd($_SESSION['username']);
    $domain = substr(strrchr($username, "@"), 1);
    $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);
plugins/password/drivers/xmail.php
@@ -20,7 +20,7 @@
function password_save($currpass, $newpass)
{
    $rcmail = rcmail::get_instance();
    list($user,$domain) = split('@',$_SESSION['username']);
    list($user,$domain) = explode('@', $_SESSION['username']);
    $xmail = new XMail;
plugins/password/localization/es_ES.inc
@@ -1,21 +1,21 @@
<?php
$labels = array();
$labels['changepasswd']  = 'Cambiar Contraseña';
$labels['curpasswd']  = 'Contraseña Actual:';
$labels['newpasswd']  = 'Contraseña Nueva:';
$labels['confpasswd']  = 'Confirmar Contraseña:';
$labels['changepasswd']  = 'Cambiar contraseña';
$labels['curpasswd']  = 'Contraseña actual:';
$labels['newpasswd']  = 'Contraseña nueva:';
$labels['confpasswd']  = 'Confirmar contraseña:';
$messages = array();
$messages['nopassword'] = 'Por favor introduce una nueva contraseña.';
$messages['nocurpassword'] = 'Por favor introduce la contraseña actual.';
$messages['passwordincorrect'] = 'Contraseña actual incorrecta.';
$messages['passwordinconsistency'] = 'Las contraseñas no coinciden, por favor inténtalo de nuevo.';
$messages['nopassword'] = 'Por favor introduzca una contraseña nueva.';
$messages['nocurpassword'] = 'Por favor introduzca la contraseña actual.';
$messages['passwordincorrect'] = 'La contraseña actual es incorrecta.';
$messages['passwordinconsistency'] = 'Las contraseñas no coinciden. Por favor, inténtelo de nuevo.';
$messages['crypterror'] = 'No se pudo guardar la contraseña nueva. Falta la función de cifrado.';
$messages['connecterror'] = 'No se pudo guardar la contraseña nueva. Error de conexión';
$messages['internalerror'] = 'No se pudo guardar la contraseña nueva.';
$messages['passwordshort'] = 'Tu contraseña debe tener una longitud mínima de $length.';
$messages['passwordweak'] = 'Tu nueva contraseña debe incluir al menos un número y un signo de puntuación.';
$messages['passwordforbidden'] = 'La contraseña contiene caracteres prohibidos.';
$messages['passwordshort'] = 'La contraseña debe tener por lo menos $length caracteres.';
$messages['passwordweak'] = 'La contraseña debe incluir al menos un número y un signo de puntuación.';
$messages['passwordforbidden'] = 'La contraseña introducida contiene caracteres no permitidos.';
?>
plugins/password/localization/ru_RU.inc
@@ -5,7 +5,7 @@
| plugins/password/localization/ru_RU.inc                               |
|                                                                       |
| Language file of the Roundcube help plugin                            |
| Copyright (C) 2005-2010, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2010, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/password/package.xml
@@ -15,8 +15,8 @@
        <email>alec@alec.pl</email>
        <active>yes</active>
    </lead>
    <date></date>
    <time></time>
    <date>2011-02-15</date>
    <time>12:00</time>
    <version>
        <release>2.2</release>
        <api>1.6</api>
@@ -34,6 +34,11 @@
- ldap_simple driver: fix parse error
- ldap/ldap_simple drivers: support %dc variable in config
- ldap/ldap_simple drivers: support Samba password change
- Fix extended error messages handling (#1487676)
- Fix double request when clicking on Password tab in Firefox
- Fix deprecated split() usage in xmail and directadmin drivers (#1487769)
- Added option (password_log) for logging password changes
- Virtualmin driver: Add option for setting username format (#1487781)
    </notes>
    <contents>
        <dir baseinstalldir="/" name="/">
@@ -61,6 +66,7 @@
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/fi_FI.inc" role="data"></file>
            <file name="localization/fr_FR.inc" role="data"></file>
            <file name="localization/gl_ES.inc" role="data"></file>
            <file name="localization/hu_HU.inc" role="data"></file>
            <file name="localization/it_IT.inc" role="data"></file>
            <file name="localization/lt_LT.inc" role="data"></file>
plugins/password/password.js
@@ -7,13 +7,11 @@
  rcmail.addEventListener('init', function(evt) {
    // <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span>
    var tab = $('<span>').attr('id', 'settingstabpluginpassword').addClass('tablink');
    var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.password').html(rcmail.gettext('password')).appendTo(tab);
    button.bind('click', function(e){ return rcmail.command('plugin.password', this) });
    var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.password')
      .html(rcmail.gettext('password')).appendTo(tab);
    // add button and register commands
    rcmail.add_element(tab, 'tabs');
    rcmail.register_command('plugin.password', function() { rcmail.goto_url('plugin.password') }, true);
    rcmail.register_command('plugin.password-save', function() { 
      var input_curpasswd = rcube_find_object('_curpasswd');
      var input_newpasswd = rcube_find_object('_newpasswd');
plugins/password/password.php
@@ -128,7 +128,15 @@
            // try to save the password
            else if (!($res = $this->_save($curpwd, $newpwd))) {
                $rcmail->output->command('display_message', $this->gettext('successfullysaved'), 'confirmation');
                // Reset session password
                $_SESSION['password'] = $rcmail->encrypt($newpwd);
                // Log password change
                if ($rcmail->config->get('password_log')) {
                    write_log('password', sprintf('Password changed for user %s (ID: %d) from %s',
                        $rcmail->user->get_username(), $rcmail->user->ID, rcmail_remote_ip()));
                }
            }
            else {
                $rcmail->output->command('display_message', $res, 'error');
@@ -232,8 +240,8 @@
        $result = password_save($curpass, $passwd);
        if (is_array($result)) {
            $result  = $result['code'];
            $message = $result['message'];
            $result  = $result['code'];
        }
        switch ($result) {
plugins/show_additional_headers/show_additional_headers.php
@@ -44,7 +44,7 @@
    foreach ((array)$rcmail->config->get('show_additional_headers', array()) as $header) {
      $key = strtolower($header);
      if ($value = $p['headers']->others[$key])
        $p['output'][$key] = array('title' => $header, 'value' => $value);
        $p['output'][$key] = array('title' => $header, 'value' => Q($value));
    }
    return $p;
plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
@@ -73,8 +73,8 @@
                foreach ($this->abook as $rec) {
                    // #1487096 handle multi-address and/or too long items
                    $rec['email'] = array_shift(explode(';', $rec['email']));
                    if (check_email(idn_to_ascii($rec['email']))) {
                        $rec['email'] = idn_to_utf8($rec['email']);
                    if (check_email(rcube_idn_to_ascii($rec['email']))) {
                        $rec['email'] = rcube_idn_to_utf8($rec['email']);
                        $contacts->insert($rec, true);
                    }
                }
plugins/subscriptions_option/localization/cs_CZ.inc
@@ -6,7 +6,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube subscriptions option plugin            |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2009, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/userinfo/localization/cs_CZ.inc
@@ -6,7 +6,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube userinfo plugin                        |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Copyright (C) 2005-2009, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/vcard_attachments/localization/cs_CZ.inc
@@ -5,7 +5,7 @@
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube Webmail client                         |
| Copyright (C) 2008-2010, RoundQube Dev. - Switzerland                 |
| Copyright (C) 2008-2010, The Roundcube Dev Team                       |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
plugins/vcard_attachments/localization/es_ES.inc
@@ -1,7 +1,7 @@
<?php
$labels = array();
$labels['addvcardmsg'] = 'Añadir tarjeta a la libreta de direcciones';
$labels['vcardsavefailed'] = 'Imposible guardar la tarjeta';
$labels['addvcardmsg'] = 'Añadir la tarjeta a la libreta de direcciones';
$labels['vcardsavefailed'] = 'No ha sido posible guardar la tarjeta';
?>
plugins/vcard_attachments/package.xml
@@ -51,6 +51,7 @@
            <file name="localization/de_DE.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/gl_ES.inc" role="data"></file>
            <file name="localization/it_IT.inc" role="data"></file>
            <file name="localization/ja_JP.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
plugins/virtuser_file/virtuser_file.php
@@ -40,7 +40,7 @@
            $arr = preg_split('/\s+/', $r[$i]);
            if (count($arr) > 0 && strpos($arr[0], '@')) {
                $result[] = idn_to_ascii(trim(str_replace('\\@', '@', $arr[0])));
                $result[] = rcube_idn_to_ascii(trim(str_replace('\\@', '@', $arr[0])));
                if ($p['first']) {
                    $p['email'] = $result[0];
plugins/virtuser_query/virtuser_query.php
@@ -58,11 +58,11 @@
            if (strpos($sql_arr[0], '@')) {
                if ($p['extended'] && count($sql_arr) > 1) {
                    $result[] = array(
                        'email'         => idn_to_ascii($sql_arr[0]),
                        'email'         => rcube_idn_to_ascii($sql_arr[0]),
                        'name'             => $sql_arr[1],
                        'organization'  => $sql_arr[2],
                        'reply-to'         => idn_to_ascii($sql_arr[3]),
                        'bcc'             => idn_to_ascii($sql_arr[4]),
                        'reply-to'         => rcube_idn_to_ascii($sql_arr[3]),
                        'bcc'             => rcube_idn_to_ascii($sql_arr[4]),
                        'signature'     => $sql_arr[5],
                        'html_signature' => (int)$sql_arr[6],
                    );