From 207cc0b9b3cfdfb29e4f02e83014320fd12eeb68 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Wed, 16 Feb 2011 05:48:11 -0500
Subject: [PATCH] - Applied plugin changes since 0.5-stable release

---
 plugins/http_authentication/http_authentication.php         |    3 
 plugins/archive/archive.php                                 |    2 
 plugins/managesieve/localization/fr_FR.inc                  |    2 
 plugins/password/drivers/virtualmin.php                     |   43 +
 plugins/vcard_attachments/package.xml                       |    1 
 plugins/password/localization/ru_RU.inc                     |    2 
 plugins/help/localization/cs_CZ.inc                         |    2 
 plugins/managesieve/managesieve.php                         |    4 
 plugins/subscriptions_option/localization/cs_CZ.inc         |    2 
 plugins/managesieve/localization/es_ES.inc                  |    2 
 plugins/password/drivers/xmail.php                          |    2 
 plugins/help/localization/ru_RU.inc                         |    2 
 plugins/markasjunk/markasjunk.php                           |    4 
 plugins/password/drivers/ldap.php                           |   57 +
 plugins/archive/localization/cs_CZ.inc                      |    2 
 plugins/squirrelmail_usercopy/squirrelmail_usercopy.php     |    4 
 plugins/password/README                                     |    2 
 plugins/password/config.inc.php.dist                        |   27 +
 plugins/password/package.xml                                |   10 
 CHANGELOG                                                   |    1 
 plugins/managesieve/tests/Makefile                          |    7 
 plugins/managesieve/tests/tokenize.phpt                     |   66 +++
 plugins/userinfo/localization/cs_CZ.inc                     |    2 
 plugins/markasjunk/package.xml                              |    1 
 plugins/vcard_attachments/localization/cs_CZ.inc            |    2 
 plugins/managesieve/localization/ru_RU.inc                  |   12 
 plugins/managesieve/tests/parser.phpt                       |  103 ++++
 plugins/managesieve/Changelog                               |    9 
 plugins/virtuser_query/virtuser_query.php                   |    6 
 plugins/markasjunk/localization/cs_CZ.inc                   |    2 
 plugins/new_user_identity/new_user_identity.php             |    2 
 plugins/managesieve/lib/Net/Sieve.php                       |   55 +
 plugins/password/drivers/directadmin.php                    |   12 
 plugins/password/localization/es_ES.inc                     |   22 
 plugins/managesieve/lib/rcube_sieve.php                     |  661 +++++++++++++++++-------------
 plugins/vcard_attachments/localization/es_ES.inc            |    4 
 plugins/password/password.js                                |    6 
 plugins/virtuser_file/virtuser_file.php                     |    2 
 plugins/password/drivers/ldap_simple.php                    |   72 ++-
 plugins/managesieve/managesieve.js                          |    2 
 plugins/autologon/autologon.php                             |    2 
 plugins/password/password.php                               |   10 
 plugins/show_additional_headers/show_additional_headers.php |    2 
 43 files changed, 838 insertions(+), 398 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 2f9cd15..2a304c5 100644
--- a/CHANGELOG
+++ b/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
diff --git a/plugins/archive/archive.php b/plugins/archive/archive.php
index fbb0129..843b612 100644
--- a/plugins/archive/archive.php
+++ b/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,
         ),
diff --git a/plugins/archive/localization/cs_CZ.inc b/plugins/archive/localization/cs_CZ.inc
index 1396fb8..bb257bc 100644
--- a/plugins/archive/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/autologon/autologon.php b/plugins/autologon/autologon.php
index bc3d2ee..9601d61 100644
--- a/plugins/autologon/autologon.php
+++ b/plugins/autologon/autologon.php
@@ -31,6 +31,8 @@
       $args['user'] = 'me';
       $args['pass'] = '******';
       $args['host'] = 'localhost';
+      $args['cookiecheck'] = false;
+      $args['valid'] = true;
     }
   
     return $args;
diff --git a/plugins/help/localization/cs_CZ.inc b/plugins/help/localization/cs_CZ.inc
index 472c753..ae8b39a 100644
--- a/plugins/help/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/help/localization/ru_RU.inc b/plugins/help/localization/ru_RU.inc
index aad0a61..9f1de41 100644
--- a/plugins/help/localization/ru_RU.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/http_authentication/http_authentication.php b/plugins/http_authentication/http_authentication.php
index a8003cf..6da6488 100644
--- a/plugins/http_authentication/http_authentication.php
+++ b/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;
   }
diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog
index 61d198c..ef75739 100644
--- a/plugins/managesieve/Changelog
+++ b/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]
 -----------------------------------------------------------
diff --git a/plugins/managesieve/lib/Net/Sieve.php b/plugins/managesieve/lib/Net/Sieve.php
index ecc9071..d4cc3ee 100644
--- a/plugins/managesieve/lib/Net/Sieve.php
+++ b/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.
diff --git a/plugins/managesieve/lib/rcube_sieve.php b/plugins/managesieve/lib/rcube_sieve.php
index ba43f10..dff7d7b 100644
--- a/plugins/managesieve/lib/rcube_sieve.php
+++ b/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) . '"]';
-    }
 }
diff --git a/plugins/managesieve/localization/es_ES.inc b/plugins/managesieve/localization/es_ES.inc
index 6130d70..1dad18d 100644
--- a/plugins/managesieve/localization/es_ES.inc
+++ b/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';
diff --git a/plugins/managesieve/localization/fr_FR.inc b/plugins/managesieve/localization/fr_FR.inc
index 0b494f0..49bd3c6 100644
--- a/plugins/managesieve/localization/fr_FR.inc
+++ b/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:';
diff --git a/plugins/managesieve/localization/ru_RU.inc b/plugins/managesieve/localization/ru_RU.inc
index c7c8bb0..c9cd336 100644
--- a/plugins/managesieve/localization/ru_RU.inc
+++ b/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'] = 'значение меньше или равно';
diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js
index 6b96561..04977eb 100644
--- a/plugins/managesieve/managesieve.js
+++ b/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);
diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php
index 5de839d..96e6ebc 100644
--- a/plugins/managesieve/managesieve.php
+++ b/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'],
diff --git a/plugins/managesieve/tests/Makefile b/plugins/managesieve/tests/Makefile
new file mode 100644
index 0000000..072be2f
--- /dev/null
+++ b/plugins/managesieve/tests/Makefile
@@ -0,0 +1,7 @@
+
+clean:
+	rm -f *.log *.php *.diff *.exp *.out
+
+
+test:
+	pear run-tests *.phpt
diff --git a/plugins/managesieve/tests/parser.phpt b/plugins/managesieve/tests/parser.phpt
new file mode 100644
index 0000000..a3b820d
--- /dev/null
+++ b/plugins/managesieve/tests/parser.phpt
@@ -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";
+}
diff --git a/plugins/managesieve/tests/tokenize.phpt b/plugins/managesieve/tests/tokenize.phpt
new file mode 100644
index 0000000..d1f68ac
--- /dev/null
+++ b/plugins/managesieve/tests/tokenize.phpt
@@ -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"
diff --git a/plugins/markasjunk/localization/cs_CZ.inc b/plugins/markasjunk/localization/cs_CZ.inc
index c547e5a..18509cf 100644
--- a/plugins/markasjunk/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/markasjunk/markasjunk.php b/plugins/markasjunk/markasjunk.php
index 4c15a27..697d880 100644
--- a/plugins/markasjunk/markasjunk.php
+++ b/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');
     }
   }
 
diff --git a/plugins/markasjunk/package.xml b/plugins/markasjunk/package.xml
index 8ca7b18..d76bd0c 100644
--- a/plugins/markasjunk/package.xml
+++ b/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>
diff --git a/plugins/new_user_identity/new_user_identity.php b/plugins/new_user_identity/new_user_identity.php
index ecdf7c1..45750fa 100644
--- a/plugins/new_user_identity/new_user_identity.php
+++ b/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']);
                 }
             }
         }
diff --git a/plugins/password/README b/plugins/password/README
index a31a0e0..81e4f1e 100644
--- a/plugins/password/README
+++ b/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)
diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist
index 54e9e51..ddf8812 100644
--- a/plugins/password/config.inc.php.dist
+++ b/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;
diff --git a/plugins/password/drivers/directadmin.php b/plugins/password/drivers/directadmin.php
index d11aae7..6ca3264 100644
--- a/plugins/password/drivers/directadmin.php
+++ b/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;
         }
 
diff --git a/plugins/password/drivers/ldap.php b/plugins/password/drivers/ldap.php
index e4d91fe..a18f349 100644
--- a/plugins/password/drivers/ldap.php
+++ b/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;
diff --git a/plugins/password/drivers/ldap_simple.php b/plugins/password/drivers/ldap_simple.php
index 67f53d0..482b7e5 100644
--- a/plugins/password/drivers/ldap_simple.php
+++ b/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;
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
index 96200d6..78ef4e7 100644
--- a/plugins/password/drivers/virtualmin.php
+++ b/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);
 
diff --git a/plugins/password/drivers/xmail.php b/plugins/password/drivers/xmail.php
index 39d1e71..c7f4261 100644
--- a/plugins/password/drivers/xmail.php
+++ b/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;
 
diff --git a/plugins/password/localization/es_ES.inc b/plugins/password/localization/es_ES.inc
index b9a9c16..32879b4 100644
--- a/plugins/password/localization/es_ES.inc
+++ b/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.';
 
 ?>
diff --git a/plugins/password/localization/ru_RU.inc b/plugins/password/localization/ru_RU.inc
index 5a108d6..3776b45 100644
--- a/plugins/password/localization/ru_RU.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/password/package.xml b/plugins/password/package.xml
index 1d63142..a4827df 100644
--- a/plugins/password/package.xml
+++ b/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>
diff --git a/plugins/password/password.js b/plugins/password/password.js
index 17fe3f7..26376b3 100644
--- a/plugins/password/password.js
+++ b/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');
diff --git a/plugins/password/password.php b/plugins/password/password.php
index 6d3042b..8fc95ea 100644
--- a/plugins/password/password.php
+++ b/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) {
diff --git a/plugins/show_additional_headers/show_additional_headers.php b/plugins/show_additional_headers/show_additional_headers.php
index 1cbe690..0007ce3 100644
--- a/plugins/show_additional_headers/show_additional_headers.php
+++ b/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;
diff --git a/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php b/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
index 5d7cc01..ee6bcc0 100644
--- a/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
+++ b/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);
 			        }
 			    }
diff --git a/plugins/subscriptions_option/localization/cs_CZ.inc b/plugins/subscriptions_option/localization/cs_CZ.inc
index d625201..0d9c1fc 100644
--- a/plugins/subscriptions_option/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/userinfo/localization/cs_CZ.inc b/plugins/userinfo/localization/cs_CZ.inc
index 30f8221..20cd4ae 100644
--- a/plugins/userinfo/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/vcard_attachments/localization/cs_CZ.inc b/plugins/vcard_attachments/localization/cs_CZ.inc
index 5d7c9c4..11ae8c9 100644
--- a/plugins/vcard_attachments/localization/cs_CZ.inc
+++ b/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                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
diff --git a/plugins/vcard_attachments/localization/es_ES.inc b/plugins/vcard_attachments/localization/es_ES.inc
index c00b499..0aba6b3 100644
--- a/plugins/vcard_attachments/localization/es_ES.inc
+++ b/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';
 
 ?>
\ No newline at end of file
diff --git a/plugins/vcard_attachments/package.xml b/plugins/vcard_attachments/package.xml
index 714750b..e5eaf71 100644
--- a/plugins/vcard_attachments/package.xml
+++ b/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>
diff --git a/plugins/virtuser_file/virtuser_file.php b/plugins/virtuser_file/virtuser_file.php
index d91e532..9d4efd1 100644
--- a/plugins/virtuser_file/virtuser_file.php
+++ b/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];
diff --git a/plugins/virtuser_query/virtuser_query.php b/plugins/virtuser_query/virtuser_query.php
index 8f154c2..e59095f 100644
--- a/plugins/virtuser_query/virtuser_query.php
+++ b/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],
     		        );

--
Gitblit v1.9.1