From ade4b9e7ae7e28535ed261535526bc4e98ff48b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andre.peters@debinux.de>
Date: Sun, 19 Nov 2017 15:13:43 +0100
Subject: [PATCH 01/40] [Postfix, Web] Feature: BCC maps

---
 data/Dockerfiles/postfix/postfix.sh      |  22 ++
 data/conf/postfix/main.cf                |  29 ++-
 data/web/admin.php                       |  10 +-
 data/web/edit.php                        |  52 +++-
 data/web/inc/functions.bcc.inc.php       | 292 +++++++++++++++++++++++
 data/web/inc/functions.customize.inc.php |   1 -
 data/web/inc/functions.fail2ban.inc.php  |   4 +-
 data/web/inc/functions.mailbox.inc.php   |  22 +-
 data/web/inc/init_db.inc.php             |  24 +-
 data/web/inc/prerequisites.inc.php       |   1 +
 data/web/inc/vars.inc.php                |   3 +
 data/web/js/mailbox.js                   |  52 +++-
 data/web/json_api.php                    | 156 +++++++++++-
 data/web/mailbox.php                     |  29 +++
 data/web/modals/mailbox.php              |  71 ++++++
 docker-compose.yml                       |   2 +-
 16 files changed, 748 insertions(+), 22 deletions(-)
 create mode 100644 data/web/inc/functions.bcc.inc.php

diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
index 326a6d18..5e60bb18 100755
--- a/data/Dockerfiles/postfix/postfix.sh
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -119,6 +119,28 @@ query = SELECT goto FROM alias
     AND active='1';
 EOF
 
+cat <<EOF > /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf
+user = ${DBUSER}
+password = ${DBPASS}
+hosts = mysql
+dbname = ${DBNAME}
+query = SELECT bcc_dest FROM bcc_maps
+  WHERE local_dest='%s'
+    AND type='rcpt'
+    AND active='1';
+EOF
+
+cat <<EOF > /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf
+user = ${DBUSER}
+password = ${DBPASS}
+hosts = mysql
+dbname = ${DBNAME}
+query = SELECT bcc_dest FROM bcc_maps
+  WHERE local_dest='%s'
+    AND type='sender'
+    AND active='1';
+EOF
+
 cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
 user = ${DBUSER}
 password = ${DBPASS}
diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf
index edf5c6d5..4e8c577f 100644
--- a/data/conf/postfix/main.cf
+++ b/data/conf/postfix/main.cf
@@ -39,7 +39,27 @@ postscreen_greet_ttl = 2d
 postscreen_greet_wait = 3s
 postscreen_non_smtp_command_enable = no
 postscreen_pipelining_enable = no
-proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
+proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf,
+  $local_recipient_maps,
+  $mydestination,
+  $virtual_alias_maps,
+  $virtual_alias_domains,
+  $virtual_mailbox_maps,
+  $virtual_mailbox_domains,
+  $relay_recipient_maps,
+  $relay_domains,
+  $canonical_maps,
+  $sender_canonical_maps,
+  $recipient_canonical_maps,
+  $relocated_maps,
+  $transport_maps,
+  $mynetworks,
+  $smtpd_sender_login_maps
 queue_run_delay = 300s
 relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf
 relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
@@ -79,10 +99,15 @@ smtpd_tls_mandatory_ciphers = high
 smtpd_tls_security_level = may
 tls_ssl_options = NO_COMPRESSION
 tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
-virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
+virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
 virtual_gid_maps = static:5000
 virtual_mailbox_base = /var/vmail/
 virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
+recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf
+sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf
 virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
 virtual_minimum_uid = 104
 virtual_transport = lmtp:inet:dovecot:24
diff --git a/data/web/admin.php b/data/web/admin.php
index 06af3db9..0d7c6466 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -7,7 +7,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 $tfa_data = get_tfa();
 ?>
 <div class="container">
-
   <ul class="nav nav-tabs" role="tablist">
     <li role="presentation" class="active">
       <a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a>
@@ -22,7 +21,9 @@ $tfa_data = get_tfa();
     <li role="presentation"><a href="#tab-postfix-logs" aria-controls="tab-postfix-logs" role="tab" data-toggle="tab">Postfix</a></li>
     <li role="presentation"><a href="#tab-dovecot-logs" aria-controls="tab-dovecot-logs" role="tab" data-toggle="tab">Dovecot</a></li>
     <li role="presentation"><a href="#tab-sogo-logs" aria-controls="tab-sogo-logs" role="tab" data-toggle="tab">SOGo</a></li>
+    <?php if (F2B == 1): ?>
     <li role="presentation"><a href="#tab-fail2ban-logs" aria-controls="tab-fail2ban-logs" role="tab" data-toggle="tab">Fail2ban</a></li>
+    <?php endif; ?>
     <li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li>
     <li role="presentation"><a href="#tab-autodiscover-logs" aria-controls="tab-autodiscover-logs" role="tab" data-toggle="tab">Autodiscover</a></li>
     </ul>
@@ -127,7 +128,9 @@ $tfa_data = get_tfa();
       <div id="scrollbox" class="list-group">
         <a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a>
         <a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
+        <?php if (F2B == 1): ?>
         <a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
+        <?php endif; ?>
         <a href="#relayhosts" class="list-group-item">Relayhosts</a>
         <a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
         <a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
@@ -307,6 +310,7 @@ $tfa_data = get_tfa();
       </div>
     </div>
 
+    <?php if (F2B == 1): ?>
     <span class="anchor" id="f2bparams"></span>
     <div class="panel panel-default">
       <div class="panel-heading"><?=$lang['admin']['f2b_parameters'];?></div>
@@ -335,6 +339,7 @@ $tfa_data = get_tfa();
         </form>
       </div>
     </div>
+    <?php endif; ?>
 
     <span class="anchor" id="relayhosts"></span>
     <div class="panel panel-default">
@@ -506,6 +511,8 @@ $tfa_data = get_tfa();
     </div>
   </div>
 
+  
+  <?php if (F2B == 1): ?>
   <div role="tabpanel" class="tab-pane" id="tab-fail2ban-logs">
     <div class="panel panel-default">
       <div class="panel-heading">Fail2ban <span class="badge badge-info log-lines"></span>
@@ -522,6 +529,7 @@ $tfa_data = get_tfa();
       </div>
     </div>
   </div>
+  <?php endif; ?>
 
   <div role="tabpanel" class="tab-pane" id="tab-rspamd-history">
     <div class="panel panel-default">
diff --git a/data/web/edit.php b/data/web/edit.php
index e06a0229..11c924b5 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -608,6 +608,52 @@ if (isset($_SESSION['mailcow_cc_role'])) {
         <?php
         }
     }
+    elseif (isset($_GET['bcc']) && !empty($_GET["bcc"])) {
+        $bcc = intval($_GET["bcc"]);
+        $result = bcc('details', $bcc);
+        if (!empty($result)) {
+          ?>
+          <h4>BCC map</h4>
+          <br />
+          <form class="form-horizontal" data-id="editbcc" role="form" method="post">
+            <input type="hidden" value="0" name="active">
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="bcc_dest">BCC destination</label>
+              <div class="col-sm-10">
+                <textarea id="bcc_dest" class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="bcc_dest" name="bcc_dest" required><?=$result['bcc_dest'];?></textarea>
+                <small>BCC destinations can only be valid email addresses. Separated by whitespace, semicolon, new line or comma.</small>
+              </div>
+            </div>
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="type">Type:</label>
+              <div class="col-sm-10">
+                <select id="addFilterType" name="type" id="type" required>
+                  <option value="sender" <?=($result['type'] == 'sender') ? 'selected' : null;?>>Sender map</option>
+                  <option value="rcpt" <?=($result['type'] == 'rcpt') ? 'selected' : null;?>>Recipient map</option>
+                </select>
+              </div>
+            </div>
+            <div class="form-group">
+              <div class="col-sm-offset-2 col-sm-10">
+                <div class="checkbox">
+                <label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
+                </div>
+              </div>
+            </div>
+            <div class="form-group">
+              <div class="col-sm-offset-2 col-sm-10">
+                <button class="btn btn-success" id="edit_selected" data-id="editbcc" data-item="<?=$bcc;?>" data-api-url='edit/bcc' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
+              </div>
+            </div>
+          </form>
+        <?php
+        }
+        else {
+        ?>
+          <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+        <?php
+        }
+    }
   }
   if ($_SESSION['mailcow_cc_role'] == "admin"  || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") {
     if (isset($_GET['syncjob']) &&
@@ -722,9 +768,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
         <?php
         }
       }
-  }
-  if ($_SESSION['mailcow_cc_role'] == "admin"  || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") {
-    if (isset($_GET['filter']) &&
+    elseif (isset($_GET['filter']) &&
       is_numeric($_GET['filter'])) {
         $id = $_GET["filter"];
         $result = mailbox('get', 'filter_details', $id);
@@ -774,7 +818,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
           <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
         <?php
         }
-      }
+    }
   }
 }
 else {
diff --git a/data/web/inc/functions.bcc.inc.php b/data/web/inc/functions.bcc.inc.php
new file mode 100644
index 00000000..1ce58a54
--- /dev/null
+++ b/data/web/inc/functions.bcc.inc.php
@@ -0,0 +1,292 @@
+<?php
+function bcc($_action, $_data = null, $attr = null) {
+	global $pdo;
+	global $lang;
+  if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+    return false;
+  }
+  switch ($_action) {
+    case 'add':
+      if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      $local_dest = strtolower(trim($_data['local_dest']));
+      $bcc_dest = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_dest']));
+      $active = intval($_data['active']);
+      $type = $_data['type'];
+      if ($type != 'sender' && $type != 'rcpt') {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Invalid BCC map type'
+        );
+        return false;
+      }
+      if (empty($bcc_dest)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'BCC destination cannot be empty'
+        );
+        return false;
+      }
+      if (is_valid_domain_name($local_dest)) {
+        if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => sprintf($lang['danger']['access_denied'])
+          );
+          return false;
+        }
+        $domain = idn_to_ascii($local_dest);
+        $local_dest_sane = '@' . idn_to_ascii($local_dest);
+      }
+      elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) {
+        if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => sprintf($lang['danger']['access_denied'])
+          );
+          return false;
+        }
+        $domain = mailbox('get', 'mailbox_details', $local_dest)['domain'];
+        if (empty($domain)) {
+          return false;
+        }
+        $local_dest_sane = $local_dest;
+      }
+      else {
+        return false;
+      }
+      foreach ($bcc_dest as &$bcc_dest_e) {
+        if (!filter_var($bcc_dest_e, FILTER_VALIDATE_EMAIL)) {
+          $bcc_dest_e = null;;
+        }
+        $bcc_dest_e = strtolower($bcc_dest_e);
+      }
+      $bcc_dest = array_filter($bcc_dest);
+      $bcc_dest = implode(",", $bcc_dest);
+      if (empty($bcc_dest)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'BCC map destination cannot be empty'
+        );
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps`
+          WHERE `local_dest` = :local_dest AND `type` = :type");
+        $stmt->execute(array(':local_dest' => $local_dest_sane, ':type' => $type));
+        $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+        return false;
+      }
+      if ($num_results != 0) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'A BCC map entry "' . htmlspecialchars($local_dest_sane) . '" exists for type "' . $type . '"'
+        );
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES
+          (:local_dest, :bcc_dest, :domain, :active, :type)");
+        $stmt->execute(array(
+          ':local_dest' => $local_dest_sane,
+          ':bcc_dest' => $bcc_dest,
+          ':domain' => $domain,
+          ':active' => $active,
+          ':type' => $type
+        ));
+      }
+      catch (PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+        return false;
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => 'BCC map entry saved'
+      );
+    break;
+    case 'edit':
+      if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        $is_now = bcc('details', $id);
+        if (!empty($is_now)) {
+          $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
+          $bcc_dest = (!empty($_data['bcc_dest'])) ? $_data['bcc_dest'] : $is_now['bcc_dest'];
+          $local_dest = $is_now['local_dest'];
+          $type = (!empty($_data['type'])) ? $_data['type'] : $is_now['type'];
+        }
+        else {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => sprintf($lang['danger']['access_denied'])
+          );
+          return false;
+        }
+        $bcc_dest = array_map('trim', preg_split( "/( |,|;|\n)/", $bcc_dest));
+        $active = intval($_data['active']);
+        foreach ($bcc_dest as &$bcc_dest_e) {
+          if (!filter_var($bcc_dest_e, FILTER_VALIDATE_EMAIL)) {
+            $bcc_dest_e = null;;
+          }
+          $bcc_dest_e = strtolower($bcc_dest_e);
+        }
+        $bcc_dest = array_filter($bcc_dest);
+        $bcc_dest = implode(",", $bcc_dest);
+        if (empty($bcc_dest)) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'BCC map destination cannot be empty'
+          );
+          return false;
+        }
+        try {
+          $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps`
+            WHERE `local_dest` = :local_dest AND `type` = :type");
+          $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type));
+          $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id'];
+        }
+        catch(PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+          return false;
+        }
+        if (isset($id_now) && $id_now != $id) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'A BCC map entry ' . htmlspecialchars($local_dest) . ' exists for this type'
+          );
+          return false;
+        }
+        try {
+          $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id");
+          $stmt->execute(array(
+            ':bcc_dest' => $bcc_dest,
+            ':active' => $active,
+            ':type' => $type,
+            ':id' => $id
+          ));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+          return false;
+        }
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => 'BCC map entry edited'
+      );
+    break;
+    case 'details':
+      $bccdata = array();
+      $id = intval($_data);
+      try {
+        $stmt = $pdo->prepare("SELECT `id`,
+          `local_dest`,
+          `bcc_dest`,
+          `active` AS `active_int`,
+          CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
+          `type`,
+          `created`,
+          `domain`,
+          `modified` FROM `bcc_maps`
+            WHERE `id` = :id");
+        $stmt->execute(array(':id' => $id));
+        $bccdata = $stmt->fetch(PDO::FETCH_ASSOC);
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+        return false;
+      }
+      if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $bccdata['domain'])) {
+        $bccdata = null;
+        return false;
+      }
+      return $bccdata;
+    break;
+    case 'get':
+      $bccdata = array();
+      $all_items = array();
+      $id = intval($_data);
+      try {
+        $stmt = $pdo->query("SELECT `id`, `domain` FROM `bcc_maps`");
+        $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+        return false;
+      }
+      foreach ($all_items as $i) {
+        if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $i['domain'])) {
+          $bccdata[] = $i['id'];
+        }
+      }
+      $all_items = null;
+      return $bccdata;
+    break;
+    case 'delete':
+      $ids = (array)$_data['id'];
+      foreach ($ids as $id) {
+        if (!is_numeric($id)) {
+          return false;
+        }
+        try {
+          $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id");
+          $stmt->execute(array(':id' => $id));
+          $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain'];
+          if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => sprintf($lang['danger']['access_denied'])
+            );
+            return false;
+          }
+          $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :id");
+          $stmt->execute(array(':id' => $id));
+        }
+        catch (PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+          return false;
+        }
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => 'Deleted BCC map id/s ' . implode(', ', $ids)
+      );
+      return true;
+    break;
+  }
+}
\ No newline at end of file
diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php
index 167647fc..7e8f7b03 100644
--- a/data/web/inc/functions.customize.inc.php
+++ b/data/web/inc/functions.customize.inc.php
@@ -1,5 +1,4 @@
 <?php
-
 function customize($_action, $_item, $_data = null) {
 	global $redis;
 	global $lang;
diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php
index 44e4e5b4..6c9e1692 100644
--- a/data/web/inc/functions.fail2ban.inc.php
+++ b/data/web/inc/functions.fail2ban.inc.php
@@ -1,4 +1,5 @@
 <?php
+if (F2B == 1) {
 function fail2ban($_action, $_data = null) {
   global $redis;
   global $lang;
@@ -95,4 +96,5 @@ function fail2ban($_action, $_data = null) {
       );
     break;
   }
-}
\ No newline at end of file
+}
+}
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 654f8972..d1410f97 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -722,7 +722,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           $active = intval($_data['active']);
           $quota_b		= ($quota_m * 1048576);
-          $maildir		= $domain."/".$local_part."/";
+          $maildir		= $domain . "/" . $local_part . "/mail-" . time() . "/";
           if (!is_valid_domain_name($domain)) {
             $_SESSION['return'] = array(
               'type' => 'danger',
@@ -3103,10 +3103,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           foreach ($ids as $id) {
             if (!is_numeric($id)) {
-              $_SESSION['return'] = array(
-                'type' => 'danger',
-                'msg' => $id
-              );
               return false;
             }
             try {
@@ -3154,10 +3150,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           foreach ($ids as $id) {
             if (!is_numeric($id)) {
-              $_SESSION['return'] = array(
-                'type' => 'danger',
-                'msg' => $id
-              );
               return false;
             }
             try {
@@ -3366,6 +3358,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $stmt->execute(array(
                 ':domain' => '%@'.$domain,
               ));
+              $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain");
+              $stmt->execute(array(
+                ':domain' => '%@'.$domain,
+              ));
             }
             catch (PDOException $e) {
               $_SESSION['return'] = array(
@@ -3486,6 +3482,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $stmt->execute(array(
                 ':alias_domain' => $alias_domain,
               ));
+              $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain");
+              $stmt->execute(array(
+                ':domain' => '%@'.$alias_domain,
+              ));
             }
             catch (PDOException $e) {
               $_SESSION['return'] = array(
@@ -3580,6 +3580,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $stmt->execute(array(
                 ':username' => $username
               ));
+              $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :username");
+              $stmt->execute(array(
+                ':username' => $username
+              ));
               $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias`
                   WHERE `goto` REGEXP :username");
               $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index 15ba214b..24a7e072 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "14112017_2103";
+    $db_version = "16112017_2259";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -202,6 +202,7 @@ function init_db_schema() {
           "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
         ),
         "keys" => array(
           "fkey" => array(
@@ -325,6 +326,27 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "bcc_maps" => array(
+        "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
+          "local_dest" => "VARCHAR(255) NOT NULL",
+          "bcc_dest" => "VARCHAR(255) NOT NULL",
+          "domain" => "VARCHAR(255) NOT NULL",
+          "type" => "ENUM('sender','rcpt')",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
+          "active" => "TINYINT(1) NOT NULL DEFAULT '0'"
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          ),
+          "key" => array(
+            "local_dest" => array("local_dest"),
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "tfa" => array(
         "cols" => array(
           "id" => "INT NOT NULL AUTO_INCREMENT",
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 1533e24f..3f13e7dc 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -67,6 +67,7 @@ include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.bcc.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 2b93af15..12d8b3d9 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -118,3 +118,6 @@ $OTP_LABEL = "mailcow UI";
 
 // Default "to" address in relay test tool
 $RELAY_TO = "null@hosted.mailcow.de";
+
+// Internal constants, can be ignored
+define("F2B", 1);
\ No newline at end of file
diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index 9e2afa2c..9e3c6868 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -314,7 +314,56 @@ jQuery(function($){
       }
     });
   }
-
+  function draw_bcc_table() {
+    ft_bcc_table = FooTable.init('#bcc_table', {
+      "columns": [
+        {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
+        {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
+        {"name":"type","title":"Type"},
+        {"name":"local_dest","title":"Local destination"},
+        {"name":"bcc_dest","title":"BCC destination/s"},
+        {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
+        {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+      ],
+      "empty": lang.empty,
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/bcc/all',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw bcc table');
+        },
+        success: function (data) {
+          $.each(data, function (i, item) {
+            item.action = '<div class="btn-group">' +
+              '<a href="/edit.php?bcc=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
+              '<a href="#" id="delete_selected" data-id="single-bcc" data-api-url="delete/bcc" data-item="' + item.id + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
+              '</div>';
+            item.chkbox = '<input type="checkbox" data-id="bcc" name="multi_select" value="' + item.id + '" />';
+            if (item.type == 'sender') {
+              item.type = '<span id="active-script" class="label label-success">Sender</span>';
+            } else {
+              item.type = '<span id="inactive-script" class="label label-warning">Recipient</span>';
+            }
+          });
+        }
+      }),
+      "paging": {
+        "enabled": true,
+        "limit": 5,
+        "size": pagination_size
+      },
+      "filtering": {
+        "enabled": true,
+        "position": "left",
+        "placeholder": lang.filter_table
+      },
+      "sorting": {
+        "enabled": true
+      }
+    });
+  }
   function draw_alias_table() {
     ft_alias_table = FooTable.init('#alias_table', {
       "columns": [
@@ -530,5 +579,6 @@ jQuery(function($){
   draw_aliasdomain_table();
   draw_sync_job_table();
   draw_filter_table();
+  draw_bcc_table();
 
 });
diff --git a/data/web/json_api.php b/data/web/json_api.php
index b622c312..9ada1044 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -258,6 +258,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "bcc":
+            if (isset($_POST['attr'])) {
+              $attr = (array)json_decode($_POST['attr'], true);
+              if (bcc('add', $attr) === false) {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'error',
+                    'msg' => 'Cannot add item'
+                  ));
+                }
+              }
+              else {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'success',
+                    'msg' => 'Task completed'
+                  ));
+                }
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Cannot find attributes in post data'
+              ));
+            }
+          break;
           case "domain-policy":
             if (isset($_POST['attr'])) {
               $attr = (array)json_decode($_POST['attr'], true);
@@ -1034,6 +1067,42 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               break;
             }
           break;
+          case "bcc":
+            switch ($object) {
+              case "all":
+                $bcc_items = bcc('get');
+                if (!empty($bcc_items)) {
+                  foreach ($bcc_items as $bcc_item) {
+                    if ($details = bcc('details', $bcc_item)) {
+                      $data[] = $details;
+                    }
+                    else {
+                      continue;
+                    }
+                  }
+                }
+                if (!isset($data) || empty($data)) {
+                  echo '{}';
+                }
+                else {
+                  echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+              break;
+
+              default:
+                $data = bcc('details', $object);
+                if (!empty($data)) {
+                  $data[] = $details;
+                }
+                if (!isset($data) || empty($data)) {
+                  echo '{}';
+                }
+                else {
+                  echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+              break;
+            }
+          break;
           case "policy_wl_mailbox":
             switch ($object) {
               default:
@@ -1464,6 +1533,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "bcc":
+            if (isset($_POST['items'])) {
+              $items = (array)json_decode($_POST['items'], true);
+              if (is_array($items)) {
+                if (bcc('delete', array('id' => $items)) === false) {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'error',
+                      'msg' => 'Deletion of items/s failed'
+                    ));
+                  }
+                }
+                else {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'success',
+                      'msg' => 'Task completed'
+                    ));
+                  }
+                }
+              }
+              else {
+                echo json_encode(array(
+                  'type' => 'error',
+                  'msg' => 'Cannot find id array in post data'
+                ));
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Cannot find items in post data'
+              ));
+            }
+          break;
           case "fwdhost":
             if (isset($_POST['items'])) {
               $items = (array)json_decode($_POST['items'], true);
@@ -2313,7 +2423,51 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
                 'msg' => 'Incomplete post data'
               ));
             }
-          break;          
+          break;
+          case "bcc":
+            if (isset($_POST['items']) && isset($_POST['attr'])) {
+              $items = (array)json_decode($_POST['items'], true);
+              $attr = (array)json_decode($_POST['attr'], true);
+              $postarray = array_merge(array('id' => $items), $attr);
+              if (is_array($postarray['id'])) {
+                if (bcc('edit', $postarray) === false) {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'error',
+                      'msg' => 'Edit failed'
+                    ));
+                  }
+                  exit();
+                }
+                else {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'success',
+                      'msg' => 'Task completed'
+                    ));
+                  }
+                }
+              }
+              else {
+                echo json_encode(array(
+                  'type' => 'error',
+                  'msg' => 'Incomplete post data'
+                ));
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Incomplete post data'
+              ));
+            }
+          break;
           case "resource":
             if (isset($_POST['items']) && isset($_POST['attr'])) {
               $items = (array)json_decode($_POST['items'], true);
diff --git a/data/web/mailbox.php b/data/web/mailbox.php
index 193b11c8..b4b8feec 100644
--- a/data/web/mailbox.php
+++ b/data/web/mailbox.php
@@ -21,6 +21,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
     </li>
     <li role="presentation"><a href="#tab-syncjobs" aria-controls="tab-syncjobs" role="tab" data-toggle="tab"><?=$lang['mailbox']['sync_jobs'];?></a></li>
     <li role="presentation"><a href="#tab-filters" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['filters'];?></a></li>
+    <li role="presentation"><a href="#tab-bcc" aria-controls="tab-filters" role="tab" data-toggle="tab">BCC maps</a></li>
   </ul>
 
 	<div class="row">
@@ -207,6 +208,34 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
           </div>
         </div>
 
+        <div role="tabpanel" class="tab-pane" id="tab-bcc">
+          <div class="panel panel-default">
+            <div class="panel-heading">
+              <h3 class="panel-title">BCC maps</h3>
+            </div>
+            <p style="margin:10px" class="help-block">A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.
+            The local destination will not be informed about a failed delivery.</p>
+            <div class="table-responsive">
+              <table class="table table-striped" id="bcc_table"></table>
+            </div>
+            <div class="mass-actions-mailbox">
+              <div class="btn-group">
+                <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="bcc" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
+                <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
+                <ul class="dropdown-menu">
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
+                  <li role="separator" class="divider"></li>
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"sender"}' href="#">Switch to sender map type</a></li>
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">Switch to recipient map type</a></li>
+                  <li role="separator" class="divider"></li>
+                  <li><a id="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#"><?=$lang['mailbox']['remove'];?></a></li>
+                </ul>
+                <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addBCCModalAdmin"><span class="glyphicon glyphicon-plus"></span> Add BCC map</a>
+              </div>
+            </div>
+          </div>
+        </div>
       </div> <!-- /tab-content -->
     </div> <!-- /col-md-12 -->
   </div> <!-- /row -->
diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php
index 192124c2..113b3289 100644
--- a/data/web/modals/mailbox.php
+++ b/data/web/modals/mailbox.php
@@ -509,6 +509,77 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
     </div>
   </div>
 </div><!-- add add_filter modal -->
+<!-- add add_bcc modal -->
+<div class="modal fade" id="addBCCModalAdmin" tabindex="-1" role="dialog" aria-hidden="true">
+  <div class="modal-dialog modal-lg">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
+        <h3 class="modal-title">BCC map</h3>
+      </div>
+      <div class="modal-body">
+				<form class="form-horizontal" role="form" data-id="add_bcc">
+          <div class="form-group">
+            <label class="control-label col-sm-2" for="local_dest">Local destination:</label>
+            <div class="col-sm-10">
+              <select id="addSelectLocalDest" name="local_dest" id="local_dest" required>
+              <?php
+              $domains = mailbox('get', 'domains');
+              $alias_domains = mailbox('get', 'alias_domains');
+              if (!empty($domains)) {
+                foreach ($domains as $domain) {
+                  echo "<option>".htmlspecialchars($domain)."</option>";
+                }
+              }
+              if (!empty($alias_domains)) {
+                foreach ($alias_domains as $alias_domain) {
+                  echo "<option>".htmlspecialchars($alias_domain)."</option>";
+                }
+              }
+              if (!empty($domains)) {
+                foreach ($domains as $domain) {
+                  $mailboxes = mailbox('get', 'mailboxes', $domain);
+                  foreach ($mailboxes as $mailbox) {
+                    echo "<option>".htmlspecialchars($mailbox)."</option>";
+                  }
+                }
+              }
+              ?>
+              </select>
+            </div>
+          </div>
+          <div class="form-group">
+            <label class="control-label col-sm-2" for="type">BCC map type:</label>
+            <div class="col-sm-10">
+              <select id="addFBCCType" name="type" id="type" required>
+                <option value="sender">Sender map</option>
+                <option value="rcpt">Recipient map</option>
+              </select>
+            </div>
+          </div>
+					<div class="form-group">
+						<label class="control-label col-sm-2" for="bcc_dest">BCC destination/s:</label>
+						<div class="col-sm-10">
+							<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="bcc_dest" name="bcc_dest" required></textarea>
+						</div>
+					</div>
+					<div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+							<div class="checkbox">
+							<label><input type="checkbox" value="1" name="active" checked> <?=$lang['add']['active'];?></label>
+							</div>
+						</div>
+					</div>
+					<div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+              <button class="btn btn-success" id="add_item" data-id="add_bcc" data-api-url='add/bcc' data-api-attr='{}' href="#"><?=$lang['admin']['add'];?></button>
+						</div>
+					</div>
+				</form>
+      </div>
+    </div>
+  </div>
+</div><!-- add add_bcc modal -->
 <!-- log modal -->
 <div class="modal fade" id="syncjobLogModal" tabindex="-1" role="dialog" aria-labelledby="syncjobLogModalLabel">
   <div class="modal-dialog modal-lg" role="document">
diff --git a/docker-compose.yml b/docker-compose.yml
index cc97cbce..d34cea93 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -179,7 +179,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.7
+      image: mailcow/postfix:1.8
       build: ./data/Dockerfiles/postfix
       volumes:
         - ./data/conf/postfix:/opt/postfix/conf

From 1bd0d9270fc44a867934d8d7b279136fe0767636 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andre.peters@debinux.de>
Date: Sun, 19 Nov 2017 15:19:37 +0100
Subject: [PATCH 02/40] Only rebuild on master pushes

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index e07dde21..c7614088 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@ script:
 - docker-compose push
 branches:
   only:
-  - dev
+  - master
 env:
   global:
   - secure: MpxpTwD7f0CNEVLitSpVmocK7O9r+BwFE1deEHK4AlQo/oc9cOlhGe1EL3mx9zbglPmjlDg/8kMUGv6vSirIabfBo9Szjps76bHckFr9lr2Ykkg0e29oC8pgPpSXD1eY/1ZIN/FvIkxpUFLETo1okS/j9q/A0DCGFmti0n3EoMORsgRz9CpNAiEh0zpSd6+euPAGHuczuCrDuO84my9bIOCjA/+aPunHNeXiuM8yIM2SxCSyGtIKT0+jvquIvLF58VxivysXBlRfhDn8fhB09nXA2Ru/derYQACfcmNSn9Pd4bDpebPJW5B9H/XA8xjb58uKinUlncbAMB/QnxoT75j9YRWJZRSQ+34XNYP6ZgK9soZ2TC6djQyEKTUu45Kp/1s+poSn42m9jytJJTmmK0KxsZTRcC8JD5nrjIMZWPUNNTwC5L4+I7ZRWg2WooK3LNyq1Ng8Hn6W77wSgsvAJw2HD3Lx58AprGUhHuBeaIZRuSN9aKwZrl9vKQJLqPnOp/nF2EC6kot5HYYtcotGtETXPUDih21gWD5ZM2BqVqYfQQnJnNMgeYmMdj6QQuTFqhuNJf7hXRIRkTnD3j1gDOLKQZazW0+N2JE8XWDFwi6fKScDsxT85lJti9HmzHa7+k4RVHmUYuDgRoPuzUgjWHvPsiz3/Z8WQ9JYpH84S8w=

From e1eb83ef87d75544aebdec11a71a64a3d67dfbf3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andre.peters@debinux.de>
Date: Tue, 21 Nov 2017 09:33:22 +0100
Subject: [PATCH 03/40] [Web] Update libs, fix U2F for Firefox Quantum

---
 data/web/inc/footer.inc.php                   |   64 +-
 data/web/inc/lib/composer.lock                |   20 +-
 .../inc/lib/vendor/composer/installed.json    |  122 +-
 .../lib/vendor/phpmailer/phpmailer/VERSION    |    2 +-
 .../phpmailer/phpmailer/class.phpmailer.php   |    4 +-
 .../vendor/phpmailer/phpmailer/class.pop3.php |    2 +-
 .../vendor/phpmailer/phpmailer/class.smtp.php |    4 +-
 .../vendor/robthree/twofactorauth/.gitignore  |    5 +-
 .../vendor/robthree/twofactorauth/.travis.yml |   13 +-
 .../.vs/config/applicationhost.config         | 1031 -----------------
 .../vendor/robthree/twofactorauth/README.md   |    2 +-
 .../robthree/twofactorauth/composer.json      |    2 +-
 .../ConvertUnixTimeDotComTimeProvider.php     |    2 +-
 .../lib/Providers/Time/HttpTimeProvider.php   |    3 +-
 .../u2flib-server/src/u2flib_server/U2F.php.1 |  507 ++++++++
 data/web/index.php                            |   14 +-
 data/web/js/u2f-api.js                        |  799 +++++++------
 data/web/json_api.php                         |   25 +-
 18 files changed, 1113 insertions(+), 1508 deletions(-)
 delete mode 100644 data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
 create mode 100644 data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1

diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
index 9246d230..3ba758be 100644
--- a/data/web/inc/footer.inc.php
+++ b/data/web/inc/footer.inc.php
@@ -40,6 +40,7 @@ $(document).ready(function() {
     backdrop: 'static',
     keyboard: false
   });
+  $('#u2f_status_auth').html('<p><span class="glyphicon glyphicon-refresh glyphicon-spin"></span> Initializing, please wait...</p>');
   $('#ConfirmTFAModal').on('shown.bs.modal', function(){
       $(this).find('#token').focus();
       // If U2F
@@ -49,20 +50,21 @@ $(document).ready(function() {
           cache: false,
           dataType: 'script',
           url: "/api/v1/get/u2f-authentication/<?= (isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null; ?>",
-          success: function(data){
+          complete: function(data){
+            $('#u2f_status_auth').html('<?=$lang['tfa']['waiting_usb_auth'];?>');
             data;
+            setTimeout(function() {
+              console.log("Ready to authenticate");
+              u2f.sign(appId, challenge, registeredKeys, function(data) {
+                var form = document.getElementById('u2f_auth_form');
+                var auth = document.getElementById('u2f_auth_data');
+                console.log("Authenticate callback", data);
+                auth.value = JSON.stringify(data);
+                form.submit();
+              });
+            }, 1000);
           }
         });
-        setTimeout(function() {
-          console.log("sign: ", req);
-          u2f.sign(req, function(data) {
-            var form = document.getElementById('u2f_auth_form');
-            var auth = document.getElementById('u2f_auth_data');
-            console.log("Authenticate callback", data);
-            auth.value = JSON.stringify(data);
-            form.submit();
-          });
-        }, 1000);
       }
   });
   <?php endif; ?>
@@ -81,32 +83,34 @@ $(document).ready(function() {
     if ($(this).val() == "u2f") {
       $('#U2FModal').modal('show');
       $("option:selected").prop("selected", false);
+      $('#u2f_status_reg').html('<p><span class="glyphicon glyphicon-refresh glyphicon-spin"></span> Initializing, please wait...</p>');
       $.ajax({
         type: "GET",
         cache: false,
         dataType: 'script',
         url: "/api/v1/get/u2f-registration/<?= (isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null; ?>",
-        success: function(data){
+        complete: function(data){
           data;
+          setTimeout(function() {
+            console.log("Ready to register");
+            $('#u2f_status_reg').html('<?=$lang['tfa']['waiting_usb_register'];?>');
+            u2f.register(appId, registerRequests, registeredKeys, function(deviceResponse) {
+              var form  = document.getElementById('u2f_reg_form');
+              var reg   = document.getElementById('u2f_register_data');
+              console.log("Register callback: ", data);
+              if (deviceResponse.errorCode && deviceResponse.errorCode != 0) {
+                var u2f_return_code = document.getElementById('u2f_return_code');
+                u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
+                if (deviceResponse.errorCode == "4") { deviceResponse.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle"; }
+                u2f_return_code.innerHTML = 'Error code: ' + deviceResponse.errorCode;
+                return;
+              }
+              reg.value = JSON.stringify(deviceResponse);
+              form.submit();
+            });
+          }, 1000);
         }
       });
-      setTimeout(function() {
-        console.log("Register: ", req);
-        u2f.register([req], sigs, function(data) {
-          var form  = document.getElementById('u2f_reg_form');
-          var reg   = document.getElementById('u2f_register_data');
-          console.log("Register callback", data);
-          if (data.errorCode && data.errorCode != 0) {
-            var u2f_return_code = document.getElementById('u2f_return_code');
-            u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
-            if (data.errorCode == "4") { data.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle"; }
-            u2f_return_code.innerHTML = 'Error code: ' + data.errorCode;
-            return;
-          }
-          reg.value = JSON.stringify(data);
-          form.submit();
-        });
-      }, 1000);
     }
     if ($(this).val() == "none") {
       $('#DisableTFAModal').modal('show');
@@ -212,4 +216,4 @@ $(document).ready(function() {
 </html>
 <?php
 $stmt = null;
-$pdo = null;
+$pdo = null;
\ No newline at end of file
diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock
index 7199acc9..cca7113e 100644
--- a/data/web/inc/lib/composer.lock
+++ b/data/web/inc/lib/composer.lock
@@ -8,16 +8,16 @@
     "packages": [
         {
             "name": "phpmailer/phpmailer",
-            "version": "v5.2.25",
+            "version": "v5.2.26",
             "source": {
                 "type": "git",
                 "url": "https://github.com/PHPMailer/PHPMailer.git",
-                "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
+                "reference": "70362997bda4376378be7d92d81e2200550923f7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
-                "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7",
+                "reference": "70362997bda4376378be7d92d81e2200550923f7",
                 "shasum": ""
             },
             "require": {
@@ -81,20 +81,20 @@
                 }
             ],
             "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
-            "time": "2017-08-28T11:12:07+00:00"
+            "time": "2017-11-04T09:26:05+00:00"
         },
         {
             "name": "robthree/twofactorauth",
-            "version": "1.6",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/RobThree/TwoFactorAuth.git",
-                "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
+                "reference": "a77e7d822343bb88112baef808839cfae7bc5abb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
-                "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
+                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/a77e7d822343bb88112baef808839cfae7bc5abb",
+                "reference": "a77e7d822343bb88112baef808839cfae7bc5abb",
                 "shasum": ""
             },
             "require": {
@@ -132,7 +132,7 @@
                 "php",
                 "tfa"
             ],
-            "time": "2017-02-17T15:24:54+00:00"
+            "time": "2017-11-06T17:55:56+00:00"
         },
         {
             "name": "yubico/u2flib-server",
diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json
index 790d0662..22cd3e15 100644
--- a/data/web/inc/lib/vendor/composer/installed.json
+++ b/data/web/inc/lib/vendor/composer/installed.json
@@ -1,57 +1,4 @@
 [
-    {
-        "name": "robthree/twofactorauth",
-        "version": "1.6",
-        "version_normalized": "1.6.0.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/RobThree/TwoFactorAuth.git",
-            "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
-            "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "@stable"
-        },
-        "time": "2017-02-17T15:24:54+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "RobThree\\Auth\\": "lib"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Rob Janssen",
-                "homepage": "http://robiii.me",
-                "role": "Developer"
-            }
-        ],
-        "description": "Two Factor Authentication",
-        "homepage": "https://github.com/RobThree/TwoFactorAuth",
-        "keywords": [
-            "Authentication",
-            "MFA",
-            "Multi Factor Authentication",
-            "Two Factor Authentication",
-            "authenticator",
-            "authy",
-            "php",
-            "tfa"
-        ]
-    },
     {
         "name": "yubico/u2flib-server",
         "version": "1.0.1",
@@ -90,18 +37,71 @@
         "homepage": "https://developers.yubico.com/php-u2flib-server"
     },
     {
-        "name": "phpmailer/phpmailer",
-        "version": "v5.2.25",
-        "version_normalized": "5.2.25.0",
+        "name": "robthree/twofactorauth",
+        "version": "1.6.1",
+        "version_normalized": "1.6.1.0",
         "source": {
             "type": "git",
-            "url": "https://github.com/PHPMailer/PHPMailer.git",
-            "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
+            "url": "https://github.com/RobThree/TwoFactorAuth.git",
+            "reference": "a77e7d822343bb88112baef808839cfae7bc5abb"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
-            "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
+            "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/a77e7d822343bb88112baef808839cfae7bc5abb",
+            "reference": "a77e7d822343bb88112baef808839cfae7bc5abb",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "@stable"
+        },
+        "time": "2017-11-06T17:55:56+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "RobThree\\Auth\\": "lib"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Rob Janssen",
+                "homepage": "http://robiii.me",
+                "role": "Developer"
+            }
+        ],
+        "description": "Two Factor Authentication",
+        "homepage": "https://github.com/RobThree/TwoFactorAuth",
+        "keywords": [
+            "Authentication",
+            "MFA",
+            "Multi Factor Authentication",
+            "Two Factor Authentication",
+            "authenticator",
+            "authy",
+            "php",
+            "tfa"
+        ]
+    },
+    {
+        "name": "phpmailer/phpmailer",
+        "version": "v5.2.26",
+        "version_normalized": "5.2.26.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/PHPMailer/PHPMailer.git",
+            "reference": "70362997bda4376378be7d92d81e2200550923f7"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7",
+            "reference": "70362997bda4376378be7d92d81e2200550923f7",
             "shasum": ""
         },
         "require": {
@@ -131,7 +131,7 @@
         "suggest": {
             "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
         },
-        "time": "2017-08-28T11:12:07+00:00",
+        "time": "2017-11-04T09:26:05+00:00",
         "type": "library",
         "installation-source": "dist",
         "autoload": {
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
index f23b9706..f0fb1a22 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
@@ -1 +1 @@
-5.2.25
+5.2.26
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
index 8042b384..99f9092c 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
@@ -31,7 +31,7 @@ class PHPMailer
      * The PHPMailer Version number.
      * @var string
      */
-    public $Version = '5.2.25';
+    public $Version = '5.2.26';
 
     /**
      * Email priority.
@@ -659,6 +659,8 @@ class PHPMailer
         if ($exceptions !== null) {
             $this->exceptions = (boolean)$exceptions;
         }
+        //Pick an appropriate debug output format automatically
+        $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
     }
 
     /**
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
index f2c4e374..f833ac61 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
@@ -34,7 +34,7 @@ class POP3
      * @var string
      * @access public
      */
-    public $Version = '5.2.25';
+    public $Version = '5.2.26';
 
     /**
      * Default POP3 port number.
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
index d8af427e..be6ddce4 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
@@ -30,7 +30,7 @@ class SMTP
      * The PHPMailer SMTP version number.
      * @var string
      */
-    const VERSION = '5.2.25';
+    const VERSION = '5.2.26';
 
     /**
      * SMTP line break constant.
@@ -81,7 +81,7 @@ class SMTP
      * @deprecated Use the `VERSION` constant instead
      * @see SMTP::VERSION
      */
-    public $Version = '5.2.25';
+    public $Version = '5.2.26';
 
     /**
      * SMTP server port number.
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore b/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
index 1a31666a..8a25841c 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
@@ -183,4 +183,7 @@ UpgradeLog*.htm
 FakesAssemblies/
 
 # Composer
-/vendor
\ No newline at end of file
+/vendor
+
+# .vs
+.vs/
\ No newline at end of file
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml b/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
index 034653bb..204dc63a 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
@@ -1,11 +1,18 @@
 language: php
 
+dist: trusty
+matrix:
+  include:
+    - php: 5.3
+      dist: precise
+
 php:
-  - 5.3
   - 5.4
   - 5.5
   - 5.6
-  - 7
+  - 7.0
+  - 7.1
   - hhvm
 
-script: phpunit --coverage-text tests
+script:
+  - if [[ "$TRAVIS_PHP_VERSION" == '5.6' ]]; then phpunit --coverage-text tests ; fi
\ No newline at end of file
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config b/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
deleted file mode 100644
index 4b9bf477..00000000
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
+++ /dev/null
@@ -1,1031 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    IIS configuration sections.
-
-    For schema documentation, see
-    %IIS_BIN%\config\schema\IIS_schema.xml.
-    
-    Please make a backup of this file before making any changes to it.
-
-    NOTE: The following environment variables are available to be used
-          within this file and are understood by the IIS Express.
-
-          %IIS_USER_HOME% - The IIS Express home directory for the user
-          %IIS_SITES_HOME% - The default home directory for sites
-          %IIS_BIN% - The location of the IIS Express binaries
-          %SYSTEMDRIVE% - The drive letter of %IIS_BIN%
-
--->
-<configuration>
-
-    <!--
-
-        The <configSections> section controls the registration of sections.
-        Section is the basic unit of deployment, locking, searching and
-        containment for configuration settings.
-        
-        Every section belongs to one section group.
-        A section group is a container of logically-related sections.
-        
-        Sections cannot be nested.
-        Section groups may be nested.
-        
-        <section
-            name=""  [Required, Collection Key] [XML name of the section]
-            allowDefinition="Everywhere" [MachineOnly|MachineToApplication|AppHostOnly|Everywhere] [Level where it can be set]
-            overrideModeDefault="Allow"  [Allow|Deny] [Default delegation mode]
-            allowLocation="true"  [true|false] [Allowed in location tags]
-        />
-        
-        The recommended way to unlock sections is by using a location tag:
-        <location path="Default Web Site" overrideMode="Allow">
-            <system.webServer>
-                <asp />
-            </system.webServer>
-        </location>
-
-    -->
-    <configSections>
-        <sectionGroup name="system.applicationHost">
-            <section name="applicationPools" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="configHistory" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="customMetadata" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="listenerAdapters" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="log" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="serviceAutoStartProviders" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="sites" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="webLimits" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-        </sectionGroup>
-
-        <sectionGroup name="system.webServer">
-            <section name="asp" overrideModeDefault="Deny" />
-            <section name="caching" overrideModeDefault="Allow" />
-            <section name="cgi" overrideModeDefault="Deny" />
-            <section name="defaultDocument" overrideModeDefault="Allow" />
-            <section name="directoryBrowse" overrideModeDefault="Allow" />
-            <section name="fastCgi" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="globalModules" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-            <section name="handlers" overrideModeDefault="Deny" />
-            <section name="httpCompression" overrideModeDefault="Allow" />
-            <section name="httpErrors" overrideModeDefault="Allow" />
-            <section name="httpLogging" overrideModeDefault="Deny" />
-            <section name="httpProtocol" overrideModeDefault="Allow" />
-            <section name="httpRedirect" overrideModeDefault="Allow" />
-            <section name="httpTracing" overrideModeDefault="Deny" />
-            <section name="isapiFilters" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
-            <section name="modules" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
-            <section name="applicationInitialization" allowDefinition="MachineToApplication" overrideModeDefault="Allow" />
-            <section name="odbcLogging" overrideModeDefault="Deny" />
-            <sectionGroup name="security">
-                <section name="access" overrideModeDefault="Deny" />
-                <section name="applicationDependencies" overrideModeDefault="Deny" />
-                <sectionGroup name="authentication">
-                    <section name="anonymousAuthentication" overrideModeDefault="Deny" />
-                    <section name="basicAuthentication" overrideModeDefault="Deny" />
-                    <section name="clientCertificateMappingAuthentication" overrideModeDefault="Deny" />
-                    <section name="digestAuthentication" overrideModeDefault="Deny" />
-                    <section name="iisClientCertificateMappingAuthentication" overrideModeDefault="Deny" />
-                    <section name="windowsAuthentication" overrideModeDefault="Deny" />
-                </sectionGroup>
-                <section name="authorization" overrideModeDefault="Allow" />
-                <section name="ipSecurity" overrideModeDefault="Deny" />
-                <section name="dynamicIpSecurity" overrideModeDefault="Deny" />
-                <section name="isapiCgiRestriction" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
-                <section name="requestFiltering" overrideModeDefault="Allow" />
-            </sectionGroup>
-            <section name="serverRuntime" overrideModeDefault="Deny" />
-            <section name="serverSideInclude" overrideModeDefault="Deny" />
-            <section name="staticContent" overrideModeDefault="Allow" />
-            <sectionGroup name="tracing">
-                <section name="traceFailedRequests" overrideModeDefault="Allow" />
-                <section name="traceProviderDefinitions" overrideModeDefault="Deny" />
-            </sectionGroup>
-            <section name="urlCompression" overrideModeDefault="Allow" />
-            <section name="validation" overrideModeDefault="Allow" />
-            <sectionGroup name="webdav">
-                <section name="globalSettings" overrideModeDefault="Deny" />
-                <section name="authoring" overrideModeDefault="Deny" />
-                <section name="authoringRules" overrideModeDefault="Deny" />
-            </sectionGroup>
-            <sectionGroup name="rewrite">
-                <section name="allowedServerVariables" overrideModeDefault="Deny" />
-                <section name="rules" overrideModeDefault="Allow" />
-                <section name="outboundRules" overrideModeDefault="Allow" />
-                <section name="globalRules" overrideModeDefault="Deny" allowDefinition="AppHostOnly" />
-                <section name="providers" overrideModeDefault="Allow" />
-                <section name="rewriteMaps" overrideModeDefault="Allow" />
-            </sectionGroup>
-            <section name="webSocket" overrideModeDefault="Deny" />
-        <section name="aspNetCore" overrideModeDefault="Allow" /></sectionGroup>
-    </configSections>
-
-    <configProtectedData>
-        <providers>
-            <add name="IISWASOnlyRsaProvider" type="" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useMachineContainer="true" useOAEP="false" />
-            <add name="AesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisConfigurationKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAAKmFQvWHDEETRz8l2bjZlRxIkwcqTFaCUnCLljn3Q1OkesrhEO9YyLyx4bUhsj1/DyShAv7OAFFhXlrlomaornnk5PLeyO4lIXxaiT33yOFUUgxDx4GSaygkqghVV0tO5yQ/XguUBp2juMfZyztnsNa4pLcz7ZNZQ6p4yn9hxwNs=" />
-            <add name="IISWASOnlyAesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAA4WoiRJ8KHwzAG8AgejPxEOO4/2Vhkolbwo/8gZeNdUDSD36m55hWv4uC9tr/MlKdnwRLL0NhT50Gccyftqz5xTZ0dg5FtvQhTw/he1NwexTKbV+I4Zrd+sZUqHZTsr7JiEr6OHGXL70qoISW5G2m9U8wKT3caPiDPNj2aAaYPLo=" />
-        </providers>
-    </configProtectedData>
-
-    <system.applicationHost>
-
-        <applicationPools>
-            <add name="Clr4IntegratedAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
-            <add name="Clr4ClassicAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
-            <add name="Clr2IntegratedAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
-            <add name="Clr2ClassicAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
-            <add name="UnmanagedClassicAppPool" managedRuntimeVersion="" managedPipelineMode="Classic" autoStart="true" />
-            <applicationPoolDefaults managedRuntimeLoader="v4.0">
-                <processModel />
-            </applicationPoolDefaults>
-        </applicationPools>
-
-        <!--
-
-          The <listenerAdapters> section defines the protocols with which the
-          Windows Process Activation Service (WAS) binds.
-
-        -->
-        <listenerAdapters>
-            <add name="http" />
-        </listenerAdapters>
-
-        <sites>
-            <site name="WebSite1" id="1" serverAutoStart="true">
-                <application path="/">
-                    <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
-                </application>
-                <bindings>
-                    <binding protocol="http" bindingInformation=":8080:localhost" />
-                </bindings>
-            </site>
-            <siteDefaults>
-                <logFile logFormat="W3C" directory="%IIS_USER_HOME%\Logs" />
-                <traceFailedRequestsLogging directory="%IIS_USER_HOME%\TraceLogFiles" enabled="true" maxLogFileSizeKB="1024" />
-            </siteDefaults>
-            <applicationDefaults applicationPool="Clr4IntegratedAppPool" />
-            <virtualDirectoryDefaults allowSubDirConfig="true" />
-        </sites>
-
-        <webLimits />
-
-    </system.applicationHost>
-
-    <system.webServer>
-
-        <serverRuntime />
-
-        <asp scriptErrorSentToBrowser="true">
-            <cache diskTemplateCacheDirectory="%TEMP%\iisexpress\ASP Compiled Templates" />
-            <limits />
-        </asp>
-
-        <caching enabled="true" enableKernelCache="true">
-        </caching>
-
-        <cgi />
-
-        <defaultDocument enabled="true">
-            <files>
-                <add value="Default.htm" />
-                <add value="Default.asp" />
-                <add value="index.htm" />
-                <add value="index.html" />
-                <add value="iisstart.htm" />
-                <add value="default.aspx" />
-            </files>
-        </defaultDocument>
-
-        <directoryBrowse enabled="false" />
-
-        <fastCgi />
-
-        <!--
-
-          The <globalModules> section defines all native-code modules.
-          To enable a module, specify it in the <modules> section.
-
-        -->
-        <globalModules>
-            <add name="HttpLoggingModule" image="%IIS_BIN%\loghttp.dll" />
-            <add name="UriCacheModule" image="%IIS_BIN%\cachuri.dll" />
-<!--            <add name="FileCacheModule" image="%IIS_BIN%\cachfile.dll" />  -->
-            <add name="TokenCacheModule" image="%IIS_BIN%\cachtokn.dll" />
-<!--            <add name="HttpCacheModule" image="%IIS_BIN%\cachhttp.dll" /> -->
-            <add name="DynamicCompressionModule" image="%IIS_BIN%\compdyn.dll" />
-            <add name="StaticCompressionModule" image="%IIS_BIN%\compstat.dll" />
-            <add name="DefaultDocumentModule" image="%IIS_BIN%\defdoc.dll" />
-            <add name="DirectoryListingModule" image="%IIS_BIN%\dirlist.dll" />
-            <add name="ProtocolSupportModule" image="%IIS_BIN%\protsup.dll" />
-            <add name="HttpRedirectionModule" image="%IIS_BIN%\redirect.dll" />
-            <add name="ServerSideIncludeModule" image="%IIS_BIN%\iis_ssi.dll" />
-            <add name="StaticFileModule" image="%IIS_BIN%\static.dll" />
-            <add name="AnonymousAuthenticationModule" image="%IIS_BIN%\authanon.dll" />
-            <add name="CertificateMappingAuthenticationModule" image="%IIS_BIN%\authcert.dll" />
-            <add name="UrlAuthorizationModule" image="%IIS_BIN%\urlauthz.dll" />
-            <add name="BasicAuthenticationModule" image="%IIS_BIN%\authbas.dll" />
-            <add name="WindowsAuthenticationModule" image="%IIS_BIN%\authsspi.dll" />
-<!--            <add name="DigestAuthenticationModule" image="%IIS_BIN%\authmd5.dll" /> -->
-            <add name="IISCertificateMappingAuthenticationModule" image="%IIS_BIN%\authmap.dll" />
-            <add name="IpRestrictionModule" image="%IIS_BIN%\iprestr.dll" />
-            <add name="DynamicIpRestrictionModule" image="%IIS_BIN%\diprestr.dll" />
-            <add name="RequestFilteringModule" image="%IIS_BIN%\modrqflt.dll" />
-            <add name="CustomLoggingModule" image="%IIS_BIN%\logcust.dll" />
-            <add name="CustomErrorModule" image="%IIS_BIN%\custerr.dll" />
-<!--            <add name="TracingModule" image="%IIS_BIN%\iisetw.dll" /> -->
-            <add name="FailedRequestsTracingModule" image="%IIS_BIN%\iisfreb.dll" />
-            <add name="RequestMonitorModule" image="%IIS_BIN%\iisreqs.dll" />
-            <add name="IsapiModule" image="%IIS_BIN%\isapi.dll" />
-            <add name="IsapiFilterModule" image="%IIS_BIN%\filter.dll" />
-            <add name="CgiModule" image="%IIS_BIN%\cgi.dll" />
-            <add name="FastCgiModule" image="%IIS_BIN%\iisfcgi.dll" />
-<!--            <add name="WebDAVModule" image="%IIS_BIN%\webdav.dll" /> -->
-            <add name="RewriteModule" image="%IIS_BIN%\rewrite.dll" />
-            <add name="ConfigurationValidationModule" image="%IIS_BIN%\validcfg.dll" />
-            <add name="WebSocketModule" image="%IIS_BIN%\iiswsock.dll" />
-            <add name="WebMatrixSupportModule" image="%IIS_BIN%\webmatrixsup.dll" />
-            <add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness32" />
-            <add name="ManagedEngine64" image="%windir%\Microsoft.NET\Framework64\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness64" />
-            <add name="ManagedEngineV4.0_32bit" image="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness32" />
-            <add name="ManagedEngineV4.0_64bit" image="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness64" />
-            <add name="ApplicationInitializationModule" image="%IIS_BIN%\warmup.dll" />
-            <add name="AspNetCoreModule" image="%IIS_BIN%\aspnetcore.dll" />
-        </globalModules>
-
-        <httpCompression directory="%TEMP%\iisexpress\IIS Temporary Compressed Files">
-            <scheme name="gzip" dll="%IIS_BIN%\gzip.dll" />
-            <dynamicTypes>
-                <add mimeType="text/*" enabled="true" />
-                <add mimeType="message/*" enabled="true" />
-                <add mimeType="application/javascript" enabled="true" />
-                <add mimeType="application/atom+xml" enabled="true" />
-                <add mimeType="application/xaml+xml" enabled="true" />
-                <add mimeType="*/*" enabled="false" />
-            </dynamicTypes>
-            <staticTypes>
-                <add mimeType="text/*" enabled="true" />
-                <add mimeType="message/*" enabled="true" />
-                <add mimeType="image/svg+xml" enabled="true" />
-                <add mimeType="application/javascript" enabled="true" />
-                <add mimeType="application/atom+xml" enabled="true" />
-                <add mimeType="application/xaml+xml" enabled="true" />
-                <add mimeType="*/*" enabled="false" />
-            </staticTypes>
-        </httpCompression>
-
-        <httpErrors lockAttributes="allowAbsolutePathsWhenDelegated,defaultPath">
-            <error statusCode="401" prefixLanguageFilePath="%IIS_BIN%\custerr" path="401.htm" />
-            <error statusCode="403" prefixLanguageFilePath="%IIS_BIN%\custerr" path="403.htm" />
-            <error statusCode="404" prefixLanguageFilePath="%IIS_BIN%\custerr" path="404.htm" />
-            <error statusCode="405" prefixLanguageFilePath="%IIS_BIN%\custerr" path="405.htm" />
-            <error statusCode="406" prefixLanguageFilePath="%IIS_BIN%\custerr" path="406.htm" />
-            <error statusCode="412" prefixLanguageFilePath="%IIS_BIN%\custerr" path="412.htm" />
-            <error statusCode="500" prefixLanguageFilePath="%IIS_BIN%\custerr" path="500.htm" />
-            <error statusCode="501" prefixLanguageFilePath="%IIS_BIN%\custerr" path="501.htm" />
-            <error statusCode="502" prefixLanguageFilePath="%IIS_BIN%\custerr" path="502.htm" />
-        </httpErrors>
-
-        <httpLogging dontLog="false" />
-
-        <httpProtocol>
-            <customHeaders>
-                <clear />
-                <add name="X-Powered-By" value="ASP.NET" />
-            </customHeaders>
-            <redirectHeaders>
-                <clear />
-            </redirectHeaders>
-        </httpProtocol>
-
-        <httpRedirect enabled="false" />
-
-        <httpTracing>
-        </httpTracing>
-
-        <isapiFilters>
-            <filter name="ASP.Net_2.0.50727-64" path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv2.0" />
-            <filter name="ASP.Net_2.0.50727.0" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv2.0" />
-            <filter name="ASP.Net_2.0_for_v1.1" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv1.1" />
-            <filter name="ASP.Net_4.0_32bit" path="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv4.0" />
-            <filter name="ASP.Net_4.0_64bit" path="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv4.0" />
-        </isapiFilters>
-
-        <odbcLogging />
-
-        <security>
-
-            <access sslFlags="None" />
-
-            <applicationDependencies>
-                <application name="Active Server Pages" groupId="ASP" />
-            </applicationDependencies>
-
-            <authentication>
-
-                <anonymousAuthentication enabled="true" userName="" />
-
-                <basicAuthentication enabled="false" />
-
-                <clientCertificateMappingAuthentication enabled="false" />
-
-                <digestAuthentication enabled="false" />
-
-                <iisClientCertificateMappingAuthentication enabled="false">
-                </iisClientCertificateMappingAuthentication>
-
-                <windowsAuthentication enabled="false">
-                    <providers>
-                        <add value="Negotiate" />
-                        <add value="NTLM" />
-                    </providers>
-                </windowsAuthentication>
-
-            </authentication>
-
-            <authorization>
-                <add accessType="Allow" users="*" />
-            </authorization>
-
-            <ipSecurity allowUnlisted="true" />
-
-            <isapiCgiRestriction notListedIsapisAllowed="true" notListedCgisAllowed="true">
-                <add path="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
-                <add path="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
-                <add path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
-                <add path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
-            </isapiCgiRestriction>
-
-            <requestFiltering>
-                <fileExtensions allowUnlisted="true" applyToWebDAV="true">
-                    <add fileExtension=".asa" allowed="false" />
-                    <add fileExtension=".asax" allowed="false" />
-                    <add fileExtension=".ascx" allowed="false" />
-                    <add fileExtension=".master" allowed="false" />
-                    <add fileExtension=".skin" allowed="false" />
-                    <add fileExtension=".browser" allowed="false" />
-                    <add fileExtension=".sitemap" allowed="false" />
-                    <add fileExtension=".config" allowed="false" />
-                    <add fileExtension=".cs" allowed="false" />
-                    <add fileExtension=".csproj" allowed="false" />
-                    <add fileExtension=".vb" allowed="false" />
-                    <add fileExtension=".vbproj" allowed="false" />
-                    <add fileExtension=".webinfo" allowed="false" />
-                    <add fileExtension=".licx" allowed="false" />
-                    <add fileExtension=".resx" allowed="false" />
-                    <add fileExtension=".resources" allowed="false" />
-                    <add fileExtension=".mdb" allowed="false" />
-                    <add fileExtension=".vjsproj" allowed="false" />
-                    <add fileExtension=".java" allowed="false" />
-                    <add fileExtension=".jsl" allowed="false" />
-                    <add fileExtension=".ldb" allowed="false" />
-                    <add fileExtension=".dsdgm" allowed="false" />
-                    <add fileExtension=".ssdgm" allowed="false" />
-                    <add fileExtension=".lsad" allowed="false" />
-                    <add fileExtension=".ssmap" allowed="false" />
-                    <add fileExtension=".cd" allowed="false" />
-                    <add fileExtension=".dsprototype" allowed="false" />
-                    <add fileExtension=".lsaprototype" allowed="false" />
-                    <add fileExtension=".sdm" allowed="false" />
-                    <add fileExtension=".sdmDocument" allowed="false" />
-                    <add fileExtension=".mdf" allowed="false" />
-                    <add fileExtension=".ldf" allowed="false" />
-                    <add fileExtension=".ad" allowed="false" />
-                    <add fileExtension=".dd" allowed="false" />
-                    <add fileExtension=".ldd" allowed="false" />
-                    <add fileExtension=".sd" allowed="false" />
-                    <add fileExtension=".adprototype" allowed="false" />
-                    <add fileExtension=".lddprototype" allowed="false" />
-                    <add fileExtension=".exclude" allowed="false" />
-                    <add fileExtension=".refresh" allowed="false" />
-                    <add fileExtension=".compiled" allowed="false" />
-                    <add fileExtension=".msgx" allowed="false" />
-                    <add fileExtension=".vsdisco" allowed="false" />
-                    <add fileExtension=".rules" allowed="false" />
-                </fileExtensions>
-                <verbs allowUnlisted="true" applyToWebDAV="true" />
-                <hiddenSegments applyToWebDAV="true">
-                    <add segment="web.config" />
-                    <add segment="bin" />
-                    <add segment="App_code" />
-                    <add segment="App_GlobalResources" />
-                    <add segment="App_LocalResources" />
-                    <add segment="App_WebReferences" />
-                    <add segment="App_Data" />
-                    <add segment="App_Browsers" />
-                </hiddenSegments>
-            </requestFiltering>
-
-        </security>
-
-        <serverSideInclude ssiExecDisable="false" />
-
-        <staticContent lockAttributes="isDocFooterFileName">
-            <mimeMap fileExtension=".323" mimeType="text/h323" />
-            <mimeMap fileExtension=".3g2" mimeType="video/3gpp2" />
-            <mimeMap fileExtension=".3gp2" mimeType="video/3gpp2" />
-            <mimeMap fileExtension=".3gp" mimeType="video/3gpp" />
-            <mimeMap fileExtension=".3gpp" mimeType="video/3gpp" />
-            <mimeMap fileExtension=".aac" mimeType="audio/aac" />
-            <mimeMap fileExtension=".aaf" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".aca" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".accdb" mimeType="application/msaccess" />
-            <mimeMap fileExtension=".accde" mimeType="application/msaccess" />
-            <mimeMap fileExtension=".accdt" mimeType="application/msaccess" />
-            <mimeMap fileExtension=".acx" mimeType="application/internet-property-stream" />
-            <mimeMap fileExtension=".adt" mimeType="audio/vnd.dlna.adts" />
-            <mimeMap fileExtension=".adts" mimeType="audio/vnd.dlna.adts" />
-            <mimeMap fileExtension=".afm" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".ai" mimeType="application/postscript" />
-            <mimeMap fileExtension=".aif" mimeType="audio/x-aiff" />
-            <mimeMap fileExtension=".aifc" mimeType="audio/aiff" />
-            <mimeMap fileExtension=".aiff" mimeType="audio/aiff" />
-            <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest" />
-            <mimeMap fileExtension=".application" mimeType="application/x-ms-application" />
-            <mimeMap fileExtension=".art" mimeType="image/x-jg" />
-            <mimeMap fileExtension=".asd" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".asf" mimeType="video/x-ms-asf" />
-            <mimeMap fileExtension=".asi" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".asm" mimeType="text/plain" />
-            <mimeMap fileExtension=".asr" mimeType="video/x-ms-asf" />
-            <mimeMap fileExtension=".asx" mimeType="video/x-ms-asf" />
-            <mimeMap fileExtension=".atom" mimeType="application/atom+xml" />
-            <mimeMap fileExtension=".au" mimeType="audio/basic" />
-            <mimeMap fileExtension=".avi" mimeType="video/msvideo" />
-            <mimeMap fileExtension=".axs" mimeType="application/olescript" />
-            <mimeMap fileExtension=".bas" mimeType="text/plain" />
-            <mimeMap fileExtension=".bcpio" mimeType="application/x-bcpio" />
-            <mimeMap fileExtension=".bin" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".bmp" mimeType="image/bmp" />
-            <mimeMap fileExtension=".c" mimeType="text/plain" />
-            <mimeMap fileExtension=".cab" mimeType="application/vnd.ms-cab-compressed" />
-            <mimeMap fileExtension=".calx" mimeType="application/vnd.ms-office.calx" />
-            <mimeMap fileExtension=".cat" mimeType="application/vnd.ms-pki.seccat" />
-            <mimeMap fileExtension=".cdf" mimeType="application/x-cdf" />
-            <mimeMap fileExtension=".chm" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".class" mimeType="application/x-java-applet" />
-            <mimeMap fileExtension=".clp" mimeType="application/x-msclip" />
-            <mimeMap fileExtension=".cmx" mimeType="image/x-cmx" />
-            <mimeMap fileExtension=".cnf" mimeType="text/plain" />
-            <mimeMap fileExtension=".cod" mimeType="image/cis-cod" />
-            <mimeMap fileExtension=".cpio" mimeType="application/x-cpio" />
-            <mimeMap fileExtension=".cpp" mimeType="text/plain" />
-            <mimeMap fileExtension=".crd" mimeType="application/x-mscardfile" />
-            <mimeMap fileExtension=".crl" mimeType="application/pkix-crl" />
-            <mimeMap fileExtension=".crt" mimeType="application/x-x509-ca-cert" />
-            <mimeMap fileExtension=".csh" mimeType="application/x-csh" />
-            <mimeMap fileExtension=".css" mimeType="text/css" />
-            <mimeMap fileExtension=".csv" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".cur" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".dcr" mimeType="application/x-director" />
-            <mimeMap fileExtension=".deploy" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".der" mimeType="application/x-x509-ca-cert" />
-            <mimeMap fileExtension=".dib" mimeType="image/bmp" />
-            <mimeMap fileExtension=".dir" mimeType="application/x-director" />
-            <mimeMap fileExtension=".disco" mimeType="text/xml" />
-            <mimeMap fileExtension=".dll" mimeType="application/x-msdownload" />
-            <mimeMap fileExtension=".dll.config" mimeType="text/xml" />
-            <mimeMap fileExtension=".dlm" mimeType="text/dlm" />
-            <mimeMap fileExtension=".doc" mimeType="application/msword" />
-            <mimeMap fileExtension=".docm" mimeType="application/vnd.ms-word.document.macroEnabled.12" />
-            <mimeMap fileExtension=".docx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
-            <mimeMap fileExtension=".dot" mimeType="application/msword" />
-            <mimeMap fileExtension=".dotm" mimeType="application/vnd.ms-word.template.macroEnabled.12" />
-            <mimeMap fileExtension=".dotx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.template" />
-            <mimeMap fileExtension=".dsp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".dtd" mimeType="text/xml" />
-            <mimeMap fileExtension=".dvi" mimeType="application/x-dvi" />
-            <mimeMap fileExtension=".dvr-ms" mimeType="video/x-ms-dvr" />
-            <mimeMap fileExtension=".dwf" mimeType="drawing/x-dwf" />
-            <mimeMap fileExtension=".dwp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".dxr" mimeType="application/x-director" />
-            <mimeMap fileExtension=".eml" mimeType="message/rfc822" />
-            <mimeMap fileExtension=".emz" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
-            <mimeMap fileExtension=".eps" mimeType="application/postscript" />
-            <mimeMap fileExtension=".etx" mimeType="text/x-setext" />
-            <mimeMap fileExtension=".evy" mimeType="application/envoy" />
-            <mimeMap fileExtension=".exe" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".exe.config" mimeType="text/xml" />
-            <mimeMap fileExtension=".fdf" mimeType="application/vnd.fdf" />
-            <mimeMap fileExtension=".fif" mimeType="application/fractals" />
-            <mimeMap fileExtension=".fla" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".flr" mimeType="x-world/x-vrml" />
-            <mimeMap fileExtension=".flv" mimeType="video/x-flv" />
-            <mimeMap fileExtension=".gif" mimeType="image/gif" />
-            <mimeMap fileExtension=".gtar" mimeType="application/x-gtar" />
-            <mimeMap fileExtension=".gz" mimeType="application/x-gzip" />
-            <mimeMap fileExtension=".h" mimeType="text/plain" />
-            <mimeMap fileExtension=".hdf" mimeType="application/x-hdf" />
-            <mimeMap fileExtension=".hdml" mimeType="text/x-hdml" />
-            <mimeMap fileExtension=".hhc" mimeType="application/x-oleobject" />
-            <mimeMap fileExtension=".hhk" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".hhp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".hlp" mimeType="application/winhlp" />
-            <mimeMap fileExtension=".hqx" mimeType="application/mac-binhex40" />
-            <mimeMap fileExtension=".hta" mimeType="application/hta" />
-            <mimeMap fileExtension=".htc" mimeType="text/x-component" />
-            <mimeMap fileExtension=".htm" mimeType="text/html" />
-            <mimeMap fileExtension=".html" mimeType="text/html" />
-            <mimeMap fileExtension=".htt" mimeType="text/webviewhtml" />
-            <mimeMap fileExtension=".hxt" mimeType="text/html" />
-            <mimeMap fileExtension=".ico" mimeType="image/x-icon" />
-            <mimeMap fileExtension=".ics" mimeType="text/calendar" />
-            <mimeMap fileExtension=".ief" mimeType="image/ief" />
-            <mimeMap fileExtension=".iii" mimeType="application/x-iphone" />
-            <mimeMap fileExtension=".inf" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".ins" mimeType="application/x-internet-signup" />
-            <mimeMap fileExtension=".isp" mimeType="application/x-internet-signup" />
-            <mimeMap fileExtension=".IVF" mimeType="video/x-ivf" />
-            <mimeMap fileExtension=".jar" mimeType="application/java-archive" />
-            <mimeMap fileExtension=".java" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".jck" mimeType="application/liquidmotion" />
-            <mimeMap fileExtension=".jcz" mimeType="application/liquidmotion" />
-            <mimeMap fileExtension=".jfif" mimeType="image/pjpeg" />
-            <mimeMap fileExtension=".jpb" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".jpe" mimeType="image/jpeg" />
-            <mimeMap fileExtension=".jpeg" mimeType="image/jpeg" />
-            <mimeMap fileExtension=".jpg" mimeType="image/jpeg" />
-            <mimeMap fileExtension=".js" mimeType="application/javascript" />
-            <mimeMap fileExtension=".json" mimeType="application/json" />
-            <mimeMap fileExtension=".jsonld" mimeType="application/ld+json" />
-            <mimeMap fileExtension=".jsx" mimeType="text/jscript" />
-            <mimeMap fileExtension=".latex" mimeType="application/x-latex" />
-            <mimeMap fileExtension=".less" mimeType="text/css" />
-            <mimeMap fileExtension=".lit" mimeType="application/x-ms-reader" />
-            <mimeMap fileExtension=".lpk" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".lsf" mimeType="video/x-la-asf" />
-            <mimeMap fileExtension=".lsx" mimeType="video/x-la-asf" />
-            <mimeMap fileExtension=".lzh" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".m13" mimeType="application/x-msmediaview" />
-            <mimeMap fileExtension=".m14" mimeType="application/x-msmediaview" />
-            <mimeMap fileExtension=".m1v" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".m2ts" mimeType="video/vnd.dlna.mpeg-tts" />
-            <mimeMap fileExtension=".m3u" mimeType="audio/x-mpegurl" />
-            <mimeMap fileExtension=".m4a" mimeType="audio/mp4" />
-            <mimeMap fileExtension=".m4v" mimeType="video/mp4" />
-            <mimeMap fileExtension=".man" mimeType="application/x-troff-man" />
-            <mimeMap fileExtension=".manifest" mimeType="application/x-ms-manifest" />
-            <mimeMap fileExtension=".map" mimeType="text/plain" />
-            <mimeMap fileExtension=".mdb" mimeType="application/x-msaccess" />
-            <mimeMap fileExtension=".mdp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".me" mimeType="application/x-troff-me" />
-            <mimeMap fileExtension=".mht" mimeType="message/rfc822" />
-            <mimeMap fileExtension=".mhtml" mimeType="message/rfc822" />
-            <mimeMap fileExtension=".mid" mimeType="audio/mid" />
-            <mimeMap fileExtension=".midi" mimeType="audio/mid" />
-            <mimeMap fileExtension=".mix" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".mmf" mimeType="application/x-smaf" />
-            <mimeMap fileExtension=".mno" mimeType="text/xml" />
-            <mimeMap fileExtension=".mny" mimeType="application/x-msmoney" />
-            <mimeMap fileExtension=".mov" mimeType="video/quicktime" />
-            <mimeMap fileExtension=".movie" mimeType="video/x-sgi-movie" />
-            <mimeMap fileExtension=".mp2" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".mp3" mimeType="audio/mpeg" />
-            <mimeMap fileExtension=".mp4" mimeType="video/mp4" />
-            <mimeMap fileExtension=".mp4v" mimeType="video/mp4" />
-            <mimeMap fileExtension=".mpa" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".mpe" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".mpeg" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".mpg" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".mpp" mimeType="application/vnd.ms-project" />
-            <mimeMap fileExtension=".mpv2" mimeType="video/mpeg" />
-            <mimeMap fileExtension=".ms" mimeType="application/x-troff-ms" />
-            <mimeMap fileExtension=".msi" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".mso" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".mvb" mimeType="application/x-msmediaview" />
-            <mimeMap fileExtension=".mvc" mimeType="application/x-miva-compiled" />
-            <mimeMap fileExtension=".nc" mimeType="application/x-netcdf" />
-            <mimeMap fileExtension=".nsc" mimeType="video/x-ms-asf" />
-            <mimeMap fileExtension=".nws" mimeType="message/rfc822" />
-            <mimeMap fileExtension=".ocx" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".oda" mimeType="application/oda" />
-            <mimeMap fileExtension=".odc" mimeType="text/x-ms-odc" />
-            <mimeMap fileExtension=".ods" mimeType="application/oleobject" />
-            <mimeMap fileExtension=".oga" mimeType="audio/ogg" />
-            <mimeMap fileExtension=".ogg" mimeType="video/ogg" />
-            <mimeMap fileExtension=".ogv" mimeType="video/ogg" />
-            <mimeMap fileExtension=".one" mimeType="application/onenote" />
-            <mimeMap fileExtension=".onea" mimeType="application/onenote" />
-            <mimeMap fileExtension=".onetoc" mimeType="application/onenote" />
-            <mimeMap fileExtension=".onetoc2" mimeType="application/onenote" />
-            <mimeMap fileExtension=".onetmp" mimeType="application/onenote" />
-            <mimeMap fileExtension=".onepkg" mimeType="application/onenote" />
-            <mimeMap fileExtension=".osdx" mimeType="application/opensearchdescription+xml" />
-            <mimeMap fileExtension=".otf" mimeType="font/otf" />
-            <mimeMap fileExtension=".p10" mimeType="application/pkcs10" />
-            <mimeMap fileExtension=".p12" mimeType="application/x-pkcs12" />
-            <mimeMap fileExtension=".p7b" mimeType="application/x-pkcs7-certificates" />
-            <mimeMap fileExtension=".p7c" mimeType="application/pkcs7-mime" />
-            <mimeMap fileExtension=".p7m" mimeType="application/pkcs7-mime" />
-            <mimeMap fileExtension=".p7r" mimeType="application/x-pkcs7-certreqresp" />
-            <mimeMap fileExtension=".p7s" mimeType="application/pkcs7-signature" />
-            <mimeMap fileExtension=".pbm" mimeType="image/x-portable-bitmap" />
-            <mimeMap fileExtension=".pcx" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".pcz" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".pdf" mimeType="application/pdf" />
-            <mimeMap fileExtension=".pfb" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".pfm" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".pfx" mimeType="application/x-pkcs12" />
-            <mimeMap fileExtension=".pgm" mimeType="image/x-portable-graymap" />
-            <mimeMap fileExtension=".pko" mimeType="application/vnd.ms-pki.pko" />
-            <mimeMap fileExtension=".pma" mimeType="application/x-perfmon" />
-            <mimeMap fileExtension=".pmc" mimeType="application/x-perfmon" />
-            <mimeMap fileExtension=".pml" mimeType="application/x-perfmon" />
-            <mimeMap fileExtension=".pmr" mimeType="application/x-perfmon" />
-            <mimeMap fileExtension=".pmw" mimeType="application/x-perfmon" />
-            <mimeMap fileExtension=".png" mimeType="image/png" />
-            <mimeMap fileExtension=".pnm" mimeType="image/x-portable-anymap" />
-            <mimeMap fileExtension=".pnz" mimeType="image/png" />
-            <mimeMap fileExtension=".pot" mimeType="application/vnd.ms-powerpoint" />
-            <mimeMap fileExtension=".potm" mimeType="application/vnd.ms-powerpoint.template.macroEnabled.12" />
-            <mimeMap fileExtension=".potx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.template" />
-            <mimeMap fileExtension=".ppam" mimeType="application/vnd.ms-powerpoint.addin.macroEnabled.12" />
-            <mimeMap fileExtension=".ppm" mimeType="image/x-portable-pixmap" />
-            <mimeMap fileExtension=".pps" mimeType="application/vnd.ms-powerpoint" />
-            <mimeMap fileExtension=".ppsm" mimeType="application/vnd.ms-powerpoint.slideshow.macroEnabled.12" />
-            <mimeMap fileExtension=".ppsx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" />
-            <mimeMap fileExtension=".ppt" mimeType="application/vnd.ms-powerpoint" />
-            <mimeMap fileExtension=".pptm" mimeType="application/vnd.ms-powerpoint.presentation.macroEnabled.12" />
-            <mimeMap fileExtension=".pptx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
-            <mimeMap fileExtension=".prf" mimeType="application/pics-rules" />
-            <mimeMap fileExtension=".prm" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".prx" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".ps" mimeType="application/postscript" />
-            <mimeMap fileExtension=".psd" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".psm" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".psp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".pub" mimeType="application/x-mspublisher" />
-            <mimeMap fileExtension=".qt" mimeType="video/quicktime" />
-            <mimeMap fileExtension=".qtl" mimeType="application/x-quicktimeplayer" />
-            <mimeMap fileExtension=".qxd" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".ra" mimeType="audio/x-pn-realaudio" />
-            <mimeMap fileExtension=".ram" mimeType="audio/x-pn-realaudio" />
-            <mimeMap fileExtension=".rar" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".ras" mimeType="image/x-cmu-raster" />
-            <mimeMap fileExtension=".rf" mimeType="image/vnd.rn-realflash" />
-            <mimeMap fileExtension=".rgb" mimeType="image/x-rgb" />
-            <mimeMap fileExtension=".rm" mimeType="application/vnd.rn-realmedia" />
-            <mimeMap fileExtension=".rmi" mimeType="audio/mid" />
-            <mimeMap fileExtension=".roff" mimeType="application/x-troff" />
-            <mimeMap fileExtension=".rpm" mimeType="audio/x-pn-realaudio-plugin" />
-            <mimeMap fileExtension=".rtf" mimeType="application/rtf" />
-            <mimeMap fileExtension=".rtx" mimeType="text/richtext" />
-            <mimeMap fileExtension=".scd" mimeType="application/x-msschedule" />
-            <mimeMap fileExtension=".sct" mimeType="text/scriptlet" />
-            <mimeMap fileExtension=".sea" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".setpay" mimeType="application/set-payment-initiation" />
-            <mimeMap fileExtension=".setreg" mimeType="application/set-registration-initiation" />
-            <mimeMap fileExtension=".sgml" mimeType="text/sgml" />
-            <mimeMap fileExtension=".sh" mimeType="application/x-sh" />
-            <mimeMap fileExtension=".shar" mimeType="application/x-shar" />
-            <mimeMap fileExtension=".sit" mimeType="application/x-stuffit" />
-            <mimeMap fileExtension=".sldm" mimeType="application/vnd.ms-powerpoint.slide.macroEnabled.12" />
-            <mimeMap fileExtension=".sldx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slide" />
-            <mimeMap fileExtension=".smd" mimeType="audio/x-smd" />
-            <mimeMap fileExtension=".smi" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".smx" mimeType="audio/x-smd" />
-            <mimeMap fileExtension=".smz" mimeType="audio/x-smd" />
-            <mimeMap fileExtension=".snd" mimeType="audio/basic" />
-            <mimeMap fileExtension=".snp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".spc" mimeType="application/x-pkcs7-certificates" />
-            <mimeMap fileExtension=".spl" mimeType="application/futuresplash" />
-            <mimeMap fileExtension=".spx" mimeType="audio/ogg" />
-            <mimeMap fileExtension=".src" mimeType="application/x-wais-source" />
-            <mimeMap fileExtension=".ssm" mimeType="application/streamingmedia" />
-            <mimeMap fileExtension=".sst" mimeType="application/vnd.ms-pki.certstore" />
-            <mimeMap fileExtension=".stl" mimeType="application/vnd.ms-pki.stl" />
-            <mimeMap fileExtension=".sv4cpio" mimeType="application/x-sv4cpio" />
-            <mimeMap fileExtension=".sv4crc" mimeType="application/x-sv4crc" />
-            <mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
-            <mimeMap fileExtension=".svgz" mimeType="image/svg+xml" />
-            <mimeMap fileExtension=".swf" mimeType="application/x-shockwave-flash" />
-            <mimeMap fileExtension=".t" mimeType="application/x-troff" />
-            <mimeMap fileExtension=".tar" mimeType="application/x-tar" />
-            <mimeMap fileExtension=".tcl" mimeType="application/x-tcl" />
-            <mimeMap fileExtension=".tex" mimeType="application/x-tex" />
-            <mimeMap fileExtension=".texi" mimeType="application/x-texinfo" />
-            <mimeMap fileExtension=".texinfo" mimeType="application/x-texinfo" />
-            <mimeMap fileExtension=".tgz" mimeType="application/x-compressed" />
-            <mimeMap fileExtension=".thmx" mimeType="application/vnd.ms-officetheme" />
-            <mimeMap fileExtension=".thn" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".tif" mimeType="image/tiff" />
-            <mimeMap fileExtension=".tiff" mimeType="image/tiff" />
-            <mimeMap fileExtension=".toc" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".tr" mimeType="application/x-troff" />
-            <mimeMap fileExtension=".trm" mimeType="application/x-msterminal" />
-            <mimeMap fileExtension=".ts" mimeType="video/vnd.dlna.mpeg-tts" />
-            <mimeMap fileExtension=".tsv" mimeType="text/tab-separated-values" />
-            <mimeMap fileExtension=".ttf" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".tts" mimeType="video/vnd.dlna.mpeg-tts" />
-            <mimeMap fileExtension=".txt" mimeType="text/plain" />
-            <mimeMap fileExtension=".u32" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".uls" mimeType="text/iuls" />
-            <mimeMap fileExtension=".ustar" mimeType="application/x-ustar" />
-            <mimeMap fileExtension=".vbs" mimeType="text/vbscript" />
-            <mimeMap fileExtension=".vcf" mimeType="text/x-vcard" />
-            <mimeMap fileExtension=".vcs" mimeType="text/plain" />
-            <mimeMap fileExtension=".vdx" mimeType="application/vnd.ms-visio.viewer" />
-            <mimeMap fileExtension=".vml" mimeType="text/xml" />
-            <mimeMap fileExtension=".vsd" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".vss" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".vst" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".vsto" mimeType="application/x-ms-vsto" />
-            <mimeMap fileExtension=".vsw" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".vsx" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".vtx" mimeType="application/vnd.visio" />
-            <mimeMap fileExtension=".wav" mimeType="audio/wav" />
-            <mimeMap fileExtension=".wax" mimeType="audio/x-ms-wax" />
-            <mimeMap fileExtension=".wbmp" mimeType="image/vnd.wap.wbmp" />
-            <mimeMap fileExtension=".wcm" mimeType="application/vnd.ms-works" />
-            <mimeMap fileExtension=".wdb" mimeType="application/vnd.ms-works" />
-            <mimeMap fileExtension=".webm" mimeType="video/webm" />
-            <mimeMap fileExtension=".wks" mimeType="application/vnd.ms-works" />
-            <mimeMap fileExtension=".wm" mimeType="video/x-ms-wm" />
-            <mimeMap fileExtension=".wma" mimeType="audio/x-ms-wma" />
-            <mimeMap fileExtension=".wmd" mimeType="application/x-ms-wmd" />
-            <mimeMap fileExtension=".wmf" mimeType="application/x-msmetafile" />
-            <mimeMap fileExtension=".wml" mimeType="text/vnd.wap.wml" />
-            <mimeMap fileExtension=".wmlc" mimeType="application/vnd.wap.wmlc" />
-            <mimeMap fileExtension=".wmls" mimeType="text/vnd.wap.wmlscript" />
-            <mimeMap fileExtension=".wmlsc" mimeType="application/vnd.wap.wmlscriptc" />
-            <mimeMap fileExtension=".wmp" mimeType="video/x-ms-wmp" />
-            <mimeMap fileExtension=".wmv" mimeType="video/x-ms-wmv" />
-            <mimeMap fileExtension=".wmx" mimeType="video/x-ms-wmx" />
-            <mimeMap fileExtension=".wmz" mimeType="application/x-ms-wmz" />
-            <mimeMap fileExtension=".woff" mimeType="font/x-woff" />
-            <mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
-            <mimeMap fileExtension=".wps" mimeType="application/vnd.ms-works" />
-            <mimeMap fileExtension=".wri" mimeType="application/x-mswrite" />
-            <mimeMap fileExtension=".wrl" mimeType="x-world/x-vrml" />
-            <mimeMap fileExtension=".wrz" mimeType="x-world/x-vrml" />
-            <mimeMap fileExtension=".wsdl" mimeType="text/xml" />
-            <mimeMap fileExtension=".wtv" mimeType="video/x-ms-wtv" />
-            <mimeMap fileExtension=".wvx" mimeType="video/x-ms-wvx" />
-            <mimeMap fileExtension=".x" mimeType="application/directx" />
-            <mimeMap fileExtension=".xaf" mimeType="x-world/x-vrml" />
-            <mimeMap fileExtension=".xaml" mimeType="application/xaml+xml" />
-            <mimeMap fileExtension=".xap" mimeType="application/x-silverlight-app" />
-            <mimeMap fileExtension=".xbap" mimeType="application/x-ms-xbap" />
-            <mimeMap fileExtension=".xbm" mimeType="image/x-xbitmap" />
-            <mimeMap fileExtension=".xdr" mimeType="text/plain" />
-            <mimeMap fileExtension=".xht" mimeType="application/xhtml+xml" />
-            <mimeMap fileExtension=".xhtml" mimeType="application/xhtml+xml" />
-            <mimeMap fileExtension=".xla" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xlam" mimeType="application/vnd.ms-excel.addin.macroEnabled.12" />
-            <mimeMap fileExtension=".xlc" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xlm" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xls" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xlsb" mimeType="application/vnd.ms-excel.sheet.binary.macroEnabled.12" />
-            <mimeMap fileExtension=".xlsm" mimeType="application/vnd.ms-excel.sheet.macroEnabled.12" />
-            <mimeMap fileExtension=".xlsx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
-            <mimeMap fileExtension=".xlt" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xltm" mimeType="application/vnd.ms-excel.template.macroEnabled.12" />
-            <mimeMap fileExtension=".xltx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.template" />
-            <mimeMap fileExtension=".xlw" mimeType="application/vnd.ms-excel" />
-            <mimeMap fileExtension=".xml" mimeType="text/xml" />
-            <mimeMap fileExtension=".xof" mimeType="x-world/x-vrml" />
-            <mimeMap fileExtension=".xpm" mimeType="image/x-xpixmap" />
-            <mimeMap fileExtension=".xps" mimeType="application/vnd.ms-xpsdocument" />
-            <mimeMap fileExtension=".xsd" mimeType="text/xml" />
-            <mimeMap fileExtension=".xsf" mimeType="text/xml" />
-            <mimeMap fileExtension=".xsl" mimeType="text/xml" />
-            <mimeMap fileExtension=".xslt" mimeType="text/xml" />
-            <mimeMap fileExtension=".xsn" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".xtp" mimeType="application/octet-stream" />
-            <mimeMap fileExtension=".xwd" mimeType="image/x-xwindowdump" />
-            <mimeMap fileExtension=".z" mimeType="application/x-compress" />
-            <mimeMap fileExtension=".zip" mimeType="application/x-zip-compressed" />
-        </staticContent>
-
-        <tracing>
-
-             <traceProviderDefinitions>
-                <add name="WWW Server" guid="{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}">
-                    <areas>
-                        <clear />
-                        <add name="Authentication" value="2" />
-                        <add name="Security" value="4" />
-                        <add name="Filter" value="8" />
-                        <add name="StaticFile" value="16" />
-                        <add name="CGI" value="32" />
-                        <add name="Compression" value="64" />
-                        <add name="Cache" value="128" />
-                        <add name="RequestNotifications" value="256" />
-                        <add name="Module" value="512" />
-                        <add name="Rewrite" value="1024" />
-                        <add name="FastCGI" value="4096" />
-                        <add name="WebSocket" value="16384" />
-                    </areas>
-                </add>
-                <add name="ASP" guid="{06b94d9a-b15e-456e-a4ef-37c984a2cb4b}">
-                    <areas>
-                        <clear />
-                    </areas>
-                </add>
-                <add name="ISAPI Extension" guid="{a1c2040e-8840-4c31-ba11-9871031a19ea}">
-                    <areas>
-                        <clear />
-                    </areas>
-                </add>
-                <add name="ASPNET" guid="{AFF081FE-0247-4275-9C4E-021F3DC1DA35}">
-                    <areas>
-                        <add name="Infrastructure" value="1" />
-                        <add name="Module" value="2" />
-                        <add name="Page" value="4" />
-                        <add name="AppServices" value="8" />
-                    </areas>
-                </add>
-            </traceProviderDefinitions>
-
-            <traceFailedRequests>
-                <add path="*">
-                    <traceAreas>
-                        <add provider="ASP" verbosity="Verbose" />
-                        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
-                        <add provider="ISAPI Extension" verbosity="Verbose" />
-                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,Rewrite,WebSocket" verbosity="Verbose" />
-                    </traceAreas>
-                    <failureDefinitions statusCodes="200-999" />
-                </add>
-            </traceFailedRequests>
-
-        </tracing>
-
-        <urlCompression />
-
-        <validation />
-        <webdav>
-            <globalSettings>
-                <propertyStores>
-                    <add name="webdav_simple_prop" image="%IIS_BIN%\webdav_simple_prop.dll" image32="%IIS_BIN%\webdav_simple_prop.dll" />
-                </propertyStores>
-                <lockStores>
-                    <add name="webdav_simple_lock" image="%IIS_BIN%\webdav_simple_lock.dll" image32="%IIS_BIN%\webdav_simple_lock.dll" />
-                </lockStores>
-
-            </globalSettings>
-            <authoring>
-                <locks enabled="true" lockStore="webdav_simple_lock" />
-            </authoring>
-            <authoringRules />
-        </webdav>
-        <webSocket />
-        <applicationInitialization />
-
-    </system.webServer>
-    <location path="" overrideMode="Allow">
-        <system.webServer>
-            <modules>
-                <add name="IsapiFilterModule" lockItem="true" />
-                <add name="BasicAuthenticationModule" lockItem="true" />
-                <add name="IsapiModule" lockItem="true" />
-                <add name="HttpLoggingModule" lockItem="true" />
-                <!--
-                <add name="HttpCacheModule" lockItem="true" />
--->
-                <add name="DynamicCompressionModule" lockItem="true" />
-                <add name="StaticCompressionModule" lockItem="true" />
-                <add name="DefaultDocumentModule" lockItem="true" />
-                <add name="DirectoryListingModule" lockItem="true" />
-
-                <add name="ProtocolSupportModule" lockItem="true" />
-                <add name="HttpRedirectionModule" lockItem="true" />
-                <add name="ServerSideIncludeModule" lockItem="true" />
-                <add name="StaticFileModule" lockItem="true" />
-                <add name="AnonymousAuthenticationModule" lockItem="true" />
-                <add name="CertificateMappingAuthenticationModule" lockItem="true" />
-                <add name="UrlAuthorizationModule" lockItem="true" />
-                <add name="WindowsAuthenticationModule" lockItem="true" />
-                <!--
-                <add name="DigestAuthenticationModule" lockItem="true" />
--->
-                <add name="IISCertificateMappingAuthenticationModule" lockItem="true" />
-                <add name="WebMatrixSupportModule" lockItem="true" />
-                <add name="IpRestrictionModule" lockItem="true" />
-                <add name="DynamicIpRestrictionModule" lockItem="true" />
-                <add name="RequestFilteringModule" lockItem="true" />
-                <add name="CustomLoggingModule" lockItem="true" />
-                <add name="CustomErrorModule" lockItem="true" />
-                <add name="FailedRequestsTracingModule" lockItem="true" />
-                <add name="CgiModule" lockItem="true" />
-                <add name="FastCgiModule" lockItem="true" />
-                <!--                <add name="WebDAVModule" /> -->
-                <add name="RewriteModule" />
-                <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" preCondition="managedHandler" />
-                <add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition="managedHandler" />
-                <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" preCondition="managedHandler" />
-                <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="managedHandler" />
-                <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="managedHandler" />
-                <add name="RoleManager" type="System.Web.Security.RoleManagerModule" preCondition="managedHandler" />
-                <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" preCondition="managedHandler" />
-                <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" preCondition="managedHandler" />
-                <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" preCondition="managedHandler" />
-                <add name="Profile" type="System.Web.Profile.ProfileModule" preCondition="managedHandler" />
-                <add name="UrlMappingsModule" type="System.Web.UrlMappingsModule" preCondition="managedHandler" />
-                <add name="ConfigurationValidationModule" lockItem="true" />
-                <add name="WebSocketModule" lockItem="true" />
-                <add name="ServiceModel-4.0" type="System.ServiceModel.Activation.ServiceHttpModule,System.ServiceModel.Activation,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
-                <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler,runtimeVersionv4.0" />
-                <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
-                <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler,runtimeVersionv2.0" />
-                <add name="ApplicationInitializationModule" lockItem="true" />
-                <add name="AspNetCoreModule" lockItem="true" />
-            </modules>
-            <handlers accessPolicy="Read, Script">
-                <!--                <add name="WebDAV" path="*" verb="PROPFIND,PROPPATCH,MKCOL,PUT,COPY,DELETE,MOVE,LOCK,UNLOCK" modules="WebDAVModule" resourceType="Unspecified" requireAccess="None" /> -->
-                <add name="AXD-ISAPI-4.0_64bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="PageHandlerFactory-ISAPI-4.0_64bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="SimpleHandlerFactory-ISAPI-4.0_64bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="WebServiceHandlerFactory-ISAPI-4.0_64bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_64bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_64bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="svc-ISAPI-4.0_64bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
-                <add name="rules-ISAPI-4.0_64bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
-                <add name="xoml-ISAPI-4.0_64bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
-                <add name="xamlx-ISAPI-4.0_64bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
-                <add name="aspq-ISAPI-4.0_64bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="cshtm-ISAPI-4.0_64bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="cshtml-ISAPI-4.0_64bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="vbhtm-ISAPI-4.0_64bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="vbhtml-ISAPI-4.0_64bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="svc-Integrated" path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="svc-ISAPI-2.0" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
-                <add name="xoml-Integrated" path="*.xoml" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="xoml-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
-                <add name="rules-Integrated" path="*.rules" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="rules-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
-                <add name="AXD-ISAPI-4.0_32bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="PageHandlerFactory-ISAPI-4.0_32bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="SimpleHandlerFactory-ISAPI-4.0_32bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="WebServiceHandlerFactory-ISAPI-4.0_32bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_32bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_32bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="svc-ISAPI-4.0_32bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
-                <add name="rules-ISAPI-4.0_32bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
-                <add name="xoml-ISAPI-4.0_32bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
-                <add name="xamlx-ISAPI-4.0_32bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
-                <add name="aspq-ISAPI-4.0_32bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="cshtm-ISAPI-4.0_32bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="cshtml-ISAPI-4.0_32bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="vbhtm-ISAPI-4.0_32bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="vbhtml-ISAPI-4.0_32bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="TraceHandler-Integrated-4.0" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="WebAdminHandler-Integrated-4.0" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="AssemblyResourceLoader-Integrated-4.0" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="PageHandlerFactory-Integrated-4.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="SimpleHandlerFactory-Integrated-4.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="WebServiceHandlerFactory-Integrated-4.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="HttpRemotingHandlerFactory-rem-Integrated-4.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="HttpRemotingHandlerFactory-soap-Integrated-4.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="svc-Integrated-4.0" path="*.svc" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="rules-Integrated-4.0" path="*.rules" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="xoml-Integrated-4.0" path="*.xoml" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="xamlx-Integrated-4.0" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" type="System.Xaml.Hosting.XamlHttpHandlerFactory, System.Xaml.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="aspq-Integrated-4.0" path="*.aspq" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="cshtm-Integrated-4.0" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="cshtml-Integrated-4.0" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="vbhtm-Integrated-4.0" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="vbhtml-Integrated-4.0" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="ScriptHandlerFactoryAppServices-Integrated-4.0" path="*_AppService.axd" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="ScriptResourceIntegrated-4.0" path="*ScriptResource.axd" verb="GET,HEAD" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
-                <add name="ASPClassic" path="*.asp" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
-                <add name="SecurityCertificate" path="*.cer" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
-                <add name="ISAPI-dll" path="*.dll" verb="*" modules="IsapiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
-                <add name="TraceHandler-Integrated" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="WebAdminHandler-Integrated" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="AssemblyResourceLoader-Integrated" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="SimpleHandlerFactory-Integrated" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="WebServiceHandlerFactory-Integrated" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Services.Protocols.WebServiceHandlerFactory,System.Web.Services,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="HttpRemotingHandlerFactory-rem-Integrated" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="HttpRemotingHandlerFactory-soap-Integrated" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
-                <add name="AXD-ISAPI-2.0" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="PageHandlerFactory-ISAPI-2.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="SimpleHandlerFactory-ISAPI-2.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="WebServiceHandlerFactory-ISAPI-2.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
-                <add name="svc-ISAPI-2.0-64" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
-                <add name="AXD-ISAPI-2.0-64" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="PageHandlerFactory-ISAPI-2.0-64" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="SimpleHandlerFactory-ISAPI-2.0-64" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="WebServiceHandlerFactory-ISAPI-2.0-64" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0-64" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0-64" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
-                <add name="rules-64-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
-                <add name="xoml-64-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
-                <add name="CGI-exe" path="*.exe" verb="*" modules="CgiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
-                <add name="SSINC-stm" path="*.stm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
-                <add name="SSINC-shtm" path="*.shtm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
-                <add name="SSINC-shtml" path="*.shtml" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
-                <add name="TRACEVerbHandler" path="*" verb="TRACE" modules="ProtocolSupportModule" requireAccess="None" />
-                <add name="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" requireAccess="None" />
-                <add name="ExtensionlessUrl-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
-                <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
-                <add name="ExtensionlessUrl-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
-                <add name="StaticFile" path="*" verb="*" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" resourceType="Either" requireAccess="Read" />
-            </handlers>
-        </system.webServer>
-    </location>
-</configuration>
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
index d65eb199..ba6cfeab 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
@@ -10,7 +10,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
 
 ## Requirements
 
-* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7 and HHVM
+* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7.0, 7.1 and HHVM
 * [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
 * [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
 
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
index a4c13758..1ea66ab0 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
@@ -1,7 +1,7 @@
 {
     "name": "robthree/twofactorauth",
     "description": "Two Factor Authentication",
-    "version": "1.6",
+    "version": "1.6.1",
     "type": "library",
     "keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
     "homepage": "https://github.com/RobThree/TwoFactorAuth",
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
index 4939f0d4..9a775fc8 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
@@ -6,7 +6,7 @@ class ConvertUnixTimeDotComTimeProvider implements ITimeProvider
 {
     public function getTime() {
         $json = @json_decode(
-            @file_get_contents('http://www.convert-unix-time.com/api?timestamp=now')
+            @file_get_contents('http://www.convert-unix-time.com/api?timestamp=now&r=' . uniqid(null, true))
         );
         if ($json === null || !is_int($json->timestamp))
             throw new \TimeException('Unable to retrieve time from convert-unix-time.com');
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
index c761bd97..8e7806e2 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
@@ -26,7 +26,8 @@ class HttpTimeProvider implements ITimeProvider
                     'request_fulluri' => true,
                     'header' => array(
                         'Connection: close',
-                        'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)'
+                        'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)',
+                        'Cache-Control: no-cache'
                     )
                 )
             );
diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1 b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1
new file mode 100644
index 00000000..a11c78fb
--- /dev/null
+++ b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1
@@ -0,0 +1,507 @@
+<?php
+/* Copyright (c) 2014 Yubico AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace u2flib_server;
+
+/** Constant for the version of the u2f protocol */
+const U2F_VERSION = "U2F_V2";
+
+/** Error for the authentication message not matching any outstanding
+ * authentication request */
+const ERR_NO_MATCHING_REQUEST = 1;
+
+/** Error for the authentication message not matching any registration */
+const ERR_NO_MATCHING_REGISTRATION = 2;
+
+/** Error for the signature on the authentication message not verifying with
+ * the correct key */
+const ERR_AUTHENTICATION_FAILURE = 3;
+
+/** Error for the challenge in the registration message not matching the
+ * registration challenge */
+const ERR_UNMATCHED_CHALLENGE = 4;
+
+/** Error for the attestation signature on the registration message not
+ * verifying */
+const ERR_ATTESTATION_SIGNATURE = 5;
+
+/** Error for the attestation verification not verifying */
+const ERR_ATTESTATION_VERIFICATION = 6;
+
+/** Error for not getting good random from the system */
+const ERR_BAD_RANDOM = 7;
+
+/** Error when the counter is lower than expected */
+const ERR_COUNTER_TOO_LOW = 8;
+
+/** Error decoding public key */
+const ERR_PUBKEY_DECODE = 9;
+
+/** Error user-agent returned error */
+const ERR_BAD_UA_RETURNING = 10;
+
+/** Error old OpenSSL version */
+const ERR_OLD_OPENSSL = 11;
+
+/** @internal */
+const PUBKEY_LEN = 65;
+
+class U2F
+{
+    /** @var string  */
+    private $appId;
+
+    /** @var null|string */
+    private $attestDir;
+
+    /** @internal */
+    private $FIXCERTS = array(
+        '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
+        'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
+        '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
+        'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
+        '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
+        'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
+    );
+
+    /**
+     * @param string $appId Application id for the running application
+     * @param string|null $attestDir Directory where trusted attestation roots may be found
+     * @throws Error If OpenSSL older than 1.0.0 is used
+     */
+    public function __construct($appId, $attestDir = null)
+    {
+        if(OPENSSL_VERSION_NUMBER < 0x10000000) {
+            throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
+        }
+        $this->appId = $appId;
+        $this->attestDir = $attestDir;
+    }
+
+    /**
+     * Called to get a registration request to send to a user.
+     * Returns an array of one registration request and a array of sign requests.
+     *
+     * @param array $registrations List of current registrations for this
+     * user, to prevent the user from registering the same authenticator several
+     * times.
+     * @return array An array of two elements, the first containing a
+     * RegisterRequest the second being an array of SignRequest
+     * @throws Error
+     */
+    public function getRegisterData(array $registrations = array())
+    {
+        $challenge = $this->createChallenge();
+        $request = new RegisterRequest($challenge, $this->appId);
+        $signs = $this->getAuthenticateData($registrations);
+        return array($request, $signs);
+    }
+
+    /**
+     * Called to verify and unpack a registration message.
+     *
+     * @param RegisterRequest $request this is a reply to
+     * @param object $response response from a user
+     * @param bool $includeCert set to true if the attestation certificate should be
+     * included in the returned Registration object
+     * @return Registration
+     * @throws Error
+     */
+    public function doRegister($request, $response, $includeCert = true)
+    {
+        if( !is_object( $request ) ) {
+            throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
+        }
+
+        if( !is_object( $response ) ) {
+            throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
+        }
+
+        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
+            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
+        }
+
+        if( !is_bool( $includeCert ) ) {
+            throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
+        }
+
+        $rawReg = $this->base64u_decode($response->registrationData);
+        $regData = array_values(unpack('C*', $rawReg));
+        $clientData = $this->base64u_decode($response->clientData);
+        $cli = json_decode($clientData);
+
+        if($cli->challenge !== $request->challenge) {
+            throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
+        }
+
+        $registration = new Registration();
+        $offs = 1;
+        $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
+        $offs += PUBKEY_LEN;
+        // decode the pubKey to make sure it's good
+        $tmpKey = $this->pubkey_to_pem($pubKey);
+        if($tmpKey === null) {
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+        }
+        $registration->publicKey = base64_encode($pubKey);
+        $khLen = $regData[$offs++];
+        $kh = substr($rawReg, $offs, $khLen);
+        $offs += $khLen;
+        $registration->keyHandle = $this->base64u_encode($kh);
+
+        // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
+        $certLen = 4;
+        $certLen += ($regData[$offs + 2] << 8);
+        $certLen += $regData[$offs + 3];
+
+        $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
+        $offs += $certLen;
+        $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
+        $pemCert .= chunk_split(base64_encode($rawCert), 64);
+        $pemCert .= "-----END CERTIFICATE-----";
+        if($includeCert) {
+            $registration->certificate = base64_encode($rawCert);
+        }
+        if($this->attestDir) {
+            if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
+                throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
+            }
+        }
+
+        if(!openssl_pkey_get_public($pemCert)) {
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+        }
+        $signature = substr($rawReg, $offs);
+
+        $dataToVerify  = chr(0);
+        $dataToVerify .= hash('sha256', $request->appId, true);
+        $dataToVerify .= hash('sha256', $clientData, true);
+        $dataToVerify .= $kh;
+        $dataToVerify .= $pubKey;
+
+        if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
+            return $registration;
+        } else {
+            throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
+        }
+    }
+
+    /**
+     * Called to get an authentication request.
+     *
+     * @param array $registrations An array of the registrations to create authentication requests for.
+     * @return array An array of SignRequest
+     * @throws Error
+     */
+    public function getAuthenticateData(array $registrations)
+    {
+        $sigs = array();
+        $challenge = $this->createChallenge();
+        foreach ($registrations as $reg) {
+            if( !is_object( $reg ) ) {
+                throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
+            }
+
+            $sig = new SignRequest();
+            $sig->appId = $this->appId;
+            $sig->keyHandle = $reg->keyHandle;
+            $sig->challenge = $challenge;
+            $sigs[] = $sig;
+        }
+        return $sigs;
+    }
+
+    /**
+     * Called to verify an authentication response
+     *
+     * @param array $requests An array of outstanding authentication requests
+     * @param array $registrations An array of current registrations
+     * @param object $response A response from the authenticator
+     * @return Registration
+     * @throws Error
+     *
+     * The Registration object returned on success contains an updated counter
+     * that should be saved for future authentications.
+     * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
+     * token cloning or similar and appropriate action should be taken.
+     */
+    public function doAuthenticate(array $requests, array $registrations, $response)
+    {
+        if( !is_object( $response ) ) {
+            throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
+        }
+
+        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
+            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
+        }
+
+        /** @var object|null $req */
+        $req = null;
+
+        /** @var object|null $reg */
+        $reg = null;
+
+        $clientData = $this->base64u_decode($response->clientData);
+        $decodedClient = json_decode($clientData);
+        foreach ($requests as $req) {
+            if( !is_object( $req ) ) {
+                throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
+            }
+
+            if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
+                break;
+            }
+
+            $req = null;
+        }
+        if($req === null) {
+            throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
+        }
+        foreach ($registrations as $reg) {
+            if( !is_object( $reg ) ) {
+                throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
+            }
+
+            if($reg->keyHandle === $response->keyHandle) {
+                break;
+            }
+            $reg = null;
+        }
+        if($reg === null) {
+            throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
+        }
+        $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
+        if($pemKey === null) {
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+        }
+
+        $signData = $this->base64u_decode($response->signatureData);
+        $dataToVerify  = hash('sha256', $req->appId, true);
+        $dataToVerify .= substr($signData, 0, 5);
+        $dataToVerify .= hash('sha256', $clientData, true);
+        $signature = substr($signData, 5);
+
+        if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
+            $ctr = unpack("Nctr", substr($signData, 1, 4));
+            $counter = $ctr['ctr'];
+            /* TODO: wrap-around should be handled somehow.. */
+            if($counter > $reg->counter) {
+                $reg->counter = $counter;
+                return $reg;
+            } else {
+                throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
+            }
+        } else {
+            throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
+        }
+    }
+
+    /**
+     * @return array
+     */
+    private function get_certs()
+    {
+        $files = array();
+        $dir = $this->attestDir;
+        if($dir && $handle = opendir($dir)) {
+            while(false !== ($entry = readdir($handle))) {
+                if(is_file("$dir/$entry")) {
+                    $files[] = "$dir/$entry";
+                }
+            }
+            closedir($handle);
+        }
+        return $files;
+    }
+
+    /**
+     * @param string $data
+     * @return string
+     */
+    private function base64u_encode($data)
+    {
+        return trim(strtr(base64_encode($data), '+/', '-_'), '=');
+    }
+
+    /**
+     * @param string $data
+     * @return string
+     */
+    private function base64u_decode($data)
+    {
+        return base64_decode(strtr($data, '-_', '+/'));
+    }
+
+    /**
+     * @param string $key
+     * @return null|string
+     */
+    private function pubkey_to_pem($key)
+    {
+        if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
+            return null;
+        }
+
+        /*
+         * Convert the public key to binary DER format first
+         * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
+         *
+         *  SEQUENCE(2 elem)                        30 59
+         *   SEQUENCE(2 elem)                       30 13
+         *    OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
+         *    OID1.2.840.10045.3.1.7 (secp256r1)    06 08 2a 86 48 ce 3d 03 01 07
+         *   BIT STRING(520 bit)                    03 42 ..key..
+         */
+        $der  = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
+        $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
+        $der .= "\0".$key;
+
+        $pem  = "-----BEGIN PUBLIC KEY-----\r\n";
+        $pem .= chunk_split(base64_encode($der), 64);
+        $pem .= "-----END PUBLIC KEY-----";
+
+        return $pem;
+    }
+
+    /**
+     * @return string
+     * @throws Error
+     */
+    private function createChallenge()
+    {
+        $challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
+        if( $crypto_strong !== true ) {
+            throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
+        }
+
+        $challenge = $this->base64u_encode( $challenge );
+
+        return $challenge;
+    }
+
+    /**
+     * Fixes a certificate where the signature contains unused bits.
+     *
+     * @param string $cert
+     * @return mixed
+     */
+    private function fixSignatureUnusedBits($cert)
+    {
+        if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
+            $cert[strlen($cert) - 257] = "\0";
+        }
+        return $cert;
+    }
+}
+
+/**
+ * Class for building a registration request
+ *
+ * @package u2flib_server
+ */
+class RegisterRequest
+{
+    /** Protocol version */
+    public $version = U2F_VERSION;
+
+    /** Registration challenge */
+    public $challenge;
+
+    /** Application id */
+    public $appId;
+
+    /**
+     * @param string $challenge
+     * @param string $appId
+     * @internal
+     */
+    public function __construct($challenge, $appId)
+    {
+        $this->challenge = $challenge;
+        $this->appId = $appId;
+    }
+}
+
+/**
+ * Class for building up an authentication request
+ *
+ * @package u2flib_server
+ */
+class SignRequest
+{
+    /** Protocol version */
+    public $version = U2F_VERSION;
+
+    /** Authentication challenge */
+    public $challenge;
+
+    /** Key handle of a registered authenticator */
+    public $keyHandle;
+
+    /** Application id */
+    public $appId;
+}
+
+/**
+ * Class returned for successful registrations
+ *
+ * @package u2flib_server
+ */
+class Registration
+{
+    /** The key handle of the registered authenticator */
+    public $keyHandle;
+
+    /** The public key of the registered authenticator */
+    public $publicKey;
+
+    /** The attestation certificate of the registered authenticator */
+    public $certificate;
+
+    /** The counter associated with this registration */
+    public $counter = -1;
+}
+
+/**
+ * Error class, returned on errors
+ *
+ * @package u2flib_server
+ */
+class Error extends \Exception
+{
+    /**
+     * Override constructor and make message and code mandatory
+     * @param string $message
+     * @param int $code
+     * @param \Exception|null $previous
+     */
+    public function __construct($message, $code, \Exception $previous = null) {
+        parent::__construct($message, $code, $previous);
+    }
+}
diff --git a/data/web/index.php b/data/web/index.php
index 6c5ba3c7..42087cdc 100644
--- a/data/web/index.php
+++ b/data/web/index.php
@@ -73,12 +73,14 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
           <?php
           endforeach;
           $app_links = customize('get', 'app_links');
-          foreach ($app_links as $row) {
-            foreach ($row as $key => $val):
-          ?>
-            <a href="<?= htmlspecialchars($val); ?>" role="button" class="btn btn-lg btn-default"><?= htmlspecialchars($key); ?></a>&nbsp;
-          <?php 
-            endforeach;
+          if (!empty($app_links)) {
+            foreach ($app_links as $row) {
+              foreach ($row as $key => $val):
+            ?>
+              <a href="<?= htmlspecialchars($val); ?>" role="button" class="btn btn-lg btn-default"><?= htmlspecialchars($key); ?></a>&nbsp;
+            <?php 
+              endforeach;
+            }
           }
           ?>
         </div>
diff --git a/data/web/js/u2f-api.js b/data/web/js/u2f-api.js
index 0f06f50d..9244d14e 100644
--- a/data/web/js/u2f-api.js
+++ b/data/web/js/u2f-api.js
@@ -1,26 +1,40 @@
-// Copyright 2014-2015 Google Inc. All rights reserved.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file or at
-// https://developers.google.com/open-source/licenses/bsd
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
 
 /**
  * @fileoverview The U2F api.
  */
-
 'use strict';
 
-/** Namespace for the U2F api.
+
+/**
+ * Namespace for the U2F api.
  * @type {Object}
  */
 var u2f = u2f || {};
 
 /**
- * The U2F extension id
- * @type {string}
- * @const
+ * FIDO U2F Javascript API Version
+ * @number
  */
-u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
 
 /**
  * Message types for messsages to/from the extension
@@ -29,11 +43,14 @@ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
  */
 u2f.MessageTypes = {
     'U2F_REGISTER_REQUEST': 'u2f_register_request',
-    'U2F_SIGN_REQUEST': 'u2f_sign_request',
     'U2F_REGISTER_RESPONSE': 'u2f_register_response',
-    'U2F_SIGN_RESPONSE': 'u2f_sign_response'
+    'U2F_SIGN_REQUEST': 'u2f_sign_request',
+    'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+    'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+    'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
 };
 
+
 /**
  * Response status codes
  * @const
@@ -48,17 +65,18 @@ u2f.ErrorCodes = {
     'TIMEOUT': 5
 };
 
+
 /**
- * A message type for registration requests
+ * A message for registration requests
  * @typedef {{
  *   type: u2f.MessageTypes,
- *   signRequests: Array<u2f.SignRequest>,
- *   registerRequests: ?Array<u2f.RegisterRequest>,
+ *   appId: ?string,
  *   timeoutSeconds: ?number,
  *   requestId: ?number
  * }}
  */
-u2f.Request;
+u2f.U2fRequest;
+
 
 /**
  * A message for registration responses
@@ -68,7 +86,8 @@ u2f.Request;
  *   requestId: ?number
  * }}
  */
-u2f.Response;
+u2f.U2fResponse;
+
 
 /**
  * An error object for responses
@@ -79,6 +98,19 @@ u2f.Response;
  */
 u2f.Error;
 
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
 /**
  * Data object for a single sign request.
  * @typedef {{
@@ -90,6 +122,7 @@ u2f.Error;
  */
 u2f.SignRequest;
 
+
 /**
  * Data object for a sign response.
  * @typedef {{
@@ -100,27 +133,51 @@ u2f.SignRequest;
  */
 u2f.SignResponse;
 
+
 /**
  * Data object for a registration request.
  * @typedef {{
  *   version: string,
- *   challenge: string,
- *   appId: string
+ *   challenge: string
  * }}
  */
 u2f.RegisterRequest;
 
+
 /**
  * Data object for a registration response.
  * @typedef {{
- *   registrationData: string,
- *   clientData: string
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: Transports,
+ *   appId: string
  * }}
  */
 u2f.RegisterResponse;
 
 
-// Low level MessagePort API support
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: ?Transports,
+ *   appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ *   js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
 
 /**
  * Sets up a MessagePort to the U2F extension using the
@@ -128,32 +185,34 @@ u2f.RegisterResponse;
  * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
  */
 u2f.getMessagePort = function(callback) {
-    if (typeof chrome != 'undefined' && chrome.runtime) {
-        // The actual message here does not matter, but we need to get a reply
-        // for the callback to run. Thus, send an empty signature request
-        // in order to get a failure response.
-        var msg = {
-            type: u2f.MessageTypes.U2F_SIGN_REQUEST,
-            signRequests: []
-        };
-        chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
-            if (!chrome.runtime.lastError) {
-                // We are on a whitelisted origin and can talk directly
-                // with the extension.
-                u2f.getChromeRuntimePort_(callback);
-            } else {
-                // chrome.runtime was available, but we couldn't message
-                // the extension directly, use iframe
-                u2f.getIframePort_(callback);
-            }
-        });
-    } else if (u2f.isAndroidChrome_()) {
-        u2f.getAuthenticatorPort_(callback);
-    } else {
-        // chrome.runtime was not available at all, which is normal
-        // when this origin doesn't have access to any extensions.
+  if (typeof chrome != 'undefined' && chrome.runtime) {
+    // The actual message here does not matter, but we need to get a reply
+    // for the callback to run. Thus, send an empty signature request
+    // in order to get a failure response.
+    var msg = {
+        type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+        signRequests: []
+    };
+    chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+      if (!chrome.runtime.lastError) {
+        // We are on a whitelisted origin and can talk directly
+        // with the extension.
+        u2f.getChromeRuntimePort_(callback);
+      } else {
+        // chrome.runtime was available, but we couldn't message
+        // the extension directly, use iframe
         u2f.getIframePort_(callback);
-    }
+      }
+    });
+  } else if (u2f.isAndroidChrome_()) {
+    u2f.getAuthenticatorPort_(callback);
+  } else if (u2f.isIosChrome_()) {
+    u2f.getIosPort_(callback);
+  } else {
+    // chrome.runtime was not available at all, which is normal
+    // when this origin doesn't have access to any extensions.
+    u2f.getIframePort_(callback);
+  }
 };
 
 /**
@@ -161,22 +220,30 @@ u2f.getMessagePort = function(callback) {
  * @private
  */
 u2f.isAndroidChrome_ = function() {
-    var userAgent = navigator.userAgent;
-    return userAgent.indexOf('Chrome') != -1 &&
-        userAgent.indexOf('Android') != -1;
+  var userAgent = navigator.userAgent;
+  return userAgent.indexOf('Chrome') != -1 &&
+  userAgent.indexOf('Android') != -1;
 };
 
 /**
- * Connects directly to the extension via chrome.runtime.connect
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+  return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
  * @param {function(u2f.WrappedChromeRuntimePort_)} callback
  * @private
  */
 u2f.getChromeRuntimePort_ = function(callback) {
-    var port = chrome.runtime.connect(u2f.EXTENSION_ID,
-        {'includeTlsChannelId': true});
-    setTimeout(function() {
-        callback(new u2f.WrappedChromeRuntimePort_(port));
-    }, 0);
+  var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+      {'includeTlsChannelId': true});
+  setTimeout(function() {
+    callback(new u2f.WrappedChromeRuntimePort_(port));
+  }, 0);
 };
 
 /**
@@ -185,9 +252,20 @@ u2f.getChromeRuntimePort_ = function(callback) {
  * @private
  */
 u2f.getAuthenticatorPort_ = function(callback) {
-    setTimeout(function() {
-        callback(new u2f.WrappedAuthenticatorPort_());
-    }, 0);
+  setTimeout(function() {
+    callback(new u2f.WrappedAuthenticatorPort_());
+  }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+  setTimeout(function() {
+    callback(new u2f.WrappedIosPort_());
+  }, 0);
 };
 
 /**
@@ -197,53 +275,100 @@ u2f.getAuthenticatorPort_ = function(callback) {
  * @private
  */
 u2f.WrappedChromeRuntimePort_ = function(port) {
-    this.port_ = port;
+  this.port_ = port;
 };
 
 /**
- * Format a return a sign request.
+ * Format and return a sign request compliant with the JS API version supported by the extension.
  * @param {Array<u2f.SignRequest>} signRequests
  * @param {number} timeoutSeconds
  * @param {number} reqId
  * @return {Object}
  */
-u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ =
-    function(signRequests, timeoutSeconds, reqId) {
-        return {
-            type: u2f.MessageTypes.U2F_SIGN_REQUEST,
-            signRequests: signRequests,
-            timeoutSeconds: timeoutSeconds,
-            requestId: reqId
-        };
+u2f.formatSignRequest_ =
+  function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: challenge,
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+      signRequests: signRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
     };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+    appId: appId,
+    challenge: challenge,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
 
 /**
- * Format a return a register request.
+ * Format and return a register request compliant with the JS API version supported by the extension..
  * @param {Array<u2f.SignRequest>} signRequests
  * @param {Array<u2f.RegisterRequest>} signRequests
  * @param {number} timeoutSeconds
  * @param {number} reqId
  * @return {Object}
  */
-u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ =
-    function(signRequests, registerRequests, timeoutSeconds, reqId) {
-        return {
-            type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
-            signRequests: signRequests,
-            registerRequests: registerRequests,
-            timeoutSeconds: timeoutSeconds,
-            requestId: reqId
-        };
+u2f.formatRegisterRequest_ =
+  function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    for (var i = 0; i < registerRequests.length; i++) {
+      registerRequests[i].appId = appId;
+    }
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: registerRequests[0],
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+      signRequests: signRequests,
+      registerRequests: registerRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
     };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+    appId: appId,
+    registerRequests: registerRequests,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
+
 
 /**
  * Posts a message on the underlying channel.
  * @param {Object} message
  */
 u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
-    this.port_.postMessage(message);
+  this.port_.postMessage(message);
 };
 
+
 /**
  * Emulates the HTML 5 addEventListener interface. Works only for the
  * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
@@ -252,16 +377,16 @@ u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
  */
 u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
     function(eventName, handler) {
-        var name = eventName.toLowerCase();
-        if (name == 'message' || name == 'onmessage') {
-            this.port_.onMessage.addListener(function(message) {
-                // Emulate a minimal MessageEvent object
-                handler({'data': message});
-            });
-        } else {
-            console.error('WrappedChromeRuntimePort only supports onMessage');
-        }
-    };
+  var name = eventName.toLowerCase();
+  if (name == 'message' || name == 'onmessage') {
+    this.port_.onMessage.addListener(function(message) {
+      // Emulate a minimal MessageEvent object
+      handler({'data': message});
+    });
+  } else {
+    console.error('WrappedChromeRuntimePort only supports onMessage');
+  }
+};
 
 /**
  * Wrap the Authenticator app with a MessagePort interface.
@@ -269,8 +394,8 @@ u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
  * @private
  */
 u2f.WrappedAuthenticatorPort_ = function() {
-    this.requestId_ = -1;
-    this.requestObject_ = null;
+  this.requestId_ = -1;
+  this.requestObject_ = null;
 }
 
 /**
@@ -278,28 +403,39 @@ u2f.WrappedAuthenticatorPort_ = function() {
  * @param {Object} message
  */
 u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
-    var intentLocation = /** @type {string} */ (message);
-    document.location = intentLocation;
+  var intentUrl =
+    u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+    ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+    ';end';
+  document.location = intentUrl;
 };
 
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+  return "WrappedAuthenticatorPort_";
+};
+
+
 /**
  * Emulates the HTML 5 addEventListener interface.
  * @param {string} eventName
  * @param {function({data: Object})} handler
  */
-u2f.WrappedAuthenticatorPort_.prototype.addEventListener =
-    function(eventName, handler) {
-        var name = eventName.toLowerCase();
-        if (name == 'message') {
-            var self = this;
-            /* Register a callback to that executes when
-             * chrome injects the response. */
-            window.addEventListener(
-                'message', self.onRequestUpdate_.bind(self, handler), false);
-        } else {
-            console.error('WrappedAuthenticatorPort only supports message');
-        }
-    };
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name == 'message') {
+    var self = this;
+    /* Register a callback to that executes when
+     * chrome injects the response. */
+    window.addEventListener(
+        'message', self.onRequestUpdate_.bind(self, handler), false);
+  } else {
+    console.error('WrappedAuthenticatorPort only supports message');
+  }
+};
 
 /**
  * Callback invoked  when a response is received from the Authenticator.
@@ -308,72 +444,18 @@ u2f.WrappedAuthenticatorPort_.prototype.addEventListener =
  */
 u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
     function(callback, message) {
-        var messageObject = JSON.parse(message.data);
-        var intentUrl = messageObject['intentURL'];
+  var messageObject = JSON.parse(message.data);
+  var intentUrl = messageObject['intentURL'];
 
-        var errorCode = messageObject['errorCode'];
-        var responseObject = null;
-        if (messageObject.hasOwnProperty('data')) {
-            responseObject = /** @type {Object} */ (
-                JSON.parse(messageObject['data']));
-            responseObject['requestId'] = this.requestId_;
-        }
+  var errorCode = messageObject['errorCode'];
+  var responseObject = null;
+  if (messageObject.hasOwnProperty('data')) {
+    responseObject = /** @type {Object} */ (
+        JSON.parse(messageObject['data']));
+  }
 
-        /* Sign responses from the authenticator do not conform to U2F,
-         * convert to U2F here. */
-        responseObject = this.doResponseFixups_(responseObject);
-        callback({'data': responseObject});
-    };
-
-/**
- * Fixup the response provided by the Authenticator to conform with
- * the U2F spec.
- * @param {Object} responseData
- * @return {Object} the U2F compliant response object
- */
-u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ =
-    function(responseObject) {
-        if (responseObject.hasOwnProperty('responseData')) {
-            return responseObject;
-        } else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) {
-            // Only sign responses require fixups.  If this is not a response
-            // to a sign request, then an internal error has occurred.
-            return {
-                'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE,
-                'responseData': {
-                    'errorCode': u2f.ErrorCodes.OTHER_ERROR,
-                    'errorMessage': 'Internal error: invalid response from Authenticator'
-                }
-            };
-        }
-
-        /* Non-conformant sign response, do fixups. */
-        var encodedChallengeObject = responseObject['challenge'];
-        if (typeof encodedChallengeObject !== 'undefined') {
-            var challengeObject = JSON.parse(atob(encodedChallengeObject));
-            var serverChallenge = challengeObject['challenge'];
-            var challengesList = this.requestObject_['signData'];
-            var requestChallengeObject = null;
-            for (var i = 0; i < challengesList.length; i++) {
-                var challengeObject = challengesList[i];
-                if (challengeObject['keyHandle'] == responseObject['keyHandle']) {
-                    requestChallengeObject = challengeObject;
-                    break;
-                }
-            }
-        }
-        var responseData = {
-            'errorCode': responseObject['resultCode'],
-            'keyHandle': responseObject['keyHandle'],
-            'signatureData': responseObject['signature'],
-            'clientData': encodedChallengeObject
-        };
-        return {
-            'type': u2f.MessageTypes.U2F_SIGN_RESPONSE,
-            'responseData': responseData,
-            'requestId': responseObject['requestId']
-        }
-    };
+  callback({'data': responseObject});
+};
 
 /**
  * Base URL for intents to Authenticator.
@@ -381,126 +463,44 @@ u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ =
  * @private
  */
 u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
-    'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+  'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
 
 /**
- * Format a return a sign request.
- * @param {Array<u2f.SignRequest>} signRequests
- * @param {number} timeoutSeconds (ignored for now)
- * @param {number} reqId
- * @return {string}
- */
-u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ =
-    function(signRequests, timeoutSeconds, reqId) {
-        if (!signRequests || signRequests.length == 0) {
-            return null;
-        }
-        /* TODO(fixme): stash away requestId, as the authenticator app does
-         * not return it for sign responses. */
-        this.requestId_ = reqId;
-        /* TODO(fixme): stash away the signRequests, to deal with the legacy
-         * response format returned by the Authenticator app. */
-        this.requestObject_ = {
-            'type': u2f.MessageTypes.U2F_SIGN_REQUEST,
-            'signData': signRequests,
-            'requestId': reqId,
-            'timeout': timeoutSeconds
-        };
-
-        var appId = signRequests[0]['appId'];
-        var intentUrl =
-            u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
-            ';S.appId=' + encodeURIComponent(appId) +
-            ';S.eventId=' + reqId +
-            ';S.challenges=' +
-            encodeURIComponent(
-                JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end';
-        return intentUrl;
-    };
-
-/**
- * Get the browser data objects from the challenge list
- * @param {Array} challenges list of challenges
- * @return {Array} list of browser data objects
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
  * @private
  */
-u2f.WrappedAuthenticatorPort_
-    .prototype.getBrowserDataList_ = function(challenges) {
-    return challenges
-        .map(function(challenge) {
-            var browserData = {
-                'typ': 'navigator.id.getAssertion',
-                'challenge': challenge['challenge']
-            };
-            var challengeObject = {
-                'challenge' : browserData,
-                'keyHandle' : challenge['keyHandle']
-            };
-            return challengeObject;
-        });
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+  var str = JSON.stringify(message);
+  var url = "u2f://auth?" + encodeURI(str);
+  location.replace(url);
 };
 
 /**
- * Format a return a register request.
- * @param {Array<u2f.SignRequest>} signRequests
- * @param {Array<u2f.RegisterRequest>} enrollChallenges
- * @param {number} timeoutSeconds (ignored for now)
- * @param {number} reqId
- * @return {Object}
+ * Tells what type of port this is.
+ * @return {String} port type
  */
-u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ =
-    function(signRequests, enrollChallenges, timeoutSeconds, reqId) {
-        if (!enrollChallenges || enrollChallenges.length == 0) {
-            return null;
-        }
-        // Assume the appId is the same for all enroll challenges.
-        var appId = enrollChallenges[0]['appId'];
-        var registerRequests = [];
-        for (var i = 0; i < enrollChallenges.length; i++) {
-            var registerRequest = {
-                'challenge': enrollChallenges[i]['challenge'],
-                'version': enrollChallenges[i]['version']
-            };
-            if (enrollChallenges[i]['appId'] != appId) {
-                // Only include the appId when it differs from the first appId.
-                registerRequest['appId'] = enrollChallenges[i]['appId'];
-            }
-            registerRequests.push(registerRequest);
-        }
-        var registeredKeys = [];
-        if (signRequests) {
-            for (i = 0; i < signRequests.length; i++) {
-                var key = {
-                    'keyHandle': signRequests[i]['keyHandle'],
-                    'version': signRequests[i]['version']
-                };
-                // Only include the appId when it differs from the appId that's
-                // being registered now.
-                if (signRequests[i]['appId'] != appId) {
-                    key['appId'] = signRequests[i]['appId'];
-                }
-                registeredKeys.push(key);
-            }
-        }
-        var request = {
-            'type': u2f.MessageTypes.U2F_REGISTER_REQUEST,
-            'appId': appId,
-            'registerRequests': registerRequests,
-            'registeredKeys': registeredKeys,
-            'requestId': reqId,
-            'timeoutSeconds': timeoutSeconds
-        };
-        var intentUrl =
-            u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
-            ';S.request=' + encodeURIComponent(JSON.stringify(request)) +
-            ';end';
-        /* TODO(fixme): stash away requestId, this is is not necessary for
-         * register requests, but here to keep parity with sign.
-         */
-        this.requestId_ = reqId;
-        return intentUrl;
-    };
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+  return "WrappedIosPort_";
+};
 
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name !== 'message') {
+    console.error('WrappedIosPort only supports message');
+  }
+};
 
 /**
  * Sets up an embedded trampoline iframe, sourced from the extension.
@@ -508,33 +508,33 @@ u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ =
  * @private
  */
 u2f.getIframePort_ = function(callback) {
-    // Create the iframe
-    var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
-    var iframe = document.createElement('iframe');
-    iframe.src = iframeOrigin + '/u2f-comms.html';
-    iframe.setAttribute('style', 'display:none');
-    document.body.appendChild(iframe);
+  // Create the iframe
+  var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+  var iframe = document.createElement('iframe');
+  iframe.src = iframeOrigin + '/u2f-comms.html';
+  iframe.setAttribute('style', 'display:none');
+  document.body.appendChild(iframe);
 
-    var channel = new MessageChannel();
-    var ready = function(message) {
-        if (message.data == 'ready') {
-            channel.port1.removeEventListener('message', ready);
-            callback(channel.port1);
-        } else {
-            console.error('First event on iframe port was not "ready"');
-        }
-    };
-    channel.port1.addEventListener('message', ready);
-    channel.port1.start();
+  var channel = new MessageChannel();
+  var ready = function(message) {
+    if (message.data == 'ready') {
+      channel.port1.removeEventListener('message', ready);
+      callback(channel.port1);
+    } else {
+      console.error('First event on iframe port was not "ready"');
+    }
+  };
+  channel.port1.addEventListener('message', ready);
+  channel.port1.start();
 
-    iframe.addEventListener('load', function() {
-        // Deliver the port to the iframe and initialize
-        iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
-    });
+  iframe.addEventListener('load', function() {
+    // Deliver the port to the iframe and initialize
+    iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+  });
 };
 
 
-// High-level JS API
+//High-level JS API
 
 /**
  * Default extension response timeout in seconds.
@@ -577,22 +577,22 @@ u2f.callbackMap_ = {};
  * @private
  */
 u2f.getPortSingleton_ = function(callback) {
-    if (u2f.port_) {
-        callback(u2f.port_);
-    } else {
-        if (u2f.waitingForPort_.length == 0) {
-            u2f.getMessagePort(function(port) {
-                u2f.port_ = port;
-                u2f.port_.addEventListener('message',
-                    /** @type {function(Event)} */ (u2f.responseHandler_));
+  if (u2f.port_) {
+    callback(u2f.port_);
+  } else {
+    if (u2f.waitingForPort_.length == 0) {
+      u2f.getMessagePort(function(port) {
+        u2f.port_ = port;
+        u2f.port_.addEventListener('message',
+            /** @type {function(Event)} */ (u2f.responseHandler_));
 
-                // Careful, here be async callbacks. Maybe.
-                while (u2f.waitingForPort_.length)
-                    u2f.waitingForPort_.shift()(u2f.port_);
-            });
-        }
-        u2f.waitingForPort_.push(callback);
+        // Careful, here be async callbacks. Maybe.
+        while (u2f.waitingForPort_.length)
+          u2f.waitingForPort_.shift()(u2f.port_);
+      });
     }
+    u2f.waitingForPort_.push(callback);
+  }
 };
 
 /**
@@ -601,51 +601,148 @@ u2f.getPortSingleton_ = function(callback) {
  * @private
  */
 u2f.responseHandler_ = function(message) {
-    var response = message.data;
-    var reqId = response['requestId'];
-    if (!reqId || !u2f.callbackMap_[reqId]) {
-        console.error('Unknown or missing requestId in response.');
-        return;
-    }
-    var cb = u2f.callbackMap_[reqId];
-    delete u2f.callbackMap_[reqId];
-    cb(response['responseData']);
+  var response = message.data;
+  var reqId = response['requestId'];
+  if (!reqId || !u2f.callbackMap_[reqId]) {
+    console.error('Unknown or missing requestId in response.');
+    return;
+  }
+  var cb = u2f.callbackMap_[reqId];
+  delete u2f.callbackMap_[reqId];
+  cb(response['responseData']);
 };
 
 /**
  * Dispatches an array of sign requests to available U2F tokens.
- * @param {Array<u2f.SignRequest>} signRequests
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
  * @param {function((u2f.Error|u2f.SignResponse))} callback
  * @param {number=} opt_timeoutSeconds
  */
-u2f.sign = function(signRequests, callback, opt_timeoutSeconds) {
-    u2f.getPortSingleton_(function(port) {
-        var reqId = ++u2f.reqCounter_;
-        u2f.callbackMap_[reqId] = callback;
-        var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
-            opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
-        var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId);
-        port.postMessage(req);
-    });
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual sign request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual sign request in the supported API version.
+    u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
 };
 
 /**
  * Dispatches register requests to available U2F tokens. An array of sign
  * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
  * @param {Array<u2f.RegisterRequest>} registerRequests
- * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
  * @param {function((u2f.Error|u2f.RegisterResponse))} callback
  * @param {number=} opt_timeoutSeconds
  */
-u2f.register = function(registerRequests, signRequests,
-                        callback, opt_timeoutSeconds) {
-    u2f.getPortSingleton_(function(port) {
-        var reqId = ++u2f.reqCounter_;
-        u2f.callbackMap_[reqId] = callback;
-        var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
-            opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
-        var req = port.formatRegisterRequest_(
-            signRequests, registerRequests, timeoutSeconds, reqId);
-        port.postMessage(req);
-    });
-};
\ No newline at end of file
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual register request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+              callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual register request in the supported API version.
+    u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+        callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatRegisterRequest_(
+        appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+   // If we are using Android Google Authenticator or iOS client app,
+   // do not fire an intent to ask which JS API version to use.
+   if (port.getPortType) {
+     var apiVersion;
+     switch (port.getPortType()) {
+       case 'WrappedIosPort_':
+       case 'WrappedAuthenticatorPort_':
+         apiVersion = 1.1;
+         break;
+
+       default:
+         apiVersion = 0;
+         break;
+     }
+     callback({ 'js_api_version': apiVersion });
+     return;
+   }
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var req = {
+      type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+      timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+          opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+      requestId: reqId
+    };
+    port.postMessage(req);
+  });
+};
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 9ada1044..cf6d8416 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -1342,10 +1342,13 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "u2f-registration":
             header('Content-Type: application/javascript');
             if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
-              $data = $u2f->getRegisterData(get_u2f_registrations($object));
-              list($req, $sigs) = $data;
+              list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
               $_SESSION['regReq'] = json_encode($req);
-              echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
+              $_SESSION['regSigs'] = json_encode($sigs);
+              echo 'var req = ' . json_encode($req) . ';';
+              echo 'var registeredKeys = ' . json_encode($sigs) . ';';
+              echo 'var appId = req.appId;';
+              echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
             }
             else {
               return;
@@ -1354,9 +1357,19 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "u2f-authentication":
             header('Content-Type: application/javascript');
             if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
-              $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
-              $_SESSION['authReq']  = $reqs;
-              echo 'var req = ' . $reqs . ';';
+              $auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
+              $challenge = $auth_data[0]->challenge;
+              $appId = $auth_data[0]->appId;
+              foreach ($auth_data as $each) {
+                $key = array(); // Empty array
+                $key['version']   = $each->version;
+                $key['keyHandle'] = $each->keyHandle;
+                $registeredKey[]  = $key;
+              }
+              $_SESSION['authReq']  = json_encode($auth_data);
+              echo 'var appId = "' . $appId . '";';
+              echo 'var challenge = ' . json_encode($challenge) . ';';
+              echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
             }
             else {
               return;

From 3ec3a341e47038dce409736e45d0e3a3f93a7ca5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9?= <andre.peters@debinux.de>
Date: Tue, 21 Nov 2017 09:33:43 +0100
Subject: [PATCH 04/40] [Postfix] Remove gw from mynetworks in case of ipv6
 failures

---
 data/conf/postfix/main.cf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf
index 4e8c577f..5a8c0fa7 100644
--- a/data/conf/postfix/main.cf
+++ b/data/conf/postfix/main.cf
@@ -9,7 +9,7 @@ smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_una
 alias_maps = hash:/etc/aliases
 alias_database = hash:/etc/aliases
 relayhost =
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64 !172.22.1.1
 mailbox_size_limit = 0
 recipient_delimiter = +
 inet_interfaces = all

From 7d6fc8e6b6ced23cf156bbe7ce69879286045bf1 Mon Sep 17 00:00:00 2001
From: Peter Schiffer <pschiffe@redhat.com>
Date: Fri, 1 Dec 2017 23:41:37 +0100
Subject: [PATCH 05/40] [Dockerapi] Auto detect version of docker server

Some older versions of docker need specific version of client api to be able to
communicate. This change allows automatically detect and set version of API to
match server version of API.

Fixes #765
---
 data/Dockerfiles/dockerapi/server.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index b406b5ee..63916eb8 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -7,7 +7,7 @@ import docker
 import signal
 import time
 
-docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock')
+docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
 app = Flask(__name__)
 api = Api(app)
 

From 21a677e024cfe5ebb76e0c3675462e4b711da4ca Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 09:06:04 +0100
Subject: [PATCH 06/40] [MariaDB] Move config to my.cnf, removed from yml

---
 data/conf/mysql/my.cnf | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/data/conf/mysql/my.cnf b/data/conf/mysql/my.cnf
index 668e390a..772c8a62 100644
--- a/data/conf/mysql/my.cnf
+++ b/data/conf/mysql/my.cnf
@@ -6,6 +6,13 @@ innodb_file_per_table          = TRUE
 innodb_file_format             = barracuda
 innodb_large_prefix            = TRUE
 #sql_mode=IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
+max_allowed_packet=192M
+max-connections=1500
+innodb-strict-mode=0
+skip-host-cache
+skip-name-resolve
+log-warnings=0
+event_scheduler=1
 
 [client]
 default-character-set = utf8mb4

From c8f41cdae20c8676fa8fa9d62c4a08cd347945ad Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 09:07:06 +0100
Subject: [PATCH 07/40] [Postfix] Listener for quarantaine, remove excluded
 Docker gw from mynetworks

---
 data/conf/postfix/main.cf   | 2 +-
 data/conf/postfix/master.cf | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf
index 5a8c0fa7..4e8c577f 100644
--- a/data/conf/postfix/main.cf
+++ b/data/conf/postfix/main.cf
@@ -9,7 +9,7 @@ smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_una
 alias_maps = hash:/etc/aliases
 alias_database = hash:/etc/aliases
 relayhost =
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64 !172.22.1.1
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64
 mailbox_size_limit = 0
 recipient_delimiter = +
 inet_interfaces = all
diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf
index 77790d56..4911271e 100644
--- a/data/conf/postfix/master.cf
+++ b/data/conf/postfix/master.cf
@@ -12,6 +12,11 @@ submission inet n       -       n       -       -       smtpd
 588 inet n      -       n       -       -       smtpd
   -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
   -o smtpd_tls_auth_only=no
+590 inet n      -       n       -       -       smtpd
+  -o smtpd_client_restrictions=permit_mynetworks,reject
+  -o smtpd_tls_auth_only=no
+  -o smtpd_milters=
+  -o non_smtpd_milters=
 smtp_enforced_tls      unix  -       -       n       -       -       smtp
   -o smtp_tls_security_level=encrypt
   -o syslog_name=enforced-tls-smtp

From 873222d5f8f9efc3355b1babe394a543698bcfa4 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 09:08:18 +0100
Subject: [PATCH 08/40] [Rspamd] Remove DKIM forced action, move ratelimit lua,
 add meta exporter

---
 data/conf/rspamd/custom/.empty                |   1 -
 data/conf/rspamd/dynmaps/settings.php         |   2 +-
 data/conf/rspamd/local.d/composites.conf      |   8 +-
 data/conf/rspamd/local.d/force_actions.conf   |  15 --
 data/conf/rspamd/local.d/statistic.conf       |   3 +-
 .../conf/rspamd/{custom => lua}/ratelimit.lua |   0
 data/conf/rspamd/meta_exporter/pipe.php       | 155 ++++++++++++++++++
 data/conf/rspamd/meta_exporter/vars.inc.php   |   3 +
 data/conf/rspamd/override.d/logging.inc       |   1 +
 data/conf/rspamd/override.d/ratelimit.conf    |   2 +-
 .../rspamd/override.d/worker-controller.inc   |   3 +-
 data/conf/rspamd/override.d/worker-normal.inc |   1 +
 data/conf/rspamd/override.d/worker-proxy.inc  |   1 +
 13 files changed, 172 insertions(+), 23 deletions(-)
 delete mode 100644 data/conf/rspamd/custom/.empty
 rename data/conf/rspamd/{custom => lua}/ratelimit.lua (100%)
 create mode 100644 data/conf/rspamd/meta_exporter/pipe.php
 create mode 100644 data/conf/rspamd/meta_exporter/vars.inc.php

diff --git a/data/conf/rspamd/custom/.empty b/data/conf/rspamd/custom/.empty
deleted file mode 100644
index d00491fd..00000000
--- a/data/conf/rspamd/custom/.empty
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php
index 335c0c66..fcc656a8 100644
--- a/data/conf/rspamd/dynmaps/settings.php
+++ b/data/conf/rspamd/dynmaps/settings.php
@@ -17,7 +17,7 @@ $opt = [
 ];
 try {
   $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
-  $stmt = $pdo->query("SELECT * FROM `filterconf`");
+  $stmt = $pdo->query("SELECT '1' FROM `filterconf`");
 }
 catch (PDOException $e) {
   echo 'settings { }';
diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf
index e895fb0f..ea35dc92 100644
--- a/data/conf/rspamd/local.d/composites.conf
+++ b/data/conf/rspamd/local.d/composites.conf
@@ -1,4 +1,8 @@
 MX_IMPLICIT {
-    expression = "MX_GOOD and MX_MISSING";
-    score = -0.01;
+  expression = "MX_GOOD and MX_MISSING";
+  score = -0.01;
+}
+VIRUS_FOUND {
+  expression = "CLAM_VIRUS & !MAILCOW_WHITE";
+  score = 2000;
 }
diff --git a/data/conf/rspamd/local.d/force_actions.conf b/data/conf/rspamd/local.d/force_actions.conf
index 956402f5..a1b9899d 100644
--- a/data/conf/rspamd/local.d/force_actions.conf
+++ b/data/conf/rspamd/local.d/force_actions.conf
@@ -1,14 +1,4 @@
 rules {
-  DKIM_FAIL {
-    action = "add header";
-    expression = "R_DKIM_REJECT & !MAILLIST & !MAILCOW_WHITE & !MAILCOW_BLACK";
-    require_action = ["no action", "greylist", "soft reject"];
-  }
-  VIRUS_FOUND {
-    action = "reject";
-    expression = "CLAM_VIRUS & !MAILCOW_WHITE";
-    honor_action = ["reject"];
-  }
   WHITELIST_FORWARDING_HOST_NO_REJECT {
     action = "add header";
     expression = "WHITELISTED_FWD_HOST";
@@ -19,9 +9,4 @@ rules {
     expression = "WHITELISTED_FWD_HOST";
     require_action = ["greylist", "soft reject"];
   }
-  ADD_UNAUTH_SUBJ {
-    action = "rewrite subject";
-    subject = "[Unauth] %s";
-    expression = "SPOOFED_SENDER";
-  }
 }
diff --git a/data/conf/rspamd/local.d/statistic.conf b/data/conf/rspamd/local.d/statistic.conf
index 4b410842..b66f5018 100644
--- a/data/conf/rspamd/local.d/statistic.conf
+++ b/data/conf/rspamd/local.d/statistic.conf
@@ -7,8 +7,7 @@ classifier "bayes" {
     servers = "redis:6379";
     min_tokens = 11;
     min_learns = 20;
-    autolearn = true;
-
+    autolearn = [-20, 50];
     per_user = <<EOD
 return function(task)
     local rcpt = task:get_recipients(1)
diff --git a/data/conf/rspamd/custom/ratelimit.lua b/data/conf/rspamd/lua/ratelimit.lua
similarity index 100%
rename from data/conf/rspamd/custom/ratelimit.lua
rename to data/conf/rspamd/lua/ratelimit.lua
diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php
new file mode 100644
index 00000000..1075032c
--- /dev/null
+++ b/data/conf/rspamd/meta_exporter/pipe.php
@@ -0,0 +1,155 @@
+<?php
+// File size is limited by Nginx site to 10M
+// To speed things up, we do not include prerequisites
+header('Content-Type: text/plain');
+require_once "vars.inc.php";
+// Do not show errors, we log to using error_log
+ini_set('error_reporting', 0);
+// Init database
+$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
+$opt = [
+    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
+    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+    PDO::ATTR_EMULATE_PREPARES   => false,
+];
+try {
+  $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+}
+catch (PDOException $e) {
+  http_response_code(501);
+  exit;
+}
+// Init Redis
+$redis = new Redis();
+$redis->connect('redis-mailcow', 6379);
+
+// Functions
+function parse_email($email) {
+  if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
+  $a = strrpos($email, '@');
+  return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
+}
+if (!function_exists('getallheaders'))  {
+  function getallheaders() {
+    if (!is_array($_SERVER)) {
+      return array();
+    }
+    $headers = array();
+    foreach ($_SERVER as $name => $value) {
+      if (substr($name, 0, 5) == 'HTTP_') {
+        $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
+      }
+    }
+    return $headers;
+  }
+}
+
+$raw_data = file_get_contents('php://input');
+$headers = getallheaders();
+
+$qid      = $headers['X-Rspamd-Qid'];
+$score    = $headers['X-Rspamd-Score'];
+$rcpts    = $headers['X-Rspamd-Rcpt'];
+$user     = $headers['X-Rspamd-User'];
+$ip       = $headers['X-Rspamd-Ip'];
+$action   = $headers['X-Rspamd-Action'];
+$sender   = $headers['X-Rspamd-From'];
+$symbols  = $headers['X-Rspamd-Symbols'];
+
+$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
+
+try {
+  if ($max_size = $redis->Get('Q_MAX_SIZE')) {
+    if (!empty($max_size) && ($max_size * 1048576) < $raw_size) {
+      error_log(sprintf("Message too large: %d exceeds %d", $raw_size, ($max_size * 1048576)));
+      http_response_code(505);
+      exit;
+    }
+  }
+  if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) {
+    $exclude_domains = json_decode($exclude_domains, true);
+  }
+  $retention_size = (int)$redis->Get('Q_RETENTION_SIZE');
+}
+catch (RedisException $e) {
+  error_log($e);
+  http_response_code(504);
+  exit;
+}
+
+$filtered_rcpts = array();
+foreach (json_decode($rcpts, true) as $rcpt) {
+  $parsed_mail = parse_email($rcpt);
+  if (in_array($parsed_mail['domain'], $exclude_domains)) {
+    error_log(sprintf("Skipped domain %s", $parsed_mail['domain']));
+    continue;
+  }
+  try {
+    $stmt = $pdo->prepare("SELECT `goto` FROM `alias`
+      WHERE
+      (
+        `address` = :rcpt
+        OR
+        `address` IN (
+          SELECT username FROM mailbox, alias_domain
+            WHERE (alias_domain.alias_domain = :domain_part
+              AND mailbox.username = CONCAT(:local_part, '@', alias_domain.target_domain)
+              AND mailbox.active = '1'
+              AND alias_domain.active='1')
+        )
+      )
+      AND `active`= '1';");
+    $stmt->execute(array(
+      ':rcpt' => $rcpt,
+      ':local_part' => $parsed_mail['local'],
+      ':domain_part' => $parsed_mail['domain']
+    ));
+    $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
+    if (!empty($gotos)) {
+      $filtered_rcpts  = array_unique(array_merge($filtered_rcpts, explode(',', $gotos)));
+    }
+  }
+  catch (PDOException $e) {
+    error_log($e->getMessage());
+    http_response_code(502);
+    exit;
+  }
+}
+foreach ($filtered_rcpts as $rcpt) {
+  
+  try {
+    $stmt = $pdo->prepare("INSERT INTO `quarantaine` (`qid`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`)
+      VALUES (:qid, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action)");
+    $stmt->execute(array(
+      ':qid' => $qid,
+      ':score' => $score,
+      ':sender' => $sender,
+      ':rcpt' => $rcpt,
+      ':symbols' => $symbols,
+      ':user' => $user,
+      ':ip' => $ip,
+      ':msg' => $raw_data,
+      ':action' => $action
+    ));
+    $stmt = $pdo->prepare('DELETE FROM `quarantaine` WHERE `id` NOT IN ( 
+      SELECT `id`
+      FROM (
+        SELECT `id`
+        FROM `quarantaine`
+        WHERE `rcpt` = :rcpt
+        ORDER BY id DESC
+        LIMIT :retention_size
+      ) x 
+    );');
+    $stmt->execute(array(
+      ':rcpt' => $rcpt,
+      ':retention_size' => $retention_size
+    ));
+  }
+  catch (PDOException $e) {
+    error_log($e->getMessage());
+    http_response_code(503);
+    exit;
+  }
+}
+
diff --git a/data/conf/rspamd/meta_exporter/vars.inc.php b/data/conf/rspamd/meta_exporter/vars.inc.php
new file mode 100644
index 00000000..d47e9079
--- /dev/null
+++ b/data/conf/rspamd/meta_exporter/vars.inc.php
@@ -0,0 +1,3 @@
+<?php
+require_once('../../../web/inc/vars.inc.php');
+?>
diff --git a/data/conf/rspamd/override.d/logging.inc b/data/conf/rspamd/override.d/logging.inc
index 64a2b7d4..23a9f3cf 100644
--- a/data/conf/rspamd/override.d/logging.inc
+++ b/data/conf/rspamd/override.d/logging.inc
@@ -1,3 +1,4 @@
 type = "console";
 systemd = false;
 .include "$CONFDIR/logging.inc"
+.include(try=true; priority=20) "$CONFDIR/override.d/logging.custom.inc"
diff --git a/data/conf/rspamd/override.d/ratelimit.conf b/data/conf/rspamd/override.d/ratelimit.conf
index 3c0a55c3..f9e359bc 100644
--- a/data/conf/rspamd/override.d/ratelimit.conf
+++ b/data/conf/rspamd/override.d/ratelimit.conf
@@ -9,6 +9,6 @@ rates {
 }
 whitelisted_rcpts = "postmaster,mailer-daemon";
 max_rcpt = 5;
-custom_keywords = "/etc/rspamd/custom/ratelimit.lua";
+custom_keywords = "/etc/rspamd/lua/ratelimit.lua";
 user_keywords = ["user", "customrl"];
 dynamic_rates = { customrl = "customrl"}
diff --git a/data/conf/rspamd/override.d/worker-controller.inc b/data/conf/rspamd/override.d/worker-controller.inc
index ae136461..22d9a024 100644
--- a/data/conf/rspamd/override.d/worker-controller.inc
+++ b/data/conf/rspamd/override.d/worker-controller.inc
@@ -1,8 +1,9 @@
 bind_socket = "*:11334";
-enable_password = "$2$pppq86q9uns51zd5ekfxecj7bxwaefo3$p7f9xdhamydjhtypcr639it3kqeiknx3dk9on7skjypyi8uwwcmy";
 secure_ip = "192.168.0.0/16";
 secure_ip = "172.16.0.0/12";
 secure_ip = "10.0.0.0/8";
 secure_ip = "127.0.0.1";
 secure_ip = "::1";
 secure_ip = "fd4d:6169:6c63:6f77::/64"
+.include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc"
+.include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc" 
diff --git a/data/conf/rspamd/override.d/worker-normal.inc b/data/conf/rspamd/override.d/worker-normal.inc
index 71569636..a7ab4baf 100644
--- a/data/conf/rspamd/override.d/worker-normal.inc
+++ b/data/conf/rspamd/override.d/worker-normal.inc
@@ -1,2 +1,3 @@
 bind_socket = "*:11333";
 task_timeout = 12s;
+.include(try=true; priority=20) "$CONFDIR/override.d/worker-normal.custom.inc"
diff --git a/data/conf/rspamd/override.d/worker-proxy.inc b/data/conf/rspamd/override.d/worker-proxy.inc
index b87c7f29..0df926a7 100644
--- a/data/conf/rspamd/override.d/worker-proxy.inc
+++ b/data/conf/rspamd/override.d/worker-proxy.inc
@@ -5,3 +5,4 @@ upstream {
   default = true;
   hosts = "rspamd:11333"
 }
+.include(try=true; priority=20) "$CONFDIR/override.d/worker-proxy.custom.inc"

From 25197380946162b35b052c325af52bcc2708d69c Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 13:15:24 +0100
Subject: [PATCH 09/40] Various changes...

---
 data/Dockerfiles/acme/Dockerfile              | 13 ++---
 data/Dockerfiles/acme/docker-entrypoint.sh    | 49 +++++++++++++------
 data/Dockerfiles/dockerapi/server.py          | 41 +++++++++++++---
 data/Dockerfiles/phpfpm/Dockerfile            |  4 +-
 .../postfix/whitelist_forwardinghosts.sh      |  2 +-
 data/Dockerfiles/watchdog/watchdog.sh         | 48 +++---------------
 6 files changed, 84 insertions(+), 73 deletions(-)

diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile
index 44e7030b..5c99cbf9 100644
--- a/data/Dockerfiles/acme/Dockerfile
+++ b/data/Dockerfiles/acme/Dockerfile
@@ -3,20 +3,21 @@ FROM alpine:3.6
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 
 RUN apk add --update --no-cache \
-	bash \
-	curl \
-	openssl \
-	bind-tools \
-	jq \
+  bash \
+  curl \
+  openssl \
+  bind-tools \
+  jq \
   libressl-dev \
   libbsd-dev \
   libseccomp-dev \
-	mariadb-client \
+  mariadb-client \
   tini \
   make \
   gcc \
   libressl \
   libc-dev \
+  redis \
   linux-headers \
   ca-certificates \
   && curl -s https://kristaps.bsd.lv/acme-client/snapshots/acme-client-portable.tgz | tar xfvz - \
diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh
index d29d812e..ef466d36 100755
--- a/data/Dockerfiles/acme/docker-entrypoint.sh
+++ b/data/Dockerfiles/acme/docker-entrypoint.sh
@@ -2,16 +2,30 @@
 set -o pipefail
 exec 5>&1
 
+log_f() {
+  if [[ ${2} == "no_nl" ]]; then
+    echo -n "$(date) - ${1}"
+  elif [[ ${2} == "no_date" ]]; then
+    echo "${1}"
+  elif [[ ${2} != "redis_only" ]]; then
+    echo "$(date) - ${1}"
+  fi
+  redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
+    tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null
+  redis-cli -h redis LTRIM ACME_LOG 0 9999 > /dev/null
+}
+
 if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
   log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
   sleep 365d
   exec $(readlink -f "$0")
 fi
 
-echo "Waiting for Docker API..."
+log_f "Waiting for Docker API..." no_nl
 until ping dockerapi -c1 > /dev/null; do
   sleep 1
 done
+log_f "Found Docker API" no_date
 
 ACME_BASE=/var/lib/acme
 SSL_EXAMPLE=/var/lib/ssl-example
@@ -20,21 +34,12 @@ mkdir -p ${ACME_BASE}/acme/private
 
 restart_containers(){
   for container in $*; do
-    echo "Restarting ${container}..."
-    curl -X POST http://dockerapi:8080/containers/${container}/restart
+    log_f "Restarting ${container}..." no_nl
+    C_REST_OUT=$(curl -X POST http://dockerapi:8080/containers/${container}/restart | jq -r '.msg')
+    log_f "${C_REST_OUT}" no_date
   done
 }
 
-log_f() {
-  if [[ ${2} == "no_nl" ]]; then
-    echo -n "$(date) - ${1}"
-  elif [[ ${2} == "no_date" ]]; then
-    echo "${1}"
-  else
-    echo "$(date) - ${1}"
-  fi
-}
-
 array_diff() {
   # https://stackoverflow.com/questions/2312762, Alex Offshore
   eval local ARR1=\(\"\${$2[@]}\"\)
@@ -123,16 +128,23 @@ while true; do
   declare -a VALIDATED_CONFIG_DOMAINS
   declare -a ADDITIONAL_VALIDATED_SAN
   IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
-  IPV4=$(get_ipv4)
+  until [[ ${IPV4} == ${EXTERNAL_IPV4} ]]; do
+    IPV4=$(get_ipv4)
+    if [[ ${IPV4} != ${EXTERNAL_IPV4} ]]; then
+      echo "Waiting for correct source ip..."
+      sleep 30s
+    fi
+  done
   # Container ids may have changed
   CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
 
-  log_f "Waiting for domain tables... " no_nl
+  log_f "Waiting for domain table... " no_nl
   while [[ -z ${DOMAIN_TABLE} ]]; do
+    curl --silent http://nginx/ >/dev/null 2>&1
     DOMAIN_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
     [[ -z ${DOMAIN_TABLE} ]] && sleep 10
   done
-  log_f "OK" no_date
+  log_f "Found domain tables." no_date
 
   while read domains; do
     SQL_DOMAIN_ARR+=("${domains}")
@@ -226,6 +238,7 @@ while true; do
 
   case "$?" in
     0) # new certs
+      log_f "${ACME_RESPONSE}" redis_only
       # cp the new certificates and keys
       cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
       cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
@@ -239,6 +252,7 @@ while true; do
       restart_containers ${CONTAINERS_RESTART[*]}
       ;;
     1) # failure
+      log_f "${ACME_RESPONSE}" redis_only
       if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then
         log_f "Registration keys are invalid, deleting old keys and restarting..."
         rm ${ACME_BASE}/acme/private/account.key
@@ -268,6 +282,7 @@ while true; do
       exec $(readlink -f "$0")
       ;;
     2) # no change
+      log_f "${ACME_RESPONSE}" redis_only
       if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then
         log_f "Certificate was not changed, but active certificate does not match the verified certificate, fixing and restarting containers..."
         cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
@@ -280,9 +295,11 @@ while true; do
         cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
         TRIGGER_RESTART=1
       fi
+      log_f "Certificate was not changed"
       [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]}
       ;;
     *) # unspecified
+      log_f "${ACME_RESPONSE}" redis_only
       if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then
         log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...."
         cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem
diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index 63916eb8..00f7ebef 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -6,6 +6,9 @@ from threading import Thread
 import docker
 import signal
 import time
+import os
+import re
+import sys
 
 docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
 app = Flask(__name__)
@@ -15,7 +18,7 @@ class containers_get(Resource):
   def get(self):
     containers = {}
     try:
-      for container in docker_client.containers.list(all=True):
+      for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME']}):
         containers.update({container.attrs['Id']: container.attrs})
       return containers
     except Exception as e:
@@ -25,19 +28,30 @@ class container_get(Resource):
   def get(self, container_id):
     if container_id and container_id.isalnum():
       try:
-        for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+        for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
           return container.attrs
       except Exception as e:
           return jsonify(type='danger', msg=e)
     else:
       return jsonify(type='danger', msg='no or invalid id defined')
 
+class container_logs(Resource):
+  def get(self, container_id, lines):
+    if container_id and container_id.isalnum() and lines:
+      try:
+        for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+          return container.logs(stdout=True, stderr=True, stream=False, tail=lines)
+      except Exception as e:
+          return jsonify(type='danger', msg=e)
+    else:
+      return jsonify(type='danger', msg='no or invalid id defined')
+
 class container_post(Resource):
   def post(self, container_id, post_action):
     if container_id and container_id.isalnum() and post_action:
       if post_action == 'stop':
         try:
-          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
             container.stop()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -45,7 +59,7 @@ class container_post(Resource):
 
       elif post_action == 'start':
         try:
-          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
             container.start()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -53,7 +67,7 @@ class container_post(Resource):
 
       elif post_action == 'restart':
         try:
-          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
             container.restart()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -66,16 +80,28 @@ class container_post(Resource):
 
         if request.json['cmd'] == 'sieve_list' and request.json['username']:
           try:
-            for container in docker_client.containers.list(filters={"id": container_id}):
+            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
               return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail')
           except Exception as e:
             return jsonify(type='danger', msg=e)
         elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']:
           try:
-            for container in docker_client.containers.list(filters={"id": container_id}):
+            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
               return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail')
           except Exception as e:
             return jsonify(type='danger', msg=e)
+        elif request.json['cmd'] == 'worker_password' and request.json['raw']:
+          try:
+            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+              hash = container.exec_run(["/bin/bash", "-c", "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "'"], user='_rspamd')
+              f = open("/access.inc", "w")
+              f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash.rstrip()) + '";\n')
+              f.close()
+              container.restart()
+              return jsonify(type='success', msg='command completed successfully')
+          except Exception as e:
+            return jsonify(type='danger', msg=e)
+
         else:
           return jsonify(type='danger', msg='Unknown command')
 
@@ -99,6 +125,7 @@ def startFlaskAPI():
 
 api.add_resource(containers_get, '/containers/json')
 api.add_resource(container_get, '/containers/<string:container_id>/json')
+api.add_resource(container_logs, '/containers/<string:container_id>/logs/<int:lines>')
 api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
 
 if __name__ == '__main__':
diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile
index 84a1f67b..5259e70d 100644
--- a/data/Dockerfiles/phpfpm/Dockerfile
+++ b/data/Dockerfiles/phpfpm/Dockerfile
@@ -41,8 +41,8 @@ RUN apk add -U --no-cache libxml2-dev \
     Net_Sieve \
     NET_SMTP \
     Mail_mime \
-	&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
-	&& docker-php-ext-enable redis apcu memcached imagick \
+	&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse \
+	&& docker-php-ext-enable redis apcu memcached imagick mailparse \
 	&& pecl clear-cache \
 	&& docker-php-ext-configure intl \
   && docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \
diff --git a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
index ab066d89..4ad5ab32 100755
--- a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
+++ b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
@@ -6,7 +6,7 @@ while read QUERY; do
 		echo "500 dunno"
 		continue
 	fi
-	result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]})
+	result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]})
 	logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
 	echo ${result}
 done
diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh
index 902f66dc..ec7133e5 100755
--- a/data/Dockerfiles/watchdog/watchdog.sh
+++ b/data/Dockerfiles/watchdog/watchdog.sh
@@ -28,21 +28,19 @@ progress() {
   [[ ${CURRENT} -gt ${TOTAL} ]] && return
   [[ ${CURRENT} -lt 0 ]] && CURRENT=0
   PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
-  log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}"
-  log_data "$(printf "%d,%d,%d,%d" ${PERCENT} ${CURRENT} ${TOTAL} ${DIFF})" "${SERVICE}"
+  redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null
+  log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis
 }
 
 log_msg() {
-	redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" > /dev/null
+  if [[ ${2} != "no_redis" ]]; then
+    redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
+      tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null
+  fi
+  redis-cli -h redis LTRIM WATCHDOG_LOG 0 9999 > /dev/null
   echo $(date) $(printf '%s\n' "${1}")
 }
 
-log_data() {
-  [[ -z ${1} ]] && return 1
-  [[ -z ${2} ]] && return 2
-	redis-cli -h redis LPUSH WATCHDOG_DATA "{\"time\":\"$(date +%s)\",\"service\":\"data\",\"$(printf '%s' "${2}")\":\"$(printf '%s' "${1}")\"}" > /dev/null
-}
-
 function mail_error() {
   [[ -z ${1} ]] && return 1
   [[ -z ${2} ]] && return 2
@@ -234,27 +232,6 @@ Empty
   return 1
 }
 
-dns_checks() {
-  err_count=0
-  diff_c=0
-  THRESHOLD=28
-  # Reduce error count by 2 after restarting an unhealthy container
-  trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
-  while [ ${err_count} -lt ${THRESHOLD} ]; do
-    host_ip=$(get_container_ip unbound-mailcow)
-    err_c_cur=${err_count}
-    /usr/lib/nagios/plugins/check_dns -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
-    /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
-    dig +dnssec org. @${host_ip} | grep -E 'flags:.+ad' 1>&2; err_count=$(( ${err_count} + ($? * 2)))
-    [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
-    [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
-    progress "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
-    diff_c=0
-    sleep $(( ( RANDOM % 30 )  + 10 ))
-  done
-  return 1
-}
-
 # Create watchdog agents
 (
 while true; do
@@ -322,17 +299,6 @@ done
 ) &
 BACKGROUND_TASKS+=($!)
 
-(
-while true; do
-  if ! dns_checks; then
-    log_msg "Unbound hit error limit"
-    [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow"
-    #echo unbound-mailcow > /tmp/com_pipe
-  fi
-done
-) &
-BACKGROUND_TASKS+=($!)
-
 (
 while true; do
   if ! rspamd_checks; then

From 5f5b6652a18df1502ce25d3180b3fcd9283d17d3 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 13:15:39 +0100
Subject: [PATCH 10/40] Remove health check

---
 docker-compose.yml | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index d34cea93..d96d0a4f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,9 +5,6 @@ services:
       image: mailcow/unbound:1.0
       build: ./data/Dockerfiles/unbound
       command: /usr/sbin/unbound
-      depends_on:
-        mysql-mailcow:
-          condition: service_healthy
       volumes:
         - ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro
       restart: always
@@ -19,12 +16,6 @@ services:
 
     mysql-mailcow:
       image: mariadb:10.2
-      command: mysqld --max_allowed_packet=192M --max-connections=1500 --innodb-strict-mode=0 --skip-host-cache --skip-name-resolve --log-warnings=0
-      healthcheck:
-        test: ["CMD", "mysqladmin", "-u$DBUSER", "-p$DBPASS",  "ping", "-h", "localhost"]
-        interval: 5s
-        timeout: 5s
-        retries: 10
       volumes:
         - mysql-vol-1:/var/lib/mysql/
         - ./data/conf/mysql/:/etc/mysql/conf.d/:ro

From adc23d86f9b16c77cde48c609c492e4cbaa29335 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 13:17:15 +0100
Subject: [PATCH 11/40] Various...

---
 .gitignore                                    |    1 -
 data/web/admin.php                            |  237 ++--
 data/web/css/breakpoint.min.css               |    1 +
 data/web/css/debug.css                        |   37 +
 data/web/css/quarantaine.css                  |   37 +
 data/web/debug.php                            |  328 ++++++
 data/web/img/rspamd_logo.png                  |  Bin 0 -> 4613 bytes
 data/web/inc/ajax/container_ctrl.php          |   53 +
 data/web/inc/ajax/log_driver.php              |   12 +
 data/web/inc/ajax/qitem_details.php           |   83 ++
 data/web/inc/ajax/sogo_ctrl.php               |   39 -
 data/web/inc/footer.inc.php                   |   70 +-
 data/web/inc/functions.customize.inc.php      |   54 +-
 data/web/inc/functions.docker.inc.php         |   49 +-
 data/web/inc/functions.fail2ban.inc.php       |    2 -
 data/web/inc/functions.inc.php                |  203 +++-
 data/web/inc/functions.mailbox.inc.php        |   26 +-
 data/web/inc/functions.quarantaine.inc.php    |  282 +++++
 data/web/inc/header.inc.php                   |   14 +-
 data/web/inc/init_db.inc.php                  |  108 +-
 data/web/inc/lib/composer.json                |    3 +-
 data/web/inc/lib/composer.lock                |  106 +-
 .../inc/lib/vendor/composer/autoload_psr4.php |    1 +
 .../lib/vendor/composer/autoload_static.php   |    8 +
 .../inc/lib/vendor/composer/installed.json    |  200 +++-
 .../php-mime-mail-parser/LICENSE              |   21 +
 .../php-mime-mail-parser/README.md            |  167 +++
 .../php-mime-mail-parser/composer.json        |   61 +
 .../php-mime-mail-parser/mailparse-stubs.php  |  303 +++++
 .../php-mime-mail-parser/phpunit.xml.dist     |    6 +
 .../php-mime-mail-parser/src/Attachment.php   |  183 +++
 .../php-mime-mail-parser/src/Charset.php      |  338 ++++++
 .../src/Contracts/CharsetManager.php          |   24 +
 .../php-mime-mail-parser/src/Exception.php    |    8 +
 .../php-mime-mail-parser/src/Parser.php       |  893 ++++++++++++++
 .../lib/vendor/phpmailer/phpmailer/VERSION    |    2 +-
 .../phpmailer/phpmailer/class.phpmailer.php   |    4 +-
 .../vendor/phpmailer/phpmailer/class.pop3.php |    2 +-
 .../vendor/phpmailer/phpmailer/class.smtp.php |    4 +-
 .../vendor/robthree/twofactorauth/.gitignore  |    5 +-
 .../vendor/robthree/twofactorauth/.travis.yml |   13 +-
 .../.vs/config/applicationhost.config         | 1031 +++++++++++++++++
 .../vendor/robthree/twofactorauth/README.md   |    2 +-
 .../robthree/twofactorauth/composer.json      |    2 +-
 .../ConvertUnixTimeDotComTimeProvider.php     |    2 +-
 .../lib/Providers/Time/HttpTimeProvider.php   |    3 +-
 .../u2flib-server/src/u2flib_server/U2F.php.1 |  507 --------
 data/web/inc/prerequisites.inc.php            |   20 +-
 data/web/inc/sessions.inc.php                 |   18 +
 data/web/inc/triggers.inc.php                 |   12 +
 data/web/inc/vars.inc.php                     |   19 +-
 data/web/index.php                            |   14 +-
 data/web/js/admin.js                          |  350 +-----
 data/web/js/api.js                            |   28 +-
 data/web/js/debug.js                          |  503 ++++++++
 data/web/js/formcache.min.js                  |   10 +
 data/web/js/mailbox.js                        |    6 +-
 data/web/js/quarantaine.js                    |   82 ++
 data/web/json_api.php                         |  446 +++++--
 data/web/lang/lang.de.php                     |   93 +-
 data/web/lang/lang.en.php                     |   70 +-
 data/web/mailbox.php                          |   13 +-
 data/web/modals/admin.php                     |    4 +-
 data/web/modals/debug.php                     |    6 +
 data/web/modals/footer.php                    |   14 +-
 data/web/modals/mailbox.php                   |   37 +-
 data/web/modals/quarantaine.php               |   32 +
 data/web/modals/user.php                      |    4 +-
 data/web/quarantaine.php                      |   56 +
 data/web/user.php                             |   17 +-
 70 files changed, 6008 insertions(+), 1381 deletions(-)
 create mode 100644 data/web/css/breakpoint.min.css
 create mode 100644 data/web/css/debug.css
 create mode 100644 data/web/css/quarantaine.css
 create mode 100644 data/web/debug.php
 create mode 100644 data/web/img/rspamd_logo.png
 create mode 100644 data/web/inc/ajax/container_ctrl.php
 create mode 100644 data/web/inc/ajax/log_driver.php
 create mode 100644 data/web/inc/ajax/qitem_details.php
 delete mode 100644 data/web/inc/ajax/sogo_ctrl.php
 create mode 100644 data/web/inc/functions.quarantaine.inc.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/LICENSE
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/phpunit.xml.dist
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Contracts/CharsetManager.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Exception.php
 create mode 100644 data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php
 create mode 100644 data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
 delete mode 100644 data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1
 create mode 100644 data/web/js/debug.js
 create mode 100644 data/web/js/formcache.min.js
 create mode 100644 data/web/js/quarantaine.js
 create mode 100644 data/web/modals/debug.php
 create mode 100644 data/web/modals/quarantaine.php
 create mode 100644 data/web/quarantaine.php

diff --git a/.gitignore b/.gitignore
index 0d081fc4..b945f8cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,3 @@ data/conf/nginx/*.conf
 data/conf/nginx/*.custom
 data/conf/nginx/*.bak
 data/conf/dovecot/extra.conf
-data/conf/rspamd/custom/*
diff --git a/data/web/admin.php b/data/web/admin.php
index 0d7c6466..e1fe74be 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -8,26 +8,8 @@ $tfa_data = get_tfa();
 ?>
 <div class="container">
   <ul class="nav nav-tabs" role="tablist">
-    <li role="presentation" class="active">
-      <a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a>
-    </li>
-    <li role="presentation">
-      <a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a>
-    </li>
-    <li class="dropdown">
-    <a class="dropdown-toggle" data-toggle="dropdown" href="#">Logs
-    <span class="caret"></span></a>
-    <ul class="dropdown-menu">
-    <li role="presentation"><a href="#tab-postfix-logs" aria-controls="tab-postfix-logs" role="tab" data-toggle="tab">Postfix</a></li>
-    <li role="presentation"><a href="#tab-dovecot-logs" aria-controls="tab-dovecot-logs" role="tab" data-toggle="tab">Dovecot</a></li>
-    <li role="presentation"><a href="#tab-sogo-logs" aria-controls="tab-sogo-logs" role="tab" data-toggle="tab">SOGo</a></li>
-    <?php if (F2B == 1): ?>
-    <li role="presentation"><a href="#tab-fail2ban-logs" aria-controls="tab-fail2ban-logs" role="tab" data-toggle="tab">Fail2ban</a></li>
-    <?php endif; ?>
-    <li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li>
-    <li role="presentation"><a href="#tab-autodiscover-logs" aria-controls="tab-autodiscover-logs" role="tab" data-toggle="tab">Autodiscover</a></li>
-    </ul>
-    </li>
+    <li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li>
+    <li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li>
   </ul>
 
   <div class="tab-content" style="padding-top:20px">
@@ -58,7 +40,7 @@ $tfa_data = get_tfa();
           </div>
           <div class="form-group">
             <div class="col-sm-offset-3 col-sm-9">
-              <button class="btn btn-default" id="edit_selected" data-id="admin" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
+              <button class="btn btn-default" id="edit_selected" data-id="admin" data-item="null" data-api-url='edit/self' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
             </div>
           </div>
         </form>
@@ -96,6 +78,42 @@ $tfa_data = get_tfa();
         </div>
       </div>
     </div>
+
+    <div class="panel panel-primary">
+      <div class="panel-heading">API</div>
+      <div class="panel-body">
+        <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+          <div class="form-group">
+            <label class="control-label col-sm-3" for="allow_from"><?=$lang['admin']['api_allow_from'];?>:</label>
+            <div class="col-sm-9">
+              <textarea class="form-control" rows="5" name="allow_from" id="allow_from" required><?=htmlspecialchars($admindetails['allow_from']);?></textarea>
+            </div>
+          </div>
+          <div class="form-group">
+            <label class="control-label col-sm-3" for="admin_api_key"><?=$lang['admin']['api_key'];?>:</label>
+            <div class="col-sm-9">
+              <input type="text" class="form-control" placeholder="-" value="<?=htmlspecialchars($admindetails['api_key']);?>" readonly>
+            </div>
+          </div>
+          <div class="form-group">
+            <div class="col-sm-offset-3 col-sm-9">
+              <label>
+                <input type="checkbox" name="active" <?=($admindetails['api_active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
+              </label>
+            </div>
+          </div>
+          <div class="form-group">
+            <div class="col-sm-offset-3 col-sm-9">
+              <div class="btn-group">
+                <button class="btn btn-default" name="admin_api" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+                <button class="btn btn-info" name="admin_api_regen_key" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+              </div>
+            </div>
+          </div>
+        </form>
+      </div>
+    </div>
+
     <div class="panel panel-default">
     <div class="panel-heading"><?=$lang['admin']['domain_admins'];?></div>
         <div class="panel-body">
@@ -121,17 +139,15 @@ $tfa_data = get_tfa();
     </div>
   </div>
 
-
   <div role="tabpanel" class="tab-pane" id="tab-config">
     <div class="row">
     <div id="sidebar-admin" class="col-sm-2 hidden-xs">
       <div id="scrollbox" class="list-group">
         <a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a>
         <a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
-        <?php if (F2B == 1): ?>
         <a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
-        <?php endif; ?>
         <a href="#relayhosts" class="list-group-item">Relayhosts</a>
+        <a href="#quarantaine" class="list-group-item">Quarantaine</a>
         <a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
         <a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
       </div>
@@ -310,7 +326,6 @@ $tfa_data = get_tfa();
       </div>
     </div>
 
-    <?php if (F2B == 1): ?>
     <span class="anchor" id="f2bparams"></span>
     <div class="panel panel-default">
       <div class="panel-heading"><?=$lang['admin']['f2b_parameters'];?></div>
@@ -332,14 +347,13 @@ $tfa_data = get_tfa();
             <input type="number" class="form-control" id="retry_window" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required>
           </div>
           <div class="form-group">
-            <label for="retry_window"><?=$lang['admin']['f2b_whitelist'];?>:</label>
+            <label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label>
             <textarea class="form-control" id="whitelist" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea>
           </div>
           <button class="btn btn-default" id="add_item" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
         </form>
       </div>
     </div>
-    <?php endif; ?>
 
     <span class="anchor" id="relayhosts"></span>
     <div class="panel panel-default">
@@ -381,6 +395,43 @@ $tfa_data = get_tfa();
       </div>
     </div>
 
+    <span class="anchor" id="quarantaine"></span>
+    <div class="panel panel-default">
+      <div class="panel-heading">Quarantäne</div>
+      <div class="panel-body">
+       <?php $q_data = quarantaine('settings'); ?>
+        <form class="form" data-id="quarantaine" role="form" method="post">
+          <div class="row">
+            <div class="col-sm-6">
+              <div class="form-group">
+                <label for="retention_size">Rückhaltungen pro Mailbox:</label>
+                <input type="number" class="form-control" id="retention_size" name="retention_size" value="<?=$q_data['retention_size'];?>" required>
+              </div>
+            </div>
+            <div class="col-sm-6">
+              <div class="form-group">
+                <label for="max_size">Maximale Größe in MiB (größere Elemente werden verworfen):</label>
+                <input type="number" class="form-control" id="max_size" name="max_size" value="<?=$q_data['max_size'];?>" required>
+              </div>
+            </div>
+          </div>
+          <div class="form-group">
+            <label for="exclude_domains">Domains und Alias-Domains ausschließen:</label><br />
+            <select data-width="100%" id="exclude_domains" name="exclude_domains" class="selectpicker" title="<?=$lang['tfa']['select'];?>" multiple>
+              <?php
+              foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain):
+              ?>
+                <option <?=(in_array($domain, $q_data['exclude_domains'])) ? 'selected' : null;?>><?=htmlspecialchars($domain);?></option>
+              <?php
+              endforeach;
+              ?>
+            </select>
+          </div>
+          <button class="btn btn-success" id="edit_selected" data-item="self" data-id="quarantaine" data-api-url='edit/quarantaine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+        </form>
+      </div>
+    </div>
+
     <span class="anchor" id="customize"></span>
     <div class="panel panel-default">
       <div class="panel-heading"><?=$lang['admin']['customize'];?></div>
@@ -449,10 +500,29 @@ $tfa_data = get_tfa();
             endforeach;
             ?>
           </table>
-          <div class="btn-group">
-            <button class="btn btn-success" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
-            <button class="btn btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
-          </div> 
+          <p><div class="btn-group">
+            <button class="btn btn-sm btn-success" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+            <button class="btn btn-sm btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
+          </div></p>
+        </form>
+        <legend><?=$lang['admin']['ui_texts'];?></legend>
+        <?php
+        $ui_texts = customize('get', 'ui_texts');
+        ?>
+        <form class="form" data-id="uitexts" role="form" method="post">
+          <div class="form-group">
+            <label for="main_name"><?=$lang['admin']['main_name'];?>:</label>
+            <input type="text" class="form-control" id="main_name" name="main_name" placeholder="mailcow UI" value="<?=$ui_texts['main_name'];?>">
+          </div>
+          <div class="form-group">
+            <label for="apps_name"><?=$lang['admin']['apps_name'];?>:</label>
+            <input type="text" class="form-control" id="apps_name" name="apps_name" placeholder="mailcow Apps" value="<?=$ui_texts['apps_name'];?>">
+          </div>
+          <div class="form-group">
+            <label for="help_text"><?=$lang['admin']['help_text'];?>:</label>
+            <textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea>
+          </div>
+          <button class="btn btn-success" id="edit_selected" data-item="null" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
         </form>
       </div>
     </div>
@@ -460,111 +530,6 @@ $tfa_data = get_tfa();
   </div>
   </div>
 
-  <div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
-    <div class="panel panel-default">
-      <div class="panel-heading">Postfix <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_postfix_log"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed" id="postfix_log"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div role="tabpanel" class="tab-pane" id="tab-dovecot-logs">
-    <div class="panel panel-default">
-      <div class="panel-heading">Dovecot <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_dovecot_log"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed" id="dovecot_log"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div role="tabpanel" class="tab-pane" id="tab-sogo-logs">
-    <div class="panel panel-default">
-      <div class="panel-heading">SOGo <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_sogo_log"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed" id="sogo_log"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  
-  <?php if (F2B == 1): ?>
-  <div role="tabpanel" class="tab-pane" id="tab-fail2ban-logs">
-    <div class="panel panel-default">
-      <div class="panel-heading">Fail2ban <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_fail2ban_log"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed" id="fail2ban_log"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-  <?php endif; ?>
-
-  <div role="tabpanel" class="tab-pane" id="tab-rspamd-history">
-    <div class="panel panel-default">
-      <div class="panel-heading">Rspamd history <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_rspamd_history"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed log-table" id="rspamd_history"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs">
-    <div class="panel panel-default">
-      <div class="panel-heading">Autodiscover <span class="badge badge-info log-lines"></span>
-        <div class="btn-group pull-right">
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="100">+ 100</button>
-          <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="1000">+ 1000</button>
-          <button class="btn btn-xs btn-default" id="refresh_autodiscover_log"><?=$lang['admin']['refresh'];?></button>
-        </div>
-      </div>
-      <div class="panel-body">
-        <div class="table-responsive">
-          <table class="table table-striped table-condensed" id="autodiscover_log"></table>
-        </div>
-      </div>
-    </div>
-  </div>
-
   </div>
 </div> <!-- /container -->
 <?php
diff --git a/data/web/css/breakpoint.min.css b/data/web/css/breakpoint.min.css
new file mode 100644
index 00000000..8d84757c
--- /dev/null
+++ b/data/web/css/breakpoint.min.css
@@ -0,0 +1 @@
+@media (max-width:1050px){.navbar-header{float:none}.navbar-left,.navbar-nav,.navbar-right{float:none!important}.navbar-toggle{display:block}.navbar-collapse{border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-collapse.collapse{display:none!important}.navbar-nav{margin-top:7.5px}.navbar-nav>li{float:none}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px}.collapse.in{display:block!important}.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}}
\ No newline at end of file
diff --git a/data/web/css/debug.css b/data/web/css/debug.css
new file mode 100644
index 00000000..585d1905
--- /dev/null
+++ b/data/web/css/debug.css
@@ -0,0 +1,37 @@
+table.footable>tbody>tr.footable-empty>td {
+  font-size:15px !important;
+  font-style:italic;
+}
+.pagination a {
+  text-decoration: none !important;
+}
+.panel panel-default {
+  overflow: visible !important;
+}
+.table-responsive {
+  overflow: visible !important;
+}
+@media screen and (max-width: 767px) {
+  .table-responsive {
+    overflow-x: scroll !important;
+  }
+}
+.footer-add-item {
+  display:block;
+  text-align: center;
+  font-style: italic;
+  padding: 10px;
+  background: #F5F5F5;
+}
+@media (min-width: 992px) {
+  .container {
+      width: 80%;
+  }
+}
+.mass-actions-debug {
+  user-select: none;
+  padding:10px 0 10px 10px;
+}
+.inputMissingAttr {
+  border-color: #FF4136;
+}
\ No newline at end of file
diff --git a/data/web/css/quarantaine.css b/data/web/css/quarantaine.css
new file mode 100644
index 00000000..7a5ee761
--- /dev/null
+++ b/data/web/css/quarantaine.css
@@ -0,0 +1,37 @@
+table.footable>tbody>tr.footable-empty>td {
+  font-size:15px !important;
+  font-style:italic;
+}
+.pagination a {
+  text-decoration: none !important;
+}
+.panel panel-default {
+  overflow: visible !important;
+}
+.table-responsive {
+  overflow: visible !important;
+}
+@media screen and (max-width: 767px) {
+  .table-responsive {
+    overflow-x: scroll !important;
+  }
+}
+.footer-add-item {
+  display:block;
+  text-align: center;
+  font-style: italic;
+  padding: 10px;
+  background: #F5F5F5;
+}
+@media (min-width: 992px) {
+  .container {
+      width: 80%;
+  }
+}
+.mass-actions-quarantaine {
+  user-select: none;
+  padding:10px 0 10px 10px;
+}
+.inputMissingAttr {
+  border-color: #FF4136;
+}
\ No newline at end of file
diff --git a/data/web/debug.php b/data/web/debug.php
new file mode 100644
index 00000000..289aa84a
--- /dev/null
+++ b/data/web/debug.php
@@ -0,0 +1,328 @@
+<?php
+require_once "inc/prerequisites.inc.php";
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+require_once "inc/header.inc.php";
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
+?>
+<div class="container">
+
+  <ul class="nav nav-tabs" role="tablist">
+    <li class="dropdown active"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Rspamd
+      <span class="caret"></span></a>
+      <ul class="dropdown-menu">
+        <li role="presentation" class="active"><a href="#tab-rspamd-ui" aria-controls="tab-rspamd-ui" role="tab" data-toggle="tab">Rspamd UI</a></li>
+        <li role="presentation"><a href="#tab-rspamd-settings" aria-controls="tab-rspamd-settings" role="tab" data-toggle="tab">Rspamd settings map</a></li>
+      </ul>
+    </li>
+    <li role="presentation"><a href="#tab-containers" aria-controls="tab-containers" role="tab" data-toggle="tab">Containers</a></li>
+    <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Logs
+      <span class="caret"></span></a>
+      <ul class="dropdown-menu">
+        <li role="presentation"><a href="#tab-postfix-logs" aria-controls="tab-postfix-logs" role="tab" data-toggle="tab">Postfix</a></li>
+        <li role="presentation"><a href="#tab-dovecot-logs" aria-controls="tab-dovecot-logs" role="tab" data-toggle="tab">Dovecot</a></li>
+        <li role="presentation"><a href="#tab-sogo-logs" aria-controls="tab-sogo-logs" role="tab" data-toggle="tab">SOGo</a></li>
+        <li role="presentation"><a href="#tab-fail2ban-logs" aria-controls="tab-fail2ban-logs" role="tab" data-toggle="tab">Fail2ban</a></li>
+        <li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li>
+        <li role="presentation"><a href="#tab-autodiscover-logs" aria-controls="tab-autodiscover-logs" role="tab" data-toggle="tab">Autodiscover</a></li>
+        <li role="presentation"><a href="#tab-watchdog-logs" aria-controls="tab-watchdog-logs" role="tab" data-toggle="tab">Watchdog</a></li>
+        <li role="presentation"><a href="#tab-acme-logs" aria-controls="tab-acme-logs" role="tab" data-toggle="tab">ACME</a></li>
+        <li role="presentation"><a href="#tab-api-logs" aria-controls="tab-api-logs" role="tab" data-toggle="tab">API</a></li>
+      </ul>
+    </li>
+  </ul>
+
+	<div class="row">
+		<div class="col-md-12">
+      <div class="tab-content" style="padding-top:20px">
+
+        <div role="tabpanel" class="tab-pane active" id="tab-rspamd-ui">
+          <div class="panel panel-default">
+            <div class="panel-heading">
+              <h3 class="panel-title">Rspamd UI</h3>
+            </div>
+            <div class="panel-body">
+              <div class="row">
+                <div class="col-sm-9">
+                <form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post">
+                  <div class="form-group">
+                    <div class="col-sm-offset-3 col-sm-9">
+                      <label>
+                        <a href="/rspamd/" target="_blank"><span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> Rspamd UI</a>
+                      </label>
+                    </div>
+                  </div>
+                  <div class="form-group">
+                    <label class="control-label col-sm-3" for="rspamd_ui_pass"><?=$lang['admin']['password'];?>:</label>
+                    <div class="col-sm-9">
+                    <input type="password" class="form-control" name="rspamd_ui_pass" id="rspamd_ui_pass">
+                    </div>
+                  </div>
+                  <div class="form-group">
+                    <label class="control-label col-sm-3" for="rspamd_ui_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
+                    <div class="col-sm-9">
+                    <input type="password" class="form-control" name="rspamd_ui_pass2" id="rspamd_ui_pass2">
+                    </div>
+                  </div>
+                  <div class="form-group">
+                    <div class="col-sm-offset-3 col-sm-9">
+                      <button type="submit" class="btn btn-default" id="rspamd_ui" name="rspamd_ui" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+                    </div>
+                  </div>
+                </form>
+                </div>
+                <div class="col-sm-3">
+                  <img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI" />
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-rspamd-settings">
+          <div class="panel panel-default">
+            <div class="panel-heading">
+              <h3 class="panel-title">Rspamd settings map</h3>
+            </div>
+            <div class="panel-body">
+            <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="settings_map" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-containers">
+          <div class="panel panel-default">
+            <div class="panel-heading">
+              <h3 class="panel-title">Container information</h3>
+            </div>
+            <div class="panel-body">
+            <ul class="list-group">
+            <?php
+            $container_array = array(
+              'nginx-mailcow',
+              'rspamd-mailcow',
+              'postfix-mailcow',
+              'dovecot-mailcow',
+              'sogo-mailcow',
+              'acme-mailcow',
+              'memcached-mailcow',
+              'watchdog-mailcow',
+              'unbound-mailcow',
+              'redis-mailcow',
+              'php-fpm-mailcow',
+              'mysql-mailcow',
+              'fail2ban-mailcow',
+              'clamd-mailcow'
+            );
+            }
+            foreach ($container_array as $container) {
+                $container_stats = docker($container, 'info');
+                ?>
+                <li class="list-group-item">
+                <?=$container;?>
+                <?php
+                date_default_timezone_set('UTC');
+                $StartedAt = date_parse($container_stats['State']['StartedAt']);
+                $date = new \DateTime();
+                $date->setTimestamp(mktime(
+                  $StartedAt['hour'],
+                  $StartedAt['minute'],
+                  $StartedAt['second'],
+                  $StartedAt['month'],
+                  $StartedAt['day'],
+                  $StartedAt['year']));
+                $user_tz = new DateTimeZone(getenv('TZ'));
+                $date->setTimezone($user_tz);
+                $started = $date->format('r');
+                ?>
+                <small>(Started on <?=$started;?>),
+                <a href data-toggle="modal" data-container="<?=$container;?>" data-target="#RestartContainer">Restart</a></small>
+                <span class="pull-right label label-<?=($container_stats['State']['Running'] == 1) ? 'success' : 'danger';?>">&nbsp;&nbsp;&nbsp;</span>
+                </li>
+              <?php
+              }
+            ?>
+            </ul>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-postfix-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">Postfix <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="postfix_log" data-log-url="postfix" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_postfix_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="postfix_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-dovecot-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">Dovecot <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="dovecot_log" data-log-url="dovecot" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_dovecot_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="dovecot_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-sogo-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">SOGo <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="sogo_log" data-log-url="sogo" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_sogo_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="sogo_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-fail2ban-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">Fail2ban <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="fail2ban_log" data-log-url="fail2ban" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_fail2ban_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="fail2ban_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-rspamd-history">
+          <div class="panel panel-default">
+            <div class="panel-heading">Rspamd history <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="rspamd_history" data-table="rspamd_history" data-log-url="rspamd-history" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_rspamd_history"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed log-table" id="rspamd_history"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">Autodiscover <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="autodiscover_log" data-table="autodiscover_log" data-log-url="autodiscover" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_autodiscover_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="autodiscover_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-watchdog-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">Watchdog <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="watchdog" data-table="watchdog_log" data-log-url="watchdog" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_watchdog_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="watchdog_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-acme-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">ACME <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="general_syslog" data-table="acme_log" data-log-url="acme" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_acme_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="acme_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div role="tabpanel" class="tab-pane" id="tab-api-logs">
+          <div class="panel panel-default">
+            <div class="panel-heading">API <span class="badge badge-info log-lines"></span>
+              <div class="btn-group pull-right">
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="100">+ 100</button>
+                <button class="btn btn-xs btn-default add_log_lines" data-post-process="apilog" data-table="api_log" data-log-url="api" data-nrows="1000">+ 1000</button>
+                <button class="btn btn-xs btn-default" id="refresh_api_log"><?=$lang['admin']['refresh'];?></button>
+              </div>
+            </div>
+            <div class="panel-body">
+              <div class="table-responsive">
+                <table class="table table-striped table-condensed" id="api_log"></table>
+              </div>
+            </div>
+          </div>
+        </div>
+
+      </div> <!-- /tab-content -->
+    </div> <!-- /col-md-12 -->
+  </div> <!-- /row -->
+</div> <!-- /container -->
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/debug.php';
+?>
+<script type='text/javascript'>
+<?php
+$lang_admin = json_encode($lang['admin']);
+echo "var lang = ". $lang_admin . ";\n";
+echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
+echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n";
+
+?>
+</script>
+<script src="js/footable.min.js"></script>
+<script src="js/debug.js"></script>
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
+} else {
+	header('Location: /');
+	exit();
+}
+?>
diff --git a/data/web/img/rspamd_logo.png b/data/web/img/rspamd_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e97426dcb4e2d20d2f3b13ec381cccb05e4703b
GIT binary patch
literal 4613
zcmV+g68i0lP)<h;3K|Lk000e1NJLTq004jh002-31^@s6(Exqq00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9
za%BK;VQFr3E^cLXAT%y8E-^XO*0N*(01<UbL_t(|UhSL-lpIwRhAWW-L=+GZqXb-{
zKv=Rbgh*Ii5S2w3WeH~M?n#0O3JNMZB8wo1iHLwl;h+QsMYhAvIYE|?%uEtM*%An0
z4G_X&$Rv}QuJXIZeU(>RcXiJ|mZ|@o|5Wwes`}mgUcGu%-A;}iJr>>sXA9lU(Uagc
zI3I9J&Lp?!yv+6>73b&)AnD`EMQ4Fmay%gg@Q4(Is&j0r@EV;@k?em$*CanEf}+!?
zW;r%Bkl-!p@xLJPtD}iGqyS!ZPjo(_E;%*@c#hMHq<@p-K9`jADBXf{fx70{*x^of
zb|vw5CM7zJ*tf&aYKWc<dc!d^#_jN)4UiY=X~y85;(VxFaL&bMZBmkLGCDjJlNFsC
zG*r(94RJgTbixM5f>R_>+v{ltkl`Nr;n*y-6PiZ8-%8jI`{s~bCWwvB{u(yN#sIg`
z8OS8>E<2IcNb-N>`xc!se$s7S$yu1+7H$M^PjI#)fip>1r=7qckwvEiUICwu$lrF4
zcec|oQUur7df$eTM%6jeKtsupdvdq3(K!izP7jd?Zr3*VZHvyE(Zm^|yQh7UGl=cv
z1l^A{ln-P(BU=@+DLJp?w}llTJ%~nYvm32DNX!lzf4m+h1h2{IP0u_En^2FoiSEgc
zqI0N!3;6CiE|2ON-ur6E037p7+fHp$C!0BdTqjA&X5Gq^Ywy}A&b}mMPBck|=w62>
zdE=bT)J2B&OCvUAisJxPhv1HPK7{_oi2WLOiZeh>a;y)?<$&IqxO}TgYG+iK;Z1Y4
zRTm$~hf2b2TPBKEs5%jyj6Alz?^AGYQgx16K;phc1D+nGBt}gt0^9Aeoe4YG4<$t?
z1%d9yr1=6PNpEza(E|I|=*=HhlSHWB+=!n`fgPwOIkG?-@@jZPgtOWU&K_!(2!DcC
zbWWx5-?9^7lb*Z~o5Z19fbRIHPiMiIs_Kd$*`x6fjT{s~ep~on!=JPjog>R7XNleT
zHqpNfCrg2;TMROD$7Wf?Z&57?q`+p`{u*Pls&jNV7{&+D$oEDY-H^s#1%D|MHFZe^
zcE3*WpP=eYaHl!_<(QEeFR|THO>%SxWa37{{+=N=Z6T)&uWFm<^jDXP&<8-FX8?>S
zPt`Mgb52cm9APJyCROL?3j8^OypF}hEZWEx@AV4KFm<U4ee`=<LYxLQsR_&Ewy@;9
zV8`O~BdX3(FX*AN_8xiwV^ex;N69%H(_Zg-WJ5>6`FcXE2c<C7q-Jog$J+5Y?>3pF
zsyat4(Cg|f7@A=Rjazh<)4(UP-$z|CLz2S8ZE+|bmla};P?Nf#Bh^Dz%+>i@#5ID3
z+mb~542C9EhSI3qdR@T&*6LC-i1EW%+$xJcSwWmpFeqBi_3Nt2Q3;->uVbY=pO;33
ze|IN4yQoWDAVxp9G8DVytZ*keJE%#QLHxg3=RHTho2qgoVa;S`SFXQz+exuWBi^@m
ziZfEpy99>*t2E}iURrdfsk&Q0uiuH7!3f5D<14Dl5d{r(G+bjE%O>(CxaU8tD-$yt
zvT8>G+|yZbio`z2@A)O?qDl)z^@6x(=yxcye}am0L_q@uKez}IFVEI#H2X<iS0VTI
z#5~_5TPA1G2SVKmzK6_Zp5vG=<jcYpKm!HO)oHMI;5puOXDd~A8|d}Nv=^Mg`tf}M
z-$h2ZRqw_3*(AV(?h`c7GjJ`uLO!di+u<Jf{=coGw}rQ{jlEFP<H4}lm!w}x1KrX-
z+1XRYn;hETeb3-)`Z0$AL-Kce%InnKHHqz!7&B(fhX)NB^r>OPhV3?d`0)PnSyeeU
z0Q4p)XTj^>1MmqF^*nro{g>h6@Gd^j7&>(55o|L(9vv}a#K-YF8~Z2OmZ4`+J}*Ug
zCEG(A8X96VY>ssR`ahzVV=siy!dKwyvX6W&+pF2040lzx%)s|>{B9Ale~yYXgYz3|
z`JbWUnm~wQCI<Y8iepf_OF7ORZ~=S<E`uwe-xhEdJXOvMm+Yg}xN+k)!_iJoHA-Rp
zQ2aDR44l*<*t})=q!Qas&N=qmJmeQ7*{9IO{%(q{;+sbX0|yT5i~Tc}e|hA{k%Ls7
z3HVPj{_-8*zN*RuX+L}xTIcY9s$xKQl4Vzw#9c(27AoZ^K%@YqA*EN&gcq?tLqFfo
zcD34=gWQzVb0rD3E&I>2J&O}M0^PxUmY#Pq+iTfMk=&}TA)pTmfc=NrUJT{DF68qi
zY;S~1VQ@}x700Oh&mZ_VM*M^~syY*hai;Ar=a|$UX`ns6XZypYs)_-fT>y*q`<u4-
zJ==dk8L3?u#6;IDmtGB%47&hu=%6Pg-f9tO8*F|9gM(oIeN_iQCIw^kbN1eQ@6<PX
z&~G_p$dJV60_f}lI9J6{^r9gTz-F<rg|DW42LZAAKp8eAt`zF4>Q^2-cyQ)X0N*Rr
zFCgcVX*rrA-46RVZC^Q;v;snJ7r<32jsabN;{2NJ8e`Ar=PMRL)&elV=5ovaNkpOR
z4f3E@qCZT<H3Q!)1>gf4`QraG_LUNWZlV=ede5omGrUgCssi8JEkD0gAbTat1Q)v*
zQD0a_9{T~T09~d6Fu-OAG=(ka)tovy)lmQ=ZTkTO283RgKrV9xUr%G#eeX={%mpC8
z=C780+GAmv@IIk-?a;p?Ve6kWew~a+nSK<4?PJD=?<vEve{6WRnj`}KXP_Kgd|!k&
zsh`KOcUQ9z(A87`0^;tAtv&fpe!;cY0>DnbfC=i!r9o9bkj=OBcSw9y)dbF^rUHm0
zA+aYD=P)bgUsNpeb1nJhDz1cH*uAYjZ|t?#UfZ%YJ#HnJlGMl2=w-I<9}6B2<+5r%
ze^JdsKwfhJ2slT(Q#7kMj5-S78<zds(F7y6PgZz8l(%$hT1X*wO$C76t;U{W_=Ac=
zKqt4kz75ym;F-ec(W5_!Y^}&SR$HY63S?em*)I_p$DA&ol{0<A2$1gy{a%URLPWjU
zo~Ay+#NS6Eud4uLLTvfDe9!ogWNImZv17+>i;c|4P0(0RL*EB^kPM6S;Ph&ZYe1aL
z1wiAE#-`KQ!*8o905+p6{e3D9fdAi(p8YdboCq3L);Nr<-?QXW$Y(RTY~y(Sd}9m9
zuQz_^{mUc!lZ_1h4}8N2(A7HvY>j;|Y3IE6Q`I^|3xMkW7_u#d5`cpEG~4~rH$hn#
zGPWz&en!KDfKH0w=WK(CnMJw&U59Qgrm3R>`f3Hxn{(R<o%EoUmhFFG>>Wd7L4x7Y
zDh_~ttQF&x=-kEzHqw)g?QJTS7~e5+c(ICo@L48iX0rI8ip586i;d5DDh>g8T?HV(
z{vFHrNENSxT>v%kX*fhfq(X)qzTgj_tX@S|1O3Kh4)IAf4*^|J08no8%!=7;DDw=t
z<_6U*#OGP6{PP+jfEG%J+Q@2R(o3kl&_80uo22p>@Rtr@>}ILhw531E#HNi&UGQWS
zncS=UKJ@<JCvMU!jU4_-#UUWCy8t+kpcAqme_g(o0w91iUSp-+kp>+}&VWlIai5a}
zsaXK*>;kArIWM;7u}yr*6y4dzmd5x0CMF5^2Ma{(FF=$yLZLs>*ul_z1HYq;j{O-T
zV_!xLQ>=ISh6;*gZ<PxZ7o(8ZRRDCr4`Od$0^*Bgb^&n0avl99h6fwcV6tLmCN8q%
zS@*rYK#oVuQ;6gDNM-C_y3pH~fXCq9EXpFx`D~|hj>9FUy2XHQ3v8F0Snw?TrX&%a
zJXbIcCfhL@CIpUIR2yNmpyXEbQPr&)HEPt3Y_HMJv6!+*qdH;Yaxn6G=K#SiyHF;(
zs#=Fg0VGZWk^j^Rw2}nwrK-9iMPMHnhI%ibGfx650srwPPOap)f737_Ah&1kp}(FH
zP$1v3V#vhLbfA0GBmlOuDrjsUQ*j9B>M8)*_(J1H?59*5MwS8qa=T(rq86vvWd`wN
zhHB!4Zp#GdyQKi|eaJe_n|zk1iIpgmTXWpiNp~&C?RA6isyKkGx}lS`d2?LDacUAk
z9!wcMv6iVg1avhQfLwC1U1j`;eY&c`$XEb!qFZ5OR?&plAg<}&-Ui1C6@Xj|RpkSH
zmkR)2nR%NSE97#Zs!EVz_gDFpvB$K?E;V|3*+dnGKtUg8Y?QHEPk|PTpD2#ohA8%S
zGrF1zK<?y-72K|~|9t8*nXCmMkY0*i@SuS&f2^tkkjMd2)%r0N`2WPjf$wna(0c}a
z?v4Vu%*24dQ*l*ryzd)7_LoH0(UHr02qtd))(U95%H5KQ^CeY9A#(;ZM_HScDCC(6
zfMfKL%MaTZj6M5pY!l})K58id0XD()kNwwWlA|ht^qxiN<QDB>3gie1V)uRa+2><&
zt&?F1y}T<RlcHdD3NKRs0N8b@0Pv9!Cb$RYTr$sF<b3h**XxyERh187-)wYzuhbum
z@O^iZP7@v7B9LWed1Nl}%5rR#2S9EYz+7TZ#OD|x-)jQf)1WN-zGm4;yS)l4zp__r
z0jPbe@quzF@WF}M_p~-8*|Bv1A;-KvX~aO@)dH}e`Oj5xX2^M2$G=&{K9JpK>108x
zOCXMYX^kzeA+rmhDmf?V^_OK@b(MtTAeAD$_N6S3GqD?iy{rj{-!cv~P~<wH8ISB{
z7)+)T*pB|+@F?8U?zNlHoAXM2T`CRu+-dx!$EY|n@Hxo%LwRZ!JgO!3Y@=hp%Wp#y
z=WY`(X{17a2y9WC(2F_BmB<&tYdP;?;+lUIND>UQqMDCf4tD0`zR0$L6O(I`EPIZS
z1q!vPglh4tp{xFav>0jfdXY6=FPV=>N0TRzLT$@#|5;p)92-77&+D!pn4=ns53K%d
zljqev)SS4;-Mld0%l!X8l`!QYcgIqzdoc`N>UclVBMYz8;!Z8LxOM*qzu4-YRctB8
zmO^X6^x584#Q8brv6Ay~*?w77Io@lOTD{Y0^w%aV^g^$IWkF$H`(P5+L67Z(e<oJq
z%a3Vjn(b{yvKxu93|~*k=e3;AX=;+=y+(1qd#K;=t#0NOH9>>teSoAlo5cBS?mSu5
zmBCP3Y<3SN*^h_(A+qw67PqD!W8~-p8vg6`CHXJX>)NNxcN5=UO+ul?Ju#HL7I!uN
zRo}cV&3A`$yxAe&burvxt9Kq1l1~EPPjC&NDG8wW-6_3O#i_uqA4z=MP9Cy~zr&k8
z-`xe9S<top*3n(UR{m2k6P7Pup8pWFd%$qq;$G<|L6Xz#enS<h!2aq`@><-N#y{*P
zK9xehHTJ(E!K*_)>wMqgm`xLAdo}%MA2as4UQ5prL1~`5pPdkrwW#4bFZHQkq1F9@
zUnrIj!+PvJJLG+^n+jhG*(zluVC1=e@`G(%`fIryT?EPK!*KqeNOFFp>JVhsPC^%k
zY+K!jo0_b31TG;I)xjh;Ho-H*=h?;<_Yn2SvA#i4#zuPeTe2vl>HzfqqGj`LG8eeP
zBh|t@cMlSON7N=D6GeC~`}tqBZ%8Q2bw5q7Y_pR^BCk}n53bSMM2B-m3jcBJqy_E{
z=q`bsw%)QU!|NwCdx^g{$g!Rw_w*FNf^g#8PJ4;B&{Do2toOV^t2ePYuk#d&C3Ht%
zlf=(qxX<tFH*_$RkD~{NCU+FY19DB5`+JhSHl*`=b$O{Rvx$CDp~XEaf3UL=fu!9K
zO(fU-<4E+sqViO7nIO*h*suAo;&ZHLG%j#INAf~F9ljME8Ip+K8b*xYlv>}Y`>*12
zYyhCQUzn8OO2p?5?f_!H-OI5NLY@lJo0lggI!b!{vQl$r>?hkfHgc4jy;4%b*U{}H
z`Ip!82sy__0DVsXXj1YS?iVmI9Id7~HdT=11MSIQvAMjQqL|i|*T-^fWMG)Tks@d>
v&Ub&C5BEI~@}!R5zngx{kt4^34afOEAn0o@nFV`)00000NkvXXu0mjf_KoBy

literal 0
HcmV?d00001

diff --git a/data/web/inc/ajax/container_ctrl.php b/data/web/inc/ajax/container_ctrl.php
new file mode 100644
index 00000000..d12f5767
--- /dev/null
+++ b/data/web/inc/ajax/container_ctrl.php
@@ -0,0 +1,53 @@
+<?php
+session_start();
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') {
+	exit();
+}
+
+if (preg_match('/^[a-z\-]{0,}-mailcow/', $_GET['service'])) {
+  if ($_GET['action'] == "start") {
+    header('Content-Type: text/html; charset=utf-8');
+    $retry = 0;
+    while (docker($_GET['service'], 'info')['State']['Running'] != 1 && $retry <= 3) {
+      $response = docker($_GET['service'], 'post', 'start');
+      $response = json_decode($response, true);
+      $last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
+      if ($response['type'] == "success") {
+        break;
+      }
+      usleep(1500000);
+      $retry++;
+    }
+    echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Already running</span></b>' : $last_response;
+  }
+  if ($_GET['action'] == "stop") {
+    header('Content-Type: text/html; charset=utf-8');
+    $retry = 0;
+    while (docker($_GET['service'], 'info')['State']['Running'] == 1 && $retry <= 3) {
+      $response = docker($_GET['service'], 'post', 'stop');
+      $response = json_decode($response, true);
+      $last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
+      if ($response['type'] == "success") {
+        break;
+      }
+      usleep(1500000);
+      $retry++;
+    }
+    echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Not running</span></b>' : $last_response;
+  }
+  if ($_GET['action'] == "restart") {
+    header('Content-Type: text/html; charset=utf-8');
+    $response = docker($_GET['service'], 'post', 'restart');
+    $response = json_decode($response, true);
+    $last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
+    echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Cannot restart container</span></b>' : $last_response;
+  }
+  if ($_GET['action'] == "logs") {
+    $lines = (empty($_GET['lines']) || !is_numeric($_GET['lines'])) ? 1000 : $_GET['lines'];
+    header('Content-Type: text/plain; charset=utf-8');
+    print_r(preg_split('/\n/', docker($_GET['service'], 'logs', $lines)));
+  }
+}
+
+?>
diff --git a/data/web/inc/ajax/log_driver.php b/data/web/inc/ajax/log_driver.php
new file mode 100644
index 00000000..319f672d
--- /dev/null
+++ b/data/web/inc/ajax/log_driver.php
@@ -0,0 +1,12 @@
+<?php
+session_start();
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+header('Content-Type: text/plain');
+if (!isset($_SESSION['mailcow_cc_role'])) {
+	exit();
+}
+if (isset($_GET['type']) && isset($_GET['msg'])) {
+  global $mailcow_hostname;
+  //empty
+}
+?>
diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php
new file mode 100644
index 00000000..a4b80be1
--- /dev/null
+++ b/data/web/inc/ajax/qitem_details.php
@@ -0,0 +1,83 @@
+<?php
+session_start();
+header("Content-Type: application/json");
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+if (!isset($_SESSION['mailcow_cc_role'])) {
+	exit();
+}
+function rrmdir($src) {
+  $dir = opendir($src);
+  while(false !== ( $file = readdir($dir)) ) {
+    if (( $file != '.' ) && ( $file != '..' )) {
+      $full = $src . '/' . $file;
+      if ( is_dir($full) ) {
+        rrmdir($full);
+      }
+      else {
+        unlink($full);
+      }
+    }
+  }
+  closedir($dir);
+  rmdir($src);
+}
+if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) {
+  $tmpdir = '/tmp/' . $_GET['id'] . '/';
+  $mailc = quarantaine('details', $_GET['id']);
+  if (strlen($mailc['msg']) > 10485760) {
+    echo json_encode(array('error' => 'Message size exceeds 10 MiB.'));
+    exit;
+  }
+  if (!empty($mailc['msg'])) {
+    // Init message array
+    $data = array();
+    // Init parser
+    $mail_parser = new PhpMimeMailParser\Parser();
+    // Load msg to parser
+    $mail_parser->setText($mailc['msg']);
+    // Get text/plain content
+    $data['text_plain'] = $mail_parser->getMessageBody('text');
+    // Get subject
+    $data['subject'] = $mail_parser->getHeader('subject');
+    // Get attachments
+    if (is_dir($tmpdir)) {
+      rrmdir($tmpdir);
+    }
+    mkdir('/tmp/' . $_GET['id']);
+    $mail_parser->saveAttachments($tmpdir, true);
+    $atts = $mail_parser->getAttachments(true);
+    if (count($atts) > 0) {
+      foreach ($atts as $key => $val) {
+        $data['attachments'][$key] = array(
+          // Index
+          // 0 => file name
+          // 1 => mime type
+          // 2 => file size
+          // 3 => vt link by sha256
+          $val->getFilename(),
+          $val->getContentType(),
+          filesize($tmpdir . $val->getFilename()),
+          'https://www.virustotal.com/file/' . hash_file('SHA256', $tmpdir . $val->getFilename()) . '/analysis/'
+        );
+      }
+    }
+    if (isset($_GET['att'])) {
+      $dl_id = intval($_GET['att']);
+      $dl_filename = $data['attachments'][$dl_id][0];
+      if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) {
+        header('Pragma: public');
+        header('Expires: 0');
+        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+        header('Cache-Control: private', false);
+        header('Content-Type: ' . $data['attachments'][$dl_id][1]);
+        header('Content-Disposition: attachment; filename="'. $dl_filename . '";');
+        header('Content-Transfer-Encoding: binary');
+        header('Content-Length: ' . $data['attachments'][$dl_id][2]);
+        readfile($tmpdir . $dl_filename);
+        exit;
+      }
+    }
+    echo json_encode($data);
+  }
+}
+?>
diff --git a/data/web/inc/ajax/sogo_ctrl.php b/data/web/inc/ajax/sogo_ctrl.php
deleted file mode 100644
index e238d9c0..00000000
--- a/data/web/inc/ajax/sogo_ctrl.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-session_start();
-require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
-header('Content-Type: text/html; charset=utf-8');
-if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') {
-	exit();
-}
-
-if ($_GET['ACTION'] == "start") {
-  $retry = 0;
-  while (docker('sogo-mailcow', 'info')['State']['Running'] != 1 && $retry <= 3) {
-    $response = docker('sogo-mailcow', 'post', 'start');
-    $response = json_decode($response, true);
-    $last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
-    if ($response['type'] == "success") {
-      break;
-    }
-    usleep(1500000);
-    $retry++;
-  }
-  echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Already running</span></b>' : $last_response;
-}
-
-if ($_GET['ACTION'] == "stop") {
-  $retry = 0;
-  while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) {
-    $response = docker('sogo-mailcow', 'post', 'stop');
-    $response = json_decode($response, true);
-    $last_response = ($response['type'] == "success") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response['msg'] . '</span></b>';
-    if ($response['type'] == "success") {
-      break;
-    }
-    usleep(1500000);
-    $retry++;
-  }
-  echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Not running</span></b>' : $last_response;
-}
-
-?>
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
index 3ba758be..a082211b 100644
--- a/data/web/inc/footer.inc.php
+++ b/data/web/inc/footer.inc.php
@@ -8,6 +8,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php';
 <script src="/js/bootstrap-select.min.js"></script>
 <script src="/js/bootstrap-filestyle.min.js"></script>
 <script src="/js/notifications.min.js"></script>
+<script src="/js/formcache.min.js"></script>
 <script src="/js/numberedtextarea.min.js"></script>
 <script src="/js/u2f-api.js"></script>
 <script src="/js/api.js"></script>
@@ -26,11 +27,19 @@ $(document).ready(function() {
     msg = $('<span/>').html(message).text();
     if (type == 'danger') {
       auto_hide = 0;
+      $('#' + localStorage.getItem("add_modal")).modal('show');
+      localStorage.removeItem("add_modal");
     } else {
       auto_hide = 5000;
     }
+    $.ajax({
+      url: '/inc/ajax/log_driver.php',
+      data: {"type": type,"msg": msg},
+      type: "GET"
+    });
     $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
   }
+  $('[data-cached-form="true"]').formcache({key: $(this).data('id')});
   <?php if (isset($_SESSION['return'])): ?>
   mailcow_alert_box(<?=json_encode($_SESSION['return']['msg']); ?>,  "<?= $_SESSION['return']['type']; ?>");
   <?php endif; unset($_SESSION['return']); ?>
@@ -118,13 +127,8 @@ $(document).ready(function() {
     }
   });
 
-  // Activate tooltips
   $(function () {
     $('[data-toggle="tooltip"]').tooltip()
-  })
-  // Hide alerts after n seconds
-  $("#alert-fade").fadeTo(7000, 500).slideUp(500, function(){
-    $("#alert-fade").alert('close');
   });
 
   // Remember last navigation pill
@@ -173,36 +177,32 @@ $(document).ready(function() {
   // Init Bootstrap Selectpicker
   $('select').selectpicker();
 
-  // Trigger SOGo restart
-  $('#triggerRestartSogo').click(function(){
-    $(this).prop("disabled",true);
-    $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
-    $('#statusTriggerRestartSogo').text('Stopping SOGo workers, this may take a while... ');
-    $.ajax({
-      method: 'get',
-      url: '/inc/ajax/sogo_ctrl.php',
-      data: {
-        'ajax': true,
-        'ACTION': 'stop'
-      },
-      success: function(data) {
-        $('#statusTriggerRestartSogo').append(data);
-        $('#statusTriggerRestartSogo').append('<br>Starting SOGo...');
-        $.ajax({
-          method: 'get',
-          url: '/inc/ajax/sogo_ctrl.php',
-          data: {
-            'ajax': true,
-            'ACTION': 'start'
-          },
-          success: function(data) {
-            $('#statusTriggerRestartSogo').append(data);
-            $('#triggerRestartSogo').html('<span class="glyphicon glyphicon-ok"></span> ');
-          }
-        });
-      }
+  // Trigger container restart
+  $('#RestartContainer').on('show.bs.modal', function(e) {
+    var container = $(e.relatedTarget).data('container');
+    $('#containerName').text(container);
+    $('#triggerRestartContainer').click(function(){
+      $(this).prop("disabled",true);
+      $(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
+      $('#statusTriggerRestartContainer').text('Restarting container, this may take a while... ');
+      $.ajax({
+        method: 'get',
+        url: '/inc/ajax/container_ctrl.php',
+        timeout: 3000,
+        data: {
+          'service': container,
+          'action': 'restart'
+        },
+        error: function() {
+          window.location = window.location.href.split("#")[0];
+        },
+        success: function(data) {
+          $('#statusTriggerRestartContainer').append(data);
+          $('#triggerRestartContainer').html('<span class="glyphicon glyphicon-ok"></span> ');
+        }
+      });
     });
-  });
+  })
 
   // CSRF
   $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form');
@@ -216,4 +216,4 @@ $(document).ready(function() {
 </html>
 <?php
 $stmt = null;
-$pdo = null;
\ No newline at end of file
+$pdo = null;
diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php
index 7e8f7b03..e3d8c64d 100644
--- a/data/web/inc/functions.customize.inc.php
+++ b/data/web/inc/functions.customize.inc.php
@@ -18,7 +18,7 @@ function customize($_action, $_item, $_data = null) {
               if (file_exists($_data['main_logo']['tmp_name']) !== true) {
                 $_SESSION['return'] = array(
                   'type' => 'danger',
-                  'msg' => 'Cannot validate image file: Temporary file not found'
+                  'msg' => $lang['danger']['img_tmp_missing']
                 );
                 return false;
               }
@@ -26,7 +26,7 @@ function customize($_action, $_item, $_data = null) {
               if ($image->valid() !== true) {
                 $_SESSION['return'] = array(
                   'type' => 'danger',
-                  'msg' => 'Cannot validate image file'
+                  'msg' => $lang['danger']['img_invalid']
                 );
                 return false;
               }
@@ -35,7 +35,7 @@ function customize($_action, $_item, $_data = null) {
             catch (ImagickException $e) {
               $_SESSION['return'] = array(
                 'type' => 'danger',
-                'msg' => 'Cannot validate image file'
+                'msg' => $lang['danger']['img_invalid']
               );
               return false;
             }
@@ -43,7 +43,7 @@ function customize($_action, $_item, $_data = null) {
           else {
             $_SESSION['return'] = array(
               'type' => 'danger',
-              'msg' => 'Invalid mime type'
+              'msg' => $lang['danger']['invalid_mime_type']
             );
             return false;
           }
@@ -59,7 +59,7 @@ function customize($_action, $_item, $_data = null) {
           }
           $_SESSION['return'] = array(
             'type' => 'success',
-            'msg' => 'File uploaded successfully'
+            'msg' => $lang['success']['upload_success']
           );
         break;
       }
@@ -77,7 +77,7 @@ function customize($_action, $_item, $_data = null) {
           $apps = (array)$_data['app'];
           $links = (array)$_data['href'];
           $out = array();
-          if (count($apps) == count($links)) {;
+          if (count($apps) == count($links)) {
             for ($i = 0; $i < count($apps); $i++) {
               $out[] = array($apps[$i] => $links[$i]);
             }
@@ -94,7 +94,28 @@ function customize($_action, $_item, $_data = null) {
           }
           $_SESSION['return'] = array(
             'type' => 'success',
-            'msg' => 'Saved changes to app links'
+            'msg' => $lang['success']['app_links']
+          );
+        break;
+        case 'ui_texts':
+          $main_name = $_data['main_name'];
+          $apps_name = $_data['apps_name'];
+          $help_text = $_data['help_text'];
+          try {
+            $redis->set('MAIN_NAME', htmlspecialchars($main_name));
+            $redis->set('APPS_NAME', htmlspecialchars($apps_name));
+            $redis->set('HELP_TEXT', $help_text);
+          }
+          catch (RedisException $e) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => 'Redis: '.$e
+            );
+            return false;
+          }
+          $_SESSION['return'] = array(
+            'type' => 'success',
+            'msg' => $lang['success']['ui_texts']
           );
         break;
       }
@@ -113,7 +134,7 @@ function customize($_action, $_item, $_data = null) {
             if ($redis->del('MAIN_LOGO')) {
               $_SESSION['return'] = array(
                 'type' => 'success',
-                'msg' => 'Reset default logo'
+                'msg' => $lang['success']['reset_main_logo']
               );
               return true;
             }
@@ -155,6 +176,21 @@ function customize($_action, $_item, $_data = null) {
             return false;
           }
         break;
+        case 'ui_texts':
+          try {
+            $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI';
+            $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : 'mailcow Apps';
+            $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
+            return $data;
+          }
+          catch (RedisException $e) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => 'Redis: '.$e
+            );
+            return false;
+          }
+        break;
         case 'main_logo_specs':
           try {
             $image = new Imagick();
@@ -167,7 +203,7 @@ function customize($_action, $_item, $_data = null) {
           catch (ImagickException $e) {
             $_SESSION['return'] = array(
               'type' => 'danger',
-              'msg' => 'Error: Imagick exception while reading image'
+              'msg' => $lang['danger']['imagick_exception']
             );
             return false;
           }
diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php
index a5f2581c..7cd5ed4e 100644
--- a/data/web/inc/functions.docker.inc.php
+++ b/data/web/inc/functions.docker.inc.php
@@ -1,5 +1,12 @@
 <?php
-function docker($service_name, $action, $post_action = null, $post_fields = null) {
+function docker($service_name, $action, $attr1 = null, $attr2 = null, $extra_headers = null) {
+  if ($_SESSION['mailcow_cc_role'] != "admin") {
+    $_SESSION['return'] = array(
+      'type' => 'danger',
+      'msg' => sprintf($lang['danger']['access_denied'])
+    );
+    return false;
+  }
   $curl = curl_init();
   curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' ));
   switch($action) {
@@ -52,14 +59,44 @@ function docker($service_name, $action, $post_action = null, $post_fields = null
         return false;
       }
     break;
+    case 'logs':
+      $container_id = docker($service_name, 'get_id');
+      if (ctype_xdigit($container_id)) {
+        $lines = (empty($attr1) || !is_numeric($attr1)) ? 100 : $attr1;
+        curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/logs/' . $lines);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($curl, CURLOPT_POST, 0);
+        $response = curl_exec($curl);
+        if ($response === false) {
+          $err = curl_error($curl);
+          curl_close($curl);
+          return $err;
+        }
+        else {
+          curl_close($curl);
+          if (empty($response)) {
+            return true;
+          }
+          else {
+            return json_decode($response, true);
+          }
+        }
+      }
+      else {
+        return false;
+      }
+    break;
     case 'post':
-      if (!empty($post_action)) {
+      if (!empty($attr1)) {
         $container_id = docker($service_name, 'get_id');
-        if (ctype_xdigit($container_id) && ctype_alnum($post_action)) {
-          curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $post_action);
+        if (ctype_xdigit($container_id) && ctype_alnum($attr1)) {
+          curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $attr1);
           curl_setopt($curl, CURLOPT_POST, 1);
-          if (!empty($post_fields)) {
-            curl_setopt( $curl, CURLOPT_POSTFIELDS, json_encode($post_fields));
+          if (!empty($attr2)) {
+            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($attr2));
+          }
+          if (!empty($extra_headers) && is_array($extra_headers)) {
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $extra_headers);
           }
           curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
           $response = curl_exec($curl);
diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php
index 6c9e1692..e1801be7 100644
--- a/data/web/inc/functions.fail2ban.inc.php
+++ b/data/web/inc/functions.fail2ban.inc.php
@@ -1,5 +1,4 @@
 <?php
-if (F2B == 1) {
 function fail2ban($_action, $_data = null) {
   global $redis;
   global $lang;
@@ -97,4 +96,3 @@ function fail2ban($_action, $_data = null) {
     break;
   }
 }
-}
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 1ba9e644..34e24028 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -443,14 +443,31 @@ function user_get_alias_details($username) {
   }
   try {
     $data['address'] = $username;
-    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `aliases` FROM `alias`
+    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `shared_aliases` FROM `alias`
       WHERE `goto` REGEXP :username_goto
       AND `address` NOT LIKE '@%'
+      AND `goto` != :username_goto2
       AND `address` != :username_address");
-    $stmt->execute(array(':username_goto' => '(^|,)'.$username.'($|,)', ':username_address' => $username));
+    $stmt->execute(array(
+      ':username_goto' => '(^|,)'.$username.'($|,)',
+      ':username_goto2' => $username,
+      ':username_address' => $username
+      ));
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     while ($row = array_shift($run)) {
-      $data['aliases'] = $row['aliases'];
+      $data['shared_aliases'] = $row['shared_aliases'];
+    }
+    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `direct_aliases` FROM `alias`
+      WHERE `goto` = :username_goto
+      AND `address` != :username_address");
+    $stmt->execute(
+      array(
+      ':username_goto' => $username,
+      ':username_address' => $username
+      ));
+    $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
+    while ($row = array_shift($run)) {
+      $data['direct_aliases'] = $row['direct_aliases'];
     }
     $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '&#10008;') AS `ad_alias` FROM `mailbox`
       LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain`
@@ -851,6 +868,135 @@ function verify_tfa_login($username, $token) {
 	}
   return false;
 }
+function admin_api($action, $data = null) {
+	global $pdo;
+	global $lang;
+	if ($_SESSION['mailcow_cc_role'] != "admin") {
+		$_SESSION['return'] = array(
+			'type' => 'danger',
+			'msg' => sprintf($lang['danger']['access_denied'])
+		);
+		return false;
+	}
+	switch ($action) {
+		case "edit":
+      $regen_key = $data['admin_api_regen_key'];
+      $active = (isset($data['active'])) ? 1 : 0;
+      $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+      foreach ($allow_from as $key => $val) {
+        if (!filter_var($val, FILTER_VALIDATE_IP)) {
+          unset($allow_from[$key]);
+          continue;
+        }
+      }
+      $allow_from = implode(',', array_unique(array_filter($allow_from)));
+      if (empty($allow_from)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'List of allowed IPs cannot be empty'
+        );
+        return false;
+      }
+      $api_key = implode('-', array(
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3)))
+      ));
+      $stmt = $pdo->prepare("INSERT INTO `api` (`username`, `api_key`, `active`, `allow_from`)
+        SELECT `username`, :api_key, :active, :allow_from FROM `admin` WHERE `superadmin`='1' AND `active`='1'
+        ON DUPLICATE KEY UPDATE `active` = :active_u, `allow_from` = :allow_from_u ;");
+      $stmt->execute(array(
+        ':api_key' => $api_key,
+        ':active' => $active,
+        ':active_u' => $active,
+        ':allow_from' => $allow_from,
+        ':allow_from_u' => $allow_from
+      ));
+    break;
+    case "regen_key":
+      $api_key = implode('-', array(
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3))),
+        strtoupper(bin2hex(random_bytes(3)))
+      ));
+      $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `username` IN
+        (SELECT `username` FROM `admin` WHERE `superadmin`='1' AND `active`='1')");
+      $stmt->execute(array(
+        ':api_key' => $api_key
+      ));
+    break;
+  }
+	$_SESSION['return'] = array(
+		'type' => 'success',
+		'msg' => sprintf($lang['success']['admin_modified'])
+	);
+}
+function rspamd_ui($action, $data = null) {
+	global $lang;
+	if ($_SESSION['mailcow_cc_role'] != "admin") {
+		$_SESSION['return'] = array(
+			'type' => 'danger',
+			'msg' => sprintf($lang['danger']['access_denied'])
+		);
+		return false;
+	}
+	switch ($action) {
+		case "edit":
+      $rspamd_ui_pass = $data['rspamd_ui_pass'];
+      $rspamd_ui_pass2 = $data['rspamd_ui_pass2'];
+      if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Password cannot be empty'
+        );
+        return false;
+      }
+      if ($rspamd_ui_pass != $rspamd_ui_pass2) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Passwords do not match'
+        );
+        return false;
+      }
+      if (strlen($rspamd_ui_pass) < 6) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Please use at least 6 characters for your password'
+        );
+        return false;
+      }
+      $docker_return = docker('rspamd-mailcow', 'post', 'exec', array('cmd' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json'));
+      if ($docker_return_array = json_decode($docker_return, true)) {
+        if ($docker_return_array['type'] == 'success') {
+          $_SESSION['return'] = array(
+            'type' => 'success',
+            'msg' => 'Rspamd UI password set successfully'
+          );
+          return true;
+        }
+        else {
+          $_SESSION['return'] = array(
+            'type' => $docker_return_array['type'],
+            'msg' => $docker_return_array['msg']
+          );
+          return false;
+        }
+      }
+      else {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Unknown error'
+        );
+        return false;
+      }
+    break;
+  }
+
+}
 function get_admin_details() {
   // No parameter to be given, only one admin should exist
 	global $pdo;
@@ -860,8 +1006,10 @@ function get_admin_details() {
     return false;
   }
   try {
-    $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin` WHERE `superadmin`='1' AND active='1'");
-    $stmt->execute();
+    $stmt = $pdo->query("SELECT `admin`.`username`, `api`.`active` AS `api_active`, `api`.`api_key`, `api`.`allow_from` FROM `admin`
+      INNER JOIN `api` ON `admin`.`username` = `api`.`username`
+      WHERE `admin`.`superadmin`='1'
+        AND `admin`.`active`='1'");
     $data = $stmt->fetch(PDO::FETCH_ASSOC);
   }
   catch(PDOException $e) {
@@ -932,6 +1080,51 @@ function get_logs($container, $lines = false) {
       return $data_array;
     }
   }
+  if ($container == "watchdog-mailcow") {
+    if (!is_numeric($lines)) {
+      list ($from, $to) = explode('-', $lines);
+      $data = $redis->lRange('WATCHDOG_LOG', intval($from), intval($to));
+    }
+    else {
+      $data = $redis->lRange('WATCHDOG_LOG', 0, intval($lines));
+    }
+    if ($data) {
+      foreach ($data as $json_line) {
+        $data_array[] = json_decode($json_line, true);
+      }
+      return $data_array;
+    }
+  }
+  if ($container == "acme-mailcow") {
+    if (!is_numeric($lines)) {
+      list ($from, $to) = explode('-', $lines);
+      $data = $redis->lRange('ACME_LOG', intval($from), intval($to));
+    }
+    else {
+      $data = $redis->lRange('ACME_LOG', 0, intval($lines));
+    }
+    if ($data) {
+      foreach ($data as $json_line) {
+        $data_array[] = json_decode($json_line, true);
+      }
+      return $data_array;
+    }
+  }
+  if ($container == "api-mailcow") {
+    if (!is_numeric($lines)) {
+      list ($from, $to) = explode('-', $lines);
+      $data = $redis->lRange('API_LOG', intval($from), intval($to));
+    }
+    else {
+      $data = $redis->lRange('API_LOG', 0, intval($lines));
+    }
+    if ($data) {
+      foreach ($data as $json_line) {
+        $data_array[] = json_decode($json_line, true);
+      }
+      return $data_array;
+    }
+  }
   if ($container == "fail2ban-mailcow") {
     if (!is_numeric($lines)) {
       list ($from, $to) = explode('-', $lines);
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index d1410f97..054a9499 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -490,9 +490,20 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
             if (in_array($address, $gotos)) {
               continue;
             }
+            $domain       = idn_to_ascii(substr(strstr($address, '@'), 1));
+            $local_part   = strstr($address, '@', true);
+            $address      = $local_part.'@'.$domain;
             $stmt = $pdo->prepare("SELECT `address` FROM `alias`
-              WHERE `address`= :address");
-            $stmt->execute(array(':address' => $address));
+              WHERE `address`= :address OR `address` IN (
+                SELECT `username` FROM `mailbox`, `alias_domain`
+                  WHERE (
+                    `alias_domain`.`alias_domain` = :address_d
+                      AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))");
+            $stmt->execute(array(
+              ':address' => $address,
+              ':address_l' => $local_part,
+              ':address_d' => $domain
+            ));
             $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
             if ($num_results != 0) {
               $_SESSION['return'] = array(
@@ -501,9 +512,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               );
               return false;
             }
-            $domain       = idn_to_ascii(substr(strstr($address, '@'), 1));
-            $local_part   = strstr($address, '@', true);
-            $address      = $local_part.'@'.$domain;
             $domaindata = mailbox('get', 'domain_details', $domain);
             if (is_array($domaindata) && $domaindata['aliases_left'] == "0") {
               $_SESSION['return'] = array(
@@ -722,7 +730,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           $active = intval($_data['active']);
           $quota_b		= ($quota_m * 1048576);
-          $maildir		= $domain . "/" . $local_part . "/mail-" . time() . "/";
+          $maildir		= $domain . "/" . $local_part . "/mails/";
           if (!is_valid_domain_name($domain)) {
             $_SESSION['return'] = array(
               'type' => 'danger',
@@ -2302,7 +2310,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           else {
             try {
-              $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
+              $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)");
               $stmt->execute(array(
                 ':username' => $_SESSION['mailcow_cc_username'],
                 ':role' => $_SESSION['mailcow_cc_role'],
@@ -3360,7 +3368,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               ));
               $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain");
               $stmt->execute(array(
-                ':domain' => '%@'.$domain,
+                ':domain' => $domain,
               ));
             }
             catch (PDOException $e) {
@@ -3484,7 +3492,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               ));
               $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain");
               $stmt->execute(array(
-                ':domain' => '%@'.$alias_domain,
+                ':domain' => $alias_domain,
               ));
             }
             catch (PDOException $e) {
diff --git a/data/web/inc/functions.quarantaine.inc.php b/data/web/inc/functions.quarantaine.inc.php
new file mode 100644
index 00000000..4b9e6b00
--- /dev/null
+++ b/data/web/inc/functions.quarantaine.inc.php
@@ -0,0 +1,282 @@
+<?php
+function quarantaine($_action, $_data = null) {
+	global $pdo;
+	global $redis;
+	global $lang;
+  switch ($_action) {
+    case 'delete':
+      if (!is_array($_data['id'])) {
+        $ids = array();
+        $ids[] = $_data['id'];
+      }
+      else {
+        $ids = $_data['id'];
+      }
+      if (!isset($_SESSION['acl']['quarantaine']) || $_SESSION['acl']['quarantaine'] != "1" ) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      foreach ($ids as $id) {
+        if (!is_numeric($id)) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => sprintf($lang['danger']['access_denied'])
+          );
+          return false;
+        }
+        try {
+          $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantaine` WHERE `id` = :id');
+          $stmt->execute(array(':id' => $id));
+          $row = $stmt->fetch(PDO::FETCH_ASSOC);
+          if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) {
+            try {
+              $stmt = $pdo->prepare("DELETE FROM `quarantaine` WHERE `id` = :id");
+              $stmt->execute(array(
+                ':id' => $id
+              ));
+            }
+            catch (PDOException $e) {
+              $_SESSION['return'] = array(
+                'type' => 'danger',
+                'msg' => 'MySQL: '.$e
+              );
+              return false;
+            }
+          }
+          else {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => sprintf($lang['danger']['access_denied'])
+            );
+            return false;
+          }
+        }
+        catch(PDOException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'MySQL: '.$e
+          );
+        }
+      }
+      $_SESSION['return'] = array(
+        'type' => 'success',
+        'msg' => sprintf($lang['success']['items_deleted'], implode(', ', $ids))
+      );
+    break;
+    case 'edit':
+      if (!isset($_SESSION['acl']['quarantaine']) || $_SESSION['acl']['quarantaine'] != "1" ) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      // Edit settings
+      if ($_data['action'] == 'settings') {
+        if ($_SESSION['mailcow_cc_role'] != "admin") {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => sprintf($lang['danger']['access_denied'])
+          );
+          return false;
+        }
+        $retention_size = $_data['retention_size'];
+        $max_size = $_data['max_size'];
+        $exclude_domains = (array)$_data['exclude_domains'];
+        try {
+          $redis->Set('Q_RETENTION_SIZE', intval($retention_size));
+          $redis->Set('Q_MAX_SIZE', intval($max_size));
+          $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains));
+        }
+        catch (RedisException $e) {
+          $_SESSION['return'] = array(
+            'type' => 'danger',
+            'msg' => 'Redis: '.$e
+          );
+          return false;
+        }
+        $_SESSION['return'] = array(
+          'type' => 'success',
+          'msg' => 'Saved settings'
+        );
+      }
+      // Release item
+      elseif ($_data['action'] == 'release') {
+        if (!is_array($_data['id'])) {
+          $ids = array();
+          $ids[] = $_data['id'];
+        }
+        else {
+          $ids = $_data['id'];
+        }
+        foreach ($ids as $id) {
+          if (!is_numeric($id)) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => sprintf($lang['danger']['access_denied'])
+            );
+            return false;
+          }
+          try {
+            $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantaine` WHERE `id` = :id');
+            $stmt->execute(array(':id' => $id));
+            $row = $stmt->fetch(PDO::FETCH_ASSOC);
+            if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) {
+              $_SESSION['return'] = array(
+                'type' => 'danger',
+                'msg' => sprintf($lang['danger']['access_denied'])
+              );
+              return false;
+            }
+          }
+          catch(PDOException $e) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => 'MySQL: '.$e
+            );
+          }
+          $sender = (isset($row['sender'])) ? $row['sender'] : 'sender-unknown@rspamd';
+          try {
+            $mail = new PHPMailer(true);
+            $mail->isSMTP();
+            $mail->SMTPDebug = 0;
+            $mail->SMTPOptions = array(
+              'ssl' => array(
+                  'verify_peer' => false,
+                  'verify_peer_name' => false,
+                  'allow_self_signed' => true
+              )
+            );
+            if (!empty(gethostbynamel('postfix-mailcow'))) {
+              $postfix = 'apostfix-mailcow';
+            }
+            if (!empty(gethostbynamel('postfix'))) {
+              $postfix = 'postfix';
+            }
+            else {
+              $_SESSION['return'] = array(
+                'type' => 'warning',
+                'msg' => sprintf($lang['danger']['release_send_failed'], 'Cannot determine Postfix host')
+              );
+              return false;
+            }
+            $mail->Host = $postfix;
+            $mail->Port = 590;
+            $mail->setFrom($sender);
+            $mail->CharSet = 'UTF-8';
+            $mail->Subject = sprintf($lang['quarantaine']['release_subject'], $row['qid']);
+            $mail->addAddress($row['rcpt']);
+            $mail->IsHTML(false);
+            $msg_tmpf = tempnam("/tmp", $row['qid']);
+            file_put_contents($msg_tmpf, $row['msg']);
+            $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml');
+            $mail->Body = sprintf($lang['quarantaine']['release_body']);
+            $mail->send();
+            unlink($msg_tmpf);
+          }
+          catch (phpmailerException $e) {
+            unlink($msg_tmpf);
+            $_SESSION['return'] = array(
+              'type' => 'warning',
+              'msg' => sprintf($lang['danger']['release_send_failed'], $e->errorMessage())
+            );
+            return false;
+          }
+          try {
+            $stmt = $pdo->prepare("DELETE FROM `quarantaine` WHERE `id` = :id");
+            $stmt->execute(array(
+              ':id' => $id
+            ));
+          }
+          catch (PDOException $e) {
+            $_SESSION['return'] = array(
+              'type' => 'danger',
+              'msg' => 'MySQL: '.$e
+            );
+            return false;
+          }
+        }
+        $_SESSION['return'] = array(
+          'type' => 'success',
+          'msg' => $lang['success']['items_released']
+        );
+      }
+      return true;
+    break;
+    case 'get':
+      try {
+        if ($_SESSION['mailcow_cc_role'] == "user") {
+          $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantaine` WHERE `rcpt` = :mbox');
+          $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username']));
+          $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+          while($row = array_shift($rows)) {
+            $q_meta[] = $row;
+          }
+        }
+        else {
+          foreach (mailbox('get', 'mailboxes') as $mbox) {
+            $stmt = $pdo->prepare('SELECT `id`, `qid`, `rcpt`, `sender`, UNIX_TIMESTAMP(`created`) AS `created` FROM `quarantaine` WHERE `rcpt` = :mbox');
+            $stmt->execute(array(':mbox' => $mbox));
+            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+            while($row = array_shift($rows)) {
+              $q_meta[] = $row;
+            }
+          }
+        }
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+      }
+      return $q_meta;
+    break;
+    case 'settings':
+      if ($_SESSION['mailcow_cc_role'] != "admin") {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['access_denied'])
+        );
+        return false;
+      }
+      try {
+        $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true);
+        $settings['max_size'] = $redis->Get('Q_MAX_SIZE');
+        $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE');
+      }
+      catch (RedisException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'Redis: '.$e
+        );
+        return false;
+      }
+      return $settings;
+    break;
+    case 'details':
+      if (!is_numeric($_data) || empty($_data)) {
+        return false;
+      }
+      try {
+        $stmt = $pdo->prepare('SELECT `rcpt`, `symbols`, `msg`, `domain` FROM `quarantaine` WHERE `id`= :id');
+        $stmt->execute(array(':id' => $_data));
+        $row = $stmt->fetch(PDO::FETCH_ASSOC);
+        if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt'])) {
+          return $row;
+        }
+        return false;
+      }
+      catch(PDOException $e) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => 'MySQL: '.$e
+        );
+      }
+      return false;
+    break;
+  }
+}
\ No newline at end of file
diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php
index cedd07ca..c8dbe4b1 100644
--- a/data/web/inc/header.inc.php
+++ b/data/web/inc/header.inc.php
@@ -15,6 +15,7 @@
 <?php else: ?>
 <link rel="stylesheet" href="/css/bootstrap.min.css">
 <?php endif; ?>
+<link rel="stylesheet" href="/css/breakpoint.min.css">
 <link rel="stylesheet" href="/css/bootstrap-select.min.css">
 <link rel="stylesheet" href="/css/bootstrap-slider.min.css">
 <link rel="stylesheet" href="/css/bootstrap-switch.min.css">
@@ -27,6 +28,8 @@
 <?= (preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/admin.css">' : null; ?>
 <?= (preg_match("/user.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/user.css">' : null; ?>
 <?= (preg_match("/edit.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/edit.css">' : null; ?>
+<?= (preg_match("/quarantaine.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/quarantaine.css">' : null; ?>
+<?= (preg_match("/debug.php/i", $_SERVER['REQUEST_URI'])) ? '<link rel="stylesheet" href="/css/debug.css">' : null; ?>
 <link rel="shortcut icon" href="/favicon.png" type="image/png">
 <link rel="icon" href="/favicon.png" type="image/png">
 </head>
@@ -35,7 +38,6 @@
   <div class="container-fluid">
     <div class="navbar-header">
       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-        <span class="sr-only">Toggle navigation</span>
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
@@ -70,6 +72,7 @@
             if (isset($_SESSION['mailcow_cc_role'])) {
               if ($_SESSION['mailcow_cc_role'] == 'admin') {
               ?>
+                <li<?= (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/debug.php"><?= $lang['header']['debug']; ?></a></li>
                 <li<?= (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/admin.php"><?= $lang['header']['administration']; ?></a></li>
               <?php
               }
@@ -88,14 +91,19 @@
           </ul>
         </li>
         <?php
+        if (isset($_SESSION['mailcow_cc_role'])) {
+        ?>
+        <li<?= (preg_match("/quarantaine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantaine.php"><span style="font-size: 12px;" class="glyphicon glyphicon-briefcase"></span> <?= $lang['header']['quarantaine']; ?></a></li>
+        <?php
+        }
         if ($_SESSION['mailcow_cc_role'] == 'admin') {
         ?>
-        <li><a href data-toggle="modal" data-target="#RestartSOGo"><span style="font-size: 12px;" class="glyphicon glyphicon-refresh" aria-hidden="true"></span> <?= $lang['header']['restart_sogo']; ?></a></li>
+        <li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><span style="font-size: 12px;" class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_sogo']; ?></a></li>
         <?php
         }
         ?>
         <li class="dropdown">
-          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="glyphicon glyphicon-link" aria-hidden="true"></span> Apps <span class="caret"></span></a>
+          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="glyphicon glyphicon-link"></span> Apps <span class="caret"></span></a>
           <ul class="dropdown-menu" role="menu">
           <?php
           foreach ($MAILCOW_APPS as $app):
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index 24a7e072..db3f2f38 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "16112017_2259";
+    $db_version = "29112017_1515";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -103,6 +103,30 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "api" => array(
+        "cols" => array(
+          "username" => "VARCHAR(255) NOT NULL",
+          "api_key" => "VARCHAR(255) NOT NULL",
+          "allow_from" => "VARCHAR(512) NOT NULL",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE NOW(0)",
+          "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("username")
+          ),
+          "fkey" => array(
+            "fk_username_api" => array(
+              "col" => "username",
+              "ref" => "admin.username",
+              "delete" => "CASCADE",
+              "update" => "NO ACTION"
+            )
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "sender_acl" => array(
         "cols" => array(
           "logged_in_as" => "VARCHAR(255) NOT NULL",
@@ -133,6 +157,28 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "quarantaine" => array(
+        "cols" => array(
+          "id" => "INT NOT NULL AUTO_INCREMENT",
+          "qid" => "VARCHAR(30) NOT NULL",
+          "score" => "FLOAT(8,2)",
+          "ip" => "VARBINARY(16)",
+          "action" => "CHAR(20) NOT NULL DEFAULT 'unknown'",
+          "symbols" => "JSON",
+          "sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
+          "rcpt" => "VARCHAR(255)",
+          "msg" => "LONGTEXT",
+          "domain" => "VARCHAR(255)",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("id")
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "mailbox" => array(
         "cols" => array(
           "username" => "VARCHAR(255) NOT NULL",
@@ -191,6 +237,51 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "imap_user_shares" => array(
+        "cols" => array(
+          "from_user" => "VARCHAR(255) NOT NULL",
+          "to_user" => "VARCHAR(255) NOT NULL",
+          "dummy" => "CHAR(1) DEFAULT '1'",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("from_user", "to_user")
+          ),
+          "fkey" => array(
+            "fk_from_user_user_shares" => array(
+              "col" => "from_user",
+              "ref" => "mailbox.username",
+              "delete" => "CASCADE",
+              "update" => "NO ACTION"
+            )
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
+      "imap_anyone_shares" => array(
+        "cols" => array(
+          "from_user" => "VARCHAR(255) NOT NULL",
+          "dummy" => "CHAR(1) DEFAULT '1'",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("from_user")
+          ),
+          "fkey" => array(
+            "fk_from_anyone_user_shares" => array(
+              "col" => "from_user",
+              "ref" => "mailbox.username",
+              "delete" => "CASCADE",
+              "update" => "NO ACTION"
+            )
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "user_acl" => array(
         "cols" => array(
           "username" => "VARCHAR(255) NOT NULL",
@@ -202,6 +293,7 @@ function init_db_schema() {
           "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "quarantaine" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
         ),
         "keys" => array(
@@ -708,6 +800,20 @@ function init_db_schema() {
       $pdo->query($create);
     }
 
+    // Create events to clean database
+    $events[] = 'DROP EVENT IF EXISTS clean_spamalias;
+DELIMITER //
+CREATE EVENT clean_spamalias 
+ON SCHEDULE EVERY 1 DAY DO 
+BEGIN
+  DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
+END;
+//
+DELIMITER ;';
+    foreach ($events as $event) {
+      $pdo->exec($event);
+    }
+
     // Inject admin if not exists
     $stmt = $pdo->query("SELECT NULL FROM `admin`"); 
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json
index 9ac68875..fd8b845e 100644
--- a/data/web/inc/lib/composer.json
+++ b/data/web/inc/lib/composer.json
@@ -2,6 +2,7 @@
     "require": {
         "robthree/twofactorauth": "^1.6",
         "yubico/u2flib-server": "^1.0",
-        "phpmailer/phpmailer": "^5.2"
+        "phpmailer/phpmailer": "^5.2",
+        "php-mime-mail-parser/php-mime-mail-parser": "^2.9"
     }
 }
diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock
index cca7113e..384ff585 100644
--- a/data/web/inc/lib/composer.lock
+++ b/data/web/inc/lib/composer.lock
@@ -4,20 +4,100 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "d51ef1712a74b0dfed729f2bdd85d1e3",
+    "content-hash": "ee4c9e269c29282221ce88bc23f1bda9",
     "packages": [
         {
-            "name": "phpmailer/phpmailer",
-            "version": "v5.2.26",
+            "name": "php-mime-mail-parser/php-mime-mail-parser",
+            "version": "2.9.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/PHPMailer/PHPMailer.git",
-                "reference": "70362997bda4376378be7d92d81e2200550923f7"
+                "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git",
+                "reference": "c6884c7bc77adbf55979db99841195b232fd30f1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7",
-                "reference": "70362997bda4376378be7d92d81e2200550923f7",
+                "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/c6884c7bc77adbf55979db99841195b232fd30f1",
+                "reference": "c6884c7bc77adbf55979db99841195b232fd30f1",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mailparse": "*",
+                "php": "^5.4.0 || ^7.0"
+            },
+            "replace": {
+                "exorus/php-mime-mail-parser": "*",
+                "messaged/php-mime-mail-parser": "*"
+            },
+            "require-dev": {
+                "phpunit/php-token-stream": "^1.3.0",
+                "phpunit/phpunit": "^4.0 || ^5.0",
+                "satooshi/php-coveralls": "0.*",
+                "squizlabs/php_codesniffer": "2.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpMimeMailParser\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "bucabay",
+                    "email": "gabe@fijiwebdesign.com",
+                    "homepage": "http://www.fijiwebdesign.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "eXorus",
+                    "email": "exorus.spam@gmail.com",
+                    "homepage": "https://github.com/eXorus/",
+                    "role": "Developer"
+                },
+                {
+                    "name": "M.Valinskis",
+                    "email": "M.Valins@gmail.com",
+                    "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                    "role": "Developer"
+                },
+                {
+                    "name": "eugene.emmett.wood",
+                    "email": "gene_w@cementhorizon.com",
+                    "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                    "role": "Developer"
+                },
+                {
+                    "name": "alknetso",
+                    "email": "alkne@gmail.com",
+                    "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+",
+            "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser",
+            "keywords": [
+                "MimeMailParser",
+                "mail",
+                "mailparse",
+                "mime"
+            ],
+            "time": "2017-11-02T05:49:00+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v5.2.25",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
+                "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
                 "shasum": ""
             },
             "require": {
@@ -81,20 +161,20 @@
                 }
             ],
             "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
-            "time": "2017-11-04T09:26:05+00:00"
+            "time": "2017-08-28T11:12:07+00:00"
         },
         {
             "name": "robthree/twofactorauth",
-            "version": "1.6.1",
+            "version": "1.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/RobThree/TwoFactorAuth.git",
-                "reference": "a77e7d822343bb88112baef808839cfae7bc5abb"
+                "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/a77e7d822343bb88112baef808839cfae7bc5abb",
-                "reference": "a77e7d822343bb88112baef808839cfae7bc5abb",
+                "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
+                "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
                 "shasum": ""
             },
             "require": {
@@ -132,7 +212,7 @@
                 "php",
                 "tfa"
             ],
-            "time": "2017-11-06T17:55:56+00:00"
+            "time": "2017-02-17T15:24:54+00:00"
         },
         {
             "name": "yubico/u2flib-server",
diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php
index 55ee8027..8abeac4b 100644
--- a/data/web/inc/lib/vendor/composer/autoload_psr4.php
+++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php
@@ -7,4 +7,5 @@ $baseDir = dirname($vendorDir);
 
 return array(
     'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
+    'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'),
 );
diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php
index ad3b924f..24f862c6 100644
--- a/data/web/inc/lib/vendor/composer/autoload_static.php
+++ b/data/web/inc/lib/vendor/composer/autoload_static.php
@@ -11,6 +11,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
         array (
             'RobThree\\Auth\\' => 14,
         ),
+        'P' => 
+        array (
+            'PhpMimeMailParser\\' => 18,
+        ),
     );
 
     public static $prefixDirsPsr4 = array (
@@ -18,6 +22,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
         array (
             0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
         ),
+        'PhpMimeMailParser\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src',
+        ),
     );
 
     public static $classMap = array (
diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json
index 22cd3e15..ba0c1451 100644
--- a/data/web/inc/lib/vendor/composer/installed.json
+++ b/data/web/inc/lib/vendor/composer/installed.json
@@ -1,4 +1,57 @@
 [
+    {
+        "name": "robthree/twofactorauth",
+        "version": "1.6",
+        "version_normalized": "1.6.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/RobThree/TwoFactorAuth.git",
+            "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
+            "reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "@stable"
+        },
+        "time": "2017-02-17T15:24:54+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "RobThree\\Auth\\": "lib"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Rob Janssen",
+                "homepage": "http://robiii.me",
+                "role": "Developer"
+            }
+        ],
+        "description": "Two Factor Authentication",
+        "homepage": "https://github.com/RobThree/TwoFactorAuth",
+        "keywords": [
+            "Authentication",
+            "MFA",
+            "Multi Factor Authentication",
+            "Two Factor Authentication",
+            "authenticator",
+            "authy",
+            "php",
+            "tfa"
+        ]
+    },
     {
         "name": "yubico/u2flib-server",
         "version": "1.0.1",
@@ -36,72 +89,19 @@
         "description": "Library for U2F implementation",
         "homepage": "https://developers.yubico.com/php-u2flib-server"
     },
-    {
-        "name": "robthree/twofactorauth",
-        "version": "1.6.1",
-        "version_normalized": "1.6.1.0",
-        "source": {
-            "type": "git",
-            "url": "https://github.com/RobThree/TwoFactorAuth.git",
-            "reference": "a77e7d822343bb88112baef808839cfae7bc5abb"
-        },
-        "dist": {
-            "type": "zip",
-            "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/a77e7d822343bb88112baef808839cfae7bc5abb",
-            "reference": "a77e7d822343bb88112baef808839cfae7bc5abb",
-            "shasum": ""
-        },
-        "require": {
-            "php": ">=5.3.0"
-        },
-        "require-dev": {
-            "phpunit/phpunit": "@stable"
-        },
-        "time": "2017-11-06T17:55:56+00:00",
-        "type": "library",
-        "installation-source": "dist",
-        "autoload": {
-            "psr-4": {
-                "RobThree\\Auth\\": "lib"
-            }
-        },
-        "notification-url": "https://packagist.org/downloads/",
-        "license": [
-            "MIT"
-        ],
-        "authors": [
-            {
-                "name": "Rob Janssen",
-                "homepage": "http://robiii.me",
-                "role": "Developer"
-            }
-        ],
-        "description": "Two Factor Authentication",
-        "homepage": "https://github.com/RobThree/TwoFactorAuth",
-        "keywords": [
-            "Authentication",
-            "MFA",
-            "Multi Factor Authentication",
-            "Two Factor Authentication",
-            "authenticator",
-            "authy",
-            "php",
-            "tfa"
-        ]
-    },
     {
         "name": "phpmailer/phpmailer",
-        "version": "v5.2.26",
-        "version_normalized": "5.2.26.0",
+        "version": "v5.2.25",
+        "version_normalized": "5.2.25.0",
         "source": {
             "type": "git",
             "url": "https://github.com/PHPMailer/PHPMailer.git",
-            "reference": "70362997bda4376378be7d92d81e2200550923f7"
+            "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7",
-            "reference": "70362997bda4376378be7d92d81e2200550923f7",
+            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
+            "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
             "shasum": ""
         },
         "require": {
@@ -131,7 +131,7 @@
         "suggest": {
             "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
         },
-        "time": "2017-11-04T09:26:05+00:00",
+        "time": "2017-08-28T11:12:07+00:00",
         "type": "library",
         "installation-source": "dist",
         "autoload": {
@@ -167,5 +167,87 @@
             }
         ],
         "description": "PHPMailer is a full-featured email creation and transfer class for PHP"
+    },
+    {
+        "name": "php-mime-mail-parser/php-mime-mail-parser",
+        "version": "2.9.3",
+        "version_normalized": "2.9.3.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git",
+            "reference": "c6884c7bc77adbf55979db99841195b232fd30f1"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/c6884c7bc77adbf55979db99841195b232fd30f1",
+            "reference": "c6884c7bc77adbf55979db99841195b232fd30f1",
+            "shasum": ""
+        },
+        "require": {
+            "ext-mailparse": "*",
+            "php": "^5.4.0 || ^7.0"
+        },
+        "replace": {
+            "exorus/php-mime-mail-parser": "*",
+            "messaged/php-mime-mail-parser": "*"
+        },
+        "require-dev": {
+            "phpunit/php-token-stream": "^1.3.0",
+            "phpunit/phpunit": "^4.0 || ^5.0",
+            "satooshi/php-coveralls": "0.*",
+            "squizlabs/php_codesniffer": "2.*"
+        },
+        "time": "2017-11-02T05:49:00+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "PhpMimeMailParser\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "bucabay",
+                "email": "gabe@fijiwebdesign.com",
+                "homepage": "http://www.fijiwebdesign.com",
+                "role": "Developer"
+            },
+            {
+                "name": "eXorus",
+                "email": "exorus.spam@gmail.com",
+                "homepage": "https://github.com/eXorus/",
+                "role": "Developer"
+            },
+            {
+                "name": "M.Valinskis",
+                "email": "M.Valins@gmail.com",
+                "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                "role": "Developer"
+            },
+            {
+                "name": "eugene.emmett.wood",
+                "email": "gene_w@cementhorizon.com",
+                "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                "role": "Developer"
+            },
+            {
+                "name": "alknetso",
+                "email": "alkne@gmail.com",
+                "homepage": "https://code.google.com/p/php-mime-mail-parser",
+                "role": "Developer"
+            }
+        ],
+        "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+",
+        "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser",
+        "keywords": [
+            "MimeMailParser",
+            "mail",
+            "mailparse",
+            "mime"
+        ]
     }
 ]
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/LICENSE b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/LICENSE
new file mode 100644
index 00000000..ab85686e
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Vincent Dauce
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md
new file mode 100644
index 00000000..7f996284
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/README.md
@@ -0,0 +1,167 @@
+# php-mime-mail-parser
+
+A fully tested mailparse extension wrapper for PHP 5.4+
+
+[![Latest Version](https://img.shields.io/packagist/v/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://github.com/php-mime-mail-parser/php-mime-mail-parser/releases)
+[![Total Downloads](https://img.shields.io/packagist/dt/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://packagist.org/packages/php-mime-mail-parser/php-mime-mail-parser)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+
+## Why?
+
+This extension can be used to...
+ * Parse and read email from Postfix
+ * Create webmail 
+ * Store email information such a subject, HTML body, attachments, and etc. into a database
+
+## Is it reliable?
+
+Yes. All known issues have been reproduced, fixed and tested.
+
+We use Travis CI to help ensure code quality. You can see real-time statistics below:
+
+[![Build Status](https://img.shields.io/travis/php-mime-mail-parser/php-mime-mail-parser/master.svg?style=flat-square)](https://travis-ci.org/php-mime-mail-parser/php-mime-mail-parser)
+[![Coverage](https://img.shields.io/coveralls/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://coveralls.io/r/php-mime-mail-parser/php-mime-mail-parser)
+[![Quality Score](https://img.shields.io/scrutinizer/g/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-mime-mail-parser/php-mime-mail-parser)
+
+## How do I install it?
+
+The easiest way is via [Composer](https://getcomposer.org/).
+
+To install the latest version of PHP MIME Mail Parser, run the command below:
+
+	composer require php-mime-mail-parser/php-mime-mail-parser
+
+## Requirements
+
+The following versions of PHP are supported:
+
+* PHP 5.4
+* PHP 5.5
+* PHP 5.6
+* PHP 7
+* HHVM
+
+```
+sudo apt install php-cli php-pear php-dev php-mbstring
+```
+
+Make sure you have the mailparse extension (http://php.net/manual/en/book.mailparse.php) properly installed. The command line `php -m | grep mailparse` need to return "mailparse" else install it:
+* PHP version > 7.0: mailparse
+* PHP version < 7.0: mailparse 2.1.6
+
+Follow this steps to install mailparse:
+
+* Compile in the temp folder the extension mailparse or mailparse-2.1.6 (workaround because pecl install doesn't work yet)
+```
+cd
+pecl download mailparse
+tar -xvf mailparse-3.0.2.tgz 
+cd mailparse-3.0.2/
+phpize
+./configure
+sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c
+make
+sudo mv modules/mailparse.so /usr/lib/php/20160303/
+```
+* Add the extension mailparse and activate it
+```
+echo "extension=mailparse.so" | sudo tee /etc/php/7.1/mods-available/mailparse.ini
+sudo phpenmod mailparse
+```
+
+On Windows, you need to download mailparse DLL from http://pecl.php.net/package/mailparse and add the line "extension=php_mailparse.dll" to php.ini accordingly.
+
+## How do I use it?
+
+```php
+<?php
+// Include the library first
+require_once __DIR__.'/vendor/autoload.php';
+
+$path = 'path/to/mail.txt';
+$Parser = new PhpMimeMailParser\Parser();
+
+// There are four methods available to indicate which mime mail to parse.
+// You only need to use one of the following four:
+
+// 1. Specify a file path to the mime mail.
+$Parser->setPath($path); 
+
+// 2. Specify a php file resource (stream) to the mime mail.
+$Parser->setStream(fopen($path, "r"));
+
+// 3. Specify the raw mime mail text.
+$Parser->setText(file_get_contents($path));
+
+// 4.  Specify a stream to work with mail server
+$Parser->setStream(fopen("php://stdin", "r"));
+
+// Once we've indicated where to find the mail, we can parse out the data
+$to = $Parser->getHeader('to');             // "test" <test@example.com>, "test2" <test2@example.com>
+$addressesTo = $Parser->getAddresses('to'); //Return an array : [[test, test@example.com, false],[test2, test2@example.com, false]]
+
+$from = $Parser->getHeader('from');             // "test" <test@example.com>
+$addressesFrom = $Parser->getAddresses('from'); //Return an array : test, test@example.com, false
+
+$subject = $Parser->getHeader('subject');
+
+$text = $Parser->getMessageBody('text');
+
+$html = $Parser->getMessageBody('html');
+$htmlEmbedded = $Parser->getMessageBody('htmlEmbedded'); //HTML Body included data
+
+$stringHeaders = $Parser->getHeadersRaw();	// Get all headers as a string, no charset conversion
+$arrayHeaders = $Parser->getHeaders();		// Get all headers as an array, with charset conversion
+
+// Pass in a writeable path to save attachments
+$attach_dir = '/path/to/save/attachments/'; 	// Be sure to include the trailing slash
+$include_inline = true;  			// Optional argument to include inline attachments (default: true)
+$Parser->saveAttachments($attach_dir [,$include_inline]);
+
+// Get an array of Attachment items from $Parser
+$attachments = $Parser->getAttachments([$include_inline]);
+
+//  Loop through all the Attachments
+if (count($attachments) > 0) {
+	foreach ($attachments as $attachment) {
+		echo 'Filename : '.$attachment->getFilename().'<br />'; // logo.jpg
+		echo 'Filesize : '.filesize($attach_dir.$attachment->getFilename()).'<br />'; // 1000
+		echo 'Filetype : '.$attachment->getContentType().'<br />'; // image/jpeg
+		echo 'MIME part string : '.$attachment->getMimePartStr().'<br />'; // (the whole MIME part of the attachment)
+	}
+}
+
+?>
+```
+
+Next you need to forward emails to this script above. For that I'm using [Postfix](http://www.postfix.org/) like a mail server, you need to configure /etc/postfix/master.cf
+
+Add this line at the end of the file (specify myhook to send all emails to the script test.php)
+```
+myhook unix - n n - - pipe
+  				flags=F user=www-data argv=php -c /etc/php5/apache2/php.ini -f /var/www/test.php ${sender} ${size} ${recipient}
+```
+
+Edit this line (register myhook)
+```
+smtp      inet  n       -       -       -       -       smtpd
+        			-o content_filter=myhook:dummy
+```
+
+The php script must use the fourth method to work with this configuration.
+
+
+## Can I contribute?
+
+Feel free to contribute!
+
+	git clone https://github.com/php-mime-mail-parser/php-mime-mail-parser
+	cd php-mime-mail-parser
+	composer install
+	./vendor/bin/phpunit
+
+If you report an issue, please provide the raw email that triggered it. This helps us reproduce the issue and fix it more quickly.
+
+### License
+
+The php-mime-mail-parser/php-mime-mail-parser is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json
new file mode 100644
index 00000000..320d08d2
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json
@@ -0,0 +1,61 @@
+{
+    "name": "php-mime-mail-parser/php-mime-mail-parser",
+    "type": "library",
+    "description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+",
+    "keywords": ["mime", "mail", "mailparse", "MimeMailParser"],
+    "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser",
+    "license": "MIT",
+    "authors": [      
+        {
+            "name":"eXorus",
+            "email":"exorus.spam@gmail.com",
+            "homepage":"https://github.com/eXorus/",
+            "role":"Developer"
+        },
+        {
+            "name":"M.Valinskis",
+            "email":"M.Valins@gmail.com",
+            "homepage":"https://code.google.com/p/php-mime-mail-parser",
+            "role":"Developer"
+        },
+        {
+            "name":"eugene.emmett.wood",
+            "email":"gene_w@cementhorizon.com",
+            "homepage":"https://code.google.com/p/php-mime-mail-parser",
+            "role":"Developer"
+        },
+        {
+            "name":"alknetso",
+            "email":"alkne@gmail.com",
+            "homepage":"https://code.google.com/p/php-mime-mail-parser",
+            "role":"Developer"
+        },
+        {
+            "name":"bucabay",
+            "email":"gabe@fijiwebdesign.com",
+            "homepage":"http://www.fijiwebdesign.com",
+            "role":"Developer"
+        }
+    ],
+    "repository":{
+        "type":"git",
+        "url":"https://github.com/php-mime-mail-parser/php-mime-mail-parser.git"
+    },
+    "require": {
+        "php":           "^5.4.0 || ^7.0",
+        "ext-mailparse": "*"
+    },
+    "require-dev": {
+        "phpunit/phpunit":              "^4.0 || ^5.0",
+        "phpunit/php-token-stream":     "^1.3.0",
+        "satooshi/php-coveralls":       "0.*",
+        "squizlabs/PHP_CodeSniffer":    "2.*"
+    },
+    "replace": {
+        "exorus/php-mime-mail-parser":   "*",
+        "messaged/php-mime-mail-parser": "*"
+    },
+    "autoload": {
+        "psr-4": { "PhpMimeMailParser\\": "src/" }
+    }
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php
new file mode 100644
index 00000000..743033ef
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/mailparse-stubs.php
@@ -0,0 +1,303 @@
+<?php
+/**
+ * @link http://php.net/manual/en/mailparse.constants.php
+ */
+define('MAILPARSE_EXTRACT_OUTPUT', 0);
+
+/**
+ * @link http://php.net/manual/en/mailparse.constants.php
+ */
+define('MAILPARSE_EXTRACT_STREAM', 1);
+
+/**
+ * @link http://php.net/manual/en/mailparse.constants.php
+ */
+define('MAILPARSE_EXTRACT_RETURN', 2);
+
+/**
+ * Parses a file. This is the optimal way of parsing a mail file that you have on
+ * disk.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-parse-file.php
+ *
+ * @param string $filename Path to the file holding the message. The file is opened
+ *                         and streamed through the parser
+ *
+ * @return resource Returns a MIME resource representing the structure, or false on error
+ */
+function mailparse_msg_parse_file($filename)
+{
+}
+
+/**
+ * .
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-get-part.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ * @param string   $mimesection
+ *
+ * @return resource
+ */
+function mailparse_msg_get_part($mimemail, $mimesection)
+{
+}
+
+/**
+ * .
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-get-structure.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ *
+ * @return array
+ */
+function mailparse_msg_get_structure($mimemail)
+{
+}
+
+/**
+ * .
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-get-part-data.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ *
+ * @return array
+ */
+function mailparse_msg_get_part_data($mimemail)
+{
+}
+
+/**
+ * .
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-extract-part.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ * @param string   $msgbody
+ * @param callable $callbackfunc
+ *
+ * @return void
+ */
+function mailparse_msg_extract_part($mimemail, $msgbody, $callbackfunc)
+{
+}
+
+/**
+ * Extracts/decodes a message section from the supplied filename.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-extract-part-file.php
+ *
+ * @param resource $mimemail     A valid MIME resource, created with
+ *                               mailparse_msg_create
+ * @param mixed    $filename     Can be a file name or a valid stream resource
+ * @param callable $callbackfunc If set, this must be either a valid callback that
+ *                               will be passed the extracted section, or null to make this function return the
+ *                               extracted section
+ *
+ * @return string If $callbackfunc is not null returns true on success
+ */
+function mailparse_msg_extract_part_file($mimemail, $filename, $callbackfunc = false)
+{
+}
+
+/**
+ * .
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-extract-whole-part-file.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ * @param string   $filename
+ * @param callable $callbackfunc
+ *
+ * @return string
+ */
+function mailparse_msg_extract_whole_part_file($mimemail, $filename, $callbackfunc)
+{
+}
+
+/**
+ * Create a MIME mail resource.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-create.php
+ * @return resource Returns a handle that can be used to parse a message
+ */
+function mailparse_msg_create()
+{
+}
+
+/**
+ * Frees a MIME resource.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-free.php
+ *
+ * @param resource $mimemail A valid MIME resource allocated by
+ *                           mailparse_msg_create or mailparse_msg_parse_file
+ *
+ * @return bool
+ */
+function mailparse_msg_free($mimemail)
+{
+}
+
+/**
+ * Incrementally parse data into the supplied mime mail resource.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-msg-parse.php
+ *
+ * @param resource $mimemail A valid MIME resource
+ * @param string   $data
+ *
+ * @return bool
+ */
+function mailparse_msg_parse($mimemail, $data)
+{
+}
+
+/**
+ * Parses a RFC 822 compliant recipient list, such as that found in the To: header.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-rfc822-parse-addresses.php
+ *
+ * @param string $addresses A string containing addresses, like in: Wez Furlong
+ *                          wez@example.com, doe@example.com
+ *
+ * @return array Returns an array of associative arrays with the following keys for each
+ *         recipient: display The recipient name, for display purpose. If this part is not
+ *         set for a recipient, this key will hold the same value as address. address The
+ *         email address is_group true if the recipient is a newsgroup, false otherwise
+ */
+function mailparse_rfc822_parse_addresses($addresses)
+{
+}
+
+/**
+ * Figures out the best way of encoding the content read from the given file
+ * pointer.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-determine-best-xfer-encoding.php
+ *
+ * @param resource $fp A valid file pointer, which must be seek-able
+ *
+ * @return string Returns one of the character encodings supported by the mbstring module
+ */
+function mailparse_determine_best_xfer_encoding($fp)
+{
+}
+
+/**
+ * Streams data from the source file pointer, apply $encoding and write to the
+ * destination file pointer.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-stream-encode.php
+ *
+ * @param resource $sourcefp A valid file handle. The file is streamed through the
+ *                           parser
+ * @param resource $destfp   The destination file handle in which the encoded data
+ *                           will be written
+ * @param string   $encoding One of the character encodings supported by the mbstring
+ *                           module
+ *
+ * @return bool
+ */
+function mailparse_stream_encode($sourcefp, $destfp, $encoding)
+{
+}
+
+/**
+ * Scans the data from the given file pointer and extract each embedded uuencoded
+ * file into a temporary file.
+ *
+ * @link http://php.net/manual/en/functions.mailparse-uudecode-all.php
+ *
+ * @param resource $fp A valid file pointer
+ *
+ * @return array Returns an array of associative arrays listing filename information.
+ *         filename Path to the temporary file name created origfilename The original
+ *         filename, for uuencoded parts only The first filename entry is the message body.
+ *         The next entries are the decoded uuencoded files
+ */
+function mailparse_uudecode_all($fp)
+{
+}
+
+/**
+ * @return
+ */
+function mailparse_test()
+{
+}
+
+class mimemessage
+{
+    /**
+     * @return
+     */
+    public function mimemessage()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function get_child()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function get_child_count()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function get_parent()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function extract_headers()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function extract_body()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function enum_uue()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function extract_uue()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function remove()
+    {
+    }
+
+    /**
+     * @return
+     */
+    public function add_child()
+    {
+    }
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/phpunit.xml.dist b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/phpunit.xml.dist
new file mode 100644
index 00000000..32b1dbbf
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/phpunit.xml.dist
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit colors="true" bootstrap="vendor/autoload.php">
+    <testsuite name="eXorus PhpMimeMailParser Test Suite">
+        <directory suffix="Test.php">tests</directory>
+    </testsuite>
+</phpunit>
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php
new file mode 100644
index 00000000..52012622
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Attachment.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace PhpMimeMailParser;
+
+/**
+ * Attachment of php-mime-mail-parser
+ *
+ * Fully Tested Mailparse Extension Wrapper for PHP 5.4+
+ *
+ */
+class Attachment
+{
+    /**
+     * @var string $filename Filename
+     */
+    protected $filename;
+
+    /**
+     * @var string $contentType Mime Type
+     */
+    protected $contentType;
+
+    /**
+     * @var string $content File Content
+     */
+    protected $content;
+
+    /**
+     * @var string $contentDisposition Content-Disposition (attachment or inline)
+     */
+    protected $contentDisposition;
+
+    /**
+     * @var string $contentId Content-ID
+     */
+    protected $contentId;
+
+    /**
+     * @var array $headers An Array of the attachment headers
+     */
+    protected $headers;
+
+    /**
+     * @var resource $stream
+     */
+    protected $stream;
+
+    /**
+     * @var string $mimePartStr
+     */
+    protected $mimePartStr;
+
+    /**
+     * Attachment constructor.
+     *
+     * @param string   $filename
+     * @param string   $contentType
+     * @param resource $stream
+     * @param string   $contentDisposition
+     * @param string   $contentId
+     * @param array    $headers
+     * @param string   $mimePartStr
+     */
+    public function __construct(
+        $filename,
+        $contentType,
+        $stream,
+        $contentDisposition = 'attachment',
+        $contentId = '',
+        $headers = [],
+        $mimePartStr = ''
+    ) {
+        $this->filename = $filename;
+        $this->contentType = $contentType;
+        $this->stream = $stream;
+        $this->content = null;
+        $this->contentDisposition = $contentDisposition;
+        $this->contentId = $contentId;
+        $this->headers = $headers;
+        $this->mimePartStr = $mimePartStr;
+    }
+
+    /**
+     * retrieve the attachment filename
+     *
+     * @return string
+     */
+    public function getFilename()
+    {
+        return $this->filename;
+    }
+
+    /**
+     * Retrieve the Attachment Content-Type
+     *
+     * @return string
+     */
+    public function getContentType()
+    {
+        return $this->contentType;
+    }
+
+    /**
+     * Retrieve the Attachment Content-Disposition
+     *
+     * @return string
+     */
+    public function getContentDisposition()
+    {
+        return $this->contentDisposition;
+    }
+
+    /**
+     * Retrieve the Attachment Content-ID
+     *
+     * @return string
+     */
+    public function getContentID()
+    {
+        return $this->contentId;
+    }
+
+    /**
+     * Retrieve the Attachment Headers
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Get a handle to the stream
+     *
+     * @return stream
+     */
+    public function getStream()
+    {
+        return $this->stream;
+    }
+
+    /**
+     * Read the contents a few bytes at a time until completed
+     * Once read to completion, it always returns false
+     *
+     * @param int $bytes (default: 2082)
+     *
+     * @return string|bool
+     */
+    public function read($bytes = 2082)
+    {
+        return feof($this->stream) ? false : fread($this->stream, $bytes);
+    }
+
+    /**
+     * Retrieve the file content in one go
+     * Once you retrieve the content you cannot use MimeMailParser_attachment::read()
+     *
+     * @return string
+     */
+    public function getContent()
+    {
+        if ($this->content === null) {
+            fseek($this->stream, 0);
+            while (($buf = $this->read()) !== false) {
+                $this->content .= $buf;
+            }
+        }
+
+        return $this->content;
+    }
+
+    /**
+     * Get mime part string for this attachment
+     *
+     * @return string
+     */
+    public function getMimePartStr()
+    {
+        return $this->mimePartStr;
+    }
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php
new file mode 100644
index 00000000..04315c1e
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Charset.php
@@ -0,0 +1,338 @@
+<?php namespace PhpMimeMailParser;
+
+use PhpMimeMailParser\Contracts\CharsetManager;
+
+class Charset implements CharsetManager
+{
+    /**
+     * Charset Aliases
+     */
+    private $charsetAlias = [
+        'ascii'                    => 'us-ascii',
+        'us-ascii'                 => 'us-ascii',
+        'ansi_x3.4-1968'           => 'us-ascii',
+        '646'                      => 'us-ascii',
+        'iso-8859-1'               => 'ISO-8859-1',
+        'iso-8859-2'               => 'ISO-8859-2',
+        'iso-8859-3'               => 'ISO-8859-3',
+        'iso-8859-4'               => 'ISO-8859-4',
+        'iso-8859-5'               => 'ISO-8859-5',
+        'iso-8859-6'               => 'ISO-8859-6',
+        'iso-8859-6-i'             => 'ISO-8859-6-I',
+        'iso-8859-6-e'             => 'ISO-8859-6-E',
+        'iso-8859-7'               => 'ISO-8859-7',
+        'iso-8859-8'               => 'ISO-8859-8',
+        'iso-8859-8-i'             => 'ISO-8859-8',
+        'iso-8859-8-e'             => 'ISO-8859-8-E',
+        'iso-8859-9'               => 'ISO-8859-9',
+        'iso-8859-10'              => 'ISO-8859-10',
+        'iso-8859-11'              => 'ISO-8859-11',
+        'iso-8859-13'              => 'ISO-8859-13',
+        'iso-8859-14'              => 'ISO-8859-14',
+        'iso-8859-15'              => 'ISO-8859-15',
+        'iso-8859-16'              => 'ISO-8859-16',
+        'iso-ir-111'               => 'ISO-IR-111',
+        'iso-2022-cn'              => 'ISO-2022-CN',
+        'iso-2022-cn-ext'          => 'ISO-2022-CN',
+        'iso-2022-kr'              => 'ISO-2022-KR',
+        'iso-2022-jp'              => 'ISO-2022-JP',
+        'utf-16be'                 => 'UTF-16BE',
+        'utf-16le'                 => 'UTF-16LE',
+        'utf-16'                   => 'UTF-16',
+        'windows-1250'             => 'windows-1250',
+        'windows-1251'             => 'windows-1251',
+        'windows-1252'             => 'windows-1252',
+        'windows-1253'             => 'windows-1253',
+        'windows-1254'             => 'windows-1254',
+        'windows-1255'             => 'windows-1255',
+        'windows-1256'             => 'windows-1256',
+        'windows-1257'             => 'windows-1257',
+        'windows-1258'             => 'windows-1258',
+        'ibm866'                   => 'IBM866',
+        'ibm850'                   => 'IBM850',
+        'ibm852'                   => 'IBM852',
+        'ibm855'                   => 'IBM855',
+        'ibm857'                   => 'IBM857',
+        'ibm862'                   => 'IBM862',
+        'ibm864'                   => 'IBM864',
+        'utf-8'                    => 'UTF-8',
+        'utf-7'                    => 'UTF-7',
+        'shift_jis'                => 'Shift_JIS',
+        'big5'                     => 'Big5',
+        'euc-jp'                   => 'EUC-JP',
+        'euc-kr'                   => 'EUC-KR',
+        'gb2312'                   => 'GB2312',
+        'gb18030'                  => 'gb18030',
+        'viscii'                   => 'VISCII',
+        'koi8-r'                   => 'KOI8-R',
+        'koi8_r'                   => 'KOI8-R',
+        'cskoi8r'                  => 'KOI8-R',
+        'koi'                      => 'KOI8-R',
+        'koi8'                     => 'KOI8-R',
+        'koi8-u'                   => 'KOI8-U',
+        'tis-620'                  => 'TIS-620',
+        't.61-8bit'                => 'T.61-8bit',
+        'hz-gb-2312'               => 'HZ-GB-2312',
+        'big5-hkscs'               => 'Big5-HKSCS',
+        'gbk'                      => 'gbk',
+        'cns11643'                 => 'x-euc-tw',
+        'x-imap4-modified-utf7'    => 'x-imap4-modified-utf7',
+        'x-euc-tw'                 => 'x-euc-tw',
+        'x-mac-ce'                 => 'x-mac-ce',
+        'x-mac-turkish'            => 'x-mac-turkish',
+        'x-mac-greek'              => 'x-mac-greek',
+        'x-mac-icelandic'          => 'x-mac-icelandic',
+        'x-mac-croatian'           => 'x-mac-croatian',
+        'x-mac-romanian'           => 'x-mac-romanian',
+        'x-mac-cyrillic'           => 'x-mac-cyrillic',
+        'x-mac-ukrainian'          => 'x-mac-cyrillic',
+        'x-mac-hebrew'             => 'x-mac-hebrew',
+        'x-mac-arabic'             => 'x-mac-arabic',
+        'x-mac-farsi'              => 'x-mac-farsi',
+        'x-mac-devanagari'         => 'x-mac-devanagari',
+        'x-mac-gujarati'           => 'x-mac-gujarati',
+        'x-mac-gurmukhi'           => 'x-mac-gurmukhi',
+        'armscii-8'                => 'armscii-8',
+        'x-viet-tcvn5712'          => 'x-viet-tcvn5712',
+        'x-viet-vps'               => 'x-viet-vps',
+        'iso-10646-ucs-2'          => 'UTF-16BE',
+        'x-iso-10646-ucs-2-be'     => 'UTF-16BE',
+        'x-iso-10646-ucs-2-le'     => 'UTF-16LE',
+        'x-user-defined'           => 'x-user-defined',
+        'x-johab'                  => 'x-johab',
+        'latin1'                   => 'ISO-8859-1',
+        'iso_8859-1'               => 'ISO-8859-1',
+        'iso8859-1'                => 'ISO-8859-1',
+        'iso8859-2'                => 'ISO-8859-2',
+        'iso8859-3'                => 'ISO-8859-3',
+        'iso8859-4'                => 'ISO-8859-4',
+        'iso8859-5'                => 'ISO-8859-5',
+        'iso8859-6'                => 'ISO-8859-6',
+        'iso8859-7'                => 'ISO-8859-7',
+        'iso8859-8'                => 'ISO-8859-8',
+        'iso8859-9'                => 'ISO-8859-9',
+        'iso8859-10'               => 'ISO-8859-10',
+        'iso8859-11'               => 'ISO-8859-11',
+        'iso8859-13'               => 'ISO-8859-13',
+        'iso8859-14'               => 'ISO-8859-14',
+        'iso8859-15'               => 'ISO-8859-15',
+        'iso_8859-1:1987'          => 'ISO-8859-1',
+        'iso-ir-100'               => 'ISO-8859-1',
+        'l1'                       => 'ISO-8859-1',
+        'ibm819'                   => 'ISO-8859-1',
+        'cp819'                    => 'ISO-8859-1',
+        'csisolatin1'              => 'ISO-8859-1',
+        'latin2'                   => 'ISO-8859-2',
+        'iso_8859-2'               => 'ISO-8859-2',
+        'iso_8859-2:1987'          => 'ISO-8859-2',
+        'iso-ir-101'               => 'ISO-8859-2',
+        'l2'                       => 'ISO-8859-2',
+        'csisolatin2'              => 'ISO-8859-2',
+        'latin3'                   => 'ISO-8859-3',
+        'iso_8859-3'               => 'ISO-8859-3',
+        'iso_8859-3:1988'          => 'ISO-8859-3',
+        'iso-ir-109'               => 'ISO-8859-3',
+        'l3'                       => 'ISO-8859-3',
+        'csisolatin3'              => 'ISO-8859-3',
+        'latin4'                   => 'ISO-8859-4',
+        'iso_8859-4'               => 'ISO-8859-4',
+        'iso_8859-4:1988'          => 'ISO-8859-4',
+        'iso-ir-110'               => 'ISO-8859-4',
+        'l4'                       => 'ISO-8859-4',
+        'csisolatin4'              => 'ISO-8859-4',
+        'cyrillic'                 => 'ISO-8859-5',
+        'iso_8859-5'               => 'ISO-8859-5',
+        'iso_8859-5:1988'          => 'ISO-8859-5',
+        'iso-ir-144'               => 'ISO-8859-5',
+        'csisolatincyrillic'       => 'ISO-8859-5',
+        'arabic'                   => 'ISO-8859-6',
+        'iso_8859-6'               => 'ISO-8859-6',
+        'iso_8859-6:1987'          => 'ISO-8859-6',
+        'iso-ir-127'               => 'ISO-8859-6',
+        'ecma-114'                 => 'ISO-8859-6',
+        'asmo-708'                 => 'ISO-8859-6',
+        'csisolatinarabic'         => 'ISO-8859-6',
+        'csiso88596i'              => 'ISO-8859-6-I',
+        'csiso88596e'              => 'ISO-8859-6-E',
+        'greek'                    => 'ISO-8859-7',
+        'greek8'                   => 'ISO-8859-7',
+        'sun_eu_greek'             => 'ISO-8859-7',
+        'iso_8859-7'               => 'ISO-8859-7',
+        'iso_8859-7:1987'          => 'ISO-8859-7',
+        'iso-ir-126'               => 'ISO-8859-7',
+        'elot_928'                 => 'ISO-8859-7',
+        'ecma-118'                 => 'ISO-8859-7',
+        'csisolatingreek'          => 'ISO-8859-7',
+        'hebrew'                   => 'ISO-8859-8',
+        'iso_8859-8'               => 'ISO-8859-8',
+        'visual'                   => 'ISO-8859-8',
+        'iso_8859-8:1988'          => 'ISO-8859-8',
+        'iso-ir-138'               => 'ISO-8859-8',
+        'csisolatinhebrew'         => 'ISO-8859-8',
+        'csiso88598i'              => 'ISO-8859-8',
+        'iso-8859-8i'              => 'ISO-8859-8',
+        'logical'                  => 'ISO-8859-8',
+        'csiso88598e'              => 'ISO-8859-8-E',
+        'latin5'                   => 'ISO-8859-9',
+        'iso_8859-9'               => 'ISO-8859-9',
+        'iso_8859-9:1989'          => 'ISO-8859-9',
+        'iso-ir-148'               => 'ISO-8859-9',
+        'l5'                       => 'ISO-8859-9',
+        'csisolatin5'              => 'ISO-8859-9',
+        'unicode-1-1-utf-8'        => 'UTF-8',
+        'utf8'                     => 'UTF-8',
+        'x-sjis'                   => 'Shift_JIS',
+        'shift-jis'                => 'Shift_JIS',
+        'ms_kanji'                 => 'Shift_JIS',
+        'csshiftjis'               => 'Shift_JIS',
+        'windows-31j'              => 'Shift_JIS',
+        'cp932'                    => 'Shift_JIS',
+        'sjis'                     => 'Shift_JIS',
+        'cseucpkdfmtjapanese'      => 'EUC-JP',
+        'x-euc-jp'                 => 'EUC-JP',
+        'csiso2022jp'              => 'ISO-2022-JP',
+        'iso-2022-jp-2'            => 'ISO-2022-JP',
+        'csiso2022jp2'             => 'ISO-2022-JP',
+        'csbig5'                   => 'Big5',
+        'cn-big5'                  => 'Big5',
+        'x-x-big5'                 => 'Big5',
+        'zh_tw-big5'               => 'Big5',
+        'cseuckr'                  => 'EUC-KR',
+        'ks_c_5601-1987'           => 'EUC-KR',
+        'iso-ir-149'               => 'EUC-KR',
+        'ks_c_5601-1989'           => 'EUC-KR',
+        'ksc_5601'                 => 'EUC-KR',
+        'ksc5601'                  => 'EUC-KR',
+        'korean'                   => 'EUC-KR',
+        'csksc56011987'            => 'EUC-KR',
+        '5601'                     => 'EUC-KR',
+        'windows-949'              => 'EUC-KR',
+        'gb_2312-80'               => 'GB2312',
+        'iso-ir-58'                => 'GB2312',
+        'chinese'                  => 'GB2312',
+        'csiso58gb231280'          => 'GB2312',
+        'csgb2312'                 => 'GB2312',
+        'zh_cn.euc'                => 'GB2312',
+        'gb_2312'                  => 'GB2312',
+        'x-cp1250'                 => 'windows-1250',
+        'x-cp1251'                 => 'windows-1251',
+        'x-cp1252'                 => 'windows-1252',
+        'x-cp1253'                 => 'windows-1253',
+        'x-cp1254'                 => 'windows-1254',
+        'x-cp1255'                 => 'windows-1255',
+        'x-cp1256'                 => 'windows-1256',
+        'x-cp1257'                 => 'windows-1257',
+        'x-cp1258'                 => 'windows-1258',
+        'windows-874'              => 'windows-874',
+        'ibm874'                   => 'windows-874',
+        'dos-874'                  => 'windows-874',
+        'macintosh'                => 'macintosh',
+        'x-mac-roman'              => 'macintosh',
+        'mac'                      => 'macintosh',
+        'csmacintosh'              => 'macintosh',
+        'cp866'                    => 'IBM866',
+        'cp-866'                   => 'IBM866',
+        '866'                      => 'IBM866',
+        'csibm866'                 => 'IBM866',
+        'cp850'                    => 'IBM850',
+        '850'                      => 'IBM850',
+        'csibm850'                 => 'IBM850',
+        'cp852'                    => 'IBM852',
+        '852'                      => 'IBM852',
+        'csibm852'                 => 'IBM852',
+        'cp855'                    => 'IBM855',
+        '855'                      => 'IBM855',
+        'csibm855'                 => 'IBM855',
+        'cp857'                    => 'IBM857',
+        '857'                      => 'IBM857',
+        'csibm857'                 => 'IBM857',
+        'cp862'                    => 'IBM862',
+        '862'                      => 'IBM862',
+        'csibm862'                 => 'IBM862',
+        'cp864'                    => 'IBM864',
+        '864'                      => 'IBM864',
+        'csibm864'                 => 'IBM864',
+        'ibm-864'                  => 'IBM864',
+        't.61'                     => 'T.61-8bit',
+        'iso-ir-103'               => 'T.61-8bit',
+        'csiso103t618bit'          => 'T.61-8bit',
+        'x-unicode-2-0-utf-7'      => 'UTF-7',
+        'unicode-2-0-utf-7'        => 'UTF-7',
+        'unicode-1-1-utf-7'        => 'UTF-7',
+        'csunicode11utf7'          => 'UTF-7',
+        'csunicode'                => 'UTF-16BE',
+        'csunicode11'              => 'UTF-16BE',
+        'iso-10646-ucs-basic'      => 'UTF-16BE',
+        'csunicodeascii'           => 'UTF-16BE',
+        'iso-10646-unicode-latin1' => 'UTF-16BE',
+        'csunicodelatin1'          => 'UTF-16BE',
+        'iso-10646'                => 'UTF-16BE',
+        'iso-10646-j-1'            => 'UTF-16BE',
+        'latin6'                   => 'ISO-8859-10',
+        'iso-ir-157'               => 'ISO-8859-10',
+        'l6'                       => 'ISO-8859-10',
+        'csisolatin6'              => 'ISO-8859-10',
+        'iso_8859-15'              => 'ISO-8859-15',
+        'csisolatin9'              => 'ISO-8859-15',
+        'l9'                       => 'ISO-8859-15',
+        'ecma-cyrillic'            => 'ISO-IR-111',
+        'csiso111ecmacyrillic'     => 'ISO-IR-111',
+        'csiso2022kr'              => 'ISO-2022-KR',
+        'csviscii'                 => 'VISCII',
+        'zh_tw-euc'                => 'x-euc-tw',
+        'iso88591'                 => 'ISO-8859-1',
+        'iso88592'                 => 'ISO-8859-2',
+        'iso88593'                 => 'ISO-8859-3',
+        'iso88594'                 => 'ISO-8859-4',
+        'iso88595'                 => 'ISO-8859-5',
+        'iso88596'                 => 'ISO-8859-6',
+        'iso88597'                 => 'ISO-8859-7',
+        'iso88598'                 => 'ISO-8859-8',
+        'iso88599'                 => 'ISO-8859-9',
+        'iso885910'                => 'ISO-8859-10',
+        'iso885911'                => 'ISO-8859-11',
+        'iso885912'                => 'ISO-8859-12',
+        'iso885913'                => 'ISO-8859-13',
+        'iso885914'                => 'ISO-8859-14',
+        'iso885915'                => 'ISO-8859-15',
+        'tis620'                   => 'TIS-620',
+        'cp1250'                   => 'windows-1250',
+        'cp1251'                   => 'windows-1251',
+        'cp1252'                   => 'windows-1252',
+        'cp1253'                   => 'windows-1253',
+        'cp1254'                   => 'windows-1254',
+        'cp1255'                   => 'windows-1255',
+        'cp1256'                   => 'windows-1256',
+        'cp1257'                   => 'windows-1257',
+        'cp1258'                   => 'windows-1258',
+        'x-gbk'                    => 'gbk',
+        'windows-936'              => 'gbk',
+        'ansi-1251'                => 'windows-1251',
+    ];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function decodeCharset($encodedString, $charset)
+    {
+        if (strtolower($charset) == 'utf-8' || strtolower($charset) == 'us-ascii') {
+            return $encodedString;
+        } else {
+            return iconv($this->getCharsetAlias($charset), 'UTF-8//TRANSLIT//IGNORE', $encodedString);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCharsetAlias($charset)
+    {
+        $charset = strtolower($charset);
+
+        if (array_key_exists($charset, $this->charsetAlias)) {
+            return $this->charsetAlias[$charset];
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Contracts/CharsetManager.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Contracts/CharsetManager.php
new file mode 100644
index 00000000..660ec00c
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Contracts/CharsetManager.php
@@ -0,0 +1,24 @@
+<?php namespace PhpMimeMailParser\Contracts;
+
+interface CharsetManager
+{
+
+    /**
+     * Decode the string from Charset
+     *
+     * @param string $encodedString The string in its original encoded state
+     * @param string $charset       The Charset header of the part.
+     *
+     * @return string The decoded string
+     */
+    public function decodeCharset($encodedString, $charset);
+
+    /**
+     * Get charset alias
+     *
+     * @param string $charset .
+     *
+     * @return string The charset alias
+     */
+    public function getCharsetAlias($charset);
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Exception.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Exception.php
new file mode 100644
index 00000000..cc537e21
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Exception.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace PhpMimeMailParser;
+
+class Exception extends \RuntimeException
+{
+
+}
diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php
new file mode 100644
index 00000000..689363cc
--- /dev/null
+++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/Parser.php
@@ -0,0 +1,893 @@
+<?php
+
+namespace PhpMimeMailParser;
+
+use PhpMimeMailParser\Contracts\CharsetManager;
+
+/**
+ * Parser of php-mime-mail-parser
+ *
+ * Fully Tested Mailparse Extension Wrapper for PHP 5.4+
+ *
+ */
+class Parser
+{
+    /**
+     * Attachment filename argument option for ->saveAttachments().
+     */
+    const ATTACHMENT_DUPLICATE_THROW  = 'DuplicateThrow';
+    const ATTACHMENT_DUPLICATE_SUFFIX = 'DuplicateSuffix';
+    const ATTACHMENT_RANDOM_FILENAME  = 'RandomFilename';
+
+    /**
+     * PHP MimeParser Resource ID
+     *
+     * @var resource $resource
+     */
+    protected $resource;
+
+    /**
+     * A file pointer to email
+     *
+     * @var resource $stream
+     */
+    protected $stream;
+
+    /**
+     * A text of an email
+     *
+     * @var string $data
+     */
+    protected $data;
+
+    /**
+     * Parts of an email
+     *
+     * @var array $parts
+     */
+    protected $parts;
+
+    /**
+     * @var CharsetManager object
+     */
+    protected $charset;
+
+    /**
+     * Parser constructor.
+     *
+     * @param CharsetManager|null $charset
+     */
+    public function __construct(CharsetManager $charset = null)
+    {
+        if ($charset == null) {
+            $charset = new Charset();
+        }
+
+        $this->charset = $charset;
+    }
+
+    /**
+     * Free the held resources
+     *
+     * @return void
+     */
+    public function __destruct()
+    {
+        // clear the email file resource
+        if (is_resource($this->stream)) {
+            fclose($this->stream);
+        }
+        // clear the MailParse resource
+        if (is_resource($this->resource)) {
+            mailparse_msg_free($this->resource);
+        }
+    }
+
+    /**
+     * Set the file path we use to get the email text
+     *
+     * @param string $path File path to the MIME mail
+     *
+     * @return Parser MimeMailParser Instance
+     */
+    public function setPath($path)
+    {
+        // should parse message incrementally from file
+        $this->resource = mailparse_msg_parse_file($path);
+        $this->stream = fopen($path, 'r');
+        $this->parse();
+
+        return $this;
+    }
+
+    /**
+     * Set the Stream resource we use to get the email text
+     *
+     * @param resource $stream
+     *
+     * @return Parser MimeMailParser Instance
+     * @throws Exception
+     */
+    public function setStream($stream)
+    {
+        // streams have to be cached to file first
+        $meta = @stream_get_meta_data($stream);
+        if (!$meta || !$meta['mode'] || $meta['mode'][0] != 'r' || $meta['eof']) {
+            throw new Exception(
+                'setStream() expects parameter stream to be readable stream resource.'
+            );
+        }
+
+        /** @var resource $tmp_fp */
+        $tmp_fp = tmpfile();
+        if ($tmp_fp) {
+            while (!feof($stream)) {
+                fwrite($tmp_fp, fread($stream, 2028));
+            }
+            fseek($tmp_fp, 0);
+            $this->stream = &$tmp_fp;
+        } else {
+            throw new Exception(
+                'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
+            );
+        }
+        fclose($stream);
+
+        $this->resource = mailparse_msg_create();
+        // parses the message incrementally (low memory usage but slower)
+        while (!feof($this->stream)) {
+            mailparse_msg_parse($this->resource, fread($this->stream, 2082));
+        }
+        $this->parse();
+
+        return $this;
+    }
+
+    /**
+     * Set the email text
+     *
+     * @param string $data
+     *
+     * @return Parser MimeMailParser Instance
+     */
+    public function setText($data)
+    {
+        if (!$data) {
+            throw new Exception('You must not call MimeMailParser::setText with an empty string parameter');
+        }
+        $this->resource = mailparse_msg_create();
+        // does not parse incrementally, fast memory hog might explode
+        mailparse_msg_parse($this->resource, $data);
+        $this->data = $data;
+        $this->parse();
+
+        return $this;
+    }
+
+    /**
+     * Parse the Message into parts
+     *
+     * @return void
+     */
+    protected function parse()
+    {
+        $structure = mailparse_msg_get_structure($this->resource);
+        $this->parts = [];
+        foreach ($structure as $part_id) {
+            $part = mailparse_msg_get_part($this->resource, $part_id);
+            $this->parts[$part_id] = mailparse_msg_get_part_data($part);
+        }
+    }
+
+    /**
+     * Retrieve a specific Email Header, without charset conversion.
+     *
+     * @param string $name Header name (case-insensitive)
+     *
+     * @return string
+     * @throws Exception
+     */
+    public function getRawHeader($name)
+    {
+        $name = strtolower($name);
+        if (isset($this->parts[1])) {
+            $headers = $this->getPart('headers', $this->parts[1]);
+
+            return (isset($headers[$name])) ? $headers[$name] : false;
+        } else {
+            throw new Exception(
+                'setPath() or setText() or setStream() must be called before retrieving email headers.'
+            );
+        }
+    }
+
+    /**
+     * Retrieve a specific Email Header
+     *
+     * @param string $name Header name (case-insensitive)
+     *
+     * @return string
+     */
+    public function getHeader($name)
+    {
+        $rawHeader = $this->getRawHeader($name);
+        if ($rawHeader === false) {
+            return false;
+        }
+
+        return $this->decodeHeader($rawHeader);
+    }
+
+    /**
+     * Retrieve all mail headers
+     *
+     * @return array
+     * @throws Exception
+     */
+    public function getHeaders()
+    {
+        if (isset($this->parts[1])) {
+            $headers = $this->getPart('headers', $this->parts[1]);
+            foreach ($headers as $name => &$value) {
+                if (is_array($value)) {
+                    foreach ($value as &$v) {
+                        $v = $this->decodeSingleHeader($v);
+                    }
+                } else {
+                    $value = $this->decodeSingleHeader($value);
+                }
+            }
+
+            return $headers;
+        } else {
+            throw new Exception(
+                'setPath() or setText() or setStream() must be called before retrieving email headers.'
+            );
+        }
+    }
+
+    /**
+     * Retrieve the raw mail headers as a string
+     *
+     * @return string
+     * @throws Exception
+     */
+    public function getHeadersRaw()
+    {
+        if (isset($this->parts[1])) {
+            return $this->getPartHeader($this->parts[1]);
+        } else {
+            throw new Exception(
+                'setPath() or setText() or setStream() must be called before retrieving email headers.'
+            );
+        }
+    }
+
+    /**
+     * Retrieve the raw Header of a MIME part
+     *
+     * @return String
+     * @param $part Object
+     * @throws Exception
+     */
+    protected function getPartHeader(&$part)
+    {
+        $header = '';
+        if ($this->stream) {
+            $header = $this->getPartHeaderFromFile($part);
+        } elseif ($this->data) {
+            $header = $this->getPartHeaderFromText($part);
+        }
+        return $header;
+    }
+
+    /**
+     * Retrieve the Header from a MIME part from file
+     *
+     * @return String Mime Header Part
+     * @param $part Array
+     */
+    protected function getPartHeaderFromFile(&$part)
+    {
+        $start = $part['starting-pos'];
+        $end = $part['starting-pos-body'];
+        fseek($this->stream, $start, SEEK_SET);
+        $header = fread($this->stream, $end-$start);
+        return $header;
+    }
+
+    /**
+     * Retrieve the Header from a MIME part from text
+     *
+     * @return String Mime Header Part
+     * @param $part Array
+     */
+    protected function getPartHeaderFromText(&$part)
+    {
+        $start = $part['starting-pos'];
+        $end = $part['starting-pos-body'];
+        $header = substr($this->data, $start, $end-$start);
+        return $header;
+    }
+
+    /**
+     * Checks whether a given part ID is a child of another part
+     * eg. an RFC822 attachment may have one or more text parts
+     *
+     * @param string $partId
+     * @param string $parentPartId
+     * @return bool
+     */
+    protected function partIdIsChildOfPart($partId, $parentPartId)
+    {
+        return substr($partId, 0, strlen($parentPartId)) == $parentPartId;
+    }
+
+    /**
+     * Whether the given part ID is a child of any attachment part in the message.
+     *
+     * @param string $checkPartId
+     * @return bool
+     */
+    protected function partIdIsChildOfAnAttachment($checkPartId)
+    {
+        foreach ($this->parts as $partId => $part) {
+            if ($this->getPart('content-disposition', $part) == 'attachment') {
+                if ($this->partIdIsChildOfPart($checkPartId, $partId)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the email message body in the specified format
+     *
+     * @param string $type text, html or htmlEmbedded
+     *
+     * @return false|string Body or False if not found
+     * @throws Exception
+     */
+    public function getMessageBody($type = 'text')
+    {
+        $body = false;
+        $mime_types = [
+        'text'         => 'text/plain',
+        'html'         => 'text/html',
+        'htmlEmbedded' => 'text/html',
+        ];
+
+        if (in_array($type, array_keys($mime_types))) {
+            $part_type  = $type === 'htmlEmbedded' ? 'html' : $type;
+            $inline_parts = $this->getInlineParts($part_type);
+            $body = empty($inline_parts) ? '' : $inline_parts[0];
+        } else {
+            throw new Exception(
+                'Invalid type specified for getMessageBody(). Expected: text, html or htmlEmbeded.'
+            );
+        }
+
+        if ($type == 'htmlEmbedded') {
+            $attachments = $this->getAttachments();
+            foreach ($attachments as $attachment) {
+                if ($attachment->getContentID() != '') {
+                    $body = str_replace(
+                        '"cid:'.$attachment->getContentID().'"',
+                        '"'.$this->getEmbeddedData($attachment->getContentID()).'"',
+                        $body
+                    );
+                }
+            }
+        }
+
+        return $body;
+    }
+
+    /**
+     * Returns the embedded data structure
+     *
+     * @param string $contentId Content-Id
+     *
+     * @return string
+     */
+    protected function getEmbeddedData($contentId)
+    {
+        foreach ($this->parts as $part) {
+            if ($this->getPart('content-id', $part) == $contentId) {
+                $embeddedData = 'data:';
+                $embeddedData .= $this->getPart('content-type', $part);
+                $embeddedData .= ';'.$this->getPart('transfer-encoding', $part);
+                $embeddedData .= ','.$this->getPartBody($part);
+                return $embeddedData;
+            }
+        }
+        return '';
+    }
+
+    /**
+     * Return an array with the following keys display, address, is_group
+     *
+     * @param string $name Header name (case-insensitive)
+     *
+     * @return array
+     */
+    public function getAddresses($name)
+    {
+        $value = $this->getHeader($name);
+
+        return mailparse_rfc822_parse_addresses($value);
+    }
+
+    /**
+     * Returns the attachments contents in order of appearance
+     *
+     * @return Attachment[]
+     */
+    public function getInlineParts($type = 'text')
+    {
+        $inline_parts = [];
+        $dispositions = ['inline'];
+        $mime_types = [
+            'text'         => 'text/plain',
+            'html'         => 'text/html',
+        ];
+
+        if (!in_array($type, array_keys($mime_types))) {
+            throw new Exception('Invalid type specified for getInlineParts(). "type" can either be text or html.');
+        }
+
+        foreach ($this->parts as $partId => $part) {
+            if ($this->getPart('content-type', $part) == $mime_types[$type]
+                && $this->getPart('content-disposition', $part) != 'attachment'
+                && !$this->partIdIsChildOfAnAttachment($partId)
+                ) {
+                $headers = $this->getPart('headers', $part);
+                $encodingType = array_key_exists('content-transfer-encoding', $headers) ?
+                    $headers['content-transfer-encoding'] : '';
+                if (is_array($encodingType)) {
+                    $encodingType = $encodingType[0];
+                }
+                $undecoded_body = $this->decodeContentTransfer($this->getPartBody($part), $encodingType);
+                $inline_parts[] = $this->charset->decodeCharset($undecoded_body, $this->getPartCharset($part));
+            }
+        }
+
+        return $inline_parts;
+    }
+
+    /**
+     * Returns the attachments contents in order of appearance
+     *
+     * @return Attachment[]
+     */
+    public function getAttachments($include_inline = true)
+    {
+        $attachments = [];
+        $dispositions = $include_inline ?
+            ['attachment', 'inline'] :
+            ['attachment'];
+        $non_attachment_types = ['text/plain', 'text/html'];
+        $nonameIter = 0;
+
+        foreach ($this->parts as $part) {
+            $disposition = $this->getPart('content-disposition', $part);
+            $filename = 'noname';
+
+            if (isset($part['disposition-filename'])) {
+                $filename = $this->decodeHeader($part['disposition-filename']);
+                // Escape all potentially unsafe characters from the filename
+                $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename);
+            } elseif (isset($part['content-name'])) {
+                // if we have no disposition but we have a content-name, it's a valid attachment.
+                // we simulate the presence of an attachment disposition with a disposition filename
+                $filename = $this->decodeHeader($part['content-name']);
+                // Escape all potentially unsafe characters from the filename
+                $filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename);
+                $disposition = 'attachment';
+            } elseif (in_array($part['content-type'], $non_attachment_types, true)
+                && $disposition !== 'attachment') {
+                // it is a message body, no attachment
+                continue;
+            } elseif (substr($part['content-type'], 0, 10) !== 'multipart/') {
+                // if we cannot get it by getMessageBody(), we assume it is an attachment
+                $disposition = 'attachment';
+            }
+
+            if (in_array($disposition, $dispositions) === true) {
+                if ($filename == 'noname') {
+                    $nonameIter++;
+                    $filename = 'noname'.$nonameIter;
+                }
+
+                $headersAttachments = $this->getPart('headers', $part);
+                $contentidAttachments = $this->getPart('content-id', $part);
+
+                $mimePartStr = $this->getPartComplete($part);
+
+                $attachments[] = new Attachment(
+                    $filename,
+                    $this->getPart('content-type', $part),
+                    $this->getAttachmentStream($part),
+                    $disposition,
+                    $contentidAttachments,
+                    $headersAttachments,
+                    $mimePartStr
+                );
+            }
+        }
+
+        return $attachments;
+    }
+
+    /**
+     * Save attachments in a folder
+     *
+     * @param string $attach_dir directory
+     * @param bool $include_inline
+     * @param string $filenameStrategy How to generate attachment filenames
+     *
+     * @return array Saved attachments paths
+     * @throws Exception
+     */
+    public function saveAttachments(
+        $attach_dir,
+        $include_inline = true,
+        $filenameStrategy = self::ATTACHMENT_DUPLICATE_SUFFIX
+    ) {
+        $attachments = $this->getAttachments($include_inline);
+        if (empty($attachments)) {
+            return false;
+        }
+
+        if (!is_dir($attach_dir)) {
+            mkdir($attach_dir);
+        }
+
+        $attachments_paths = [];
+        foreach ($attachments as $attachment) {
+            // Determine filename
+            switch ($filenameStrategy) {
+                case self::ATTACHMENT_RANDOM_FILENAME:
+                    $attachment_path = tempnam($attach_dir, '');
+                    break;
+                case self::ATTACHMENT_DUPLICATE_THROW:
+                case self::ATTACHMENT_DUPLICATE_SUFFIX:
+                    $attachment_path = $attach_dir . $attachment->getFilename();
+                    break;
+                default:
+                    throw new Exception('Invalid filename strategy argument provided.');
+            }
+
+            // Handle duplicate filename
+            if (file_exists($attachment_path)) {
+                switch ($filenameStrategy) {
+                    case self::ATTACHMENT_DUPLICATE_THROW:
+                        throw new Exception('Could not create file for attachment: duplicate filename.');
+                    case self::ATTACHMENT_DUPLICATE_SUFFIX:
+                        $attachment_path = tempnam($attach_dir, $attachment->getFilename());
+                        break;
+                }
+            }
+
+            /** @var resource $fp */
+            if ($fp = fopen($attachment_path, 'w')) {
+                while ($bytes = $attachment->read()) {
+                    fwrite($fp, $bytes);
+                }
+                fclose($fp);
+                $attachments_paths[] = realpath($attachment_path);
+            } else {
+                throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.');
+            }
+        }
+
+        return $attachments_paths;
+    }
+
+    /**
+     * Read the attachment Body and save temporary file resource
+     *
+     * @param array $part
+     *
+     * @return resource Mime Body Part
+     * @throws Exception
+     */
+    protected function getAttachmentStream(&$part)
+    {
+        /** @var resource $temp_fp */
+        $temp_fp = tmpfile();
+
+        $headers = $this->getPart('headers', $part);
+        $encodingType = array_key_exists('content-transfer-encoding', $headers) ?
+        $headers['content-transfer-encoding'] : '';
+
+        if ($temp_fp) {
+            if ($this->stream) {
+                $start = $part['starting-pos-body'];
+                $end = $part['ending-pos-body'];
+                fseek($this->stream, $start, SEEK_SET);
+                $len = $end - $start;
+                $written = 0;
+                while ($written < $len) {
+                    $write = $len;
+                    $part = fread($this->stream, $write);
+                    fwrite($temp_fp, $this->decodeContentTransfer($part, $encodingType));
+                    $written += $write;
+                }
+            } elseif ($this->data) {
+                $attachment = $this->decodeContentTransfer($this->getPartBodyFromText($part), $encodingType);
+                fwrite($temp_fp, $attachment, strlen($attachment));
+            }
+            fseek($temp_fp, 0, SEEK_SET);
+        } else {
+            throw new Exception(
+                'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
+            );
+        }
+
+        return $temp_fp;
+    }
+
+    /**
+     * Decode the string from Content-Transfer-Encoding
+     *
+     * @param string $encodedString The string in its original encoded state
+     * @param string $encodingType  The encoding type from the Content-Transfer-Encoding header of the part.
+     *
+     * @return string The decoded string
+     */
+    protected function decodeContentTransfer($encodedString, $encodingType)
+    {
+        $encodingType = strtolower($encodingType);
+        if ($encodingType == 'base64') {
+            return base64_decode($encodedString);
+        } elseif ($encodingType == 'quoted-printable') {
+            return quoted_printable_decode($encodedString);
+        } else {
+            return $encodedString; //8bit, 7bit, binary
+        }
+    }
+
+    /**
+     * $input can be a string or array
+     *
+     * @param string|array $input
+     *
+     * @return string
+     */
+    protected function decodeHeader($input)
+    {
+        //Sometimes we have 2 label From so we take only the first
+        if (is_array($input)) {
+            return $this->decodeSingleHeader($input[0]);
+        }
+
+        return $this->decodeSingleHeader($input);
+    }
+
+    /**
+     * Decodes a single header (= string)
+     *
+     * @param string $input
+     *
+     * @return string
+     */
+    protected function decodeSingleHeader($input)
+    {
+        // For each encoded-word...
+        while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)((\s+)=\?)?/i', $input, $matches)) {
+            $encoded = $matches[1];
+            $charset = $matches[2];
+            $encoding = $matches[3];
+            $text = $matches[4];
+            $space = isset($matches[6]) ? $matches[6] : '';
+
+            switch (strtolower($encoding)) {
+                case 'b':
+                    $text = $this->decodeContentTransfer($text, 'base64');
+                    break;
+
+                case 'q':
+                    $text = str_replace('_', ' ', $text);
+                    preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
+                    foreach ($matches[1] as $value) {
+                        $text = str_replace('='.$value, chr(hexdec($value)), $text);
+                    }
+                    break;
+            }
+
+            $text = $this->charset->decodeCharset($text, $this->charset->getCharsetAlias($charset));
+            $input = str_replace($encoded . $space, $text, $input);
+        }
+
+        return $input;
+    }
+
+    /**
+     * Return the charset of the MIME part
+     *
+     * @param array $part
+     *
+     * @return string|false
+     */
+    protected function getPartCharset($part)
+    {
+        if (isset($part['charset'])) {
+            return $this->charset->getCharsetAlias($part['charset']);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Retrieve a specified MIME part
+     *
+     * @param string $type
+     * @param array  $parts
+     *
+     * @return string|array
+     */
+    protected function getPart($type, $parts)
+    {
+        return (isset($parts[$type])) ? $parts[$type] : false;
+    }
+
+    /**
+     * Retrieve the Body of a MIME part
+     *
+     * @param array $part
+     *
+     * @return string
+     */
+    protected function getPartBody(&$part)
+    {
+        $body = '';
+        if ($this->stream) {
+            $body = $this->getPartBodyFromFile($part);
+        } elseif ($this->data) {
+            $body = $this->getPartBodyFromText($part);
+        }
+
+        return $body;
+    }
+
+    /**
+     * Retrieve the Body from a MIME part from file
+     *
+     * @param array $part
+     *
+     * @return string Mime Body Part
+     */
+    protected function getPartBodyFromFile(&$part)
+    {
+        $start = $part['starting-pos-body'];
+        $end = $part['ending-pos-body'];
+        $body = '';
+        if ($end - $start > 0) {
+            fseek($this->stream, $start, SEEK_SET);
+            $body = fread($this->stream, $end - $start);
+        }
+
+        return $body;
+    }
+
+    /**
+     * Retrieve the Body from a MIME part from text
+     *
+     * @param array $part
+     *
+     * @return string Mime Body Part
+     */
+    protected function getPartBodyFromText(&$part)
+    {
+        $start = $part['starting-pos-body'];
+        $end = $part['ending-pos-body'];
+
+        return substr($this->data, $start, $end - $start);
+    }
+
+    /**
+     * Retrieve the content of a MIME part
+     *
+     * @param array $part
+     *
+     * @return string
+     */
+    protected function getPartComplete(&$part)
+    {
+        $body = '';
+        if ($this->stream) {
+            $body = $this->getPartFromFile($part);
+        } elseif ($this->data) {
+            $body = $this->getPartFromText($part);
+        }
+
+        return $body;
+    }
+
+    /**
+     * Retrieve the content from a MIME part from file
+     *
+     * @param array $part
+     *
+     * @return string Mime Content
+     */
+    protected function getPartFromFile(&$part)
+    {
+        $start = $part['starting-pos'];
+        $end = $part['ending-pos'];
+        $body = '';
+        if ($end - $start > 0) {
+            fseek($this->stream, $start, SEEK_SET);
+            $body = fread($this->stream, $end - $start);
+        }
+
+        return $body;
+    }
+
+    /**
+     * Retrieve the content from a MIME part from text
+     *
+     * @param array $part
+     *
+     * @return string Mime Content
+     */
+    protected function getPartFromText(&$part)
+    {
+        $start = $part['starting-pos'];
+        $end = $part['ending-pos'];
+
+        return substr($this->data, $start, $end - $start);
+    }
+
+    /**
+     * Retrieve the resource
+     *
+     * @return resource resource
+     */
+    public function getResource()
+    {
+        return $this->resource;
+    }
+
+    /**
+     * Retrieve the file pointer to email
+     *
+     * @return resource stream
+     */
+    public function getStream()
+    {
+        return $this->stream;
+    }
+
+    /**
+     * Retrieve the text of an email
+     *
+     * @return string data
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * Retrieve the parts of an email
+     *
+     * @return array parts
+     */
+    public function getParts()
+    {
+        return $this->parts;
+    }
+
+    /**
+     * Retrieve the charset manager object
+     *
+     * @return CharsetManager charset
+     */
+    public function getCharset()
+    {
+        return $this->charset;
+    }
+}
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
index f0fb1a22..f23b9706 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION
@@ -1 +1 @@
-5.2.26
+5.2.25
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
index 99f9092c..8042b384 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.phpmailer.php
@@ -31,7 +31,7 @@ class PHPMailer
      * The PHPMailer Version number.
      * @var string
      */
-    public $Version = '5.2.26';
+    public $Version = '5.2.25';
 
     /**
      * Email priority.
@@ -659,8 +659,6 @@ class PHPMailer
         if ($exceptions !== null) {
             $this->exceptions = (boolean)$exceptions;
         }
-        //Pick an appropriate debug output format automatically
-        $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
     }
 
     /**
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
index f833ac61..f2c4e374 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.pop3.php
@@ -34,7 +34,7 @@ class POP3
      * @var string
      * @access public
      */
-    public $Version = '5.2.26';
+    public $Version = '5.2.25';
 
     /**
      * Default POP3 port number.
diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
index be6ddce4..d8af427e 100644
--- a/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
+++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/class.smtp.php
@@ -30,7 +30,7 @@ class SMTP
      * The PHPMailer SMTP version number.
      * @var string
      */
-    const VERSION = '5.2.26';
+    const VERSION = '5.2.25';
 
     /**
      * SMTP line break constant.
@@ -81,7 +81,7 @@ class SMTP
      * @deprecated Use the `VERSION` constant instead
      * @see SMTP::VERSION
      */
-    public $Version = '5.2.26';
+    public $Version = '5.2.25';
 
     /**
      * SMTP server port number.
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore b/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
index 8a25841c..1a31666a 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.gitignore
@@ -183,7 +183,4 @@ UpgradeLog*.htm
 FakesAssemblies/
 
 # Composer
-/vendor
-
-# .vs
-.vs/
\ No newline at end of file
+/vendor
\ No newline at end of file
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml b/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
index 204dc63a..034653bb 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.travis.yml
@@ -1,18 +1,11 @@
 language: php
 
-dist: trusty
-matrix:
-  include:
-    - php: 5.3
-      dist: precise
-
 php:
+  - 5.3
   - 5.4
   - 5.5
   - 5.6
-  - 7.0
-  - 7.1
+  - 7
   - hhvm
 
-script:
-  - if [[ "$TRAVIS_PHP_VERSION" == '5.6' ]]; then phpunit --coverage-text tests ; fi
\ No newline at end of file
+script: phpunit --coverage-text tests
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config b/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
new file mode 100644
index 00000000..4b9bf477
--- /dev/null
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.vs/config/applicationhost.config
@@ -0,0 +1,1031 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    IIS configuration sections.
+
+    For schema documentation, see
+    %IIS_BIN%\config\schema\IIS_schema.xml.
+    
+    Please make a backup of this file before making any changes to it.
+
+    NOTE: The following environment variables are available to be used
+          within this file and are understood by the IIS Express.
+
+          %IIS_USER_HOME% - The IIS Express home directory for the user
+          %IIS_SITES_HOME% - The default home directory for sites
+          %IIS_BIN% - The location of the IIS Express binaries
+          %SYSTEMDRIVE% - The drive letter of %IIS_BIN%
+
+-->
+<configuration>
+
+    <!--
+
+        The <configSections> section controls the registration of sections.
+        Section is the basic unit of deployment, locking, searching and
+        containment for configuration settings.
+        
+        Every section belongs to one section group.
+        A section group is a container of logically-related sections.
+        
+        Sections cannot be nested.
+        Section groups may be nested.
+        
+        <section
+            name=""  [Required, Collection Key] [XML name of the section]
+            allowDefinition="Everywhere" [MachineOnly|MachineToApplication|AppHostOnly|Everywhere] [Level where it can be set]
+            overrideModeDefault="Allow"  [Allow|Deny] [Default delegation mode]
+            allowLocation="true"  [true|false] [Allowed in location tags]
+        />
+        
+        The recommended way to unlock sections is by using a location tag:
+        <location path="Default Web Site" overrideMode="Allow">
+            <system.webServer>
+                <asp />
+            </system.webServer>
+        </location>
+
+    -->
+    <configSections>
+        <sectionGroup name="system.applicationHost">
+            <section name="applicationPools" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="configHistory" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="customMetadata" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="listenerAdapters" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="log" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="serviceAutoStartProviders" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="sites" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="webLimits" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+        </sectionGroup>
+
+        <sectionGroup name="system.webServer">
+            <section name="asp" overrideModeDefault="Deny" />
+            <section name="caching" overrideModeDefault="Allow" />
+            <section name="cgi" overrideModeDefault="Deny" />
+            <section name="defaultDocument" overrideModeDefault="Allow" />
+            <section name="directoryBrowse" overrideModeDefault="Allow" />
+            <section name="fastCgi" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="globalModules" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="handlers" overrideModeDefault="Deny" />
+            <section name="httpCompression" overrideModeDefault="Allow" />
+            <section name="httpErrors" overrideModeDefault="Allow" />
+            <section name="httpLogging" overrideModeDefault="Deny" />
+            <section name="httpProtocol" overrideModeDefault="Allow" />
+            <section name="httpRedirect" overrideModeDefault="Allow" />
+            <section name="httpTracing" overrideModeDefault="Deny" />
+            <section name="isapiFilters" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
+            <section name="modules" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
+            <section name="applicationInitialization" allowDefinition="MachineToApplication" overrideModeDefault="Allow" />
+            <section name="odbcLogging" overrideModeDefault="Deny" />
+            <sectionGroup name="security">
+                <section name="access" overrideModeDefault="Deny" />
+                <section name="applicationDependencies" overrideModeDefault="Deny" />
+                <sectionGroup name="authentication">
+                    <section name="anonymousAuthentication" overrideModeDefault="Deny" />
+                    <section name="basicAuthentication" overrideModeDefault="Deny" />
+                    <section name="clientCertificateMappingAuthentication" overrideModeDefault="Deny" />
+                    <section name="digestAuthentication" overrideModeDefault="Deny" />
+                    <section name="iisClientCertificateMappingAuthentication" overrideModeDefault="Deny" />
+                    <section name="windowsAuthentication" overrideModeDefault="Deny" />
+                </sectionGroup>
+                <section name="authorization" overrideModeDefault="Allow" />
+                <section name="ipSecurity" overrideModeDefault="Deny" />
+                <section name="dynamicIpSecurity" overrideModeDefault="Deny" />
+                <section name="isapiCgiRestriction" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+                <section name="requestFiltering" overrideModeDefault="Allow" />
+            </sectionGroup>
+            <section name="serverRuntime" overrideModeDefault="Deny" />
+            <section name="serverSideInclude" overrideModeDefault="Deny" />
+            <section name="staticContent" overrideModeDefault="Allow" />
+            <sectionGroup name="tracing">
+                <section name="traceFailedRequests" overrideModeDefault="Allow" />
+                <section name="traceProviderDefinitions" overrideModeDefault="Deny" />
+            </sectionGroup>
+            <section name="urlCompression" overrideModeDefault="Allow" />
+            <section name="validation" overrideModeDefault="Allow" />
+            <sectionGroup name="webdav">
+                <section name="globalSettings" overrideModeDefault="Deny" />
+                <section name="authoring" overrideModeDefault="Deny" />
+                <section name="authoringRules" overrideModeDefault="Deny" />
+            </sectionGroup>
+            <sectionGroup name="rewrite">
+                <section name="allowedServerVariables" overrideModeDefault="Deny" />
+                <section name="rules" overrideModeDefault="Allow" />
+                <section name="outboundRules" overrideModeDefault="Allow" />
+                <section name="globalRules" overrideModeDefault="Deny" allowDefinition="AppHostOnly" />
+                <section name="providers" overrideModeDefault="Allow" />
+                <section name="rewriteMaps" overrideModeDefault="Allow" />
+            </sectionGroup>
+            <section name="webSocket" overrideModeDefault="Deny" />
+        <section name="aspNetCore" overrideModeDefault="Allow" /></sectionGroup>
+    </configSections>
+
+    <configProtectedData>
+        <providers>
+            <add name="IISWASOnlyRsaProvider" type="" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useMachineContainer="true" useOAEP="false" />
+            <add name="AesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisConfigurationKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAAKmFQvWHDEETRz8l2bjZlRxIkwcqTFaCUnCLljn3Q1OkesrhEO9YyLyx4bUhsj1/DyShAv7OAFFhXlrlomaornnk5PLeyO4lIXxaiT33yOFUUgxDx4GSaygkqghVV0tO5yQ/XguUBp2juMfZyztnsNa4pLcz7ZNZQ6p4yn9hxwNs=" />
+            <add name="IISWASOnlyAesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAA4WoiRJ8KHwzAG8AgejPxEOO4/2Vhkolbwo/8gZeNdUDSD36m55hWv4uC9tr/MlKdnwRLL0NhT50Gccyftqz5xTZ0dg5FtvQhTw/he1NwexTKbV+I4Zrd+sZUqHZTsr7JiEr6OHGXL70qoISW5G2m9U8wKT3caPiDPNj2aAaYPLo=" />
+        </providers>
+    </configProtectedData>
+
+    <system.applicationHost>
+
+        <applicationPools>
+            <add name="Clr4IntegratedAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr4ClassicAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr2IntegratedAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr2ClassicAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="UnmanagedClassicAppPool" managedRuntimeVersion="" managedPipelineMode="Classic" autoStart="true" />
+            <applicationPoolDefaults managedRuntimeLoader="v4.0">
+                <processModel />
+            </applicationPoolDefaults>
+        </applicationPools>
+
+        <!--
+
+          The <listenerAdapters> section defines the protocols with which the
+          Windows Process Activation Service (WAS) binds.
+
+        -->
+        <listenerAdapters>
+            <add name="http" />
+        </listenerAdapters>
+
+        <sites>
+            <site name="WebSite1" id="1" serverAutoStart="true">
+                <application path="/">
+                    <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
+                </application>
+                <bindings>
+                    <binding protocol="http" bindingInformation=":8080:localhost" />
+                </bindings>
+            </site>
+            <siteDefaults>
+                <logFile logFormat="W3C" directory="%IIS_USER_HOME%\Logs" />
+                <traceFailedRequestsLogging directory="%IIS_USER_HOME%\TraceLogFiles" enabled="true" maxLogFileSizeKB="1024" />
+            </siteDefaults>
+            <applicationDefaults applicationPool="Clr4IntegratedAppPool" />
+            <virtualDirectoryDefaults allowSubDirConfig="true" />
+        </sites>
+
+        <webLimits />
+
+    </system.applicationHost>
+
+    <system.webServer>
+
+        <serverRuntime />
+
+        <asp scriptErrorSentToBrowser="true">
+            <cache diskTemplateCacheDirectory="%TEMP%\iisexpress\ASP Compiled Templates" />
+            <limits />
+        </asp>
+
+        <caching enabled="true" enableKernelCache="true">
+        </caching>
+
+        <cgi />
+
+        <defaultDocument enabled="true">
+            <files>
+                <add value="Default.htm" />
+                <add value="Default.asp" />
+                <add value="index.htm" />
+                <add value="index.html" />
+                <add value="iisstart.htm" />
+                <add value="default.aspx" />
+            </files>
+        </defaultDocument>
+
+        <directoryBrowse enabled="false" />
+
+        <fastCgi />
+
+        <!--
+
+          The <globalModules> section defines all native-code modules.
+          To enable a module, specify it in the <modules> section.
+
+        -->
+        <globalModules>
+            <add name="HttpLoggingModule" image="%IIS_BIN%\loghttp.dll" />
+            <add name="UriCacheModule" image="%IIS_BIN%\cachuri.dll" />
+<!--            <add name="FileCacheModule" image="%IIS_BIN%\cachfile.dll" />  -->
+            <add name="TokenCacheModule" image="%IIS_BIN%\cachtokn.dll" />
+<!--            <add name="HttpCacheModule" image="%IIS_BIN%\cachhttp.dll" /> -->
+            <add name="DynamicCompressionModule" image="%IIS_BIN%\compdyn.dll" />
+            <add name="StaticCompressionModule" image="%IIS_BIN%\compstat.dll" />
+            <add name="DefaultDocumentModule" image="%IIS_BIN%\defdoc.dll" />
+            <add name="DirectoryListingModule" image="%IIS_BIN%\dirlist.dll" />
+            <add name="ProtocolSupportModule" image="%IIS_BIN%\protsup.dll" />
+            <add name="HttpRedirectionModule" image="%IIS_BIN%\redirect.dll" />
+            <add name="ServerSideIncludeModule" image="%IIS_BIN%\iis_ssi.dll" />
+            <add name="StaticFileModule" image="%IIS_BIN%\static.dll" />
+            <add name="AnonymousAuthenticationModule" image="%IIS_BIN%\authanon.dll" />
+            <add name="CertificateMappingAuthenticationModule" image="%IIS_BIN%\authcert.dll" />
+            <add name="UrlAuthorizationModule" image="%IIS_BIN%\urlauthz.dll" />
+            <add name="BasicAuthenticationModule" image="%IIS_BIN%\authbas.dll" />
+            <add name="WindowsAuthenticationModule" image="%IIS_BIN%\authsspi.dll" />
+<!--            <add name="DigestAuthenticationModule" image="%IIS_BIN%\authmd5.dll" /> -->
+            <add name="IISCertificateMappingAuthenticationModule" image="%IIS_BIN%\authmap.dll" />
+            <add name="IpRestrictionModule" image="%IIS_BIN%\iprestr.dll" />
+            <add name="DynamicIpRestrictionModule" image="%IIS_BIN%\diprestr.dll" />
+            <add name="RequestFilteringModule" image="%IIS_BIN%\modrqflt.dll" />
+            <add name="CustomLoggingModule" image="%IIS_BIN%\logcust.dll" />
+            <add name="CustomErrorModule" image="%IIS_BIN%\custerr.dll" />
+<!--            <add name="TracingModule" image="%IIS_BIN%\iisetw.dll" /> -->
+            <add name="FailedRequestsTracingModule" image="%IIS_BIN%\iisfreb.dll" />
+            <add name="RequestMonitorModule" image="%IIS_BIN%\iisreqs.dll" />
+            <add name="IsapiModule" image="%IIS_BIN%\isapi.dll" />
+            <add name="IsapiFilterModule" image="%IIS_BIN%\filter.dll" />
+            <add name="CgiModule" image="%IIS_BIN%\cgi.dll" />
+            <add name="FastCgiModule" image="%IIS_BIN%\iisfcgi.dll" />
+<!--            <add name="WebDAVModule" image="%IIS_BIN%\webdav.dll" /> -->
+            <add name="RewriteModule" image="%IIS_BIN%\rewrite.dll" />
+            <add name="ConfigurationValidationModule" image="%IIS_BIN%\validcfg.dll" />
+            <add name="WebSocketModule" image="%IIS_BIN%\iiswsock.dll" />
+            <add name="WebMatrixSupportModule" image="%IIS_BIN%\webmatrixsup.dll" />
+            <add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness32" />
+            <add name="ManagedEngine64" image="%windir%\Microsoft.NET\Framework64\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness64" />
+            <add name="ManagedEngineV4.0_32bit" image="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness32" />
+            <add name="ManagedEngineV4.0_64bit" image="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness64" />
+            <add name="ApplicationInitializationModule" image="%IIS_BIN%\warmup.dll" />
+            <add name="AspNetCoreModule" image="%IIS_BIN%\aspnetcore.dll" />
+        </globalModules>
+
+        <httpCompression directory="%TEMP%\iisexpress\IIS Temporary Compressed Files">
+            <scheme name="gzip" dll="%IIS_BIN%\gzip.dll" />
+            <dynamicTypes>
+                <add mimeType="text/*" enabled="true" />
+                <add mimeType="message/*" enabled="true" />
+                <add mimeType="application/javascript" enabled="true" />
+                <add mimeType="application/atom+xml" enabled="true" />
+                <add mimeType="application/xaml+xml" enabled="true" />
+                <add mimeType="*/*" enabled="false" />
+            </dynamicTypes>
+            <staticTypes>
+                <add mimeType="text/*" enabled="true" />
+                <add mimeType="message/*" enabled="true" />
+                <add mimeType="image/svg+xml" enabled="true" />
+                <add mimeType="application/javascript" enabled="true" />
+                <add mimeType="application/atom+xml" enabled="true" />
+                <add mimeType="application/xaml+xml" enabled="true" />
+                <add mimeType="*/*" enabled="false" />
+            </staticTypes>
+        </httpCompression>
+
+        <httpErrors lockAttributes="allowAbsolutePathsWhenDelegated,defaultPath">
+            <error statusCode="401" prefixLanguageFilePath="%IIS_BIN%\custerr" path="401.htm" />
+            <error statusCode="403" prefixLanguageFilePath="%IIS_BIN%\custerr" path="403.htm" />
+            <error statusCode="404" prefixLanguageFilePath="%IIS_BIN%\custerr" path="404.htm" />
+            <error statusCode="405" prefixLanguageFilePath="%IIS_BIN%\custerr" path="405.htm" />
+            <error statusCode="406" prefixLanguageFilePath="%IIS_BIN%\custerr" path="406.htm" />
+            <error statusCode="412" prefixLanguageFilePath="%IIS_BIN%\custerr" path="412.htm" />
+            <error statusCode="500" prefixLanguageFilePath="%IIS_BIN%\custerr" path="500.htm" />
+            <error statusCode="501" prefixLanguageFilePath="%IIS_BIN%\custerr" path="501.htm" />
+            <error statusCode="502" prefixLanguageFilePath="%IIS_BIN%\custerr" path="502.htm" />
+        </httpErrors>
+
+        <httpLogging dontLog="false" />
+
+        <httpProtocol>
+            <customHeaders>
+                <clear />
+                <add name="X-Powered-By" value="ASP.NET" />
+            </customHeaders>
+            <redirectHeaders>
+                <clear />
+            </redirectHeaders>
+        </httpProtocol>
+
+        <httpRedirect enabled="false" />
+
+        <httpTracing>
+        </httpTracing>
+
+        <isapiFilters>
+            <filter name="ASP.Net_2.0.50727-64" path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv2.0" />
+            <filter name="ASP.Net_2.0.50727.0" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv2.0" />
+            <filter name="ASP.Net_2.0_for_v1.1" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv1.1" />
+            <filter name="ASP.Net_4.0_32bit" path="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv4.0" />
+            <filter name="ASP.Net_4.0_64bit" path="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv4.0" />
+        </isapiFilters>
+
+        <odbcLogging />
+
+        <security>
+
+            <access sslFlags="None" />
+
+            <applicationDependencies>
+                <application name="Active Server Pages" groupId="ASP" />
+            </applicationDependencies>
+
+            <authentication>
+
+                <anonymousAuthentication enabled="true" userName="" />
+
+                <basicAuthentication enabled="false" />
+
+                <clientCertificateMappingAuthentication enabled="false" />
+
+                <digestAuthentication enabled="false" />
+
+                <iisClientCertificateMappingAuthentication enabled="false">
+                </iisClientCertificateMappingAuthentication>
+
+                <windowsAuthentication enabled="false">
+                    <providers>
+                        <add value="Negotiate" />
+                        <add value="NTLM" />
+                    </providers>
+                </windowsAuthentication>
+
+            </authentication>
+
+            <authorization>
+                <add accessType="Allow" users="*" />
+            </authorization>
+
+            <ipSecurity allowUnlisted="true" />
+
+            <isapiCgiRestriction notListedIsapisAllowed="true" notListedCgisAllowed="true">
+                <add path="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
+                <add path="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
+                <add path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
+                <add path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
+            </isapiCgiRestriction>
+
+            <requestFiltering>
+                <fileExtensions allowUnlisted="true" applyToWebDAV="true">
+                    <add fileExtension=".asa" allowed="false" />
+                    <add fileExtension=".asax" allowed="false" />
+                    <add fileExtension=".ascx" allowed="false" />
+                    <add fileExtension=".master" allowed="false" />
+                    <add fileExtension=".skin" allowed="false" />
+                    <add fileExtension=".browser" allowed="false" />
+                    <add fileExtension=".sitemap" allowed="false" />
+                    <add fileExtension=".config" allowed="false" />
+                    <add fileExtension=".cs" allowed="false" />
+                    <add fileExtension=".csproj" allowed="false" />
+                    <add fileExtension=".vb" allowed="false" />
+                    <add fileExtension=".vbproj" allowed="false" />
+                    <add fileExtension=".webinfo" allowed="false" />
+                    <add fileExtension=".licx" allowed="false" />
+                    <add fileExtension=".resx" allowed="false" />
+                    <add fileExtension=".resources" allowed="false" />
+                    <add fileExtension=".mdb" allowed="false" />
+                    <add fileExtension=".vjsproj" allowed="false" />
+                    <add fileExtension=".java" allowed="false" />
+                    <add fileExtension=".jsl" allowed="false" />
+                    <add fileExtension=".ldb" allowed="false" />
+                    <add fileExtension=".dsdgm" allowed="false" />
+                    <add fileExtension=".ssdgm" allowed="false" />
+                    <add fileExtension=".lsad" allowed="false" />
+                    <add fileExtension=".ssmap" allowed="false" />
+                    <add fileExtension=".cd" allowed="false" />
+                    <add fileExtension=".dsprototype" allowed="false" />
+                    <add fileExtension=".lsaprototype" allowed="false" />
+                    <add fileExtension=".sdm" allowed="false" />
+                    <add fileExtension=".sdmDocument" allowed="false" />
+                    <add fileExtension=".mdf" allowed="false" />
+                    <add fileExtension=".ldf" allowed="false" />
+                    <add fileExtension=".ad" allowed="false" />
+                    <add fileExtension=".dd" allowed="false" />
+                    <add fileExtension=".ldd" allowed="false" />
+                    <add fileExtension=".sd" allowed="false" />
+                    <add fileExtension=".adprototype" allowed="false" />
+                    <add fileExtension=".lddprototype" allowed="false" />
+                    <add fileExtension=".exclude" allowed="false" />
+                    <add fileExtension=".refresh" allowed="false" />
+                    <add fileExtension=".compiled" allowed="false" />
+                    <add fileExtension=".msgx" allowed="false" />
+                    <add fileExtension=".vsdisco" allowed="false" />
+                    <add fileExtension=".rules" allowed="false" />
+                </fileExtensions>
+                <verbs allowUnlisted="true" applyToWebDAV="true" />
+                <hiddenSegments applyToWebDAV="true">
+                    <add segment="web.config" />
+                    <add segment="bin" />
+                    <add segment="App_code" />
+                    <add segment="App_GlobalResources" />
+                    <add segment="App_LocalResources" />
+                    <add segment="App_WebReferences" />
+                    <add segment="App_Data" />
+                    <add segment="App_Browsers" />
+                </hiddenSegments>
+            </requestFiltering>
+
+        </security>
+
+        <serverSideInclude ssiExecDisable="false" />
+
+        <staticContent lockAttributes="isDocFooterFileName">
+            <mimeMap fileExtension=".323" mimeType="text/h323" />
+            <mimeMap fileExtension=".3g2" mimeType="video/3gpp2" />
+            <mimeMap fileExtension=".3gp2" mimeType="video/3gpp2" />
+            <mimeMap fileExtension=".3gp" mimeType="video/3gpp" />
+            <mimeMap fileExtension=".3gpp" mimeType="video/3gpp" />
+            <mimeMap fileExtension=".aac" mimeType="audio/aac" />
+            <mimeMap fileExtension=".aaf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".aca" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".accdb" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".accde" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".accdt" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".acx" mimeType="application/internet-property-stream" />
+            <mimeMap fileExtension=".adt" mimeType="audio/vnd.dlna.adts" />
+            <mimeMap fileExtension=".adts" mimeType="audio/vnd.dlna.adts" />
+            <mimeMap fileExtension=".afm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ai" mimeType="application/postscript" />
+            <mimeMap fileExtension=".aif" mimeType="audio/x-aiff" />
+            <mimeMap fileExtension=".aifc" mimeType="audio/aiff" />
+            <mimeMap fileExtension=".aiff" mimeType="audio/aiff" />
+            <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest" />
+            <mimeMap fileExtension=".application" mimeType="application/x-ms-application" />
+            <mimeMap fileExtension=".art" mimeType="image/x-jg" />
+            <mimeMap fileExtension=".asd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".asf" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".asi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".asm" mimeType="text/plain" />
+            <mimeMap fileExtension=".asr" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".asx" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".atom" mimeType="application/atom+xml" />
+            <mimeMap fileExtension=".au" mimeType="audio/basic" />
+            <mimeMap fileExtension=".avi" mimeType="video/msvideo" />
+            <mimeMap fileExtension=".axs" mimeType="application/olescript" />
+            <mimeMap fileExtension=".bas" mimeType="text/plain" />
+            <mimeMap fileExtension=".bcpio" mimeType="application/x-bcpio" />
+            <mimeMap fileExtension=".bin" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".bmp" mimeType="image/bmp" />
+            <mimeMap fileExtension=".c" mimeType="text/plain" />
+            <mimeMap fileExtension=".cab" mimeType="application/vnd.ms-cab-compressed" />
+            <mimeMap fileExtension=".calx" mimeType="application/vnd.ms-office.calx" />
+            <mimeMap fileExtension=".cat" mimeType="application/vnd.ms-pki.seccat" />
+            <mimeMap fileExtension=".cdf" mimeType="application/x-cdf" />
+            <mimeMap fileExtension=".chm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".class" mimeType="application/x-java-applet" />
+            <mimeMap fileExtension=".clp" mimeType="application/x-msclip" />
+            <mimeMap fileExtension=".cmx" mimeType="image/x-cmx" />
+            <mimeMap fileExtension=".cnf" mimeType="text/plain" />
+            <mimeMap fileExtension=".cod" mimeType="image/cis-cod" />
+            <mimeMap fileExtension=".cpio" mimeType="application/x-cpio" />
+            <mimeMap fileExtension=".cpp" mimeType="text/plain" />
+            <mimeMap fileExtension=".crd" mimeType="application/x-mscardfile" />
+            <mimeMap fileExtension=".crl" mimeType="application/pkix-crl" />
+            <mimeMap fileExtension=".crt" mimeType="application/x-x509-ca-cert" />
+            <mimeMap fileExtension=".csh" mimeType="application/x-csh" />
+            <mimeMap fileExtension=".css" mimeType="text/css" />
+            <mimeMap fileExtension=".csv" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".cur" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dcr" mimeType="application/x-director" />
+            <mimeMap fileExtension=".deploy" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".der" mimeType="application/x-x509-ca-cert" />
+            <mimeMap fileExtension=".dib" mimeType="image/bmp" />
+            <mimeMap fileExtension=".dir" mimeType="application/x-director" />
+            <mimeMap fileExtension=".disco" mimeType="text/xml" />
+            <mimeMap fileExtension=".dll" mimeType="application/x-msdownload" />
+            <mimeMap fileExtension=".dll.config" mimeType="text/xml" />
+            <mimeMap fileExtension=".dlm" mimeType="text/dlm" />
+            <mimeMap fileExtension=".doc" mimeType="application/msword" />
+            <mimeMap fileExtension=".docm" mimeType="application/vnd.ms-word.document.macroEnabled.12" />
+            <mimeMap fileExtension=".docx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
+            <mimeMap fileExtension=".dot" mimeType="application/msword" />
+            <mimeMap fileExtension=".dotm" mimeType="application/vnd.ms-word.template.macroEnabled.12" />
+            <mimeMap fileExtension=".dotx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.template" />
+            <mimeMap fileExtension=".dsp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dtd" mimeType="text/xml" />
+            <mimeMap fileExtension=".dvi" mimeType="application/x-dvi" />
+            <mimeMap fileExtension=".dvr-ms" mimeType="video/x-ms-dvr" />
+            <mimeMap fileExtension=".dwf" mimeType="drawing/x-dwf" />
+            <mimeMap fileExtension=".dwp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dxr" mimeType="application/x-director" />
+            <mimeMap fileExtension=".eml" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".emz" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
+            <mimeMap fileExtension=".eps" mimeType="application/postscript" />
+            <mimeMap fileExtension=".etx" mimeType="text/x-setext" />
+            <mimeMap fileExtension=".evy" mimeType="application/envoy" />
+            <mimeMap fileExtension=".exe" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".exe.config" mimeType="text/xml" />
+            <mimeMap fileExtension=".fdf" mimeType="application/vnd.fdf" />
+            <mimeMap fileExtension=".fif" mimeType="application/fractals" />
+            <mimeMap fileExtension=".fla" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".flr" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".flv" mimeType="video/x-flv" />
+            <mimeMap fileExtension=".gif" mimeType="image/gif" />
+            <mimeMap fileExtension=".gtar" mimeType="application/x-gtar" />
+            <mimeMap fileExtension=".gz" mimeType="application/x-gzip" />
+            <mimeMap fileExtension=".h" mimeType="text/plain" />
+            <mimeMap fileExtension=".hdf" mimeType="application/x-hdf" />
+            <mimeMap fileExtension=".hdml" mimeType="text/x-hdml" />
+            <mimeMap fileExtension=".hhc" mimeType="application/x-oleobject" />
+            <mimeMap fileExtension=".hhk" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".hhp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".hlp" mimeType="application/winhlp" />
+            <mimeMap fileExtension=".hqx" mimeType="application/mac-binhex40" />
+            <mimeMap fileExtension=".hta" mimeType="application/hta" />
+            <mimeMap fileExtension=".htc" mimeType="text/x-component" />
+            <mimeMap fileExtension=".htm" mimeType="text/html" />
+            <mimeMap fileExtension=".html" mimeType="text/html" />
+            <mimeMap fileExtension=".htt" mimeType="text/webviewhtml" />
+            <mimeMap fileExtension=".hxt" mimeType="text/html" />
+            <mimeMap fileExtension=".ico" mimeType="image/x-icon" />
+            <mimeMap fileExtension=".ics" mimeType="text/calendar" />
+            <mimeMap fileExtension=".ief" mimeType="image/ief" />
+            <mimeMap fileExtension=".iii" mimeType="application/x-iphone" />
+            <mimeMap fileExtension=".inf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ins" mimeType="application/x-internet-signup" />
+            <mimeMap fileExtension=".isp" mimeType="application/x-internet-signup" />
+            <mimeMap fileExtension=".IVF" mimeType="video/x-ivf" />
+            <mimeMap fileExtension=".jar" mimeType="application/java-archive" />
+            <mimeMap fileExtension=".java" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".jck" mimeType="application/liquidmotion" />
+            <mimeMap fileExtension=".jcz" mimeType="application/liquidmotion" />
+            <mimeMap fileExtension=".jfif" mimeType="image/pjpeg" />
+            <mimeMap fileExtension=".jpb" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".jpe" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".jpeg" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".jpg" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".js" mimeType="application/javascript" />
+            <mimeMap fileExtension=".json" mimeType="application/json" />
+            <mimeMap fileExtension=".jsonld" mimeType="application/ld+json" />
+            <mimeMap fileExtension=".jsx" mimeType="text/jscript" />
+            <mimeMap fileExtension=".latex" mimeType="application/x-latex" />
+            <mimeMap fileExtension=".less" mimeType="text/css" />
+            <mimeMap fileExtension=".lit" mimeType="application/x-ms-reader" />
+            <mimeMap fileExtension=".lpk" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".lsf" mimeType="video/x-la-asf" />
+            <mimeMap fileExtension=".lsx" mimeType="video/x-la-asf" />
+            <mimeMap fileExtension=".lzh" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".m13" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".m14" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".m1v" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".m2ts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".m3u" mimeType="audio/x-mpegurl" />
+            <mimeMap fileExtension=".m4a" mimeType="audio/mp4" />
+            <mimeMap fileExtension=".m4v" mimeType="video/mp4" />
+            <mimeMap fileExtension=".man" mimeType="application/x-troff-man" />
+            <mimeMap fileExtension=".manifest" mimeType="application/x-ms-manifest" />
+            <mimeMap fileExtension=".map" mimeType="text/plain" />
+            <mimeMap fileExtension=".mdb" mimeType="application/x-msaccess" />
+            <mimeMap fileExtension=".mdp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".me" mimeType="application/x-troff-me" />
+            <mimeMap fileExtension=".mht" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".mhtml" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".mid" mimeType="audio/mid" />
+            <mimeMap fileExtension=".midi" mimeType="audio/mid" />
+            <mimeMap fileExtension=".mix" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mmf" mimeType="application/x-smaf" />
+            <mimeMap fileExtension=".mno" mimeType="text/xml" />
+            <mimeMap fileExtension=".mny" mimeType="application/x-msmoney" />
+            <mimeMap fileExtension=".mov" mimeType="video/quicktime" />
+            <mimeMap fileExtension=".movie" mimeType="video/x-sgi-movie" />
+            <mimeMap fileExtension=".mp2" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mp3" mimeType="audio/mpeg" />
+            <mimeMap fileExtension=".mp4" mimeType="video/mp4" />
+            <mimeMap fileExtension=".mp4v" mimeType="video/mp4" />
+            <mimeMap fileExtension=".mpa" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpe" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpeg" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpg" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpp" mimeType="application/vnd.ms-project" />
+            <mimeMap fileExtension=".mpv2" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".ms" mimeType="application/x-troff-ms" />
+            <mimeMap fileExtension=".msi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mso" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mvb" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".mvc" mimeType="application/x-miva-compiled" />
+            <mimeMap fileExtension=".nc" mimeType="application/x-netcdf" />
+            <mimeMap fileExtension=".nsc" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".nws" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".ocx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".oda" mimeType="application/oda" />
+            <mimeMap fileExtension=".odc" mimeType="text/x-ms-odc" />
+            <mimeMap fileExtension=".ods" mimeType="application/oleobject" />
+            <mimeMap fileExtension=".oga" mimeType="audio/ogg" />
+            <mimeMap fileExtension=".ogg" mimeType="video/ogg" />
+            <mimeMap fileExtension=".ogv" mimeType="video/ogg" />
+            <mimeMap fileExtension=".one" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onea" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetoc" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetoc2" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetmp" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onepkg" mimeType="application/onenote" />
+            <mimeMap fileExtension=".osdx" mimeType="application/opensearchdescription+xml" />
+            <mimeMap fileExtension=".otf" mimeType="font/otf" />
+            <mimeMap fileExtension=".p10" mimeType="application/pkcs10" />
+            <mimeMap fileExtension=".p12" mimeType="application/x-pkcs12" />
+            <mimeMap fileExtension=".p7b" mimeType="application/x-pkcs7-certificates" />
+            <mimeMap fileExtension=".p7c" mimeType="application/pkcs7-mime" />
+            <mimeMap fileExtension=".p7m" mimeType="application/pkcs7-mime" />
+            <mimeMap fileExtension=".p7r" mimeType="application/x-pkcs7-certreqresp" />
+            <mimeMap fileExtension=".p7s" mimeType="application/pkcs7-signature" />
+            <mimeMap fileExtension=".pbm" mimeType="image/x-portable-bitmap" />
+            <mimeMap fileExtension=".pcx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pcz" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pdf" mimeType="application/pdf" />
+            <mimeMap fileExtension=".pfb" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pfm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pfx" mimeType="application/x-pkcs12" />
+            <mimeMap fileExtension=".pgm" mimeType="image/x-portable-graymap" />
+            <mimeMap fileExtension=".pko" mimeType="application/vnd.ms-pki.pko" />
+            <mimeMap fileExtension=".pma" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmc" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pml" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmr" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmw" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".png" mimeType="image/png" />
+            <mimeMap fileExtension=".pnm" mimeType="image/x-portable-anymap" />
+            <mimeMap fileExtension=".pnz" mimeType="image/png" />
+            <mimeMap fileExtension=".pot" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".potm" mimeType="application/vnd.ms-powerpoint.template.macroEnabled.12" />
+            <mimeMap fileExtension=".potx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.template" />
+            <mimeMap fileExtension=".ppam" mimeType="application/vnd.ms-powerpoint.addin.macroEnabled.12" />
+            <mimeMap fileExtension=".ppm" mimeType="image/x-portable-pixmap" />
+            <mimeMap fileExtension=".pps" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".ppsm" mimeType="application/vnd.ms-powerpoint.slideshow.macroEnabled.12" />
+            <mimeMap fileExtension=".ppsx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" />
+            <mimeMap fileExtension=".ppt" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".pptm" mimeType="application/vnd.ms-powerpoint.presentation.macroEnabled.12" />
+            <mimeMap fileExtension=".pptx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
+            <mimeMap fileExtension=".prf" mimeType="application/pics-rules" />
+            <mimeMap fileExtension=".prm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".prx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ps" mimeType="application/postscript" />
+            <mimeMap fileExtension=".psd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".psm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".psp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pub" mimeType="application/x-mspublisher" />
+            <mimeMap fileExtension=".qt" mimeType="video/quicktime" />
+            <mimeMap fileExtension=".qtl" mimeType="application/x-quicktimeplayer" />
+            <mimeMap fileExtension=".qxd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ra" mimeType="audio/x-pn-realaudio" />
+            <mimeMap fileExtension=".ram" mimeType="audio/x-pn-realaudio" />
+            <mimeMap fileExtension=".rar" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ras" mimeType="image/x-cmu-raster" />
+            <mimeMap fileExtension=".rf" mimeType="image/vnd.rn-realflash" />
+            <mimeMap fileExtension=".rgb" mimeType="image/x-rgb" />
+            <mimeMap fileExtension=".rm" mimeType="application/vnd.rn-realmedia" />
+            <mimeMap fileExtension=".rmi" mimeType="audio/mid" />
+            <mimeMap fileExtension=".roff" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".rpm" mimeType="audio/x-pn-realaudio-plugin" />
+            <mimeMap fileExtension=".rtf" mimeType="application/rtf" />
+            <mimeMap fileExtension=".rtx" mimeType="text/richtext" />
+            <mimeMap fileExtension=".scd" mimeType="application/x-msschedule" />
+            <mimeMap fileExtension=".sct" mimeType="text/scriptlet" />
+            <mimeMap fileExtension=".sea" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".setpay" mimeType="application/set-payment-initiation" />
+            <mimeMap fileExtension=".setreg" mimeType="application/set-registration-initiation" />
+            <mimeMap fileExtension=".sgml" mimeType="text/sgml" />
+            <mimeMap fileExtension=".sh" mimeType="application/x-sh" />
+            <mimeMap fileExtension=".shar" mimeType="application/x-shar" />
+            <mimeMap fileExtension=".sit" mimeType="application/x-stuffit" />
+            <mimeMap fileExtension=".sldm" mimeType="application/vnd.ms-powerpoint.slide.macroEnabled.12" />
+            <mimeMap fileExtension=".sldx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slide" />
+            <mimeMap fileExtension=".smd" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".smi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".smx" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".smz" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".snd" mimeType="audio/basic" />
+            <mimeMap fileExtension=".snp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".spc" mimeType="application/x-pkcs7-certificates" />
+            <mimeMap fileExtension=".spl" mimeType="application/futuresplash" />
+            <mimeMap fileExtension=".spx" mimeType="audio/ogg" />
+            <mimeMap fileExtension=".src" mimeType="application/x-wais-source" />
+            <mimeMap fileExtension=".ssm" mimeType="application/streamingmedia" />
+            <mimeMap fileExtension=".sst" mimeType="application/vnd.ms-pki.certstore" />
+            <mimeMap fileExtension=".stl" mimeType="application/vnd.ms-pki.stl" />
+            <mimeMap fileExtension=".sv4cpio" mimeType="application/x-sv4cpio" />
+            <mimeMap fileExtension=".sv4crc" mimeType="application/x-sv4crc" />
+            <mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
+            <mimeMap fileExtension=".svgz" mimeType="image/svg+xml" />
+            <mimeMap fileExtension=".swf" mimeType="application/x-shockwave-flash" />
+            <mimeMap fileExtension=".t" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".tar" mimeType="application/x-tar" />
+            <mimeMap fileExtension=".tcl" mimeType="application/x-tcl" />
+            <mimeMap fileExtension=".tex" mimeType="application/x-tex" />
+            <mimeMap fileExtension=".texi" mimeType="application/x-texinfo" />
+            <mimeMap fileExtension=".texinfo" mimeType="application/x-texinfo" />
+            <mimeMap fileExtension=".tgz" mimeType="application/x-compressed" />
+            <mimeMap fileExtension=".thmx" mimeType="application/vnd.ms-officetheme" />
+            <mimeMap fileExtension=".thn" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tif" mimeType="image/tiff" />
+            <mimeMap fileExtension=".tiff" mimeType="image/tiff" />
+            <mimeMap fileExtension=".toc" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tr" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".trm" mimeType="application/x-msterminal" />
+            <mimeMap fileExtension=".ts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".tsv" mimeType="text/tab-separated-values" />
+            <mimeMap fileExtension=".ttf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".txt" mimeType="text/plain" />
+            <mimeMap fileExtension=".u32" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".uls" mimeType="text/iuls" />
+            <mimeMap fileExtension=".ustar" mimeType="application/x-ustar" />
+            <mimeMap fileExtension=".vbs" mimeType="text/vbscript" />
+            <mimeMap fileExtension=".vcf" mimeType="text/x-vcard" />
+            <mimeMap fileExtension=".vcs" mimeType="text/plain" />
+            <mimeMap fileExtension=".vdx" mimeType="application/vnd.ms-visio.viewer" />
+            <mimeMap fileExtension=".vml" mimeType="text/xml" />
+            <mimeMap fileExtension=".vsd" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vss" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vst" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vsto" mimeType="application/x-ms-vsto" />
+            <mimeMap fileExtension=".vsw" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vsx" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vtx" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".wav" mimeType="audio/wav" />
+            <mimeMap fileExtension=".wax" mimeType="audio/x-ms-wax" />
+            <mimeMap fileExtension=".wbmp" mimeType="image/vnd.wap.wbmp" />
+            <mimeMap fileExtension=".wcm" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wdb" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".webm" mimeType="video/webm" />
+            <mimeMap fileExtension=".wks" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wm" mimeType="video/x-ms-wm" />
+            <mimeMap fileExtension=".wma" mimeType="audio/x-ms-wma" />
+            <mimeMap fileExtension=".wmd" mimeType="application/x-ms-wmd" />
+            <mimeMap fileExtension=".wmf" mimeType="application/x-msmetafile" />
+            <mimeMap fileExtension=".wml" mimeType="text/vnd.wap.wml" />
+            <mimeMap fileExtension=".wmlc" mimeType="application/vnd.wap.wmlc" />
+            <mimeMap fileExtension=".wmls" mimeType="text/vnd.wap.wmlscript" />
+            <mimeMap fileExtension=".wmlsc" mimeType="application/vnd.wap.wmlscriptc" />
+            <mimeMap fileExtension=".wmp" mimeType="video/x-ms-wmp" />
+            <mimeMap fileExtension=".wmv" mimeType="video/x-ms-wmv" />
+            <mimeMap fileExtension=".wmx" mimeType="video/x-ms-wmx" />
+            <mimeMap fileExtension=".wmz" mimeType="application/x-ms-wmz" />
+            <mimeMap fileExtension=".woff" mimeType="font/x-woff" />
+            <mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
+            <mimeMap fileExtension=".wps" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wri" mimeType="application/x-mswrite" />
+            <mimeMap fileExtension=".wrl" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".wrz" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".wsdl" mimeType="text/xml" />
+            <mimeMap fileExtension=".wtv" mimeType="video/x-ms-wtv" />
+            <mimeMap fileExtension=".wvx" mimeType="video/x-ms-wvx" />
+            <mimeMap fileExtension=".x" mimeType="application/directx" />
+            <mimeMap fileExtension=".xaf" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".xaml" mimeType="application/xaml+xml" />
+            <mimeMap fileExtension=".xap" mimeType="application/x-silverlight-app" />
+            <mimeMap fileExtension=".xbap" mimeType="application/x-ms-xbap" />
+            <mimeMap fileExtension=".xbm" mimeType="image/x-xbitmap" />
+            <mimeMap fileExtension=".xdr" mimeType="text/plain" />
+            <mimeMap fileExtension=".xht" mimeType="application/xhtml+xml" />
+            <mimeMap fileExtension=".xhtml" mimeType="application/xhtml+xml" />
+            <mimeMap fileExtension=".xla" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlam" mimeType="application/vnd.ms-excel.addin.macroEnabled.12" />
+            <mimeMap fileExtension=".xlc" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlm" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xls" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlsb" mimeType="application/vnd.ms-excel.sheet.binary.macroEnabled.12" />
+            <mimeMap fileExtension=".xlsm" mimeType="application/vnd.ms-excel.sheet.macroEnabled.12" />
+            <mimeMap fileExtension=".xlsx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
+            <mimeMap fileExtension=".xlt" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xltm" mimeType="application/vnd.ms-excel.template.macroEnabled.12" />
+            <mimeMap fileExtension=".xltx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.template" />
+            <mimeMap fileExtension=".xlw" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xml" mimeType="text/xml" />
+            <mimeMap fileExtension=".xof" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".xpm" mimeType="image/x-xpixmap" />
+            <mimeMap fileExtension=".xps" mimeType="application/vnd.ms-xpsdocument" />
+            <mimeMap fileExtension=".xsd" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsf" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsl" mimeType="text/xml" />
+            <mimeMap fileExtension=".xslt" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsn" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".xtp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".xwd" mimeType="image/x-xwindowdump" />
+            <mimeMap fileExtension=".z" mimeType="application/x-compress" />
+            <mimeMap fileExtension=".zip" mimeType="application/x-zip-compressed" />
+        </staticContent>
+
+        <tracing>
+
+             <traceProviderDefinitions>
+                <add name="WWW Server" guid="{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}">
+                    <areas>
+                        <clear />
+                        <add name="Authentication" value="2" />
+                        <add name="Security" value="4" />
+                        <add name="Filter" value="8" />
+                        <add name="StaticFile" value="16" />
+                        <add name="CGI" value="32" />
+                        <add name="Compression" value="64" />
+                        <add name="Cache" value="128" />
+                        <add name="RequestNotifications" value="256" />
+                        <add name="Module" value="512" />
+                        <add name="Rewrite" value="1024" />
+                        <add name="FastCGI" value="4096" />
+                        <add name="WebSocket" value="16384" />
+                    </areas>
+                </add>
+                <add name="ASP" guid="{06b94d9a-b15e-456e-a4ef-37c984a2cb4b}">
+                    <areas>
+                        <clear />
+                    </areas>
+                </add>
+                <add name="ISAPI Extension" guid="{a1c2040e-8840-4c31-ba11-9871031a19ea}">
+                    <areas>
+                        <clear />
+                    </areas>
+                </add>
+                <add name="ASPNET" guid="{AFF081FE-0247-4275-9C4E-021F3DC1DA35}">
+                    <areas>
+                        <add name="Infrastructure" value="1" />
+                        <add name="Module" value="2" />
+                        <add name="Page" value="4" />
+                        <add name="AppServices" value="8" />
+                    </areas>
+                </add>
+            </traceProviderDefinitions>
+
+            <traceFailedRequests>
+                <add path="*">
+                    <traceAreas>
+                        <add provider="ASP" verbosity="Verbose" />
+                        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
+                        <add provider="ISAPI Extension" verbosity="Verbose" />
+                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,Rewrite,WebSocket" verbosity="Verbose" />
+                    </traceAreas>
+                    <failureDefinitions statusCodes="200-999" />
+                </add>
+            </traceFailedRequests>
+
+        </tracing>
+
+        <urlCompression />
+
+        <validation />
+        <webdav>
+            <globalSettings>
+                <propertyStores>
+                    <add name="webdav_simple_prop" image="%IIS_BIN%\webdav_simple_prop.dll" image32="%IIS_BIN%\webdav_simple_prop.dll" />
+                </propertyStores>
+                <lockStores>
+                    <add name="webdav_simple_lock" image="%IIS_BIN%\webdav_simple_lock.dll" image32="%IIS_BIN%\webdav_simple_lock.dll" />
+                </lockStores>
+
+            </globalSettings>
+            <authoring>
+                <locks enabled="true" lockStore="webdav_simple_lock" />
+            </authoring>
+            <authoringRules />
+        </webdav>
+        <webSocket />
+        <applicationInitialization />
+
+    </system.webServer>
+    <location path="" overrideMode="Allow">
+        <system.webServer>
+            <modules>
+                <add name="IsapiFilterModule" lockItem="true" />
+                <add name="BasicAuthenticationModule" lockItem="true" />
+                <add name="IsapiModule" lockItem="true" />
+                <add name="HttpLoggingModule" lockItem="true" />
+                <!--
+                <add name="HttpCacheModule" lockItem="true" />
+-->
+                <add name="DynamicCompressionModule" lockItem="true" />
+                <add name="StaticCompressionModule" lockItem="true" />
+                <add name="DefaultDocumentModule" lockItem="true" />
+                <add name="DirectoryListingModule" lockItem="true" />
+
+                <add name="ProtocolSupportModule" lockItem="true" />
+                <add name="HttpRedirectionModule" lockItem="true" />
+                <add name="ServerSideIncludeModule" lockItem="true" />
+                <add name="StaticFileModule" lockItem="true" />
+                <add name="AnonymousAuthenticationModule" lockItem="true" />
+                <add name="CertificateMappingAuthenticationModule" lockItem="true" />
+                <add name="UrlAuthorizationModule" lockItem="true" />
+                <add name="WindowsAuthenticationModule" lockItem="true" />
+                <!--
+                <add name="DigestAuthenticationModule" lockItem="true" />
+-->
+                <add name="IISCertificateMappingAuthenticationModule" lockItem="true" />
+                <add name="WebMatrixSupportModule" lockItem="true" />
+                <add name="IpRestrictionModule" lockItem="true" />
+                <add name="DynamicIpRestrictionModule" lockItem="true" />
+                <add name="RequestFilteringModule" lockItem="true" />
+                <add name="CustomLoggingModule" lockItem="true" />
+                <add name="CustomErrorModule" lockItem="true" />
+                <add name="FailedRequestsTracingModule" lockItem="true" />
+                <add name="CgiModule" lockItem="true" />
+                <add name="FastCgiModule" lockItem="true" />
+                <!--                <add name="WebDAVModule" /> -->
+                <add name="RewriteModule" />
+                <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" preCondition="managedHandler" />
+                <add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition="managedHandler" />
+                <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" preCondition="managedHandler" />
+                <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="managedHandler" />
+                <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="managedHandler" />
+                <add name="RoleManager" type="System.Web.Security.RoleManagerModule" preCondition="managedHandler" />
+                <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" preCondition="managedHandler" />
+                <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" preCondition="managedHandler" />
+                <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" preCondition="managedHandler" />
+                <add name="Profile" type="System.Web.Profile.ProfileModule" preCondition="managedHandler" />
+                <add name="UrlMappingsModule" type="System.Web.UrlMappingsModule" preCondition="managedHandler" />
+                <add name="ConfigurationValidationModule" lockItem="true" />
+                <add name="WebSocketModule" lockItem="true" />
+                <add name="ServiceModel-4.0" type="System.ServiceModel.Activation.ServiceHttpModule,System.ServiceModel.Activation,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler,runtimeVersionv2.0" />
+                <add name="ApplicationInitializationModule" lockItem="true" />
+                <add name="AspNetCoreModule" lockItem="true" />
+            </modules>
+            <handlers accessPolicy="Read, Script">
+                <!--                <add name="WebDAV" path="*" verb="PROPFIND,PROPPATCH,MKCOL,PUT,COPY,DELETE,MOVE,LOCK,UNLOCK" modules="WebDAVModule" resourceType="Unspecified" requireAccess="None" /> -->
+                <add name="AXD-ISAPI-4.0_64bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-4.0_64bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-4.0_64bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-4.0_64bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_64bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_64bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="svc-ISAPI-4.0_64bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="rules-ISAPI-4.0_64bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="xoml-ISAPI-4.0_64bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="xamlx-ISAPI-4.0_64bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="aspq-ISAPI-4.0_64bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="cshtm-ISAPI-4.0_64bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="cshtml-ISAPI-4.0_64bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="vbhtm-ISAPI-4.0_64bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="vbhtml-ISAPI-4.0_64bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="svc-Integrated" path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="svc-ISAPI-2.0" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
+                <add name="xoml-Integrated" path="*.xoml" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="xoml-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
+                <add name="rules-Integrated" path="*.rules" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="rules-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
+                <add name="AXD-ISAPI-4.0_32bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-4.0_32bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-4.0_32bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-4.0_32bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_32bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_32bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="svc-ISAPI-4.0_32bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="rules-ISAPI-4.0_32bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="xoml-ISAPI-4.0_32bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="xamlx-ISAPI-4.0_32bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="aspq-ISAPI-4.0_32bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="cshtm-ISAPI-4.0_32bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="cshtml-ISAPI-4.0_32bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="vbhtm-ISAPI-4.0_32bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="vbhtml-ISAPI-4.0_32bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="TraceHandler-Integrated-4.0" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="WebAdminHandler-Integrated-4.0" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="AssemblyResourceLoader-Integrated-4.0" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="PageHandlerFactory-Integrated-4.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="SimpleHandlerFactory-Integrated-4.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="WebServiceHandlerFactory-Integrated-4.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="HttpRemotingHandlerFactory-rem-Integrated-4.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="HttpRemotingHandlerFactory-soap-Integrated-4.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="svc-Integrated-4.0" path="*.svc" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="rules-Integrated-4.0" path="*.rules" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="xoml-Integrated-4.0" path="*.xoml" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="xamlx-Integrated-4.0" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" type="System.Xaml.Hosting.XamlHttpHandlerFactory, System.Xaml.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="aspq-Integrated-4.0" path="*.aspq" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="cshtm-Integrated-4.0" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="cshtml-Integrated-4.0" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="vbhtm-Integrated-4.0" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="vbhtml-Integrated-4.0" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ScriptHandlerFactoryAppServices-Integrated-4.0" path="*_AppService.axd" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ScriptResourceIntegrated-4.0" path="*ScriptResource.axd" verb="GET,HEAD" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ASPClassic" path="*.asp" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
+                <add name="SecurityCertificate" path="*.cer" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
+                <add name="ISAPI-dll" path="*.dll" verb="*" modules="IsapiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
+                <add name="TraceHandler-Integrated" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="WebAdminHandler-Integrated" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="AssemblyResourceLoader-Integrated" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="SimpleHandlerFactory-Integrated" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="WebServiceHandlerFactory-Integrated" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Services.Protocols.WebServiceHandlerFactory,System.Web.Services,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="HttpRemotingHandlerFactory-rem-Integrated" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="HttpRemotingHandlerFactory-soap-Integrated" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="AXD-ISAPI-2.0" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-2.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-2.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-2.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="svc-ISAPI-2.0-64" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
+                <add name="AXD-ISAPI-2.0-64" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-2.0-64" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-2.0-64" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-2.0-64" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0-64" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0-64" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="rules-64-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
+                <add name="xoml-64-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
+                <add name="CGI-exe" path="*.exe" verb="*" modules="CgiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
+                <add name="SSINC-stm" path="*.stm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="SSINC-shtm" path="*.shtm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="SSINC-shtml" path="*.shtml" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="TRACEVerbHandler" path="*" verb="TRACE" modules="ProtocolSupportModule" requireAccess="None" />
+                <add name="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" requireAccess="None" />
+                <add name="ExtensionlessUrl-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="ExtensionlessUrl-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
+                <add name="StaticFile" path="*" verb="*" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" resourceType="Either" requireAccess="Read" />
+            </handlers>
+        </system.webServer>
+    </location>
+</configuration>
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
index ba6cfeab..d65eb199 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/README.md
@@ -10,7 +10,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
 
 ## Requirements
 
-* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7.0, 7.1 and HHVM
+* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7 and HHVM
 * [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
 * [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
 
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
index 1ea66ab0..a4c13758 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json
@@ -1,7 +1,7 @@
 {
     "name": "robthree/twofactorauth",
     "description": "Two Factor Authentication",
-    "version": "1.6.1",
+    "version": "1.6",
     "type": "library",
     "keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
     "homepage": "https://github.com/RobThree/TwoFactorAuth",
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
index 9a775fc8..4939f0d4 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php
@@ -6,7 +6,7 @@ class ConvertUnixTimeDotComTimeProvider implements ITimeProvider
 {
     public function getTime() {
         $json = @json_decode(
-            @file_get_contents('http://www.convert-unix-time.com/api?timestamp=now&r=' . uniqid(null, true))
+            @file_get_contents('http://www.convert-unix-time.com/api?timestamp=now')
         );
         if ($json === null || !is_int($json->timestamp))
             throw new \TimeException('Unable to retrieve time from convert-unix-time.com');
diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
index 8e7806e2..c761bd97 100644
--- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
+++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Time/HttpTimeProvider.php
@@ -26,8 +26,7 @@ class HttpTimeProvider implements ITimeProvider
                     'request_fulluri' => true,
                     'header' => array(
                         'Connection: close',
-                        'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)',
-                        'Cache-Control: no-cache'
+                        'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)'
                     )
                 )
             );
diff --git a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1 b/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1
deleted file mode 100644
index a11c78fb..00000000
--- a/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php.1
+++ /dev/null
@@ -1,507 +0,0 @@
-<?php
-/* Copyright (c) 2014 Yubico AB
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *   * Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *
- *   * Redistributions in binary form must reproduce the above
- *     copyright notice, this list of conditions and the following
- *     disclaimer in the documentation and/or other materials provided
- *     with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-namespace u2flib_server;
-
-/** Constant for the version of the u2f protocol */
-const U2F_VERSION = "U2F_V2";
-
-/** Error for the authentication message not matching any outstanding
- * authentication request */
-const ERR_NO_MATCHING_REQUEST = 1;
-
-/** Error for the authentication message not matching any registration */
-const ERR_NO_MATCHING_REGISTRATION = 2;
-
-/** Error for the signature on the authentication message not verifying with
- * the correct key */
-const ERR_AUTHENTICATION_FAILURE = 3;
-
-/** Error for the challenge in the registration message not matching the
- * registration challenge */
-const ERR_UNMATCHED_CHALLENGE = 4;
-
-/** Error for the attestation signature on the registration message not
- * verifying */
-const ERR_ATTESTATION_SIGNATURE = 5;
-
-/** Error for the attestation verification not verifying */
-const ERR_ATTESTATION_VERIFICATION = 6;
-
-/** Error for not getting good random from the system */
-const ERR_BAD_RANDOM = 7;
-
-/** Error when the counter is lower than expected */
-const ERR_COUNTER_TOO_LOW = 8;
-
-/** Error decoding public key */
-const ERR_PUBKEY_DECODE = 9;
-
-/** Error user-agent returned error */
-const ERR_BAD_UA_RETURNING = 10;
-
-/** Error old OpenSSL version */
-const ERR_OLD_OPENSSL = 11;
-
-/** @internal */
-const PUBKEY_LEN = 65;
-
-class U2F
-{
-    /** @var string  */
-    private $appId;
-
-    /** @var null|string */
-    private $attestDir;
-
-    /** @internal */
-    private $FIXCERTS = array(
-        '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
-        'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
-        '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
-        'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
-        '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
-        'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
-    );
-
-    /**
-     * @param string $appId Application id for the running application
-     * @param string|null $attestDir Directory where trusted attestation roots may be found
-     * @throws Error If OpenSSL older than 1.0.0 is used
-     */
-    public function __construct($appId, $attestDir = null)
-    {
-        if(OPENSSL_VERSION_NUMBER < 0x10000000) {
-            throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
-        }
-        $this->appId = $appId;
-        $this->attestDir = $attestDir;
-    }
-
-    /**
-     * Called to get a registration request to send to a user.
-     * Returns an array of one registration request and a array of sign requests.
-     *
-     * @param array $registrations List of current registrations for this
-     * user, to prevent the user from registering the same authenticator several
-     * times.
-     * @return array An array of two elements, the first containing a
-     * RegisterRequest the second being an array of SignRequest
-     * @throws Error
-     */
-    public function getRegisterData(array $registrations = array())
-    {
-        $challenge = $this->createChallenge();
-        $request = new RegisterRequest($challenge, $this->appId);
-        $signs = $this->getAuthenticateData($registrations);
-        return array($request, $signs);
-    }
-
-    /**
-     * Called to verify and unpack a registration message.
-     *
-     * @param RegisterRequest $request this is a reply to
-     * @param object $response response from a user
-     * @param bool $includeCert set to true if the attestation certificate should be
-     * included in the returned Registration object
-     * @return Registration
-     * @throws Error
-     */
-    public function doRegister($request, $response, $includeCert = true)
-    {
-        if( !is_object( $request ) ) {
-            throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
-        }
-
-        if( !is_object( $response ) ) {
-            throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
-        }
-
-        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
-            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
-        }
-
-        if( !is_bool( $includeCert ) ) {
-            throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
-        }
-
-        $rawReg = $this->base64u_decode($response->registrationData);
-        $regData = array_values(unpack('C*', $rawReg));
-        $clientData = $this->base64u_decode($response->clientData);
-        $cli = json_decode($clientData);
-
-        if($cli->challenge !== $request->challenge) {
-            throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
-        }
-
-        $registration = new Registration();
-        $offs = 1;
-        $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
-        $offs += PUBKEY_LEN;
-        // decode the pubKey to make sure it's good
-        $tmpKey = $this->pubkey_to_pem($pubKey);
-        if($tmpKey === null) {
-            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
-        }
-        $registration->publicKey = base64_encode($pubKey);
-        $khLen = $regData[$offs++];
-        $kh = substr($rawReg, $offs, $khLen);
-        $offs += $khLen;
-        $registration->keyHandle = $this->base64u_encode($kh);
-
-        // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
-        $certLen = 4;
-        $certLen += ($regData[$offs + 2] << 8);
-        $certLen += $regData[$offs + 3];
-
-        $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
-        $offs += $certLen;
-        $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
-        $pemCert .= chunk_split(base64_encode($rawCert), 64);
-        $pemCert .= "-----END CERTIFICATE-----";
-        if($includeCert) {
-            $registration->certificate = base64_encode($rawCert);
-        }
-        if($this->attestDir) {
-            if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
-                throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
-            }
-        }
-
-        if(!openssl_pkey_get_public($pemCert)) {
-            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
-        }
-        $signature = substr($rawReg, $offs);
-
-        $dataToVerify  = chr(0);
-        $dataToVerify .= hash('sha256', $request->appId, true);
-        $dataToVerify .= hash('sha256', $clientData, true);
-        $dataToVerify .= $kh;
-        $dataToVerify .= $pubKey;
-
-        if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
-            return $registration;
-        } else {
-            throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
-        }
-    }
-
-    /**
-     * Called to get an authentication request.
-     *
-     * @param array $registrations An array of the registrations to create authentication requests for.
-     * @return array An array of SignRequest
-     * @throws Error
-     */
-    public function getAuthenticateData(array $registrations)
-    {
-        $sigs = array();
-        $challenge = $this->createChallenge();
-        foreach ($registrations as $reg) {
-            if( !is_object( $reg ) ) {
-                throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
-            }
-
-            $sig = new SignRequest();
-            $sig->appId = $this->appId;
-            $sig->keyHandle = $reg->keyHandle;
-            $sig->challenge = $challenge;
-            $sigs[] = $sig;
-        }
-        return $sigs;
-    }
-
-    /**
-     * Called to verify an authentication response
-     *
-     * @param array $requests An array of outstanding authentication requests
-     * @param array $registrations An array of current registrations
-     * @param object $response A response from the authenticator
-     * @return Registration
-     * @throws Error
-     *
-     * The Registration object returned on success contains an updated counter
-     * that should be saved for future authentications.
-     * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
-     * token cloning or similar and appropriate action should be taken.
-     */
-    public function doAuthenticate(array $requests, array $registrations, $response)
-    {
-        if( !is_object( $response ) ) {
-            throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
-        }
-
-        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
-            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
-        }
-
-        /** @var object|null $req */
-        $req = null;
-
-        /** @var object|null $reg */
-        $reg = null;
-
-        $clientData = $this->base64u_decode($response->clientData);
-        $decodedClient = json_decode($clientData);
-        foreach ($requests as $req) {
-            if( !is_object( $req ) ) {
-                throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
-            }
-
-            if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
-                break;
-            }
-
-            $req = null;
-        }
-        if($req === null) {
-            throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
-        }
-        foreach ($registrations as $reg) {
-            if( !is_object( $reg ) ) {
-                throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
-            }
-
-            if($reg->keyHandle === $response->keyHandle) {
-                break;
-            }
-            $reg = null;
-        }
-        if($reg === null) {
-            throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
-        }
-        $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
-        if($pemKey === null) {
-            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
-        }
-
-        $signData = $this->base64u_decode($response->signatureData);
-        $dataToVerify  = hash('sha256', $req->appId, true);
-        $dataToVerify .= substr($signData, 0, 5);
-        $dataToVerify .= hash('sha256', $clientData, true);
-        $signature = substr($signData, 5);
-
-        if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
-            $ctr = unpack("Nctr", substr($signData, 1, 4));
-            $counter = $ctr['ctr'];
-            /* TODO: wrap-around should be handled somehow.. */
-            if($counter > $reg->counter) {
-                $reg->counter = $counter;
-                return $reg;
-            } else {
-                throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
-            }
-        } else {
-            throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
-        }
-    }
-
-    /**
-     * @return array
-     */
-    private function get_certs()
-    {
-        $files = array();
-        $dir = $this->attestDir;
-        if($dir && $handle = opendir($dir)) {
-            while(false !== ($entry = readdir($handle))) {
-                if(is_file("$dir/$entry")) {
-                    $files[] = "$dir/$entry";
-                }
-            }
-            closedir($handle);
-        }
-        return $files;
-    }
-
-    /**
-     * @param string $data
-     * @return string
-     */
-    private function base64u_encode($data)
-    {
-        return trim(strtr(base64_encode($data), '+/', '-_'), '=');
-    }
-
-    /**
-     * @param string $data
-     * @return string
-     */
-    private function base64u_decode($data)
-    {
-        return base64_decode(strtr($data, '-_', '+/'));
-    }
-
-    /**
-     * @param string $key
-     * @return null|string
-     */
-    private function pubkey_to_pem($key)
-    {
-        if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
-            return null;
-        }
-
-        /*
-         * Convert the public key to binary DER format first
-         * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
-         *
-         *  SEQUENCE(2 elem)                        30 59
-         *   SEQUENCE(2 elem)                       30 13
-         *    OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
-         *    OID1.2.840.10045.3.1.7 (secp256r1)    06 08 2a 86 48 ce 3d 03 01 07
-         *   BIT STRING(520 bit)                    03 42 ..key..
-         */
-        $der  = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
-        $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
-        $der .= "\0".$key;
-
-        $pem  = "-----BEGIN PUBLIC KEY-----\r\n";
-        $pem .= chunk_split(base64_encode($der), 64);
-        $pem .= "-----END PUBLIC KEY-----";
-
-        return $pem;
-    }
-
-    /**
-     * @return string
-     * @throws Error
-     */
-    private function createChallenge()
-    {
-        $challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
-        if( $crypto_strong !== true ) {
-            throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
-        }
-
-        $challenge = $this->base64u_encode( $challenge );
-
-        return $challenge;
-    }
-
-    /**
-     * Fixes a certificate where the signature contains unused bits.
-     *
-     * @param string $cert
-     * @return mixed
-     */
-    private function fixSignatureUnusedBits($cert)
-    {
-        if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
-            $cert[strlen($cert) - 257] = "\0";
-        }
-        return $cert;
-    }
-}
-
-/**
- * Class for building a registration request
- *
- * @package u2flib_server
- */
-class RegisterRequest
-{
-    /** Protocol version */
-    public $version = U2F_VERSION;
-
-    /** Registration challenge */
-    public $challenge;
-
-    /** Application id */
-    public $appId;
-
-    /**
-     * @param string $challenge
-     * @param string $appId
-     * @internal
-     */
-    public function __construct($challenge, $appId)
-    {
-        $this->challenge = $challenge;
-        $this->appId = $appId;
-    }
-}
-
-/**
- * Class for building up an authentication request
- *
- * @package u2flib_server
- */
-class SignRequest
-{
-    /** Protocol version */
-    public $version = U2F_VERSION;
-
-    /** Authentication challenge */
-    public $challenge;
-
-    /** Key handle of a registered authenticator */
-    public $keyHandle;
-
-    /** Application id */
-    public $appId;
-}
-
-/**
- * Class returned for successful registrations
- *
- * @package u2flib_server
- */
-class Registration
-{
-    /** The key handle of the registered authenticator */
-    public $keyHandle;
-
-    /** The public key of the registered authenticator */
-    public $publicKey;
-
-    /** The attestation certificate of the registered authenticator */
-    public $certificate;
-
-    /** The counter associated with this registration */
-    public $counter = -1;
-}
-
-/**
- * Error class, returned on errors
- *
- * @package u2flib_server
- */
-class Error extends \Exception
-{
-    /**
-     * Override constructor and make message and code mandatory
-     * @param string $message
-     * @param int $code
-     * @param \Exception|null $previous
-     */
-    public function __construct($message, $code, \Exception $previous = null) {
-        parent::__construct($message, $code, $previous);
-    }
-}
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 3f13e7dc..c78b5aad 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -6,8 +6,6 @@ if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
 }
 $autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
 
-require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
-
 header_remove("X-Powered-By");
 
 // Yubi OTP API
@@ -49,14 +47,26 @@ try {
 }
 catch (PDOException $e) {
 ?>
-<center style='font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;'>Connection failed, database may be in warm-up state, please try again later.<br /><br />The following error was reported:<br/>  <?=$e->getMessage();?></center>
+<center style='font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/>  <?=$e->getMessage();?></center>
 <?php
 exit;
 }
 
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
+
 // Set language
 if (!isset($_SESSION['mailcow_locale'])) {
-  $_SESSION['mailcow_locale'] = strtolower(trim($DEFAULT_LANG));
+  if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+    $header_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
+    foreach ($AVAILABLE_LANGUAGES as $available_lang) {
+      if ($header_lang == $available_lang) {
+        $_SESSION['mailcow_locale'] = strtolower(trim($header_lang));
+      }
+    }
+  }
+  else {
+    $_SESSION['mailcow_locale'] = strtolower(trim($DEFAULT_LANG));
+  }
 }
 if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) {
   $_SESSION['mailcow_locale'] = $_GET['lang'];
@@ -69,6 +79,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.bcc.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantaine.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
@@ -81,3 +92,4 @@ init_db_schema();
 if (isset($_SESSION['mailcow_cc_role'])) {
   set_acl();
 }
+$UI_TEXTS = customize('get', 'ui_texts');
diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php
index fbdabfdc..f5a2184f 100644
--- a/data/web/inc/sessions.inc.php
+++ b/data/web/inc/sessions.inc.php
@@ -26,11 +26,29 @@ if (!isset($_SESSION['SESS_REMOTE_UA'])) {
   $_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
 }
 
+// API
+if (!empty($_SERVER['HTTP_X_API_KEY'])) {
+  $stmt = $pdo->prepare("SELECT `username`, `allow_from` FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
+  $stmt->execute(array(
+    ':api_key' => preg_replace('/[^A-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
+  ));
+  $api_return = $stmt->fetch(PDO::FETCH_ASSOC);
+  if (!empty($api_return['username'])) {
+    if (in_array($_SERVER['REMOTE_ADDR'], explode(',', $api_return['allow_from']))) {
+      $_SESSION['mailcow_cc_username'] = $api_return['username'];
+      $_SESSION['mailcow_cc_role'] = 'admin';
+      $_SESSION['mailcow_cc_api'] = true;
+    }
+  }
+}
 // Update session cookie
 // setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME);
 
 // Check session
 function session_check() {
+  if ($_SESSION['mailcow_cc_api'] === true) {
+    return true;
+  }
   if (!isset($_SESSION['SESS_REMOTE_UA'])) {
     return false;
   }
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
index 10fbe4cc..bce8a20f 100644
--- a/data/web/inc/triggers.inc.php
+++ b/data/web/inc/triggers.inc.php
@@ -64,6 +64,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
 	}
 }
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+  // TODO: Move file upload to API?
 	if (isset($_POST["submit_main_logo"])) {
     if ($_FILES['main_logo']['error'] == 0) {
       customize('add', 'main_logo', $_FILES);
@@ -72,5 +73,16 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
 	if (isset($_POST["reset_main_logo"])) {
     customize('delete', 'main_logo');
 	}
+  // API cannot be controlled by API
+	if (isset($_POST["admin_api"])) {
+		admin_api('edit', $_POST);
+	}
+	if (isset($_POST["admin_api_regen_key"])) {
+		admin_api('regen_key', $_POST);
+	}
+  // Not available via API
+	if (isset($_POST["rspamd_ui"])) {
+		rspamd_ui('edit', $_POST);
+	}
 }
 ?>
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 12d8b3d9..5e5923b5 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -68,8 +68,12 @@ $autodiscover_config = array(
 );
 unset($https_port);
 
+// If false, we will use DEFAULT_LANG
+// Uses HTTP_ACCEPT_LANGUAGE header
+$DETECT_LANGUAGE = true;
+
 // Change default language, "de", "en", "es", "nl", "pt", "ru"
-$DEFAULT_LANG = 'en';
+$DEFAULT_LANG = 'de';
 
 // Available languages
 $AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'nl', 'pl', 'pt', 'ru', 'it');
@@ -92,13 +96,7 @@ $MAILCOW_APPS = array(
   array(
     'name' => 'SOGo',
     'link' => '/SOGo/',
-    'description' => 'SOGo is a web-based client for email, address book and calendar.'
-  ),
-  // array(
-    // 'name' => 'Roundcube',
-    // 'link' => '/rc/',
-    // 'description' => 'Roundcube is a web-based email client.',
-  // ),
+  )
 );
 
 // Rows until pagination begins
@@ -119,5 +117,6 @@ $OTP_LABEL = "mailcow UI";
 // Default "to" address in relay test tool
 $RELAY_TO = "null@hosted.mailcow.de";
 
-// Internal constants, can be ignored
-define("F2B", 1);
\ No newline at end of file
+// Quarantaine data age in days to keep
+$QUARANTAINE_AGE = 10;
+
diff --git a/data/web/index.php b/data/web/index.php
index 42087cdc..8f7dcb20 100644
--- a/data/web/index.php
+++ b/data/web/index.php
@@ -13,9 +13,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   header('Location: /user.php');
   exit();
 }
-
 require_once 'inc/header.inc.php';
 $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
 ?>
 <div class="container">
   <div class="row">
@@ -24,7 +24,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
         <div class="panel-heading"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?= $lang['login']['login']; ?></div>
         <div class="panel-body">
           <div class="text-center mailcow-logo"><img src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>" alt="mailcow"></div>
-          <legend>mailcow UI</legend>
+          <legend><?=$UI_TEXTS['main_name'];?></legend>
             <form method="post" autofill="off">
             <div class="form-group">
               <label class="sr-only" for="login_user"><?= $lang['login']['username']; ?></label>
@@ -65,7 +65,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
             <?php
             endif;
             ?>
-          <legend>mailcow Apps</legend>
+          <legend><?=$UI_TEXTS['apps_name'];?></legend>
           <?php
           foreach ($MAILCOW_APPS as $app):
           ?>
@@ -93,10 +93,14 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
         </div>
         <div id="collapse1" class="panel-collapse collapse">
           <div class="panel-body">
-            <p><span style="border-bottom: 1px dotted #999;">mailcow UI</span></p>
+            <?php if ($UI_TEXTS['help_text']): ?>
+            <p><?=$UI_TEXTS['help_text'];?></p>
+            <?php else: ?>
+            <p><span style="border-bottom: 1px dotted #999;"><?=$UI_TEXTS['main_name'];?></span></p>
             <p><?= $lang['start']['mailcow_panel_detail']; ?></p>
-            <p><span style="border-bottom: 1px dotted #999;">mailcow Apps</span></p>
+            <p><span style="border-bottom: 1px dotted #999;"><?=$UI_TEXTS['apps_name'];?></span></p>
             <p><?= $lang['start']['mailcow_apps_detail']; ?></p>
+            <?php endif; ?>
           </div>
         </div>
       </div>
diff --git a/data/web/js/admin.js b/data/web/js/admin.js
index 997e76a3..c0929538 100644
--- a/data/web/js/admin.js
+++ b/data/web/js/admin.js
@@ -5,199 +5,10 @@ jQuery(function($){
   var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
   function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
   function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
-  $("#refresh_postfix_log").on('click', function(e) {
-    e.preventDefault();
-    draw_postfix_logs();
-  });
-  $("#refresh_autodiscover_log").on('click', function(e) {
-    e.preventDefault();
-    draw_autodiscover_logs();
-  });
-  $("#refresh_dovecot_log").on('click', function(e) {
-    e.preventDefault();
-    draw_dovecot_logs();
-  });
-  $("#refresh_sogo_log").on('click', function(e) {
-    e.preventDefault();
-    draw_sogo_logs();
-  });
-  $("#refresh_fail2ban_log").on('click', function(e) {
-    e.preventDefault();
-    draw_fail2ban_logs();
-  });
-  $("#refresh_rspamd_history").on('click', function(e) {
-    e.preventDefault();
-    draw_rspamd_history();
-  });
   $("#import_dkim_legend").on('click', function(e) {
     e.preventDefault();
     $('#import_dkim_arrow').toggleClass("animation"); 
   });
-  function draw_autodiscover_logs() {
-    ft_autodiscover_logs = FooTable.init('#autodiscover_log', {
-      "columns": [
-        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name":"ua","title":"User-Agent","style":{"min-width":"200px"}},
-        {"name":"user","title":"Username","style":{"min-width":"200px"}},
-        {"name":"service","title":"Service"},
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/autodiscover/100',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw autodiscover log table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'autodiscover_log');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {"ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
-  function draw_postfix_logs() {
-    ft_postfix_logs = FooTable.init('#postfix_log', {
-      "columns": [
-        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
-        {"name":"message","title":lang.message},
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/postfix',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw postfix log table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'general_syslog');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {
-        "ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
-  function draw_fail2ban_logs() {
-    ft_fail2ban_logs = FooTable.init('#fail2ban_log', {
-      "columns": [
-        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
-        {"name":"message","title":lang.message},
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/fail2ban',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw fail2ban log table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'general_syslog');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {
-        "ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
-  function draw_sogo_logs() {
-    ft_sogo_logs = FooTable.init('#sogo_log', {
-      "columns": [
-        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
-        {"name":"message","title":lang.message},
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/sogo',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw sogo log table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'general_syslog');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {
-        "ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
-  function draw_dovecot_logs() {
-    ft_dovecot_logs = FooTable.init('#dovecot_log', {
-      "columns": [
-        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
-        {"name":"message","title":lang.message},
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/dovecot',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw dovecot log table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'general_syslog');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {
-        "ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
   function draw_domain_admins() {
     ft_domainadmins = FooTable.init('#domainadminstable', {
       "columns": [
@@ -278,108 +89,9 @@ jQuery(function($){
       "sorting": {"enabled": true}
     });
   }
-  function draw_rspamd_history() {
-    ft_rspamd_history = FooTable.init('#rspamd_history', {
-      "columns": [
-        {"name":"unix_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
-        {"name": "ip","title": "IP address","breakpoints": "all","style": {"minWidth": 88}},
-        {"name": "sender_mime","title": "From","breakpoints": "xs sm md","style": {"minWidth": 100}},
-        {"name": "rcpt_mime","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}},
-        {"name": "subject","title": "Subject","breakpoints": "all","style": {"word-break": "break-all","minWidth": 150}},
-        {"name": "action","title": "Action","style": {"minwidth": 82}},
-        {"name": "score","title": "Score","style": {"maxWidth": 110},},
-        {"name": "symbols","title": "Symbols","breakpoints": "all",},
-        {"name": "size","title": "Msg size","breakpoints": "all","style": {"minwidth": 50},"formatter": function(value){return humanFileSize(value);}},
-        {"name": "scan_time","title": "Scan time","breakpoints": "all","style": {"maxWidth": 72},},
-        {"name": "message-id","title": "ID","breakpoints": "all","style": {"minWidth": 130,"overflow": "hidden","textOverflow": "ellipsis","wordBreak": "break-all","whiteSpace": "normal"}},
-        {"name": "user","title": "Authenticated user","breakpoints": "xs sm md","style": {"minWidth": 100}}
-      ],
-      "rows": $.ajax({
-        dataType: 'json',
-        url: '/api/v1/get/logs/rspamd-history',
-        jsonp: false,
-        error: function () {
-          console.log('Cannot draw rspamd history table');
-        },
-        success: function (data) {
-          return process_table_data(data, 'rspamd_history');
-        }
-      }),
-      "empty": lang.empty,
-      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
-      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
-      "sorting": {"enabled": true},
-      "on": {
-        "ready.ft.table": function(e, ft){
-          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-          $(heading).children('.log-lines').text(function(){
-            var ft_paging = ft.use(FooTable.Paging)
-            return ft_paging.totalRows;
-          })
-        }
-      }
-    });
-  }
 
   function process_table_data(data, table) {
-    if (table == 'rspamd_history') {
-    $.each(data, function (i, item) {
-      item.rcpt_mime = item.rcpt_mime.join(",&#8203;");
-      Object.keys(item.symbols).map(function(key) {
-        var sym = item.symbols[key];
-        if (sym.score <= 0) {
-          sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
-        }
-        else {
-          sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
-        }
-        var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
-        if (sym.options) {
-          str += ' [' + sym.options.join(",") + "]";
-        }
-        item.symbols[key].str = str;
-      });
-      item.symbols = Object.keys(item.symbols).
-      map(function(key) {
-        return item.symbols[key];
-      }).sort(function(e1, e2) {
-        return Math.abs(e1.score) < Math.abs(e2.score);
-      }).map(function(e) {
-        return e.str;
-      }).join("<br>\n");
-      var scan_time = item.time_real.toFixed(3) + ' / ' + item.time_virtual.toFixed(3);
-      item.scan_time = {
-        "options": {
-          "sortValue": item.time_real
-        },
-        "value": scan_time
-      };
-      if (item.action === 'clean' || item.action === 'no action') {
-        item.action = "<div class='label label-success'>" + item.action + "</div>";
-      } else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
-        item.action = "<div class='label label-warning'>" + item.action + "</div>";
-      } else if (item.action === 'spam' || item.action === 'reject') {
-        item.action = "<div class='label label-danger'>" + item.action + "</div>";
-      } else {
-        item.action = "<div class='label label-info'>" + item.action + "</div>";
-      }
-      var score_content;
-      if (item.score < item.required_score) {
-        score_content = "[ <span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
-      } else {
-        score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
-      }
-      item.score = {
-        "options": {
-          "sortValue": item.score
-        },
-        "value": score_content
-      };
-      if (item.user == null) {
-        item.user = "none";
-      }
-    });
-    } else if (table == 'relayhoststable') {
+    if (table == 'relayhoststable') {
       $.each(data, function (i, item) {
         item.action = '<div class="btn-group">' +
           '<a href="#" data-toggle="modal" id="miau" data-target="#testRelayhostModal" data-relayhost-id="' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-stats"></span> Test</a>' +
@@ -409,48 +121,13 @@ jQuery(function($){
           '<a href="#" id="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
           '</div>';
       });
-    } else if (table == 'autodiscover_log') {
-      $.each(data, function (i, item) {
-        item.ua = '<span style="font-size:small">' + item.ua + '</span>';
-        if (item.service == "activesync") {
-          item.service = '<span class="label label-info">ActiveSync</span>';
-        }
-        else if (item.service == "imap") {
-          item.service = '<span class="label label-success">IMAP, SMTP, Cal-/CardDAV</span>';
-        }
-        else {
-          item.service = '<span class="label label-danger">' + item.service + '</span>';
-        }
-      });
-    } else if (table == 'general_syslog') {
-      $.each(data, function (i, item) {
-        item.message = escapeHtml(item.message);
-        var danger_class = ["emerg", "alert", "crit", "err"];
-        var warning_class = ["warning", "warn"];
-        var info_class = ["notice", "info", "debug"];
-        if (jQuery.inArray(item.priority, danger_class) !== -1) {
-          item.priority = '<span class="label label-danger">' + item.priority + '</span>';
-        } 
-        else if (jQuery.inArray(item.priority, warning_class) !== -1) {
-          item.priority = '<span class="label label-warning">' + item.priority + '</span>';
-        }
-        else if (jQuery.inArray(item.priority, info_class) !== -1) {
-          item.priority = '<span class="label label-info">' + item.priority + '</span>';
-        }
-      });
     }
     return data
   };
   // Initial table drawings
-  draw_postfix_logs();
-  draw_autodiscover_logs();
-  draw_dovecot_logs();
-  draw_sogo_logs();
-  draw_fail2ban_logs();
   draw_domain_admins();
   draw_fwd_hosts();
   draw_relayhosts();
-  draw_rspamd_history();
   // Relayhost
   $('#testRelayhostModal').on('show.bs.modal', function (e) {
     $('#test_relayhost_result').text("-");
@@ -485,31 +162,6 @@ jQuery(function($){
       $('#priv_key_pre').text(decoded_key);
     }
   })
-
-  $('.add_log_lines').on('click', function (e) {
-    e.preventDefault();
-    var log_table= $(this).data("table")
-    var new_nrows = ($(this).data("nrows") - 1)
-    var post_process = $(this).data("post-process")
-    var log_url = $(this).data("log-url")
-    if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
-      console.log("no data-table or data-nrows or log_url or data-post-process attr found");
-      return;
-    }
-    if (ft = FooTable.get($('#' + log_table))) {
-      var heading = ft.$el.parents('.tab-pane').find('.panel-heading')
-      var ft_paging = ft.use(FooTable.Paging)
-      var load_rows = ft_paging.totalRows + '-' + (ft_paging.totalRows + new_nrows)
-      $.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
-        if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
-        var rows = process_table_data(data, post_process);
-        var rows_now = (ft_paging.totalRows + data.length);
-        $(heading).children('.log-lines').text(rows_now)
-        mailcow_alert_box(data.length + lang.additional_rows, "success");
-        ft.rows.load(rows, true);
-      });
-    }
-  })
   // App links
   function add_table_row(table_id) {
     var row = $('<tr />');
diff --git a/data/web/js/api.js b/data/web/js/api.js
index 824e2d64..cc036d2f 100644
--- a/data/web/js/api.js
+++ b/data/web/js/api.js
@@ -102,6 +102,7 @@ $(document).ready(function() {
         return false;
       }
     }
+    // alert(JSON.stringify(api_attr));
     // If clicked element #edit_selected has data-item attribute, it is added to "items"
     if (typeof $(this).data('item') !== 'undefined') {
       var id = $(this).data('id');
@@ -126,7 +127,9 @@ $(document).ready(function() {
         jsonp: false,
         complete: function(data) {
           var response = (data.responseText);
-          response_obj = JSON.parse(response);
+          if (typeof response !== 'undefined' && response.length !== 0) {
+            response_obj = JSON.parse(response);
+          }
           if (api_reload_window === true) {
             window.location = window.location.href.split("#")[0];
           }
@@ -141,6 +144,11 @@ $(document).ready(function() {
     var id = $(this).data('id');
     var api_url = $(this).data('api-url');
     var api_attr = $(this).data('api-attr');
+    if (typeof $(this).data('api-reload-window') !== 'undefined') {
+      api_reload_window = $(this).data('api-reload-window');
+    } else {
+      api_reload_window = true;
+    }
     // If clicked button is in a form with the same data-id as the button,
     // we merge all input fields by {"name":"value"} into api-attr
     if ($(this).closest("form").data('id') == id) {
@@ -188,10 +196,20 @@ $(document).ready(function() {
       url: '/api/v1/' + api_url,
       jsonp: false,
       complete: function(data) {
-        // var reponse = (JSON.parse(data.responseText));
-        // console.log(reponse.type);
-        // console.log(reponse.msg);
-        window.location = window.location.href.split("#")[0];
+        var response = (data.responseText);
+        if (typeof response !== 'undefined' && response.length !== 0) {
+          response_obj = JSON.parse(response);
+          if (response_obj.type == 'success') {
+            $('form').formcache('clear');
+          }
+          else {
+            var add_modal = $('.modal.in').attr('id');
+            localStorage.setItem("add_modal", add_modal);
+          }
+        }
+        if (api_reload_window === true) {
+          window.location = window.location.href.split("#")[0];
+        }
       }
     });
   });
diff --git a/data/web/js/debug.js b/data/web/js/debug.js
new file mode 100644
index 00000000..48ebd5e9
--- /dev/null
+++ b/data/web/js/debug.js
@@ -0,0 +1,503 @@
+jQuery(function($){
+  // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
+  var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
+  function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
+  function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
+  $("#refresh_postfix_log").on('click', function(e) {
+    e.preventDefault();
+    draw_postfix_logs();
+  });
+  $("#refresh_autodiscover_log").on('click', function(e) {
+    e.preventDefault();
+    draw_autodiscover_logs();
+  });
+  $("#refresh_dovecot_log").on('click', function(e) {
+    e.preventDefault();
+    draw_dovecot_logs();
+  });
+  $("#refresh_sogo_log").on('click', function(e) {
+    e.preventDefault();
+    draw_sogo_logs();
+  });
+  $("#refresh_watchdog_log").on('click', function(e) {
+    e.preventDefault();
+    draw_watchdog_logs();
+  });
+  $("#refresh_api_log").on('click', function(e) {
+    e.preventDefault();
+    draw_api_logs();
+  });
+  $("#refresh_acme_log").on('click', function(e) {
+    e.preventDefault();
+    draw_acme_logs();
+  });
+  $("#refresh_fail2ban_log").on('click', function(e) {
+    e.preventDefault();
+    draw_fail2ban_logs();
+  });
+  $("#refresh_rspamd_history").on('click', function(e) {
+    e.preventDefault();
+    draw_rspamd_history();
+  });
+  function draw_autodiscover_logs() {
+    ft_autodiscover_logs = FooTable.init('#autodiscover_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"ua","title":"User-Agent","style":{"min-width":"200px"}},
+        {"name":"user","title":"Username","style":{"min-width":"200px"}},
+        {"name":"service","title":"Service"},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/autodiscover/100',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw autodiscover log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'autodiscover_log');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {"ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_postfix_logs() {
+    ft_postfix_logs = FooTable.init('#postfix_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/postfix',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw postfix log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'general_syslog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_watchdog_logs() {
+    ft_watchdog_logs = FooTable.init('#watchdog_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"service","title":"Service"},
+        {"name":"trend","title":"Trend"},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/watchdog',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw watchdog log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'watchdog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_api_logs() {
+    ft_api_logs = FooTable.init('#api_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"uri","title":"URI","style":{"width":"310px"}},
+        {"name":"method","title":"Method","style":{"width":"80px"}},
+        {"name":"remote","title":"IP","style":{"width":"80px"}},
+        {"name":"data","title":"Data","style":{"word-break":"break-all"}},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/api',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw api log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'apilog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_acme_logs() {
+    ft_acme_logs = FooTable.init('#acme_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/acme',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw acme log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'general_syslog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_fail2ban_logs() {
+    ft_fail2ban_logs = FooTable.init('#fail2ban_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/fail2ban',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw fail2ban log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'general_syslog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_sogo_logs() {
+    ft_sogo_logs = FooTable.init('#sogo_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/sogo',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw sogo log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'general_syslog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_dovecot_logs() {
+    ft_dovecot_logs = FooTable.init('#dovecot_log', {
+      "columns": [
+        {"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name":"priority","title":lang.priority,"style":{"width":"80px"}},
+        {"name":"message","title":lang.message},
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/dovecot',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw dovecot log table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'general_syslog');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+  function draw_rspamd_history() {
+    ft_rspamd_history = FooTable.init('#rspamd_history', {
+      "columns": [
+        {"name":"unix_time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
+        {"name": "ip","title": "IP address","breakpoints": "all","style": {"minWidth": 88}},
+        {"name": "sender_mime","title": "From","breakpoints": "xs sm md","style": {"minWidth": 100}},
+        {"name": "rcpt_mime","title": "To","breakpoints": "xs sm md","style": {"minWidth": 100}},
+        {"name": "subject","title": "Subject","breakpoints": "all","style": {"word-break": "break-all","minWidth": 150}},
+        {"name": "action","title": "Action","style": {"minwidth": 82}},
+        {"name": "score","title": "Score","style": {"maxWidth": 110},},
+        {"name": "symbols","title": "Symbols","breakpoints": "all",},
+        {"name": "size","title": "Msg size","breakpoints": "all","style": {"minwidth": 50},"formatter": function(value){return humanFileSize(value);}},
+        {"name": "scan_time","title": "Scan time","breakpoints": "all","style": {"maxWidth": 72},},
+        {"name": "message-id","title": "ID","breakpoints": "all","style": {"minWidth": 130,"overflow": "hidden","textOverflow": "ellipsis","wordBreak": "break-all","whiteSpace": "normal"}},
+        {"name": "user","title": "Authenticated user","breakpoints": "xs sm md","style": {"minWidth": 100}}
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/logs/rspamd-history',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw rspamd history table');
+        },
+        success: function (data) {
+          return process_table_data(data, 'rspamd_history');
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(e, ft){
+          heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+          $(heading).children('.log-lines').text(function(){
+            var ft_paging = ft.use(FooTable.Paging)
+            return ft_paging.totalRows;
+          })
+        }
+      }
+    });
+  }
+
+  function process_table_data(data, table) {
+    if (table == 'rspamd_history') {
+    $.each(data, function (i, item) {
+      item.rcpt_mime = item.rcpt_mime.join(",&#8203;");
+      Object.keys(item.symbols).map(function(key) {
+        var sym = item.symbols[key];
+        if (sym.score <= 0) {
+          sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
+        }
+        else {
+          sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
+        }
+        var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
+        if (sym.options) {
+          str += ' [' + sym.options.join(",") + "]";
+        }
+        item.symbols[key].str = str;
+      });
+      item.symbols = Object.keys(item.symbols).
+      map(function(key) {
+        return item.symbols[key];
+      }).sort(function(e1, e2) {
+        return Math.abs(e1.score) < Math.abs(e2.score);
+      }).map(function(e) {
+        return e.str;
+      }).join("<br>\n");
+      var scan_time = item.time_real.toFixed(3) + ' / ' + item.time_virtual.toFixed(3);
+      item.scan_time = {
+        "options": {
+          "sortValue": item.time_real
+        },
+        "value": scan_time
+      };
+      if (item.action === 'clean' || item.action === 'no action') {
+        item.action = "<div class='label label-success'>" + item.action + "</div>";
+      } else if (item.action === 'rewrite subject' || item.action === 'add header' || item.action === 'probable spam') {
+        item.action = "<div class='label label-warning'>" + item.action + "</div>";
+      } else if (item.action === 'spam' || item.action === 'reject') {
+        item.action = "<div class='label label-danger'>" + item.action + "</div>";
+      } else {
+        item.action = "<div class='label label-info'>" + item.action + "</div>";
+      }
+      var score_content;
+      if (item.score < item.required_score) {
+        score_content = "[ <span class='text-success'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
+      } else {
+        score_content = "[ <span class='text-danger'>" + item.score.toFixed(2) + " / " + item.required_score + "</span> ]";
+      }
+      item.score = {
+        "options": {
+          "sortValue": item.score
+        },
+        "value": score_content
+      };
+      if (item.user == null) {
+        item.user = "none";
+      }
+    });
+    } else if (table == 'autodiscover_log') {
+      $.each(data, function (i, item) {
+        item.ua = '<span style="font-size:small">' + item.ua + '</span>';
+        if (item.service == "activesync") {
+          item.service = '<span class="label label-info">ActiveSync</span>';
+        }
+        else if (item.service == "imap") {
+          item.service = '<span class="label label-success">IMAP, SMTP, Cal-/CardDAV</span>';
+        }
+        else {
+          item.service = '<span class="label label-danger">' + item.service + '</span>';
+        }
+      });
+    } else if (table == 'watchdog') {
+      $.each(data, function (i, item) {
+        if (item.message == null) {
+          item.message = 'Health level: ' + item.lvl + '% (' + item.hpnow + '/' + item.hptotal + ')';
+          if (item.hpdiff < 0) {
+            item.trend = '<span class="label label-danger"><span class="glyphicon glyphicon-arrow-down"></span> ' + item.hpdiff + '</span>';
+          }
+          else if (item.hpdiff == 0) {
+            item.trend = '<span class="label label-info"><span class="glyphicon glyphicon-arrow-right"></span> ' + item.hpdiff + '</span>';
+          }
+          else {
+            item.trend = '<span class="label label-success"><span class="glyphicon glyphicon-arrow-up"></span> ' + item.hpdiff + '</span>';
+          }
+        }
+        else {
+          item.trend = '';
+          item.service = '';
+        }
+      });
+    } else if (table == 'general_syslog') {
+      $.each(data, function (i, item) {
+        if (item === null) { return true; }
+        item.message = escapeHtml(item.message);
+        var danger_class = ["emerg", "alert", "crit", "err"];
+        var warning_class = ["warning", "warn"];
+        var info_class = ["notice", "info", "debug"];
+        if (jQuery.inArray(item.priority, danger_class) !== -1) {
+          item.priority = '<span class="label label-danger">' + item.priority + '</span>';
+        } else if (jQuery.inArray(item.priority, warning_class) !== -1) {
+          item.priority = '<span class="label label-warning">' + item.priority + '</span>';
+        } else if (jQuery.inArray(item.priority, info_class) !== -1) {
+          item.priority = '<span class="label label-info">' + item.priority + '</span>';
+        }
+      });
+    } else if (table == 'apilog') {
+      $.each(data, function (i, item) {
+        if (item === null) { return true; }
+        if (item.method == 'GET') {
+          item.method = '<span class="label label-success">' + item.method + '</span>';
+        } else if (item.method == 'POST') {
+          item.method = '<span class="label label-warning">' + item.method + '</span>';
+        }
+      });
+    }
+    return data
+  };
+  $('.add_log_lines').on('click', function (e) {
+    e.preventDefault();
+    var log_table= $(this).data("table")
+    var new_nrows = ($(this).data("nrows") - 1)
+    var post_process = $(this).data("post-process")
+    var log_url = $(this).data("log-url")
+    if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
+      console.log("no data-table or data-nrows or log_url or data-post-process attr found");
+      return;
+    }
+    if (ft = FooTable.get($('#' + log_table))) {
+      var heading = ft.$el.parents('.tab-pane').find('.panel-heading')
+      var ft_paging = ft.use(FooTable.Paging)
+      var load_rows = ft_paging.totalRows + '-' + (ft_paging.totalRows + new_nrows)
+      $.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
+        if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
+        var rows = process_table_data(data, post_process);
+        var rows_now = (ft_paging.totalRows + data.length);
+        $(heading).children('.log-lines').text(rows_now)
+        mailcow_alert_box(data.length + lang.additional_rows, "success");
+        ft.rows.load(rows, true);
+      });
+    }
+  })
+  // Initial table drawings
+  draw_postfix_logs();
+  draw_autodiscover_logs();
+  draw_dovecot_logs();
+  draw_sogo_logs();
+  draw_watchdog_logs();
+  draw_acme_logs();
+  draw_api_logs();
+  draw_fail2ban_logs();
+  draw_rspamd_history();
+
+});
\ No newline at end of file
diff --git a/data/web/js/formcache.min.js b/data/web/js/formcache.min.js
new file mode 100644
index 00000000..10a02884
--- /dev/null
+++ b/data/web/js/formcache.min.js
@@ -0,0 +1,10 @@
+/*!
+ * Form Cache v@VERSION
+ * https://github.com/fengyuanchen/formcache
+ *
+ * Copyright 2014 Fengyuan Chen
+ * Released under the MIT license
+ *
+ * Date: @DATE
+ */
+!function(t){"function"==typeof define&&define.amd?define("formcache",["jquery"],t):t(jQuery)}(function(t){"use strict";var e=t(window),i=window.sessionStorage,s=window.localStorage,n="undefined",o=".formcache",a=/[\.\*\+\^\$\:\!\[\]#>~]+/g,c="change"+o,h="beforeunload"+o,r=function(t){return"checkbox"===t.type||"radio"===t.type},f=function(t){return parseInt(t,10)},u=function(e,i){this.form=e,this.$form=t(e),this.defaults=t.extend({},u.DEFAULTS,t.isPlainObject(i)?i:{}),this.init()};u.prototype={constructor:u,init:function(){var e=this.defaults;e.maxAge=Math.abs(e.maxAge||e.maxage),e.autoStore=Boolean(e.autoStore||e.autostore),this.initKey(),this.initStorage(),this.caches=this.storage.caches,this.index=0,this.activeIndex=0,this.storing=null,t.isArray(e.controls)||(e.controls=[]),this.$controls=this.$form.find(e.controls.join()).not(":file"),this.addListeners(),this.outputCache()},initKey:function(){var e=this.$form,i=this.defaults.key||e.data("key");i||(t("form").each(function(e){t(this).data("key",e)}),i=e.data("key")),this.key=location.pathname+"#formcache-"+i},initStorage:function(){var e,n=this.defaults,o=this.key,a=new Date,c={date:a,maxAge:n.maxAge,caches:[]};i&&(e=i.getItem(o)),!e&&s&&(e=s.getItem(o)),e="string"==typeof e?JSON.parse(e):null,t.isPlainObject(e)?"number"==typeof e.maxAge&&(a-new Date(e.date))/1e3>e.maxAge&&(e=c):e=c,this.storage=e},addListeners:function(){this.defaults.autoStore&&(this.$controls.on(c,t.proxy(this.change,this)),e.on(h,t.proxy(this.beforeunload,this)))},removeListeners:function(){this.defaults.autoStore&&(this.$controls.off(c,this.change),e.off(h,this.beforeunload))},change:function(e){var i,s,n=e.target,o=t(n),c=o.attr("name"),h=[];c&&(i=c.replace(a,""),this.$controls.filter('[name*="'+i+'"]').each(function(){r(n)?h.push(this.checked):(s=t(this).val(),s&&h.push(s))}),h.length&&(this.update(c,h),clearTimeout(this.storing),this.storing=setTimeout(t.proxy(this.store,this),1e3)))},beforeunload:function(){this.update(),this.store()},update:function(t,e){var i=this.activeIndex||this.index,s=this.getCache(i);"string"==typeof t?s[t]=e:s=this.serialize(),this.setCache(i,s)},serialize:function(){var e={};return this.$controls.each(function(){var i,s,n=t(this),o=n.attr("name");o&&(i=e[o],i=t.isArray(i)?i:[],r(this)?i.push(this.checked):(s=n.val(),s&&i.push(s)),i.length&&(e[o]=i))}),e},getCache:function(t){return this.caches[f(t)||this.index]||{}},getCaches:function(){return this.caches},setCache:function(e,i){typeof i===n&&(i=e,e=NaN),t.isPlainObject(i)&&(e=f(e)||this.index,this.caches[e]=i,this.store())},setCaches:function(e){t.isArray(e)&&(this.caches=e,this.store())},removeCache:function(t){this.caches.splice(f(t)||this.index,1),this.store()},removeCaches:function(){this.caches=[],this.store()},outputCache:function(e){var i=this.getCache(e);t.isPlainObject(i)&&(this.activeIndex=f(e)||this.index,i=t.extend(!0,{},i),this.$controls.each(function(){var e,s,n=t(this),o=n.attr("name");o&&(e=i[o],t.isArray(e)&&e.length&&(s=e.shift(),r(this)?this.checked=s:n.val(s)))}))},store:function(){var t=this.storage,e=this.key,n=this.defaults;t.date=new Date,t.maxAge=n.maxAge,t=JSON.stringify(t),n.session&&i&&i.setItem(e,t),n.local&&s&&s.setItem(e,t)},clear:function(){var t=this.key,e=this.defaults;e.session&&i&&i.removeItem(t),e.local&&s&&s.removeItem(t)},destroy:function(){this.removeListeners(),this.$form.removeData("formcache")}},u.DEFAULTS={key:"",local:!0,session:0,autoStore:!0,maxAge:void 0,controls:["select","textarea","input"]},u.setDefaults=function(e){t.extend(u.DEFAULTS,e)},u.other=t.fn.formcache,t.fn.formcache=function(e){var i,s=[].slice.call(arguments,1);return this.each(function(){var n,o=t(this),a=o.data("formcache");a||o.data("formcache",a=new u(this,e)),"string"==typeof e&&t.isFunction(n=a[e])&&(i=n.apply(a,s))}),typeof i!==n?i:this},t.fn.formcache.Constructor=u,t.fn.formcache.setDefaults=u.setDefaults,t.fn.formcache.noConflict=function(){return t.fn.formcache=u.other,this},t(function(){t('form[data-toggle="formcache"]').formcache()})});
\ No newline at end of file
diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index 9e3c6868..b30ceb80 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -319,9 +319,9 @@ jQuery(function($){
       "columns": [
         {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
         {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
-        {"name":"type","title":"Type"},
-        {"name":"local_dest","title":"Local destination"},
-        {"name":"bcc_dest","title":"BCC destination/s"},
+        {"name":"type","title":lang.bcc_type},
+        {"name":"local_dest","title":lang.bcc_local_dest},
+        {"name":"bcc_dest","title":lang.bcc_destinations},
         {"name":"domain","title":lang.domain,"breakpoints":"xs sm"},
         {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
         {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
diff --git a/data/web/js/quarantaine.js b/data/web/js/quarantaine.js
new file mode 100644
index 00000000..ba9e3bb3
--- /dev/null
+++ b/data/web/js/quarantaine.js
@@ -0,0 +1,82 @@
+// Base64 functions
+var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
+jQuery(function($){
+  // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
+  var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
+  function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
+  function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
+
+  function draw_quarantaine_table() {
+    ft_quarantainetable = FooTable.init('#quarantainetable', {
+      "columns": [
+        {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
+        {"name":"id","type":"ID","filterable": false,"sorted": true,"direction":"DESC","title":"ID","style":{"width":"50px"}},
+        {"name":"qid","type":"text","title":lang.qid,"style":{"width":"125px"}},
+        {"name":"sender","title":lang.sender,"breakpoints":"xs sm"},
+        {"name":"rcpt","title":lang.rcpt, "type": "text"},
+        {"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}},
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"205px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+      ],
+      "rows": $.ajax({
+        dataType: 'json',
+        url: '/api/v1/get/quarantaine/all',
+        jsonp: false,
+        error: function () {
+          console.log('Cannot draw quarantaine table');
+        },
+        success: function (data) {
+          $.each(data, function (i, item) {
+            item.action = '<div class="btn-group">' +
+              '<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' +
+              '<a href="#" id="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
+              '</div>';
+            item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
+          });
+        }
+      }),
+      "empty": lang.empty,
+      "paging": {"enabled": true,"limit": 5,"size": pagination_size},
+      "sorting": {"enabled": true},
+      "on": {
+        "ready.ft.table": function(ev, ft){
+          $('.show_qid_info').on('click', function (e) {
+            e.preventDefault();
+            var qitem = $(this).data('item');
+            $('#qidDetailModal').modal('show');
+            $( "#qid_error" ).hide();
+            $.ajax({
+              url: '/inc/ajax/qitem_details.php',
+              data: { id: qitem },
+              dataType: 'json',
+              success: function(data){
+                if (typeof data.error !== 'undefined') {
+                  $( "#qid_error" ).text(data.error);
+                  $( "#qid_error" ).show();
+                }
+                $('#qid_detail_subj').text(escapeHtml(data.subject));
+                $('#qid_detail_text').text(escapeHtml(data.text_plain));
+                if (typeof data.attachments !== 'undefined') {
+                  $( "#qid_detail_atts" ).text('');
+                  $.each(data.attachments, function( index, value ) {
+                    $( "#qid_detail_atts" ).append(
+                      '<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
+                      ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
+                    );
+                  });
+                }
+                else {
+                  $( "#qid_detail_atts" ).text('-');
+                }
+              }
+            });
+          })
+        }
+      },
+      "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
+    });
+  }
+
+  // Initial table drawings
+  draw_quarantaine_table();
+
+});
\ No newline at end of file
diff --git a/data/web/json_api.php b/data/web/json_api.php
index cf6d8416..3bcb2ac4 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -15,6 +15,41 @@ delete/alias => POST data:
 header('Content-Type: application/json');
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
 error_reporting(0);
+
+function api_log($postarray) {
+  global $redis;
+  $data_var = array();
+  foreach ($postarray as $data => &$value) {
+    if ($data == 'csrf_token') {
+      continue;
+    }
+    if ($value = json_decode($value, true)) {
+      unset($value["csrf_token"]);
+      $value = json_encode($value);
+    }
+    $data_var[] = $data . "='" . $value . "'";
+  }
+  try {
+    $log_line = array(
+      'time' => time(),
+      'uri' => $_SERVER['REQUEST_URI'],
+      'method' => $_SERVER['REQUEST_METHOD'],
+      'remote' => $_SERVER['REMOTE_ADDR'],
+      'data' => implode(', ', $data_var)
+    );
+    $redis->lPush('API_LOG', json_encode($log_line));
+  }
+  catch (RedisException $e) {
+    $_SESSION['return'] = array(
+      'type' => 'danger',
+      'msg' => 'Redis: '.$e
+    );
+    return false;
+  }
+}
+
+api_log($_POST);
+
 if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
   if (isset($_GET['query'])) {
 
@@ -258,39 +293,6 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
-          case "bcc":
-            if (isset($_POST['attr'])) {
-              $attr = (array)json_decode($_POST['attr'], true);
-              if (bcc('add', $attr) === false) {
-                if (isset($_SESSION['return'])) {
-                  echo json_encode($_SESSION['return']);
-                }
-                else {
-                  echo json_encode(array(
-                    'type' => 'error',
-                    'msg' => 'Cannot add item'
-                  ));
-                }
-              }
-              else {
-                if (isset($_SESSION['return'])) {
-                  echo json_encode($_SESSION['return']);
-                }
-                else {
-                  echo json_encode(array(
-                    'type' => 'success',
-                    'msg' => 'Task completed'
-                  ));
-                }
-              }
-            }
-            else {
-              echo json_encode(array(
-                'type' => 'error',
-                'msg' => 'Cannot find attributes in post data'
-              ));
-            }
-          break;
           case "domain-policy":
             if (isset($_POST['attr'])) {
               $attr = (array)json_decode($_POST['attr'], true);
@@ -555,6 +557,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "bcc":
+            if (isset($_POST['attr'])) {
+              $attr = (array)json_decode($_POST['attr'], true);
+              if (bcc('add', $attr) === false) {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'error',
+                    'msg' => 'Cannot add item'
+                  ));
+                }
+              }
+              else {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'success',
+                    'msg' => 'Task completed'
+                  ));
+                }
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Cannot find attributes in post data'
+              ));
+            }
+          break;
         }
       break;
       case "get":
@@ -752,6 +787,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               break;
             }
           break;
+
           case "relayhost":
             switch ($object) {
               case "all":
@@ -870,6 +906,54 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
                   echo '{}';
                 }
               break;
+              case "watchdog":
+                // 0 is first record, so empty is fine
+                if (isset($extra)) {
+                  $extra = preg_replace('/[^\d\-]/i', '', $extra);
+                  $logs = get_logs('watchdog-mailcow', $extra);
+                }
+                else {
+                  $logs = get_logs('watchdog-mailcow');
+                }
+                if (isset($logs) && !empty($logs)) {
+                  echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+                else {
+                  echo '{}';
+                }
+              break;
+              case "acme":
+                // 0 is first record, so empty is fine
+                if (isset($extra)) {
+                  $extra = preg_replace('/[^\d\-]/i', '', $extra);
+                  $logs = get_logs('acme-mailcow', $extra);
+                }
+                else {
+                  $logs = get_logs('acme-mailcow');
+                }
+                if (isset($logs) && !empty($logs)) {
+                  echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+                else {
+                  echo '{}';
+                }
+              break;
+              case "api":
+                // 0 is first record, so empty is fine
+                if (isset($extra)) {
+                  $extra = preg_replace('/[^\d\-]/i', '', $extra);
+                  $logs = get_logs('api-mailcow', $extra);
+                }
+                else {
+                  $logs = get_logs('api-mailcow');
+                }
+                if (isset($logs) && !empty($logs)) {
+                  echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+                else {
+                  echo '{}';
+                }
+              break;
               case "rspamd-history":
                 // 0 is first record, so empty is fine
                 if (isset($extra)) {
@@ -1088,7 +1172,6 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
                   echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
                 }
               break;
-
               default:
                 $data = bcc('details', $object);
                 if (!empty($data)) {
@@ -1230,6 +1313,29 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               break;
             }
           break;
+          case "quarantaine":
+            // "all" will not print details
+            switch ($object) {
+              case "all":
+                $data = quarantaine('get');
+                if (!isset($data) || empty($data)) {
+                  echo '{}';
+                }
+                else {
+                  echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+              break;
+              default:
+                $data = quarantaine('details', $object);
+                if (!isset($data) || empty($data)) {
+                  echo '{}';
+                }
+                else {
+                  echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+                }
+              break;
+            }
+          break;
           case "alias-domain":
             switch ($object) {
               case "all":
@@ -1342,13 +1448,10 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "u2f-registration":
             header('Content-Type: application/javascript');
             if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
-              list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
+              $data = $u2f->getRegisterData(get_u2f_registrations($object));
+              list($req, $sigs) = $data;
               $_SESSION['regReq'] = json_encode($req);
-              $_SESSION['regSigs'] = json_encode($sigs);
-              echo 'var req = ' . json_encode($req) . ';';
-              echo 'var registeredKeys = ' . json_encode($sigs) . ';';
-              echo 'var appId = req.appId;';
-              echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
+              echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
             }
             else {
               return;
@@ -1357,19 +1460,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
           case "u2f-authentication":
             header('Content-Type: application/javascript');
             if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
-              $auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
-              $challenge = $auth_data[0]->challenge;
-              $appId = $auth_data[0]->appId;
-              foreach ($auth_data as $each) {
-                $key = array(); // Empty array
-                $key['version']   = $each->version;
-                $key['keyHandle'] = $each->keyHandle;
-                $registeredKey[]  = $key;
-              }
-              $_SESSION['authReq']  = json_encode($auth_data);
-              echo 'var appId = "' . $appId . '";';
-              echo 'var challenge = ' . json_encode($challenge) . ';';
-              echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
+              $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
+              $_SESSION['authReq']  = $reqs;
+              echo 'var req = ' . $reqs . ';';
             }
             else {
               return;
@@ -1546,6 +1639,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "qitem":
+            if (isset($_POST['items'])) {
+              $items = (array)json_decode($_POST['items'], true);
+              if (is_array($items)) {
+                if (quarantaine('delete', array('id' => $items)) === false) {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'error',
+                      'msg' => 'Deletion of items/s failed'
+                    ));
+                  }
+                }
+                else {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'success',
+                      'msg' => 'Task completed'
+                    ));
+                  }
+                }
+              }
+              else {
+                echo json_encode(array(
+                  'type' => 'error',
+                  'msg' => 'Cannot find id array in post data'
+                ));
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Cannot find items in post data'
+              ));
+            }
+          break;
           case "bcc":
             if (isset($_POST['items'])) {
               $items = (array)json_decode($_POST['items'], true);
@@ -2042,6 +2176,50 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
       break;
       case "edit":
         switch ($category) {
+          case "bcc":
+            if (isset($_POST['items']) && isset($_POST['attr'])) {
+              $items = (array)json_decode($_POST['items'], true);
+              $attr = (array)json_decode($_POST['attr'], true);
+              $postarray = array_merge(array('id' => $items), $attr);
+              if (is_array($postarray['id'])) {
+                if (bcc('edit', $postarray) === false) {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'error',
+                      'msg' => 'Edit failed'
+                    ));
+                  }
+                  exit();
+                }
+                else {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'success',
+                      'msg' => 'Task completed'
+                    ));
+                  }
+                }
+              }
+              else {
+                echo json_encode(array(
+                  'type' => 'error',
+                  'msg' => 'Incomplete post data'
+                ));
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Incomplete post data'
+              ));
+            }
+          break;
           case "alias":
             if (isset($_POST['items']) && isset($_POST['attr'])) {
               $items = (array)json_decode($_POST['items'], true);
@@ -2260,6 +2438,85 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "qitem":
+            if (isset($_POST['items']) && isset($_POST['attr'])) {
+              $items = (array)json_decode($_POST['items'], true);
+              $attr = (array)json_decode($_POST['attr'], true);
+              $postarray = array_merge(array('id' => $items), $attr);
+              if (is_array($postarray['id'])) {
+                if (quarantaine('edit', $postarray) === false) {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'error',
+                      'msg' => 'Edit failed'
+                    ));
+                  }
+                  exit();
+                }
+                else {
+                  if (isset($_SESSION['return'])) {
+                    echo json_encode($_SESSION['return']);
+                  }
+                  else {
+                    echo json_encode(array(
+                      'type' => 'success',
+                      'msg' => 'Task completed'
+                    ));
+                  }
+                }
+              }
+              else {
+                echo json_encode(array(
+                  'type' => 'error',
+                  'msg' => 'Incomplete post data'
+                ));
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Incomplete post data'
+              ));
+            }
+          break;
+          case "quarantaine":
+            // Edit settings, does not need IDs
+            if (isset($_POST['attr'])) {
+              $postarray = json_decode($_POST['attr'], true);
+              if (quarantaine('edit', $postarray) === false) {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'error',
+                    'msg' => 'Edit failed'
+                  ));
+                }
+                exit();
+              }
+              else {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'success',
+                    'msg' => 'Task completed'
+                  ));
+                }
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Incomplete post data'
+              ));
+            }
+          break;
           case "time_limited_alias":
             if (isset($_POST['items']) && isset($_POST['attr'])) {
               $items = (array)json_decode($_POST['items'], true);
@@ -2436,51 +2693,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
                 'msg' => 'Incomplete post data'
               ));
             }
-          break;
-          case "bcc":
-            if (isset($_POST['items']) && isset($_POST['attr'])) {
-              $items = (array)json_decode($_POST['items'], true);
-              $attr = (array)json_decode($_POST['attr'], true);
-              $postarray = array_merge(array('id' => $items), $attr);
-              if (is_array($postarray['id'])) {
-                if (bcc('edit', $postarray) === false) {
-                  if (isset($_SESSION['return'])) {
-                    echo json_encode($_SESSION['return']);
-                  }
-                  else {
-                    echo json_encode(array(
-                      'type' => 'error',
-                      'msg' => 'Edit failed'
-                    ));
-                  }
-                  exit();
-                }
-                else {
-                  if (isset($_SESSION['return'])) {
-                    echo json_encode($_SESSION['return']);
-                  }
-                  else {
-                    echo json_encode(array(
-                      'type' => 'success',
-                      'msg' => 'Task completed'
-                    ));
-                  }
-                }
-              }
-              else {
-                echo json_encode(array(
-                  'type' => 'error',
-                  'msg' => 'Incomplete post data'
-                ));
-              }
-            }
-            else {
-              echo json_encode(array(
-                'type' => 'error',
-                'msg' => 'Incomplete post data'
-              ));
-            }
-          break;
+          break;          
           case "resource":
             if (isset($_POST['items']) && isset($_POST['attr'])) {
               $items = (array)json_decode($_POST['items'], true);
@@ -2531,7 +2744,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               $attr = (array)json_decode($_POST['attr'], true);
               $postarray = array_merge(array('domain' => $items), $attr);
               if (is_array($postarray['domain'])) {
-                if (mailbox('edit', 'domain', $postarray) === false) {
+                if (mailbox('edit', 'domain', $postarray)) {
                   if (isset($_SESSION['return'])) {
                     echo json_encode($_SESSION['return']);
                   }
@@ -2824,6 +3037,41 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
               ));
             }
           break;
+          case "ui_texts":
+            // No items
+            if (isset($_POST['attr'])) {
+              $attr = (array)json_decode($_POST['attr'], true);
+              if (customize('edit', 'ui_texts', $attr) === false) {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'error',
+                    'msg' => 'Edit failed'
+                  ));
+                }
+                exit();
+              }
+              else {
+                if (isset($_SESSION['return'])) {
+                  echo json_encode($_SESSION['return']);
+                }
+                else {
+                  echo json_encode(array(
+                    'type' => 'success',
+                    'msg' => 'Task completed'
+                  ));
+                }
+              }
+            }
+            else {
+              echo json_encode(array(
+                'type' => 'error',
+                'msg' => 'Incomplete post data'
+              ));
+            }
+          break;
           case "self":
             // No items, logged-in user, users and domain admins
             if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php
index 2dd5cd9e..700be0c7 100644
--- a/data/web/lang/lang.de.php
+++ b/data/web/lang/lang.de.php
@@ -5,9 +5,9 @@
 
 $lang['footer']['loading'] = 'Einen Moment bitte...';
 $lang['header']['restart_sogo'] = 'SOGo neustarten';
-$lang['footer']['restart_sogo'] = 'SOGo neustarten';
+$lang['footer']['restart_container'] = 'Container neustarten';
 $lang['footer']['restart_now'] = 'Jetzt neustarten';
-$lang['footer']['restart_sogo_info'] = 'Einige Änderungen an Domains benötigen einen Neustart SOGos. Hier können Sie SOGo neustarten.<br><br><b>Wichtig:</b> Ein korrekter Neustart SOGos kann eine Weile in Anspruch nehmen, bitte warten Sie, bis der Prozess vollständig beendet wurde.';
+$lang['footer']['restart_container_info'] = '<b>Wichtig:</b> Ein korrekter Neustart eines Containers kann eine Weile in Anspruch nehmen, bitte warten Sie, bis der Prozess vollständig beendet wurde.';
 
 $lang['footer']['confirm_delete'] = 'Löschen bestätigen';
 $lang['footer']['delete_these_items'] = 'Sind Sie sicher, dass die Änderungen an Elementen mit folgender ID durchgeführt werden sollen?';
@@ -73,7 +73,7 @@ $lang['danger']['resource_invalid'] = 'Ressourcenname ist ungültig';
 $lang['danger']['description_invalid'] = 'Ressourcenbeschreibung ist ungültig';
 $lang['danger']['mailbox_invalid_suggest'] = 'Mailboxname ist ungültig, meinten Sie vielleicht %s?';
 $lang['danger']['is_alias'] = '%s lautet bereits eine Alias-Adresse';
-$lang['danger']['is_alias_or_mailbox'] = "Eine Mailbox oder ein Alias mit der Adresse %s ist bereits vorhanden";
+$lang['danger']['is_alias_or_mailbox'] = "Eine Mailbox, ein Alias oder eine sich aus einer Alias-Domain ergebende Adresse mit dem Namen %s ist bereits vorhanden";
 $lang['danger']['is_spam_alias'] = '%s lautet bereits eine Spam-Alias-Adresse';
 $lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein';
 $lang['danger']['domain_not_found'] = 'Domain %s nicht gefunden';
@@ -121,6 +121,8 @@ $lang['user']['did_you_know'] = '<b>Wussten Sie schon?</b> Sie können Ihre E-Ma
 $lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliasse';
+$lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen';
+$lang['user']['direct_aliases'] = 'Direkte Alias-Adressen';
 $lang['user']['domain_aliases'] = 'Domain-Alias Adressen';
 $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)';
 $lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als Benutzer';
@@ -248,7 +250,7 @@ $lang['mailbox']['target_domain'] = 'Ziel-Domain';
 $lang['mailbox']['target_address'] = 'Ziel-Adresse';
 $lang['mailbox']['username'] = 'Benutzername';
 $lang['mailbox']['fname'] = 'Name';
-$lang['mailbox']['filter_table'] = 'Tabelle filtern';
+$lang['mailbox']['filter_table'] = 'Filtern';
 $lang['mailbox']['yes'] = '&#10004;';
 $lang['mailbox']['no'] = '&#10008;';
 $lang['mailbox']['quota'] = 'Speicherplatz';
@@ -533,6 +535,10 @@ $lang['admin']['host'] = 'Host';
 $lang['admin']['source'] = 'Quelle';
 $lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen';
 $lang['admin']['add_relayhost'] = 'Relayhost hinzufügen';
+$lang['admin']['api_allow_from'] = "IP-Adressen für Zugriff";
+$lang['admin']['api_key'] = "API-Key";
+$lang['admin']['activate_api'] = "API aktivieren";
+$lang['admin']['regen_api_key'] = "API-Key regenerieren";
 $lang['delete']['remove_forwardinghost_warning'] = '<b>Warnung:</b> Sie entfernen den Weiterleitungs-Host <b>%s</b>!';
 $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt";
 $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt";
@@ -559,4 +565,81 @@ $lang['admin']['reset_default'] = "Auf Standard zurücksetzen";
 $lang['admin']['merged_vars_hint'] = 'Ausgegraute Zeilen wurden aus der Datei <code>vars.inc.(local.)php</code> gelesen und können nicht mittels UI verändert werden.';
 $lang['mailbox']['waiting'] = "Wartend";
 $lang['mailbox']['status'] = "Status";
-$lang['mailbox']['running'] = "In Ausführung";
\ No newline at end of file
+$lang['mailbox']['running'] = "In Ausführung";
+
+$lang['admin']['ui_texts'] = "UI Label und Texte";
+$lang['admin']['help_text'] = "Hilfstext unter Login-Maske (HTML zulässig)";
+$lang['admin']['main_name'] = '"mailcow UI" Name';
+$lang['admin']['apps_name'] = '"mailcow Apps" Name';
+
+$lang['admin']['customize'] = "UI Anpassung";
+$lang['admin']['change_logo'] = "Logo ändern";
+$lang['admin']['logo_info'] = "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.";
+$lang['admin']['upload'] = "Hochladen";
+$lang['admin']['app_links'] = "App Links";
+$lang['admin']['app_name'] = "App Name";
+$lang['admin']['link'] = "Link";
+$lang['admin']['remove_row'] = "Entfernen";
+$lang['admin']['add_row'] = "Reihe hinzufügen";
+$lang['admin']['reset_default'] = "Zurücksetzen auf Standard";
+$lang['admin']['merged_vars_hint'] = 'Ausgegraute Reihen wurden aus der Datei <code>vars.inc.(local.)php</code> gelesen und können hier nicht verändert werden.';
+
+$lang['edit']['tls_policy'] = "TLS Policy ändern";
+$lang['edit']['spam_score'] = "Einen benutzerdefiniterten Spam-Score festlegen";
+$lang['edit']['spam_policy'] = "Hinzufügen und Entfernen von Einträgen in White- und Blacklists";
+$lang['edit']['delimiter_action'] = "Delimiter Aktion verändern";
+$lang['edit']['syncjobs'] = "Sync job hinzufügen oder anpassen";
+$lang['edit']['eas_reset'] = "ActiveSync Geräte-Cache zurücksetzen";
+$lang['edit']['spam_alias'] = "Anpassen temporärer Alias-Adressen";
+
+$lang['danger']['img_tmp_missing'] = "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen";
+$lang['danger']['img_invalid'] = "Grafik konnte nicht validiert werden";
+$lang['danger']['invalid_mime_type'] = "Grafik konnte nicht validiert werden: Ungültiger MIME-Type";
+$lang['success']['upload_success'] = "Datei wurde erfolgreich hochgeladen";
+$lang['success']['app_links'] = "Änderungen an App Links wurden gespeichert";
+$lang['success']['ui_texts'] = "Änderungen an UI-Texten";
+$lang['success']['reset_main_logo'] = "Standardgrafik wurde wiederhergestellt";
+$lang['success']['items_released'] = "Ausgewählte Objekte wurden an Mailbox versendet";
+$lang['danger']['imagick_exception'] = "Fataler Bildverarbeitungsfehler";
+
+$lang['quarantaine']['quarantaine'] = "Quarantäne";
+$lang['quarantaine']['qinfo'] = "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank. Die Nachricht wird <em>nicht</em> angekommen. Der Absender erhält keinen Eindruck einer zugestellten E-Mail.<br />
+  E-Mails mit einer maximalen Größe von 10 MiB werden gespeichert.";
+$lang['quarantaine']['release'] = "Freigeben";
+$lang['quarantaine']['empty'] = 'Keine Einträge';
+$lang['quarantaine']['toggle_all'] = 'Alle auswählen';
+$lang['quarantaine']['quick_actions'] = 'Aktionen';
+$lang['quarantaine']['remove'] = 'Entfernen';
+$lang['quarantaine']['received'] = "Empfangen";
+$lang['quarantaine']['action'] = "Aktion";
+$lang['quarantaine']['rcpt'] = "Empfänger";
+$lang['quarantaine']['qid'] = "Rspamd QID";
+$lang['quarantaine']['sender'] = "Sender";
+$lang['quarantaine']['show_item'] = "Details";
+$lang['quarantaine']['check_hash'] = "Checksumme auf VirusTotal suchen";
+$lang['quarantaine']['qitem'] = "Quarantäneeintrag";
+$lang['quarantaine']['subj'] = "Betreff";
+$lang['quarantaine']['text_plain_content'] = "Inhalt (text/plain)";
+$lang['quarantaine']['atts'] = "Anhänge";
+
+$lang['header']['quarantaine'] = "Quarantäne";
+$lang['header']['debug'] = "Debugging";
+
+$lang['quarantaine']['release_body'] = "Die ursprüngliche Nachricht wurde als EML-Datei im Anhang hinterlegt.";
+$lang['danger']['release_send_failed'] = "Die Nachricht konnte nicht versendet werden: %s";
+$lang['quarantaine']['release_subject'] = "Potentiell schädliche Nachricht aus Quarantäne: %s";
+
+$lang['mailbox']['bcc_map_type'] = "BCC Typ";
+$lang['mailbox']['bcc_type'] = "BCC Typ";
+$lang['mailbox']['bcc_sender_map'] = "Senderabhängig";
+$lang['mailbox']['bcc_rcpt_map'] = "Empfängerabhängig";
+$lang['mailbox']['bcc_local_dest'] = "Lokales Ziel";
+$lang['mailbox']['bcc_destinations'] = "BCC Ziel(e)";
+
+$lang['mailbox']['bcc'] = "BCC";
+$lang['mailbox']['bcc_maps'] = "BCC-Maps";
+$lang['mailbox']['bcc_to_sender'] = "Map senderabhängig verwenden";
+$lang['mailbox']['bcc_to_rcpt'] = "Map empfängerabhängig verwenden";
+$lang['mailbox']['add_bcc_entry'] = "BCC-Eintrag hinzufügen";
+$lang['mailbox']['bcc_info'] = "Eine empfängerabhängige Map wird verwendet, wenn die BCC-Map Eintragung auf den Eingang einer E-Mail auf das lokale Ziel reagieren soll. Senderabhängige Maps verfahren nach dem gleichen Prinzip.<br/>
+  Das lokale Ziel wird bei Fehlzustellungen an ein BCC-Ziel nicht informiert.";
diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php
index 6b404862..b68cf5ee 100644
--- a/data/web/lang/lang.en.php
+++ b/data/web/lang/lang.en.php
@@ -5,9 +5,9 @@
 
 $lang['footer']['loading'] = "Please wait...";
 $lang['header']['restart_sogo'] = 'Restart SOGo';
-$lang['footer']['restart_sogo'] = 'Restart SOGo';
+$lang['footer']['restart_container'] = 'Restart container';
 $lang['footer']['restart_now'] = 'Restart now';
-$lang['footer']['restart_sogo_info'] = 'Some tasks, e.g. adding a domain, require you to restart SOGo to catch changes made in the mailcow UI.<br><br><b>Important:</b> A graceful restart may take a while to complete, please wait for it to finish.';
+$lang['footer']['restart_container_info'] = '<b>Important:</b> A graceful restart may take a while to complete, please wait for it to finish.';
 
 $lang['footer']['confirm_delete'] = 'Confirm deletion';
 $lang['footer']['delete_these_items'] = 'Please confirm your changes to the following object id:';
@@ -73,7 +73,7 @@ $lang['danger']['description_invalid'] = 'Resource description is invalid';
 $lang['danger']['resource_invalid'] = "Resource name is invalid";
 $lang['danger']['mailbox_invalid_suggest'] = 'Mailbox name is invalid, did you mean to type "%s"?';
 $lang['danger']['is_alias'] = "%s is already known as an alias address";
-$lang['danger']['is_alias_or_mailbox'] = "%s is already known as an alias or a mailbox";
+$lang['danger']['is_alias_or_mailbox'] = "%s is already known as an alias, a mailbox or an alias address expanded from an alias domain.";
 $lang['danger']['is_spam_alias'] = "%s is already known as a spam alias address";
 $lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0";
 $lang['danger']['domain_not_found'] = 'Domain %s not found';
@@ -121,6 +121,8 @@ $lang['user']['did_you_know'] = '<b>Did you know?</b> You can use tags in your e
 $lang['user']['spam_aliases'] = 'Temporary email aliases';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliases';
+$lang['user']['shared_aliases'] = 'Shared alias addresses';
+$lang['user']['direct_aliases'] = 'Direct alias addresses';
 $lang['user']['domain_aliases'] = 'Domain alias addresses';
 $lang['user']['is_catch_all'] = 'Catch-all for domain/s';
 $lang['user']['aliases_also_send_as'] = 'Also allowed to send as user';
@@ -550,7 +552,15 @@ $lang['diagnostics']['dns_records_type'] = 'Type';
 $lang['diagnostics']['dns_records_data'] = 'Correct Data';
 $lang['diagnostics']['dns_records_status'] = 'Current State';
 $lang['admin']['relay_from'] = '"From:" address';
-$lang['admin']['relay_run'] = "Run test";
+$lang['admin']['api_allow_from'] = "Allow API access from these IPs";
+$lang['admin']['api_key'] = "API key";
+$lang['admin']['activate_api'] = "Activate API";
+$lang['admin']['regen_api_key'] = "Regenerate API key";
+
+$lang['admin']['ui_texts'] = "UI labels and texts";
+$lang['admin']['help_text'] = "Override help text below login mask (HTML allowed)";
+$lang['admin']['main_name'] = '"mailcow UI" name';
+$lang['admin']['apps_name'] = '"mailcow Apps" name';
 
 $lang['admin']['customize'] = "Customize";
 $lang['admin']['change_logo'] = "Change logo";
@@ -574,3 +584,55 @@ $lang['edit']['delimiter_action'] = "Change delimiter action";
 $lang['edit']['syncjobs'] = "Add or change sync jobs";
 $lang['edit']['eas_reset'] = "Reset EAS devices";
 $lang['edit']['spam_alias'] = "Create or change time limited alias addresses";
+
+$lang['danger']['img_tmp_missing'] = "Cannot validate image file: Temporary file not found";
+$lang['danger']['img_invalid'] = "Cannot validate image file";
+$lang['danger']['invalid_mime_type'] = "Invalid mime type";
+$lang['success']['upload_success'] = "File uploaded successfully";
+$lang['success']['app_links'] = "Saved changes to app links";
+$lang['success']['ui_texts'] = "Saved changes to UI texts";
+$lang['success']['reset_main_logo'] = "Reset to default logo";
+$lang['success']['items_released'] = "Selected items were released";
+$lang['danger']['imagick_exception'] = "Error: Imagick exception while reading image";
+
+$lang['quarantaine']['quarantaine'] = "Quarantaine";
+$lang['quarantaine']['qinfo'] = "The quarantaine system will save rejected mail to the database, while the sender will <em>not</em> be given the impression of a delivered mail.<br />
+  Only mails up to 10 MiB will be saved in the quarantaine.";
+$lang['quarantaine']['release'] = "Release";
+$lang['quarantaine']['empty'] = 'No results';
+$lang['quarantaine']['toggle_all'] = 'Toggle all';
+$lang['quarantaine']['quick_actions'] = 'Actions';
+$lang['quarantaine']['remove'] = 'Remove';
+$lang['quarantaine']['received'] = "Received";
+$lang['quarantaine']['action'] = "Action";
+$lang['quarantaine']['rcpt'] = "Recipient";
+$lang['quarantaine']['qid'] = "Rspamd QID";
+$lang['quarantaine']['sender'] = "Sender";
+$lang['quarantaine']['show_item'] = "Show item";
+$lang['quarantaine']['check_hash'] = "Search file hash @ VT";
+$lang['quarantaine']['qitem'] = "Quarantaine item";
+$lang['quarantaine']['subj'] = "Subject";
+$lang['quarantaine']['text_plain_content'] = "Content (text/plain)";
+$lang['quarantaine']['atts'] = "Attachments";
+
+$lang['header']['quarantaine'] = "Quarantaine";
+$lang['header']['debug'] = "Debug";
+
+$lang['quarantaine']['release_body'] = "We have attached your message as eml file to this message.";
+$lang['danger']['release_send_failed'] = "Message could not be released: %s";
+$lang['quarantaine']['release_subject'] = "Potentially damaging quarantaine item %s";
+
+$lang['mailbox']['bcc_map_type'] = "BCC type";
+$lang['mailbox']['bcc_type'] = "BCC type";
+$lang['mailbox']['bcc_sender_map'] = "Sender map";
+$lang['mailbox']['bcc_rcpt_map'] = "Recipient map";
+$lang['mailbox']['bcc_local_dest'] = "Local destination";
+$lang['mailbox']['bcc_destinations'] = "BCC destination/s";
+
+$lang['mailbox']['bcc'] = "BCC";
+$lang['mailbox']['bcc_maps'] = "BCC maps";
+$lang['mailbox']['bcc_to_sender'] = "Switch to sender map type";
+$lang['mailbox']['bcc_to_rcpt'] = "Switch to recipient map type";
+$lang['mailbox']['add_bcc_entry'] = "Add BCC map";
+$lang['mailbox']['bcc_info'] = "A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.<br/>
+  The local destination will not be informed about a failed delivery.";
diff --git a/data/web/mailbox.php b/data/web/mailbox.php
index b4b8feec..19ec683e 100644
--- a/data/web/mailbox.php
+++ b/data/web/mailbox.php
@@ -21,7 +21,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
     </li>
     <li role="presentation"><a href="#tab-syncjobs" aria-controls="tab-syncjobs" role="tab" data-toggle="tab"><?=$lang['mailbox']['sync_jobs'];?></a></li>
     <li role="presentation"><a href="#tab-filters" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['filters'];?></a></li>
-    <li role="presentation"><a href="#tab-bcc" aria-controls="tab-filters" role="tab" data-toggle="tab">BCC maps</a></li>
+    <li role="presentation"><a href="#tab-bcc" aria-controls="tab-filters" role="tab" data-toggle="tab"><?=$lang['mailbox']['bcc_maps'];?></a></li>
   </ul>
 
 	<div class="row">
@@ -211,10 +211,9 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
         <div role="tabpanel" class="tab-pane" id="tab-bcc">
           <div class="panel panel-default">
             <div class="panel-heading">
-              <h3 class="panel-title">BCC maps</h3>
+              <h3 class="panel-title"><?=$lang['mailbox']['bcc_maps'];?></h3>
             </div>
-            <p style="margin:10px" class="help-block">A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.
-            The local destination will not be informed about a failed delivery.</p>
+            <p style="margin:10px" class="help-block"><?=$lang['mailbox']['bcc_info'];?></p>
             <div class="table-responsive">
               <table class="table table-striped" id="bcc_table"></table>
             </div>
@@ -226,12 +225,12 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
                   <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
                   <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
                   <li role="separator" class="divider"></li>
-                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"sender"}' href="#">Switch to sender map type</a></li>
-                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#">Switch to recipient map type</a></li>
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"sender"}' href="#"><?=$lang['mailbox']['bcc_to_sender'];?></a></li>
+                  <li><a id="edit_selected" data-id="bcc" data-api-url='edit/bcc' data-api-attr='{"type":"rcpt"}' href="#"><?=$lang['mailbox']['bcc_to_rcpt'];?></a></li>
                   <li role="separator" class="divider"></li>
                   <li><a id="delete_selected" data-id="bcc" data-api-url='delete/bcc' href="#"><?=$lang['mailbox']['remove'];?></a></li>
                 </ul>
-                <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addBCCModalAdmin"><span class="glyphicon glyphicon-plus"></span> Add BCC map</a>
+                <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addBCCModalAdmin"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_bcc_entry'];?></a>
               </div>
             </div>
           </div>
diff --git a/data/web/modals/admin.php b/data/web/modals/admin.php
index bf17296c..3a387540 100644
--- a/data/web/modals/admin.php
+++ b/data/web/modals/admin.php
@@ -13,7 +13,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['admin']['add_domain_admin'];?></h3>
       </div>
       <div class="modal-body">
-          <form class="form-horizontal" data-id="domain_admin" role="form" method="post">
+          <form class="form-horizontal" data-cached-form="true" data-id="domain_admin" role="form" method="post">
             <div class="form-group">
               <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label>
               <div class="col-sm-10">
@@ -71,7 +71,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><span class="glyphicon glyphicon-stats"></span> Relayhost</h3>
       </div>
       <div class="modal-body">
-          <form class="form-horizontal" id="test_relayhost_form" role="form" method="post">
+          <form class="form-horizontal" data-cached-form="true" id="test_relayhost_form" role="form" method="post">
             <input type="hidden" class="form-control" name="relayhost_id" id="relayhost_id">
             <div class="form-group">
               <label class="control-label col-sm-2" for="mail_from"><?=$lang['admin']['relay_from'];?></label>
diff --git a/data/web/modals/debug.php b/data/web/modals/debug.php
new file mode 100644
index 00000000..e955a20b
--- /dev/null
+++ b/data/web/modals/debug.php
@@ -0,0 +1,6 @@
+<?php
+if (!isset($_SESSION['mailcow_cc_role'])) {
+	header('Location: /');
+	exit();
+}
+?>
diff --git a/data/web/modals/footer.php b/data/web/modals/footer.php
index 593ff197..e861ab4e 100644
--- a/data/web/modals/footer.php
+++ b/data/web/modals/footer.php
@@ -49,7 +49,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
             <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
           </div>
           <hr>
-          <p><?=$lang['tfa']['waiting_usb_register'];?></p>
+          <p id="u2f_status_reg"></p>
           <div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
           <input type="hidden" name="token" id="u2f_register_data"/>
           <input type="hidden" name="tfa_method" value="u2f">
@@ -146,7 +146,7 @@ if (isset($_SESSION['pending_tfa_method'])):
         case "u2f":
       ?>
         <form role="form" method="post" id="u2f_auth_form">
-          <p><?=$lang['tfa']['waiting_usb_auth'];?></p>
+          <p id="u2f_status_auth"></p>
           <div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
           <input type="hidden" name="token" id="u2f_auth_data"/>
           <input type="hidden" name="tfa_method" value="u2f">
@@ -183,19 +183,19 @@ if (isset($_SESSION['pending_tfa_method'])):
 endif;
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin'):
 ?>
-<div id="RestartSOGo" class="modal fade" role="dialog">
+<div id="RestartContainer" class="modal fade" role="dialog">
   <div class="modal-dialog">
     <div class="modal-content">
     <div class="modal-header">
       <button type="button" class="close" data-dismiss="modal">&times;</button>
-      <h4 class="modal-title"><?= $lang['footer']['restart_sogo']; ?></h4>
+      <h4 class="modal-title"><?= $lang['footer']['restart_container']; ?> (<code id="containerName"></code>)</h4>
     </div>
     <div class="modal-body">
-      <p><?= $lang['footer']['restart_sogo_info']; ?></p>
+      <p><?= $lang['footer']['restart_container_info']; ?></p>
       <hr>
-      <button class="btn btn-md btn-primary" id="triggerRestartSogo"><?= $lang['footer']['restart_now']; ?></button>
+      <button class="btn btn-md btn-primary" id="triggerRestartContainer"><?= $lang['footer']['restart_now']; ?></button>
       <br><br>
-      <div id="statusTriggerRestartSogo"></div>
+      <div id="statusTriggerRestartContainer"></div>
     </div>
     </div>
   </div>
diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php
index 113b3289..c60ddb45 100644
--- a/data/web/modals/mailbox.php
+++ b/data/web/modals/mailbox.php
@@ -13,7 +13,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['mailbox']['add_mailbox'];?></h3>
       </div>
       <div class="modal-body">
-        <form class="form-horizontal" data-id="add_mailbox" role="form">
+        <form class="form-horizontal" data-cached-form="true" data-id="add_mailbox" role="form">
           <div class="form-group">
             <label class="control-label col-sm-2" for="local_part"><?=$lang['add']['mailbox_username'];?></label>
             <div class="col-sm-10">
@@ -86,7 +86,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['mailbox']['add_domain'];?></h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" data-id="add_domain" role="form">
+				<form class="form-horizontal" data-cached-form="true" data-id="add_domain" role="form">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label>
 						<div class="col-sm-10">
@@ -96,7 +96,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
 						<div class="col-sm-10">
-						<input type="text" class="form-control" name="description" id="description">
+						<input type="text" class="form-control" name="description" id="description" required>
 						</div>
 					</div>
 					<div class="form-group">
@@ -161,7 +161,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['mailbox']['add_resource'];?></h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" role="form" data-id="add_resource">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_resource">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
 						<div class="col-sm-10">
@@ -223,7 +223,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['mailbox']['add_alias'];?></h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" role="form" data-id="add_alias">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_alias">
 					<input type="hidden" value="0" name="active">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="address"><?=$lang['add']['alias_address'];?></label>
@@ -268,7 +268,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title"><?=$lang['mailbox']['add_domain_alias'];?></h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" role="form" data-id="add_alias_domain">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_alias_domain">
 					<input type="hidden" value="0" name="active">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="alias_domain"><?=$lang['add']['alias_domain'];?></label>
@@ -316,7 +316,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
       </div>
       <div class="modal-body">
         <p class="help-block"><?=$lang['add']['syncjob_hint'];?></p>
-				<form class="form-horizontal" role="form" data-id="add_syncjob">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
           <div class="form-group">
             <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label>
             <div class="col-sm-10">
@@ -417,13 +417,6 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 							</div>
 						</div>
 					</div>
-					<div class="form-group">
-						<div class="col-sm-offset-2 col-sm-10">
-							<div class="checkbox">
-							<label><input type="checkbox" value="1" name="delete2"> <?=$lang['add']['delete2'];?></label>
-							</div>
-						</div>
-					</div>
 					<div class="form-group">
 						<div class="col-sm-offset-2 col-sm-10">
 							<div class="checkbox">
@@ -450,7 +443,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
         <h3 class="modal-title">Filter</h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" role="form" data-id="add_filter">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_filter">
           <div class="form-group">
             <label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?>:</label>
             <div class="col-sm-10">
@@ -515,12 +508,12 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
     <div class="modal-content">
       <div class="modal-header">
         <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
-        <h3 class="modal-title">BCC map</h3>
+        <h3 class="modal-title"><?=$lang['mailbox']['bcc_maps'];?></h3>
       </div>
       <div class="modal-body">
-				<form class="form-horizontal" role="form" data-id="add_bcc">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_bcc">
           <div class="form-group">
-            <label class="control-label col-sm-2" for="local_dest">Local destination:</label>
+            <label class="control-label col-sm-2" for="local_dest"><?=$lang['mailbox']['bcc_local_dest'];?>:</label>
             <div class="col-sm-10">
               <select id="addSelectLocalDest" name="local_dest" id="local_dest" required>
               <?php
@@ -549,16 +542,16 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             </div>
           </div>
           <div class="form-group">
-            <label class="control-label col-sm-2" for="type">BCC map type:</label>
+            <label class="control-label col-sm-2" for="type"><?=$lang['mailbox']['bcc_map_type'];?>:</label>
             <div class="col-sm-10">
               <select id="addFBCCType" name="type" id="type" required>
-                <option value="sender">Sender map</option>
-                <option value="rcpt">Recipient map</option>
+                <option value="sender"><?=$lang['mailbox']['bcc_sender_map'];?></option>
+                <option value="rcpt"><?=$lang['mailbox']['bcc_rcpt_map'];?></option>
               </select>
             </div>
           </div>
 					<div class="form-group">
-						<label class="control-label col-sm-2" for="bcc_dest">BCC destination/s:</label>
+						<label class="control-label col-sm-2" for="bcc_dest"><?=$lang['mailbox']['bcc_destinations'];?>:</label>
 						<div class="col-sm-10">
 							<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control" rows="20" id="bcc_dest" name="bcc_dest" required></textarea>
 						</div>
diff --git a/data/web/modals/quarantaine.php b/data/web/modals/quarantaine.php
new file mode 100644
index 00000000..7d8a5152
--- /dev/null
+++ b/data/web/modals/quarantaine.php
@@ -0,0 +1,32 @@
+<?php
+if (!isset($_SESSION['mailcow_cc_role'])) {
+	header('Location: /');
+	exit();
+}
+?>
+<div class="modal fade" id="qidDetailModal" tabindex="-1" role="dialog" aria-hidden="true">
+  <div class="modal-dialog modal-lg">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
+        <h3 class="modal-title"><span class="glyphicon glyphicon-info"></span> <?=$lang['quarantaine']['qitem'];?></h3>
+      </div>
+      <div class="modal-body">
+        <div id="qid_error" style="display:none" class="alert alert-danger"></div>
+        <div class="form-group">
+          <label for="qid_detail_subj"><h4><?=$lang['quarantaine']['subj'];?>:</h4></label>
+          <p id="qid_detail_subj"></p>
+        </div>
+        <div class="form-group">
+          <label for="qid_detail_text"><h4><?=$lang['quarantaine']['text_plain_content'];?>:</h4></label>
+          <pre id="qid_detail_text"></pre>
+        </div>
+        <div class="form-group">
+          <label for="qid_detail_atts"><h4><?=$lang['quarantaine']['atts'];?>:</h4></label>
+          <div id="qid_detail_atts">-</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
diff --git a/data/web/modals/user.php b/data/web/modals/user.php
index a7cc3995..50a6922c 100644
--- a/data/web/modals/user.php
+++ b/data/web/modals/user.php
@@ -14,7 +14,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
       </div>
       <div class="modal-body">
         <p><?=$lang['add']['syncjob_hint'];?></p>
-				<form class="form-horizontal" role="form" data-id="add_syncjob">
+				<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="host1"><?=$lang['add']['hostname'];?></label>
 						<div class="col-sm-10">
@@ -130,7 +130,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
   <div class="modal-dialog" role="document">
     <div class="modal-content">
       <div class="modal-body">
-        <form class="form-horizontal" data-id="pwchange" role="form" method="post" autocomplete="off">
+        <form class="form-horizontal" data-cached-form="true" data-id="pwchange" role="form" method="post" autocomplete="off">
           <div class="form-group">
             <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
             <div class="col-sm-5">
diff --git a/data/web/quarantaine.php b/data/web/quarantaine.php
new file mode 100644
index 00000000..2d436d8d
--- /dev/null
+++ b/data/web/quarantaine.php
@@ -0,0 +1,56 @@
+<?php
+require_once "inc/prerequisites.inc.php";
+
+if (isset($_SESSION['mailcow_cc_role'])) {
+require_once "inc/header.inc.php";
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
+?>
+<div class="container">
+	<div class="row">
+		<div class="col-md-12">
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h3 class="panel-title"><?=$lang['quarantaine']['quarantaine'];?></h3>
+        </div>
+        <p style="margin:10px" class="help-block"><?=$lang['quarantaine']['qinfo'];?></p>
+        <div class="table-responsive">
+          <table id="quarantainetable" class="table table-striped"></table>
+        </div>
+        <div class="mass-actions-quarantaine">
+          <div class="btn-group">
+            <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="qitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['quarantaine']['toggle_all'];?></a>
+            <a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['quarantaine']['quick_actions'];?> <span class="caret"></span></a>
+            <ul class="dropdown-menu">
+              <li><a id="edit_selected" data-id="qitems" data-api-url='edit/qitem' data-api-attr='{"action":"release"}' href="#"><?=$lang['quarantaine']['release'];?></a></li>
+              <li role="separator" class="divider"></li>
+              <li><a id="delete_selected" data-id="qitems" data-api-url='delete/qitem' href="#"><?=$lang['quarantaine']['remove'];?></a></li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div> <!-- /col-md-12 -->
+  </div> <!-- /row -->
+</div> <!-- /container -->
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/quarantaine.php';
+?>
+<script type='text/javascript'>
+<?php
+$lang_mailbox = json_encode($lang['quarantaine']);
+echo "var lang = ". $lang_mailbox . ";\n";
+echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
+$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
+echo "var role = '". $role . "';\n";
+echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
+?>
+</script>
+<script src="js/footable.min.js"></script>
+<script src="js/quarantaine.js"></script>
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
+} else {
+	header('Location: /');
+	exit();
+}
+?>
diff --git a/data/web/user.php b/data/web/user.php
index 5c5d6720..3e170cfe 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -23,7 +23,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
     </div>
     <hr>
     <div class="row">
-      <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
+      <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
         <div class="col-sm-9 col-xs-7">
           <p id="tfa_pretty"><?=$tfa_data['pretty'];?></p>
             <div id="tfa_additional">
@@ -40,8 +40,8 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
         </div>
     </div>
     <div class="row">
-      <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div>
-      <div class="col-md-9 col-xs-7">
+      <div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div>
+      <div class="col-sm-9 col-xs-7">
         <select id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
           <option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
           <option value="u2f"><?=$lang['tfa']['u2f'];?></option>
@@ -105,11 +105,18 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   $user_get_alias_details = user_get_alias_details($username);
   ?>
   <div class="row">
-    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases'];?>:</div>
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:</div>
     <div class="col-md-9 col-xs-7">
-    <p><?=$user_get_alias_details['aliases'];?></p>
+    <p><?=$user_get_alias_details['direct_aliases'];?></p>
     </div>
   </div>
+  <div class="row">
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:</div>
+    <div class="col-md-9 col-xs-7">
+    <p><?=$user_get_alias_details['shared_aliases'];?></p>
+    </div>
+  </div>
+  <hr>
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['domain_aliases'];?>:</div>
     <div class="col-md-9 col-xs-7">

From eb08b39c0ab1970f0f6ba225d546a503fad05c01 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 22:12:58 +0100
Subject: [PATCH 12/40] Update images

---
 docker-compose.yml | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index d96d0a4f..e2cfab27 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,7 +2,7 @@ version: '2.1'
 services:
 
     unbound-mailcow:
-      image: mailcow/unbound:1.0
+      image: mailcow/unbound:1.1
       build: ./data/Dockerfiles/unbound
       command: /usr/sbin/unbound
       volumes:
@@ -47,7 +47,7 @@ services:
             - redis
 
     clamd-mailcow:
-      image: mailcow/clamd:1.5
+      image: mailcow/clamd:1.6
       build: ./data/Dockerfiles/clamd
       restart: always
       environment:
@@ -60,7 +60,7 @@ services:
             - clamd
 
     rspamd-mailcow:
-      image: mailcow/rspamd:1.14
+      image: mailcow/rspamd:1.15
       build: ./data/Dockerfiles/rspamd
       stop_grace_period: 30s
       depends_on:
@@ -83,7 +83,7 @@ services:
             - rspamd
 
     php-fpm-mailcow:
-      image: mailcow/phpfpm:1.5
+      image: mailcow/phpfpm:1.6
       build: ./data/Dockerfiles/phpfpm
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       depends_on:
@@ -114,7 +114,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: mailcow/sogo:1.10
+      image: mailcow/sogo:1.11
       build: ./data/Dockerfiles/sogo
       environment:
         - DBNAME=${DBNAME}
@@ -134,7 +134,7 @@ services:
             - sogo
 
     dovecot-mailcow:
-      image: mailcow/dovecot:1.12
+      image: mailcow/dovecot:1.13
       build: ./data/Dockerfiles/dovecot
       cap_add:
         - NET_BIND_SERVICE
@@ -170,7 +170,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.8
+      image: mailcow/postfix:1.9
       build: ./data/Dockerfiles/postfix
       volumes:
         - ./data/conf/postfix:/opt/postfix/conf
@@ -243,7 +243,7 @@ services:
       depends_on:
         - nginx-mailcow
         - mysql-mailcow
-      image: mailcow/acme:1.24
+      image: mailcow/acme:1.25
       build: ./data/Dockerfiles/acme
       dns:
         - 172.22.1.254
@@ -266,7 +266,7 @@ services:
             - acme
 
     fail2ban-mailcow:
-      image: mailcow/fail2ban:1.9
+      image: mailcow/fail2ban:1.10
       build: ./data/Dockerfiles/fail2ban
       stop_grace_period: 30s
       depends_on:
@@ -287,7 +287,7 @@ services:
         - /lib/modules:/lib/modules:ro
 
     watchdog-mailcow:
-      image: mailcow/watchdog:1.10
+      image: mailcow/watchdog:1.11
       build: ./data/Dockerfiles/watchdog
       volumes:
         - vmail-vol-1:/vmail:ro
@@ -306,7 +306,7 @@ services:
             - watchdog
 
     dockerapi-mailcow:
-      image: mailcow/dockerapi:1.2
+      image: mailcow/dockerapi:1.3
       restart: always
       build: ./data/Dockerfiles/dockerapi
       oom_score_adj: -10

From 6c67b9df82d02f172cf4672914603f642ad8c98b Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sat, 9 Dec 2017 22:30:18 +0100
Subject: [PATCH 13/40] Replace name by IP, remove unused tables

---
 .../postfix/whitelist_forwardinghosts.sh      |  2 +-
 data/web/inc/init_db.inc.php                  | 45 -------------------
 2 files changed, 1 insertion(+), 46 deletions(-)

diff --git a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
index 4ad5ab32..ab066d89 100755
--- a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
+++ b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
@@ -6,7 +6,7 @@ while read QUERY; do
 		echo "500 dunno"
 		continue
 	fi
-	result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]})
+	result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]})
 	logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
 	echo ${result}
 done
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index db3f2f38..a69e1f3e 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -237,51 +237,6 @@ function init_db_schema() {
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
-      "imap_user_shares" => array(
-        "cols" => array(
-          "from_user" => "VARCHAR(255) NOT NULL",
-          "to_user" => "VARCHAR(255) NOT NULL",
-          "dummy" => "CHAR(1) DEFAULT '1'",
-          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
-          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
-        ),
-        "keys" => array(
-          "primary" => array(
-            "" => array("from_user", "to_user")
-          ),
-          "fkey" => array(
-            "fk_from_user_user_shares" => array(
-              "col" => "from_user",
-              "ref" => "mailbox.username",
-              "delete" => "CASCADE",
-              "update" => "NO ACTION"
-            )
-          )
-        ),
-        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
-      ),
-      "imap_anyone_shares" => array(
-        "cols" => array(
-          "from_user" => "VARCHAR(255) NOT NULL",
-          "dummy" => "CHAR(1) DEFAULT '1'",
-          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
-          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
-        ),
-        "keys" => array(
-          "primary" => array(
-            "" => array("from_user")
-          ),
-          "fkey" => array(
-            "fk_from_anyone_user_shares" => array(
-              "col" => "from_user",
-              "ref" => "mailbox.username",
-              "delete" => "CASCADE",
-              "update" => "NO ACTION"
-            )
-          )
-        ),
-        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
-      ),
       "user_acl" => array(
         "cols" => array(
           "username" => "VARCHAR(255) NOT NULL",

From 6865402ae049a0df5b36e4f8fe932f2bfff39834 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 10 Dec 2017 19:10:49 +0100
Subject: [PATCH 14/40] Fix maildir path

---
 data/web/inc/functions.mailbox.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 054a9499..0bc0b2db 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -730,7 +730,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
           }
           $active = intval($_data['active']);
           $quota_b		= ($quota_m * 1048576);
-          $maildir		= $domain . "/" . $local_part . "/mails/";
+          $maildir		= $domain . "/" . $local_part . "/";
           if (!is_valid_domain_name($domain)) {
             $_SESSION['return'] = array(
               'type' => 'danger',

From d71b6f0ad1dd74cbabc3f5a9d51d18b9afa8b0fd Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 11 Dec 2017 09:41:29 +0100
Subject: [PATCH 15/40] Add placeholder for Rspamd controller password written
 via UI

---
 data/conf/rspamd/override.d/worker-controller-password.inc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 data/conf/rspamd/override.d/worker-controller-password.inc

diff --git a/data/conf/rspamd/override.d/worker-controller-password.inc b/data/conf/rspamd/override.d/worker-controller-password.inc
new file mode 100644
index 00000000..0d5443fa
--- /dev/null
+++ b/data/conf/rspamd/override.d/worker-controller-password.inc
@@ -0,0 +1 @@
+enable_password = "$2$hd67jioinjy4nu6t65tsrb7srexeemp5$tjgnnj3az1gfiqi1tkat7kh8qyoi4gpd5hemkm48k9fmzyf8ypwb";

From f1d533eef6dcfde9b2ce8447b8a3916ce34d59a7 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 11 Dec 2017 09:42:23 +0100
Subject: [PATCH 16/40] [Compose] Add TZ for libc to containers, add Rspamd
 controller password file to Docker API

---
 docker-compose.yml | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/docker-compose.yml b/docker-compose.yml
index e2cfab27..948ef8d8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,8 @@ services:
       image: mailcow/unbound:1.1
       build: ./data/Dockerfiles/unbound
       command: /usr/sbin/unbound
+      environment:
+        - TZ=${TZ}
       volumes:
         - ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro
       restart: always
@@ -20,6 +22,7 @@ services:
         - mysql-vol-1:/var/lib/mysql/
         - ./data/conf/mysql/:/etc/mysql/conf.d/:ro
       environment:
+        - TZ=${TZ}
         - MYSQL_ROOT_PASSWORD=${DBROOT}
         - MYSQL_DATABASE=${DBNAME}
         - MYSQL_USER=${DBUSER}
@@ -38,6 +41,8 @@ services:
       volumes:
         - redis-vol-1:/data/
       restart: always
+      environment:
+        - TZ=${TZ}
       dns:
         - 172.22.1.254
       networks:
@@ -52,6 +57,7 @@ services:
       restart: always
       environment:
         - SKIP_CLAMD=${SKIP_CLAMD:-n}
+        - TZ=${TZ}
       dns:
         - 172.22.1.254
       networks:
@@ -65,6 +71,8 @@ services:
       stop_grace_period: 30s
       depends_on:
         - nginx-mailcow
+      environment:
+        - TZ=${TZ}
       volumes:
         - ./data/conf/rspamd/custom/:/etc/rspamd/custom:ro
         - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
@@ -93,6 +101,7 @@ services:
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
         - dkim-vol-1:/data/dkim
       environment:
+        - TZ=${TZ}
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBPASS=${DBPASS}
@@ -148,6 +157,7 @@ services:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBPASS=${DBPASS}
+        - TZ=${TZ}
       ports:
         - "${DOVEADM_PORT:-127.0.0.1:19991}:12345"
         - "${IMAP_PORT:-143}:143"
@@ -178,6 +188,7 @@ services:
         - postfix-vol-1:/var/spool/postfix
         - crypt-vol-1:/var/lib/zeyple
       environment:
+        - TZ=${TZ}
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBPASS=${DBPASS}
@@ -293,6 +304,7 @@ services:
         - vmail-vol-1:/vmail:ro
       restart: always
       environment:
+        - TZ=${TZ}
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBPASS=${DBPASS}
@@ -310,8 +322,11 @@ services:
       restart: always
       build: ./data/Dockerfiles/dockerapi
       oom_score_adj: -10
+      environment:
+        - TZ=${TZ}
       volumes:
         - /var/run/docker.sock:/var/run/docker.sock:ro
+        - ./data/conf/rspamd/override.d/worker-controller-password.inc:/access.inc:rw
       networks:
         mailcow-network:
           aliases:

From 2994b94b6c4ba85e018a7cd4475b6344acf85f85 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 11 Dec 2017 09:43:01 +0100
Subject: [PATCH 17/40] [Docker API] Remove logs, remove env var for compose
 project name

---
 data/Dockerfiles/dockerapi/server.py | 29 +++++++++-------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index 00f7ebef..1f0223ac 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -18,7 +18,7 @@ class containers_get(Resource):
   def get(self):
     containers = {}
     try:
-      for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME']}):
+      for container in docker_client.containers.list(all=True):
         containers.update({container.attrs['Id']: container.attrs})
       return containers
     except Exception as e:
@@ -28,30 +28,19 @@ class container_get(Resource):
   def get(self, container_id):
     if container_id and container_id.isalnum():
       try:
-        for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+        for container in docker_client.containers.list(all=True, filters={"id": container_id}):
           return container.attrs
       except Exception as e:
           return jsonify(type='danger', msg=e)
     else:
       return jsonify(type='danger', msg='no or invalid id defined')
 
-class container_logs(Resource):
-  def get(self, container_id, lines):
-    if container_id and container_id.isalnum() and lines:
-      try:
-        for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
-          return container.logs(stdout=True, stderr=True, stream=False, tail=lines)
-      except Exception as e:
-          return jsonify(type='danger', msg=e)
-    else:
-      return jsonify(type='danger', msg='no or invalid id defined')
-
 class container_post(Resource):
   def post(self, container_id, post_action):
     if container_id and container_id.isalnum() and post_action:
       if post_action == 'stop':
         try:
-          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
             container.stop()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -59,7 +48,7 @@ class container_post(Resource):
 
       elif post_action == 'start':
         try:
-          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
             container.start()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -67,7 +56,7 @@ class container_post(Resource):
 
       elif post_action == 'restart':
         try:
-          for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+          for container in docker_client.containers.list(all=True, filters={"id": container_id}):
             container.restart()
           return jsonify(type='success', msg='command completed successfully')
         except Exception as e:
@@ -80,19 +69,19 @@ class container_post(Resource):
 
         if request.json['cmd'] == 'sieve_list' and request.json['username']:
           try:
-            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+            for container in docker_client.containers.list(filters={"id": container_id}):
               return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail')
           except Exception as e:
             return jsonify(type='danger', msg=e)
         elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']:
           try:
-            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+            for container in docker_client.containers.list(filters={"id": container_id}):
               return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail')
           except Exception as e:
             return jsonify(type='danger', msg=e)
         elif request.json['cmd'] == 'worker_password' and request.json['raw']:
           try:
-            for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+            for container in docker_client.containers.list(filters={"id": container_id}):
               hash = container.exec_run(["/bin/bash", "-c", "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "'"], user='_rspamd')
               f = open("/access.inc", "w")
               f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash.rstrip()) + '";\n')
@@ -125,7 +114,6 @@ def startFlaskAPI():
 
 api.add_resource(containers_get, '/containers/json')
 api.add_resource(container_get, '/containers/<string:container_id>/json')
-api.add_resource(container_logs, '/containers/<string:container_id>/logs/<int:lines>')
 api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
 
 if __name__ == '__main__':
@@ -138,3 +126,4 @@ if __name__ == '__main__':
     if killer.kill_now:
       break
   print "Stopping dockerapi-mailcow"
+

From c875508687dcebf4051ef151c6a1d4b60241acff Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 11 Dec 2017 09:43:43 +0100
Subject: [PATCH 18/40] [Web] Remove logs from docker functions, fix debug
 syntax

---
 data/web/debug.php                    |  4 ++--
 data/web/inc/functions.docker.inc.php | 27 ---------------------------
 2 files changed, 2 insertions(+), 29 deletions(-)

diff --git a/data/web/debug.php b/data/web/debug.php
index 289aa84a..0723ba6a 100644
--- a/data/web/debug.php
+++ b/data/web/debug.php
@@ -115,7 +115,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               'fail2ban-mailcow',
               'clamd-mailcow'
             );
-            }
             foreach ($container_array as $container) {
                 $container_stats = docker($container, 'info');
                 ?>
@@ -321,7 +320,8 @@ echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n";
 <script src="js/debug.js"></script>
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
-} else {
+}
+else {
 	header('Location: /');
 	exit();
 }
diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php
index 7cd5ed4e..fed2e1d1 100644
--- a/data/web/inc/functions.docker.inc.php
+++ b/data/web/inc/functions.docker.inc.php
@@ -59,33 +59,6 @@ function docker($service_name, $action, $attr1 = null, $attr2 = null, $extra_hea
         return false;
       }
     break;
-    case 'logs':
-      $container_id = docker($service_name, 'get_id');
-      if (ctype_xdigit($container_id)) {
-        $lines = (empty($attr1) || !is_numeric($attr1)) ? 100 : $attr1;
-        curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/logs/' . $lines);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
-        curl_setopt($curl, CURLOPT_POST, 0);
-        $response = curl_exec($curl);
-        if ($response === false) {
-          $err = curl_error($curl);
-          curl_close($curl);
-          return $err;
-        }
-        else {
-          curl_close($curl);
-          if (empty($response)) {
-            return true;
-          }
-          else {
-            return json_decode($response, true);
-          }
-        }
-      }
-      else {
-        return false;
-      }
-    break;
     case 'post':
       if (!empty($attr1)) {
         $container_id = docker($service_name, 'get_id');

From ae56c3b59e6c2bc2dbddedc5dbb1ec29d5643855 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 11 Dec 2017 09:55:22 +0100
Subject: [PATCH 19/40] Fix quarantaine

---
 .gitignore                                 |  1 +
 data/conf/nginx/meta_exporter.conf         | 19 +++++++++++++++++++
 data/web/inc/functions.quarantaine.inc.php |  2 +-
 docker-compose.yml                         |  2 ++
 4 files changed, 23 insertions(+), 1 deletion(-)
 create mode 100644 data/conf/nginx/meta_exporter.conf

diff --git a/.gitignore b/.gitignore
index b945f8cf..798d9603 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ data/conf/rspamd/local.d/*
 data/conf/rspamd/override.d/*
 !data/conf/nginx/dynmaps.conf
 !data/conf/nginx/site.conf
+!data/conf/nginx/meta_exporter.conf
 data/conf/nginx/*.conf
 data/conf/nginx/*.custom
 data/conf/nginx/*.bak
diff --git a/data/conf/nginx/meta_exporter.conf b/data/conf/nginx/meta_exporter.conf
new file mode 100644
index 00000000..30369a32
--- /dev/null
+++ b/data/conf/nginx/meta_exporter.conf
@@ -0,0 +1,19 @@
+server {
+  listen 9081;
+  index index.php index.html;
+  server_name _;
+  error_log  /var/log/nginx/error.log;
+  access_log /var/log/nginx/access.log;
+  root /meta_exporter;
+  client_max_body_size 10M;
+  location ~ \.php$ {
+    client_max_body_size 10M;
+    try_files $uri =404;
+    fastcgi_split_path_info ^(.+\.php)(/.+)$;
+    fastcgi_pass phpfpm:9000;
+    fastcgi_index pipe.php;
+    include fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    fastcgi_param PATH_INFO $fastcgi_path_info;
+  }
+}
diff --git a/data/web/inc/functions.quarantaine.inc.php b/data/web/inc/functions.quarantaine.inc.php
index 4b9e6b00..9b55e4e7 100644
--- a/data/web/inc/functions.quarantaine.inc.php
+++ b/data/web/inc/functions.quarantaine.inc.php
@@ -151,7 +151,7 @@ function quarantaine($_action, $_data = null) {
               )
             );
             if (!empty(gethostbynamel('postfix-mailcow'))) {
-              $postfix = 'apostfix-mailcow';
+              $postfix = 'postfix-mailcow';
             }
             if (!empty(gethostbynamel('postfix'))) {
               $postfix = 'postfix';
diff --git a/docker-compose.yml b/docker-compose.yml
index 948ef8d8..ad0e9da6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -100,6 +100,7 @@ services:
         - ./data/web:/web:rw
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
         - dkim-vol-1:/data/dkim
+        - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
       environment:
         - TZ=${TZ}
         - DBNAME=${DBNAME}
@@ -238,6 +239,7 @@ services:
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
         - ./data/assets/ssl/:/etc/ssl/mail/:ro
         - ./data/conf/nginx/:/etc/nginx/conf.d/:rw
+        - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
       ports:
         - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
         - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"

From f5a6667aad5739d2a9d17a7592c9bcd8aa65cc7a Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 17 Dec 2017 17:44:28 +0100
Subject: [PATCH 20/40] [Web] Fix removal of alias domain

---
 data/web/inc/functions.mailbox.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 0bc0b2db..4e728aaa 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -3492,7 +3492,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               ));
               $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain");
               $stmt->execute(array(
-                ':domain' => $alias_domain,
+                ':alias_domain' => $alias_domain,
               ));
             }
             catch (PDOException $e) {

From a771d668892ec53877340be249d2f3eb45b78617 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 17 Dec 2017 17:45:05 +0100
Subject: [PATCH 21/40] [Dovecot] Update imapsync

---
 data/Dockerfiles/dovecot/Dockerfile |    10 +
 data/Dockerfiles/dovecot/imapsync   | 12066 ++++++++++++++++----------
 2 files changed, 7269 insertions(+), 4807 deletions(-)

diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile
index d2cdd0e3..1da6678a 100644
--- a/data/Dockerfiles/dovecot/Dockerfile
+++ b/data/Dockerfiles/dovecot/Dockerfile
@@ -14,6 +14,16 @@ RUN apt-get update && apt-get -y install \
 	cpanminus \
 	curl \
 	default-libmysqlclient-dev \
+  libjson-webtoken-perl \
+  libcgi-pm-perl \
+  libcrypt-openssl-rsa-perl \
+  libdata-uniqid-perl \
+  libhtml-parser-perl \
+  libmail-imapclient-perl \
+  libparse-recdescent-perl \
+  libsys-meminfo-perl \
+  libtest-mockobject-perl \
+  libwww-perl \
 	libauthen-ntlm-perl \
 	libbz2-dev \
 	libcrypt-ssleay-perl \
diff --git a/data/Dockerfiles/dovecot/imapsync b/data/Dockerfiles/dovecot/imapsync
index 8df547c6..c31abb18 100755
--- a/data/Dockerfiles/dovecot/imapsync
+++ b/data/Dockerfiles/dovecot/imapsync
@@ -1,6 +1,6 @@
 #!/usr/bin/perl
 
-# $Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $
+# $Id: imapsync,v 1.836 2017/09/05 16:14:53 gilles Exp gilles $
 # structure
 # pod documentation
 # pragmas
@@ -11,7 +11,7 @@
 # folder loop
 # subroutines
 # sub usage {
-# IMAPClient 3.xx ads
+
 
 # pod documentation
 
@@ -19,15 +19,15 @@
 
 =head1 NAME
 
-imapsync - Email IMAP tool for syncing, copying and migrating email mailboxes.
+imapsync - Email IMAP tool for syncing, copying and migrating 
+email mailboxes between two imap servers, one way, 
+and without duplicates.
 
-The imapsync command synchronises mailboxes between two imap servers.
-More than 69 different IMAP server softwares supported with success, 
-few failures.
+=head1 VERSION
 
-$Revision: 1.727 $
+This documentation refers to Imapsync $Revision: 1.836 $
 
-=head1 SYNOPSIS
+=head1 USAGE
 
  To synchronize the source imap account
    "test1" on server "test1.lamiral.info" with password "secret1"
@@ -39,12 +39,547 @@ $Revision: 1.727 $
    --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
    --host2 test2.lamiral.info --user2 test2 --password2 secret2
 
-=head1 REQUIRED ARGUMENTS
+=head1 DESCRIPTION
 
-The required argmuments are the six values, three on each sides,
-needed to login into the IMAP servers, 
+We sometimes need to transfer mailboxes from one imap server to
+another.
+
+Imapsync command is a tool allowing incremental and
+recursive imap transfers from one mailbox to another.
+
+By default all folders are transferred, recursively, meaning 
+the whole folder hierarchy is taken, all messages in them, 
+and all messages flags (\Seen \Answered \Flagged etc.) 
+are synced too.
+
+Imapsync reduces the amount
+of data transferred by not transferring a given message
+if it resides already on both sides. Same specific headers
+and the transfer is done only once (by default it's
+"Message-Id:" and "Received:" lines but it can be changed with
+--useheader option).
+
+All flags are preserved, unread will stay unread, read will stay read,
+deleted will stay deleted. 
+
+You can stop the transfer at any
+time and restart it later, imapsync works well with bad
+connections and interruptions.
+
+You can decide to delete the messages from the source mailbox
+after a successful transfer, it can be a good feature when migrating
+live mailboxes since messages will be only on one side.
+In that case, use the --delete1 option. Option --delete1 implies
+also option --expunge1 so all messages marked deleted on host1
+will be really deleted.
+
+A different scenario is synchronizing a mailbox B from another mailbox A
+in case you just want to keep a "live" copy of A in B.
+In that case --delete2 has to be used, it deletes messages in host2
+folder B that are not in host1 folder A. If you also need to destroy
+host2 folders that are not in host1 then use --delete2folders (see also
+--delete2foldersonly and --delete2foldersbutnot).
+
+Imapsync is not adequate for maintaining two active imap accounts
+in synchronization when the user plays independently on both sides.
+Use offlineimap (written by John Goerzen) or mbsync (written by
+Michael R. Elkins) for a 2 ways synchronization.
+
+
+=head1 OPTIONS
+
+ usage: imapsync [options]
+
+Mandatory options are the six values, three on each sides,
+needed to log in into the IMAP servers, ie, 
 a host, a username, and a password, two times.
 
+Conventions used:
+
+ str means string
+ int means integer
+ reg means regular expression
+ cmd means command
+
+ --dry               : Makes imapsync doing nothing for real, just print what 
+                       would be done without --dry.
+
+=head2 OPTIONS/credentials
+
+
+ --host1        str  : Source or "from" imap server. Mandatory.
+ --port1        int  : Port to connect on host1. Default is 143, 993 if --ssl1
+ --user1        str  : User to login on host1. Mandatory.
+ --password1    str  : Password for the user1.
+ --host2        str  : "destination" imap server. Mandatory.
+ --port2        int  : Port to connect on host2. Default is 143, 993 if --ssl2
+ --user2        str  : User to login on host2. Mandatory.
+ --password2    str  : Password for the user2.
+
+ --showpasswords     : Shows passwords on output instead of "MASKED".
+                       Useful to restart a complete run by just reading the log,
+                       or to debug passwords. It's not a secure practice.
+
+ --passfile1    str  : Password file for the user1. It must contain the
+                       password on the first line. This option avoids to show
+                       the password on the command line like --password1 does.
+ --passfile2    str  : Password file for the user2. Contains the password.
+
+=head2 OPTIONS/encryption
+
+ --nossl1            : Do not use a SSL connection on host1.
+ --ssl1              : Use a SSL connection on host1. On by default if possible.
+ --nossl2            : Do not use a SSL connection on host2.
+ --ssl2              : Use a SSL connection on host2. On by default if possible.
+ --notls1            : Do not use a TLS connection on host1.
+ --tls1              : Use a TLS connection on host1. On by default if possible.
+ --notls2            : Do not use a TLS connection on host2.
+ --tls2              : Use a TLS connection on host2. On by default if possible.
+ --debugssl     int  : SSL debug mode from 0 to 4.
+ --sslargs1     str  : Pass any ssl parameter for host1 ssl or tls connection. Example:
+                       --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
+                       See all possibilities in the new() method of IO::Socket::SSL
+                       http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
+ --sslargs2     str  : Pass any ssl parameter for host2 ssl or tls connection.
+                       See --sslargs1
+
+ --timeout1     int  : Connection timeout in seconds for host1.
+                       Default is 120 and 0 means no timeout at all.
+ --timeout2     int  : Connection timeout in seconds for host2.
+                       Default is 120 and 0 means no timeout at all.
+
+
+=head2 OPTIONS/authentication
+
+ --authmech1    str  : Auth mechanism to use with host1:
+                       PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
+ --authmech2    str  : Auth mechanism to use with host2. See --authmech1
+
+ --authuser1    str  : User to auth with on host1 (admin user).
+                       Avoid using --authmech1 SOMETHING with --authuser1.
+ --authuser2    str  : User to auth with on host2 (admin user).
+ --proxyauth1        : Use proxyauth on host1. Requires --authuser1.
+                       Required by Sun/iPlanet/Netscape IMAP servers to
+                       be able to use an administrative user.
+ --proxyauth2        : Use proxyauth on host2. Requires --authuser2.
+
+ --authmd51          : Use MD5 authentication for host1.
+ --authmd52          : Use MD5 authentication for host2.
+ --domain1      str  : Domain on host1 (NTLM authentication).
+ --domain2      str  : Domain on host2 (NTLM authentication).
+
+
+=head2 OPTIONS/folders
+
+
+ --folder       str  : Sync this folder.
+ --folder       str  : and this one, etc.
+ --folderrec    str  : Sync this folder recursively.
+ --folderrec    str  : and this one, etc.
+
+ --folderfirst  str  : Sync this folder first. --folderfirst "Work"
+ --folderfirst  str  : then this one, etc.
+ --folderlast   str  : Sync this folder last. --folderlast "[Gmail]/All Mail"
+ --folderlast   str  : then this one, etc.
+
+ --nomixfolders      : Do not merge folders when host1 is case-sensitive
+                       while host2 is not (like Exchange). Only the first
+                       similar folder is synced (ex: Sent SENT sent -> Sent).
+
+ --skipemptyfolders  : Empty host1 folders are not created on host2.
+
+ --include      reg  : Sync folders matching this regular expression
+ --include      reg  : or this one, etc.
+                       If both --include --exclude options are used, then
+                       include is done before.
+ --exclude      reg  : Skips folders matching this regular expression
+                       Several folders to avoid:
+                        --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
+ --exclude      reg  : or this one, etc.
+
+ --subfolder2   str  : Move whole host1 folders hierarchy under this
+                       host2 folder  str    .
+                       It does it by adding two --regextrans2 options before
+                       all others. Add --debug to see what's really going on.
+
+ --automap           : guesses folders mapping, for folders like
+                       "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
+ --f1f2    str1=str2 : Force folder str1 to be synced to str2,
+                       --f1f2 overrides --automap and --regextrans2.
+
+ --nomixfolders      : Avoid merging folders that are considered different on
+                       host1 but the same on destination host2 because of
+                       case sensitivities and insensitivities.
+
+ --subscribed        : Transfers subscribed folders.
+ --subscribe         : Subscribe to the folders transferred on the
+                       host2 that are subscribed on host1. On by default.
+ --subscribeall      : Subscribe to the folders transferred on the
+                       host2 even if they are not subscribed on host1.
+
+ --prefix1      str  : Remove prefix str to all destination folders,
+                       usually INBOX. or INBOX/ or an empty string "".
+                       imapsync guesses the prefix if host1 imap server
+                       does not have NAMESPACE capability. This option
+                       should not be used, most of the time.
+ --prefix2      str  : Add prefix to all host2 folders. See --prefix1
+ --sep1         str  : Host1 separator in case NAMESPACE is not supported.
+ --sep2         str  : Host2 separator in case NAMESPACE is not supported.
+
+ --regextrans2  reg  : Apply the whole regex to each destination folders.
+ --regextrans2  reg  : and this one. etc.
+                       When you play with the --regextrans2 option, first
+                       add also the safe options --dry --justfolders
+                       Then, when happy, remove --dry, remove --justfolders.
+                       Have in mind that --regextrans2 is applied after prefix
+                       and separator inversion. For examples see
+                       http://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
+
+=head2 OPTIONS/folders sizes
+
+ --nofoldersizes     : Do not calculate the size of each folder at the
+                       beginning of the sync. Default is to calculate them.
+ --nofoldersizesatend: Do not calculate the size of each folder at the 
+                       end of the sync. Default is to calculate them.
+ --justfoldersizes   : Exit after having printed the initial folder sizes.
+
+
+=head2 OPTIONS/tmp
+
+
+ --tmpdir       str  : Where to store temporary files and subdirectories.
+                       Will be created if it doesn't exist.
+                       Default is system specific, Unix is /tmp but
+                       /tmp is often too small and deleted at reboot.
+                       --tmpdir /var/tmp should be better.
+ --pidfile      str  : The file where imapsync pid is written,
+                       it can be dirname/filename.
+                       Default name is imapsync.pid in tmpdir.
+ --pidfilelocking    : Abort if pidfile already exists. Useful to avoid
+                       concurrent transfers on the same mailbox.
+
+
+=head2 OPTIONS/log
+
+ --nolog             : Turn off logging on file
+ --logfile      str  : Change the default log filename (can be dirname/filename).
+ --logdir       str  : Change the default log directory. Default is LOG_imapsync/
+
+
+=head2 OPTIONS/messages
+
+ --skipmess     reg  : Skips messages matching the regex.
+                       Example: 'm/[\x80-ff]/' # to avoid 8bits messages.
+                       --skipmess is applied before --regexmess
+ --skipmess     reg  : or this one, etc.
+
+ --pipemess     cmd  : Apply this cmd command to each message content
+                       before the copy.
+ --pipemess     cmd  : and this one, etc.
+
+ --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
+
+ --regexmess    reg  : Apply the whole regex to each message before transfer.
+                       Example: 's/\000/ /g' # to replace null by space.
+ --regexmess    reg  : and this one, etc.
+
+
+=head2 OPTIONS/flags
+
+ --regexflag    reg  : Apply the whole regex to each flags list.
+                       Example: 's/"Junk"//g' # to remove "Junk" flag.
+ --regexflag    reg  : then this one, etc.
+
+
+=head2 OPTIONS/deletions
+
+ --delete1           : Deletes messages on host1 server after a successful
+                       transfer. Option --delete1 has the following behavior:
+                       it marks messages as deleted with the IMAP flag
+                       \Deleted, then messages are really deleted with an
+                       EXPUNGE IMAP command. If expunging after each message
+                       slows down too much the sync then use
+                       --noexpungeaftereach to speed up.
+ --expunge1          : Expunge messages on host1 just before syncing a folder.
+                       Expunge is done per folder.
+                       Expunge aims is to really delete messages marked deleted.
+                       An expunge is also done after each message copied
+                       if option --delete1 is set.
+ --noexpunge1        : Do not expunge messages on host1.
+ --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted.
+                         Useful with --delete1 since what remains on host1
+                         is only what failed to be synced.
+
+ --delete2           : Delete messages in host2 that are not in
+                       host1 server. Useful for backup or pre-sync.
+ --delete2duplicates : Delete messages in host2 that are duplicates.
+                       Works only without --useuid since duplicates are
+                       detected with an header part of each message.
+
+ --delete2folders    : Delete folders in host2 that are not in host1 server.
+                       For safety, first try it like this (it is safe):
+                       --delete2folders --dry --justfolders --nofoldersizes
+ --delete2foldersonly   reg : Deleted only folders matching regex.
+                              Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
+ --delete2foldersbutnot reg : Do not delete folders matching regex.
+                              Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
+
+ --expunge2          : Expunge messages on host2 after messages transfer.
+ --uidexpunge2       : uidexpunge messages on the host2 account
+                       that are not on the host1 account, requires --delete2
+
+
+=head2 OPTIONS/dates
+
+ --syncinternaldates : Sets the internal dates on host2 same as host1.
+                       Turned on by default. Internal date is the date
+                       a message arrived on a host (mtime).
+ --idatefromheader   : Sets the internal dates on host2 same as the
+                       "Date:" headers.
+
+
+=head2 OPTIONS/message selection
+
+ --maxsize      int  : Skip messages larger  (or equal) than  int  bytes
+ --minsize      int  : Skip messages smaller (or equal) than  int  bytes
+ --maxage       int  : Skip messages older than  int  days.
+                       final stats (skipped) don't count older messages
+                       see also --minage
+ --minage       int  : Skip messages newer than  int  days.
+                       final stats (skipped) don't count newer messages
+                       You can do (+ are the messages selected):
+                       past|----maxage+++++++++++++++>now
+                       past|+++++++++++++++minage---->now
+                       past|----maxage+++++minage---->now (intersection)
+                       past|++++minage-----maxage++++>now (union)
+
+ --search       str  : Selects only messages returned by this IMAP SEARCH
+                       command. Applied on both sides.
+ --search1      str  : Same as --search for selecting host1 messages only.
+ --search2      str  : Same as --search for selecting host2 messages only.
+                       --search CRIT equals --search1 CRIT --search2 CRIT
+
+ --maxlinelength int : skip messages with a line length longer than  int  bytes.
+                       RFC 2822 says it must be no more than 1000 bytes.
+
+
+ --useheader    str  : Use this header to compare messages on both sides.
+                       Ex: Message-ID or Subject or Date.
+ --useheader    str    and this one, etc.
+
+ --usecache          : Use cache to speed up the sync.
+ --nousecache        : Do not use cache. Caveat: --useuid --nousecache creates
+                       duplicates on multiple runs.
+ --useuid            : Use uid instead of header as a criterium to recognize
+                       messages. Option --usecache is then implied unless
+                       --nousecache is used.
+
+
+=head2 OPTIONS/miscelaneous
+
+ --syncacls          : Synchronizes acls (Access Control Lists).
+ --nosyncacls        : Does not synchronize acls. This is the default.
+                       Acls in IMAP are not standardized, be careful.
+
+
+
+=head2 OPTIONS/debugging
+
+ --debug             : Debug mode.
+ --debugfolders      : Debug mode for the folders part only.
+ --debugcontent      : Debug content of the messages transferred. Huge output.
+ --debugflags        : Debug mode for flags.
+ --debugimap1        : IMAP debug mode for host1. Very verbose.
+ --debugimap2        : IMAP debug mode for host2. Very verbose.
+ --debugimap         : IMAP debug mode for host1 and host2.
+ --debugmemory       : Debug mode showing memory consumption after each copy.
+
+ --errorsmax     int : Exit when int number of errors is reached. Default is 50.
+
+ --tests             : Run local non-regression tests. Exit code 0 means all ok.
+ --testslive         : Run a live test with test1.lamiral.info imap server.
+                       Useful to check the basics. Needs internet connexion.
+ --testslive6        : Run a live test with ks2ipv6.lamiral.info imap server.
+                       Useful to check the ipv6 connectivity. Needs internet.
+
+
+=head2 OPTIONS/specific 
+
+  --gmail1           : sets --host1 to Gmail and options from FAQ.Gmail.txt
+  --gmail2           : sets --host2 to Gmail and options from FAQ.Gmail.txt
+  
+  --office1          : sets --host1 to Office365 options from FAQ.Exchange.txt
+  --office2          : sets --host2 to Office365 options from FAQ.Exchange.txt
+
+  --exchange1        : sets options from FAQ.Exchange.txt, account1 part
+  --exchange2        : sets options from FAQ.Exchange.txt, account2 part
+  
+  --domino1          : sets options from FAQ.Domino.txt, account1 part
+  --domino2          : sets options from FAQ.Domino.txt, account2 part
+  
+  
+  
+
+=head2 OPTIONS/behavior 
+
+ --maxmessagespersecond int : limits the number of messages transferred per second.
+ 
+ --maxbytespersecond int : limits the average transfer rate per second.
+ --maxbytesafter     int : starts --maxbytespersecond limitation only after 
+                           --maxbytesafter amount of data transferred.
+ 
+ --maxsleep      int : do not sleep more than int seconds.
+                       On by default, 2 seconds max, like --maxsleep 2
+
+ --abort             : terminates a previous call still running. 
+                       It uses the pidfile to know what processus to abort.
+
+ --exitwhenover int  : Stop syncing when total bytes transferred reached.
+
+ --version           : Print only software version.
+ --noreleasecheck    : Do not check for new imapsync release (a http request).
+ --releasecheck      : Check for new imapsync release (a http request).
+ --noid              : Do not send/receive ID command to imap servers.
+ --justconnect       : Just connect to both servers and print useful
+                       information. Need only --host1 and --host2 options.
+ --justlogin         : Just login to both host1 and host2 with users
+                       credentials, then exit.
+ --justfolders       : Do only things about folders (ignore messages).
+
+ --help              : print this help.
+
+ Example: to synchronize imap account "test1" on "test1.lamiral.info"
+                     to  imap account "test2" on "test2.lamiral.info"
+                     with test1 password "secret1"
+                     and  test2 password "secret2"
+
+ imapsync \
+    --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
+    --host2 test2.lamiral.info --user2 test2 --password2 secret2
+
+
+=cut
+# comment
+
+=pod
+
+
+
+=head1 SECURITY
+
+You can use --passfile1  instead of --password1 to give the
+password since it is safer. With --password1 option any user
+on your host can see the password by using the 'ps auxwwww'
+command. Using a variable (like $PASSWORD1) is also
+dangerous because of the 'ps auxwwwwe' command. So, saving
+the password in a well protected file (600 or rw-------) is
+the best solution.
+
+Imapsync activates ssl or tls encryption by default, if possible. 
+What details are under this "if possible"? 
+Imapsync activates ssl if the well known port imaps port (993) is open 
+on the imap servers. If the imaps port is closed then it open a 
+normal (clear) connection on port 143 but it looks for TLS support 
+in the CAPABILITY list of the servers. If TLS is supported 
+then imapsync goes to encryption.
+
+If the automatic ssl/tls detection fails then imapsync will
+not protect against sniffing activities on the
+network, especially for passwords.
+
+See also the document FAQ.Security.txt in the FAQ.d/ directory
+or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt
+
+=head1 EXIT STATUS
+
+Imapsync will exit with a 0 status (return code) if everything went good.
+Otherwise, it exits with a non-zero status.
+
+
+
+=head1 LICENSE AND COPYRIGHT
+
+Imapsync is free, open, public but not always gratis software
+cover by the NOLIMIT Public License.
+See the LICENSE file included in the distribution or just read this
+simple sentence as it IS the licence text:
+
+ "No limit to do anything with this work and this license."
+
+In case it is not long enough, I repeat:
+
+ "No limit to do anything with this work and this license."
+
+https://imapsync.lamiral.info/LICENSE
+
+=head1 AUTHOR
+
+Gilles LAMIRAL <gilles.lamiral@laposte.net>
+
+Feedback good or bad is very often welcome.
+
+Gilles LAMIRAL earns his living by writing, installing,
+configuring and teaching free, open and often gratis
+software. Imapsync used to be "always gratis" but now it is
+only "often gratis" because imapsync is sold by its author, 
+a good way to maintain and support free open public
+software over decades.
+
+=head1 BUGS AND LIMITATIONS
+
+See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt
+
+=head1 IMAP SERVERS supported
+
+See https://imapsync.lamiral.info/S/imapservers.shtml
+
+=head1 HUGE MIGRATION
+
+Pay special attention to options
+--subscribed
+--subscribe
+--delete1
+--delete2
+--delete2folders
+--maxage
+--minage
+--maxsize
+--useuid
+--usecache
+
+If you have many mailboxes to migrate think about a little
+shell program. Write a file called file.txt (for example)
+containing users and passwords.
+The separator used in this example is ';'
+
+The file.txt file contains:
+
+user001_1;password001_1;user001_2;password001_2
+user002_1;password002_1;user002_2;password002_2
+user003_1;password003_1;user003_2;password003_2
+user004_1;password004_1;user004_2;password004_2
+user005_1;password005_1;user005_2;password005_2
+...
+
+On Unix the shell program can be:
+
+ { while IFS=';' read  u1 p1 u2 p2; do
+        imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
+                 --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
+ done ; } < file.txt
+
+On Windows the batch program can be:
+
+  FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
+  --host1 imap.side1.org --user1 %%G --password1 %%H ^
+  --host2 imap.side2.org --user2 %%I --password2 %%J ...
+
+The ... have to be replaced by nothing or any imapsync option.
+Welcome in shell or batch programming !
+
+You will find already written scripts at
+http://imapsync.lamiral.info/examples/
+
 =head1 INSTALL
 
  Imapsync works under any Unix with perl.
@@ -72,598 +607,14 @@ a host, a username, and a password, two times.
 =head1 CONFIGURATION
 
 There is no specific configuration file for imapsync,
-everything is specified by the command line parameteres
+everything is specified by the command line parameters
 and the default behavior.
 
-=head1 USAGE
-
-To get a description of each option just run imapsync
-with no argument, like this:
-
-  imapsync
-
-This description of options is also available at
-http://imapsync.lamiral.info/OPTIONS and is
-reproduced here:
-
- usage: ./imapsync [options]
-
- Several options are mandatory.
- str means string
- int means integer
- reg means regular expression
- cmd means command
-
- --dry               : Makes imapsync doing nothing, just print what would
-                       be done without --dry.
-
- --host1        str  : Source or "from" imap server. Mandatory.
- --port1        int  : Port to connect on host1. Default is 143, 993 if --ssl1
- --user1        str  : User to login on host1. Mandatory.
- --showpasswords     : Shows passwords on output instead of "MASKED".
-                       Useful to restart a complete run by just reading the log.
- --password1    str  : Password for the user1.
- --host2        str  : "destination" imap server. Mandatory.
- --port2        int  : Port to connect on host2. Default is 143, 993 if --ssl2
- --user2        str  : User to login on host2. Mandatory.
- --password2    str  : Password for the user2.
-
- --passfile1    str  : Password file for the user1. It must contain the
-                       password on the first line. This option avoids to show
-                       the password on the command line like --password1 does.
- --passfile2    str  : Password file for the user2. Contains the password.
-
- --ssl1              : Use a SSL connection on host1.
- --ssl2              : Use a SSL connection on host2.
- --tls1              : Use a TLS connection on host1.
- --tls2              : Use a TLS connection on host2.
- --debugssl     int  : SSL debug mode from 0 to 4.
- --sslargs1     str  : Pass any ssl parameter for host1 ssl or tls connection. Example:
-                       --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
-                       See all possibilities in the new() method of IO::Socket::SSL
-                       http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
- --sslargs2     str  : Pass any ssl parameter for host2 ssl or tls connection.
-                       See --sslargs1
-
- --timeout1     int  : Connection timeout in seconds for host1.
-                       Default is 120 and 0 means no timeout at all.
- --timeout2     int  : Connection timeout in seconds for host2.
-                       Default is 120 and 0 means no timeout at all.
-
- --authmech1    str  : Auth mechanism to use with host1:
-                       PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
- --authmech2    str  : Auth mechanism to use with host2. See --authmech1
-
- --authuser1    str  : User to auth with on host1 (admin user).
-                       Avoid using --authmech1 SOMETHING with --authuser1.
- --authuser2    str  : User to auth with on host2 (admin user).
- --proxyauth1        : Use proxyauth on host1. Requires --authuser1.
-                       Required by Sun/iPlanet/Netscape IMAP servers to
-                       be able to use an administrative user.
- --proxyauth2        : Use proxyauth on host2. Requires --authuser2.
-
- --authmd51          : Use MD5 authentification for host1.
- --authmd52          : Use MD5 authentification for host2.
- --domain1      str  : Domain on host1 (NTLM authentication).
- --domain2      str  : Domain on host2 (NTLM authentication).
-
-
- --folder       str  : Sync this folder.
- --folder       str  : and this one, etc.
- --folderrec    str  : Sync this folder recursively.
- --folderrec    str  : and this one, etc.
-
- --folderfirst  str  : Sync this folder first. --folderfirst "Work"
- --folderfirst  str  : then this one, etc.
- --folderlast   str  : Sync this folder last. --folderlast "[Gmail]/All Mail"
- --folderlast   str  : then this one, etc.
-
- --nomixfolders      : Do not merge folders when host1 is case sensitive
-                       while host2 is not (like Exchange). Only the first
-                       similar folder is synced (ex: Sent SENT sent -> Sent).
-
- --skipemptyfolders  : Empty host1 folders are not created on host2.
-
- --f1f2    str1=str2 : Force folder str1 to be synced to str2.
- --include      reg  : Sync folders matching this regular expression
- --include      reg  : or this one, etc.
-                       in case both --include --exclude options are
-                       use, include is done before.
- --exclude      reg  : Skips folders matching this regular expression
-                       Several folders to avoid:
-                        --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
- --exclude      reg  : or this one, etc.
-
- --subfolder2   str  : Move whole host1 folders hierarchy under this
-                       host2 folder  str    .
-                       It does it by adding two --regextrans2 options before
-                       all others. Add --debug to see what's really going on.
-
- --regextrans2  reg  : Apply the whole regex to each destination folders.
- --regextrans2  reg  : and this one. etc.
-                       When you play with the --regextrans2 option, first
-                       add also the safe options --dry --justfolders
-                       Then, when happy, remove --dry, remove --justfolders.
-                       Have in mind that --regextrans2 is applied after prefix
-                       and separator inversion.
-
- --tmpdir       str  : Where to store temporary files and subdirectories.
-                       Will be created if it doesn't exist.
-                       Default is system specific, Unix is /tmp but
-                       it's often small and deleted at reboot.
-                       --tmpdir /var/tmp should be better.
- --pidfile      str  : The file where imapsync pid is written.
- --pidfilelocking    : Abort if pidfile already exists. Usefull to avoid
-                       concurrent transfers on the same mailbox.
-
- --nolog             : Turn off logging on file
- --logfile      str  : Change the default log filename (can be dirname/filename).
- --logdir       str  : Change the default log directory. Default is LOG_imapsync
-
- --prefix1      str  : Remove prefix to all destination folders
-                       (usually INBOX. or INBOX/ or an empty string "")
-                       you have to use --prefix1 if host1 imap server
-                       does not have NAMESPACE capability, so imapsync
-                       suggests to use it. All other cases are bad.
- --prefix2      str  : Add prefix to all host2 folders. See --prefix1
- --sep1         str  : Host1 separator in case NAMESPACE is not supported.
- --sep2         str  : Host2 separator in case NAMESPACE is not supported.
-
- --skipmess     reg  : Skips messages maching the regex.
-                       Example: 'm/[\x80-ff]/' # to avoid 8bits messages.
-                       --skipmess is applied before --regexmess
- --skipmess     reg  : or this one, etc.
-
- --pipemess     cmd  : Apply this cmd command to each message content
-                       before the copy.
- --pipemess     cmd  : and this one, etc.
-
- --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
-
- --regexmess    reg  : Apply the whole regex to each message before transfer.
-                       Example: 's/\000/ /g' # to replace null by space.
- --regexmess    reg  : and this one, etc.
-
- --regexflag    reg  : Apply the whole regex to each flags list.
-                       Example: 's/"Junk"//g' # to remove "Junk" flag.
- --regexflag    reg  : and this one, etc.
-
- --delete            : Deletes messages on host1 server after a successful
-                       transfer. Option --delete has the following behavior:
-                       it marks messages as deleted with the IMAP flag
-                       \Deleted, then messages are really deleted with an
-                       EXPUNGE IMAP command.
-
- --delete2           : Delete messages in host2 that are not in
-                       host1 server. Useful for backup or pre-sync.
- --delete2duplicates : Delete messages in host2 that are duplicates.
-                       Works only without --useuid since duplicates are
-                       detected with an header part of each message.
-
- --delete2folders    : Delete folders in host2 that are not in host1 server.
-                       For safety, first try it like this (it is safe):
-                       --delete2folders --dry --justfolders --nofoldersizes
- --delete2foldersonly   reg : Deleted only folders matching regex.
-                              Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/"
- --delete2foldersbutnot reg : Do not delete folders matching regex.
-                              Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/"
- --noexpunge         : Do not expunge messages on host1.
-                       Expunge really deletes messages marked deleted.
-                       Expunge is made at the beginning, on host1 only.
-                       Newly transferred messages are also expunged if
-                       option --delete is given.
-                       No expunge is done on host2 account (unless --expunge2)
- --expunge1          : Expunge messages on host1 after messages transfer.
- --expunge2          : Expunge messages on host2 after messages transfer.
- --uidexpunge2       : uidexpunge messages on the host2 account
-                       that are not on the host1 account, requires --delete2
- --nomixfolders      : Avoid merging folders that are considered different on
-                       host1 but the same on destination host2 because of
-                       case sensitivities and insensitivities.
-
- --syncinternaldates : Sets the internal dates on host2 same as host1.
-                       Turned on by default. Internal date is the date
-                       a message arrived on a host (mtime).
- --idatefromheader   : Sets the internal dates on host2 same as the
-                       "Date:" headers.
-
- --maxsize      int  : Skip messages larger  (or equal) than  int  bytes
- --minsize      int  : Skip messages smaller (or equal) than  int  bytes
- --maxage       int  : Skip messages older than  int  days.
-                       final stats (skipped) don't count older messages
-                       see also --minage
- --minage       int  : Skip messages newer than  int  days.
-                       final stats (skipped) don't count newer messages
-                       You can do (+ are the messages selected):
-                       past|----maxage+++++++++++++++>now
-                       past|+++++++++++++++minage---->now
-                       past|----maxage+++++minage---->now (intersection)
-                       past|++++minage-----maxage++++>now (union)
-
- --search       str  : Selects only messages returned by this IMAP SEARCH
-                       command. Applied on both sides.
- --search1      str  : Same as --search for selecting host1 messages only.
- --search2      str  : Same as --search for selecting host2 messages only.
-                       --search CRIT equals --search1 CRIT --search2 CRIT
-
- --exitwhenover int  : Stop syncing when total bytes transferred reached.
-                       Gmail per day allows
-                       2500000000 = 2.5 GB downloaded from Gmail as host2
-                        500000000 = 500 MB uploaded to Gmail as host1.
-
- --maxlinelength int : skip messages with a line length longer than  int  bytes.
-                       RFC 2822 says it must be no more than 1000 bytes.
-
- --useheader    str  : Use this header to compare messages on both sides.
-                       Ex: Message-ID or Subject or Date.
- --useheader    str    and this one, etc.
-
- --subscribed        : Transfers subscribed folders.
- --subscribe         : Subscribe to the folders transferred on the
-                       host2 that are subscribed on host1. On by default.
- --subscribeall      : Subscribe to the folders transferred on the
-                       host2 even if they are not subscribed on host1.
-
- --nofoldersizes     : Do not calculate the size of each folder in bytes
-                       and message counts. Default is to calculate them.
- --nofoldersizesatend: Do not calculate the size of each folder in bytes
-                       and message counts at the end. Default is on.
- --justfoldersizes   : Exit after having printed the folder sizes.
-
- --syncacls          : Synchronises acls (Access Control Lists).
- --nosyncacls        : Does not synchronize acls. This is the default.
-                       Acls in IMAP are not standardized, be careful.
-
- --usecache          : Use cache to speedup.
- --nousecache        : Do not use cache. Caveat: --useuid --nousecache creates
-                       duplicates on multiple runs.
- --useuid            : Use uid instead of header as a criterium to recognize
-                       messages. Option --usecache is then implied unless
-                       --nousecache is used.
-
- --debug             : Debug mode.
- --debugfolders      : Debug mode for the folders part only.
- --debugcontent      : Debug content of the messages transfered. Huge ouput.
- --debugflags        : Debug mode for flags.
- --debugimap1        : IMAP debug mode for host1. Very verbose.
- --debugimap2        : IMAP debug mode for host2. Very verbose.
- --debugimap         : IMAP debug mode for host1 and host2.
- --debugmemory       : Debug mode showing memory consumption after each copy.
-
- --errorsmax     int : Exit when int number of errors is reached. Default is 50.
-
- --tests             : Run local non-regression tests. Exit code 0 means all ok.
- --testslive         : Run a live test with test1.lamiral.info imap server.
-                       Useful to check the basics. Needs internet connexion.
-
- --version           : Print only software version.
- --noreleasecheck    : Do not check for new imapsync release (a http request).
- --releasecheck      : Check for new imapsync release (a http request).
- --noid              : Do not send/receive ID command to imap servers.
- --justconnect       : Just connect to both servers and print useful
-                       information. Need only --host1 and --host2 options.
- --justlogin         : Just login to both host1 and host2 with users
-                       credentials, then exit.
- --justfolders       : Do only things about folders (ignore messages).
-
- --help              : print this help.
-
- Example:
- To synchronize the source imap account
-   "test1" on server "test1.lamiral.info" with password "secret1"
- to the destination imap account
-   "test2" on server "test2.lamiral.info" with password "secret2"
- do:
-
- imapsync \
-    --host1 test1.lamiral.info --user1 test1 --password1 secret1 \
-    --host2 test2.lamiral.info --user2 test2 --password2 secret2
-
-=cut
-# comment
-
-=pod
-
-=head1 DESCRIPTION
-
-Imapsync command is a tool allowing incremental and
-recursive imap transfers from one mailbox to another.
-
-By default all folders are transferred, recursively, all
-possible flags (\Seen \Answered \Flagged etc.) are synced too.
-
-We sometimes need to transfer mailboxes from one imap server to
-another. This is called migration.
-
-Imapsync reduces the amount
-of data transferred by not transferring a given message
-if it resides already on both sides. Same specific headers
-and the transfer is done only once; taken into account are by default
-Message-Id and Received header lines.
-All flags are
-preserved, unread will stay unread, read will stay read,
-deleted will stay deleted. You can stop the transfer at any
-time and restart it later, imapsync works well with bad
-connections and interruptions.
-
-You can decide to delete the messages from the source mailbox
-after a successful transfer, it can be a good feature when migrating
-live mailboxes since messages will be only on one side.
-In that case, use the --delete option. Option --delete implies
-also option --expunge so all messages marked deleted on host1
-will be really deleted.
-(you can use --noexpunge to avoid this but I don't see any
-good real world scenario for the combination --delete --noexpunge).
-
-A different scenario is synchronizing a mailbox B from another mailbox A
-in case you just want to keep a "live" copy of A in B.
-In that case --delete2 has to be used, it deletes messages in host2
-folder B that are not in host1 folder A. If you also need to destroy
-host2 folders that are not in host1 then use --delete2folders (see also
---delete2foldersonly and --delete2foldersbutnot).
-
-Imapsync is not adequate for maintaining two active imap accounts
-in synchronization when the user plays independently on both sides.
-Use offlineimap (written by John Goerzen) or mbsync (written by
-Michael R. Elkins) for 2 ways synchronizations.
-
-
-=head1 OPTIONS
-
-To get a description of each option just invoke:
-
-  imapsync
-
-or read the previous section named USAGE,
-
-or read http://imapsync.lamiral.info/OPTIONS
-
-=head1 HISTORY
-
-I wrote imapsync because an enterprise (basystemes) paid me to install
-a new imap server without losing huge old mailboxes located on a far
-away remote imap server accessible by a low bandwidth link. The tool
-imapcp (written in python) could not help me because I had to verify
-every mailbox was well transferred and delete it after a good
-transfer. imapsync started its life as a copy_folder.pl patch.
-The tool copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
-module tarball source (in the examples/ directory of the tarball).
-
-=head1 EXAMPLE
-
-While working on imapsync parameters please run imapsync in
-dry mode (no modification induced) with the --dry
-option. Nothing bad can be done this way.
-
-To synchronize the imap account "buddy" (with password "secret1")
-on host "imap.src.fr" to the imap account "max" (with password "secret2")
-on host "imap.dest.fr":
-
- imapsync --host1 imap.src.fr  --user1 buddy --password1 secret1 \
-          --host2 imap.dest.fr --user2 max   --password2 secret2
-
-Then you will have max's mailbox updated from buddy's
-mailbox.
-
-=head1 SECURITY
-
-You can use --passfile1  instead of --password1 to give the
-password since it is safer. With --password1 option any user
-on your host can see the password by using the 'ps auxwwww'
-command. Using a variable (like $PASSWORD1) is also
-dangerous because of the 'ps auxwwwwe' command. So, saving
-the password in a well protected file (600 or rw-------) is
-the best solution.
-
-imasync is not totally protected against sniffers on the
-network since passwords may be transferred in plain text
-if CRAM-MD5 is not supported by your imap servers.  Use
---ssl1 (or --tls1) and --ssl2 (or --tls2) to enable
-encryption on host1 and host2.
-
-You may authenticate as one user (typically an admin user),
-but be authorized as someone else, which means you don't
-need to know every user's personal password.  Specify
---authuser1 "adminuser" to enable this on host1.  In this
-case, --authmech1 PLAIN will be used by default since it
-is the only way to go for now. So don't use --authmech1 SOMETHING
-with --authuser1 "adminuser", it will not work.
-Same behavior with the --authuser2 option.
-Authenticate with an admin account must be supported by your
-imap server to work with imapsync.
-
-When working on Sun/iPlanet/Netscape IMAP servers you must use
---proxyauth1 to enable administrative user to masquerade as another user.
-Can also be used on destination server with --proxyauth2
-
-You can authenticate with OAUTH when transfering from Google Apps.
-The consumer key will be the domain part of the --user, and the
---password will be used as the consumer secret. It does not work
-with Google Apps free edition.
-
-=head1 EXIT STATUS
-
-imapsync will exit with a 0 status (return code) if everything went good.
-Otherwise, it exits with a non-zero status.
-
-So if you have an unreliable internet connection, you can use this loop
-in a Bourne shell:
-
-        while ! imapsync ...; do
-              echo imapsync not complete
-        done
-
-=head1 LICENSE AND COPYRIGHT
-
-imapsync is free, open, public but not always gratis software
-cover by the NOLIMIT Public License.
-See the LICENSE file included in the distribution or just read this
-simple sentence as it is the licence text:
-
- "No limit to do anything with this work and this license."
-
-In case it is not long enough I repeat:
-
- "No limit to do anything with this work and this license."
-
-=head1 MAILING-LIST
-
-The public mailing-list may be the best way to get free support.
-
-To write on the mailing-list, the address is:
-<imapsync@linux-france.org>
-
-To subscribe, send any message (even empty) to:
-<imapsync-subscribe@listes.linux-france.org>
-then just reply to the confirmation message.
-
-To unsubscribe, send a message to:
-<imapsync-unsubscribe@listes.linux-france.org>
-
-To contact the person in charge for the list:
-<imapsync-request@listes.linux-france.org>
-
-The list archives are available at:
-http://www.linux-france.org/prj/imapsync_list/
-So consider that the list is public, anyone
-can see your post. Use a pseudonym or do not
-post to this list if you want to stay private.
-
-Thank you for your participation.
-
-=head1 AUTHOR
-
-Gilles LAMIRAL <gilles.lamiral@laposte.net>
-
-Feedback good or bad is very often welcome.
-
-Gilles LAMIRAL earns his living by writing, installing,
-configuring and teaching free, open and often gratis
-softwares. It used to be "always gratis" but now it is
-"often" because imapsync is sold by its author, a good
-way to stay maintening and supporting free open public
-softwares (see the license) over decades.
-
-=head1 BUGS AND LIMITATIONS
-
-Help me to help you: follow the following guidelines.
-
-Report any bugs or feature requests to the public mailing-list
-or to the author.
-
-Before reporting bugs, read the FAQs, the README and the
-TODO files. http://imapsync.lamiral.info/
-
-Upgrade to last imapsync release, maybe the bug
-is already fixed.
-
-Upgrade to last Mail-IMAPClient Perl module.
-http://search.cpan.org/dist/Mail-IMAPClient/
-maybe the bug is already fixed there.
-
-Make a good title with word "imapsync" in it (my spam filters won't filter it),
-Try to write an email title with more words than just "imapsync" or "problem",
-a good title is made of keywords summary, but not too long (one visible line).
-
-Help us to help you: in your report, please include:
-
- - imapsync version.
-
- - output near the first failures, a few lines before is good to get the context
-   of the issue. First failures messages are often more significant than
-   the last ones.
-
- - if the issue is always related to the same messages, include the output
-   with --debug --debugimap, near the failure point. For example,
-   Isolate a buggy message or two in a folder 'BUG' and use
-
-     imapsync ... --folder 'BUG' --debug --debugimap
-
- - imap server softwares on both sides and their version number.
-
- - imapsync with all the options you use,  the full command line
-   you use (except the passwords of course).
-
- - IMAPClient.pm version.
-
- - the run context. Do you run imapsync.exe, a unix binary
-   or the perl script imapsync.
-
- - operating system running imapsync.
-
- - virtual software context (vmware, xen etc.)
-
- - operating systems on both sides and the third side in case
-   you run imapsync on a foreign host from the both.
-
-Most of those values can be found as a copy/paste at the begining of the output,
-so a carbon copy of the output is a very easy and very good debug report for me.
-
-One time in your life, read the paper
-"How To Ask Questions The Smart Way"
-http://www.catb.org/~esr/faqs/smart-questions.html
-and then forget it.
-
-=head1 IMAP SERVERS
-
-See http://imapsync.lamiral.info/S/imapservers.shtml
-
-=head1 HUGE MIGRATION
-
-Pay special attention to options
---subscribed
---subscribe
---delete
---delete2
---delete2folders
---maxage
---minage
---maxsize
---useuid
---usecache
-
-If you have many mailboxes to migrate think about a little
-shell program. Write a file called file.txt (for example)
-containing users and passwords.
-The separator used in this example is ';'
-
-The file.txt file contains:
-
-user001_1;password001_1;user001_2;password001_2
-user002_1;password002_1;user002_2;password002_2
-user003_1;password003_1;user003_2;password003_2
-user004_1;password004_1;user004_2;password004_2
-user005_1;password005_1;user005_2;password005_2
-...
-
-On Unix the shell program can be:
-
- { while IFS=';' read  u1 p1 u2 p2; do
-	imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \
-                 --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ...
- done ; } < file.txt
-
-On Windows the batch program can be:
-
-  FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^
-  --host1 imap.side1.org --user1 %%G --password1 %%H ^
-  --host2 imap.side2.org --user2 %%I --password2 %%J ...
-
-The ... have to be replaced by nothing or any imapsync option.
-Welcome in shell programming !
-
-You will find already written scripts at
-http://imapsync.lamiral.info/examples/
-
 
 =head1 HACKING
 
 Feel free to hack imapsync as the NOLIMIT license permits it.
 
-=head1 LINKS
-
-Entries for imapsync:
-https://web.archive.org/web/20070202005121/http://www.imap.org/products/showall.php
 
 =head1 SIMILAR SOFTWARES
 
@@ -686,7 +637,18 @@ https://web.archive.org/web/20070202005121/http://www.imap.org/products/showall.
 
 Feedback (good or bad) will often be welcome.
 
-$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $
+=head1 HISTORY
+
+I wrote imapsync because an enterprise (basystemes) paid me to install
+a new imap server without losing huge old mailboxes located in a far
+away remote imap server, accessible by a low-bandwidth link. The tool
+imapcp (written in python) could not help me because I had to verify
+every mailbox was well transferred, and then delete it after a good
+transfer. Imapsync started its life as a patch of the copy_folder.pl 
+script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl
+module tarball source (more precisely in the examples/ directory of the 
+Mail-IMAPClient tarball).
+
 
 =cut
 
@@ -695,11 +657,9 @@ $Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $
 
 use strict ;
 use warnings ;
-++$| ;
-
 use Carp ;
 use Data::Dumper ;
-use Digest::HMAC_SHA1 qw( hmac_sha1 ) ;
+use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ;
 use Digest::MD5  qw( md5 md5_hex md5_base64 ) ;
 use English qw( -no_match_vars ) ;
 use Errno qw(EAGAIN EPIPE ECONNRESET) ;
@@ -710,15 +670,18 @@ use File::Glob qw( :glob ) ;
 use File::Path qw( mkpath rmtree ) ;
 use File::Spec ;
 use File::stat ;
-#use Imapsync::Getopt::Long ;
+use Getopt::Long (  ) ;
 use IO::File ;
-use IO::Socket qw(:crlf SOL_SOCKET SO_KEEPALIVE) ;
-#use IO::Socket::SSL ;
+use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ;
+use IO::Socket::INET6 ;
+use IO::Socket::SSL ;
 use IO::Tee ;
 use IPC::Open3 'open3' ;
 use Mail::IMAPClient 3.30 ;
 use MIME::Base64 ;
+use Pod::Usage qw(pod2usage) ;
 use POSIX qw(uname SIGALRM) ;
+use Sys::Hostname ;
 use Term::ReadKey ;
 use Test::More ;
 use Time::HiRes qw( time sleep ) ;
@@ -726,6 +689,10 @@ use Time::Local ;
 use Unicode::String ;
 use Cwd ;
 use Readonly ;
+#use Net::Ping ;
+use Sys::MemInfo ;
+
+local $OUTPUT_AUTOFLUSH = 1 ;
 
 # constants
 
@@ -735,10 +702,10 @@ use Readonly ;
 Readonly my $EX_OK          => 0  ; #/* successful termination */
 Readonly my $EX_USAGE       => 64 ; #/* command line usage error */
 #Readonly my $EX_DATAERR     => 65 ; #/* data format error */
-#Readonly my $EX_NOINPUT     => 66 ; #/* cannot open input */
+Readonly my $EX_NOINPUT     => 66 ; #/* cannot open input */
 #Readonly my $EX_NOUSER      => 67 ; #/* addressee unknown */
 #Readonly my $EX_NOHOST      => 68 ; #/* host name unknown */
-#Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
+Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */
 Readonly my $EX_SOFTWARE    => 70 ; #/* internal software error */
 #Readonly my $EX_OSERR       => 71 ; #/* system error (e.g., can't fork) */
 #Readonly my $EX_OSFILE      => 72 ; #/* critical OS file missing */
@@ -752,11 +719,17 @@ Readonly my $EX_SOFTWARE    => 70 ; #/* internal software error */
 # Mine
 Readonly my $EXIT_BY_SIGNAL              =>   6 ;
 Readonly my $EXIT_PID_FILE_ALREADY_EXIST =>   8 ;
+
 Readonly my $EXIT_WITH_ERRORS            => 111 ;
 Readonly my $EXIT_WITH_ERRORS_MAX        => 112 ;
 Readonly my $EXIT_UNKNOWN                => 126 ;
 
-Readonly my $ERRORS_MAX =>  50 ; # exit after 50 errors.
+Readonly my $EXIT_TESTS_FAILED           => 254 ; # Like Test::More API
+
+
+
+Readonly my $ERRORS_MAX     =>  50 ; # exit after 50 errors.
+Readonly my $ERRORS_MAX_CGI =>  20 ; # exit after 20 errors in CGI context.
 
 
 Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect
@@ -768,18 +741,20 @@ Readonly my $SPLIT_FACTOR =>  10 ; # init_imap() calls Maxcommandlength( $SPLIT_
 Readonly my $IMAP_PORT     => 143 ; # Well know port for IMAP
 Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL
 
-Readonly my $LAST => -1 ; 
-Readonly my $MINUS_ONE => -1 ; 
+Readonly my $LAST => -1 ;
+Readonly my $MINUS_ONE => -1 ;
 
-Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ; 
+Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ;
 Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ;
 
-
+Readonly my $TCP_PING_TIMEOUT => 5 ;
 Readonly my $DEFAULT_TIMEOUT => 120 ;
 Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ;
-Readonly my $DEFAULT_UIDNEXT => 999999 ;
+Readonly my $DEFAULT_UIDNEXT => 999_999 ;
 Readonly my $DEFAULT_BUFFER_SIZE => 4096 ;
 
+Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond
+
 Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ;
 
 Readonly my $PERMISSION_FILTER => 7777 ;
@@ -796,11 +771,11 @@ Readonly my $NUMBER_20_000 => 20_000 ;
 
 Readonly my $QUOTA_PERCENT_LIMIT => 90 ;
 
-Readonly my $NUMBER_104857600 => 104857600 ;
+Readonly my $NUMBER_104_857_600 => 104_857_600 ;
 
 Readonly my $SIZE_MAX_STR => 64 ;
 
-Readonly my $NB_SECONDS_IN_A_DAY => 86400 ;
+Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ;
 
 Readonly my $STD_CHAR_PER_LINE => 80 ;
 
@@ -809,20 +784,22 @@ Readonly my $FALSE => 0 ;
 
 Readonly my $LAST_RESSORT_SEPARATOR => q{/} ;
 
+Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ;
+Readonly my $CGI_HASHFILE   => '/var/tmp/imapsync_hash' ;
+Readonly my $UMASK_PARANO   => '0077' ;
+
 # global variables
 
 my(
-	$sync,
-	$rcs,
+        $sync,
         $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags,
         $debuglist, $debugdev, $debugmaxlinelength, @debugbasket, $debugcgi,
-        $host1, $host2, $port1, $port2,
-        $user1, $user2, $domain1, $domain2,
-        $password1, $password2, $passfile1, $passfile2,
+        $domain1, $domain2,
+        $passfile1, $passfile2,
         @folder, @include, @exclude, @folderrec,
         @folderfirst, @folderlast,
         $prefix1, $prefix2,
-	$subfolder2,
+        $subfolder2,
         @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck,
         $flagscase, $filterflags, $syncflagsaftercopy,
         $sep1, $sep2,
@@ -835,19 +812,16 @@ my(
         $search, $search1, $search2,
         $skipheader, @useheader,
         $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize,
-        $delete, $delete2, $delete2duplicates,
-        $expunge, $expunge1, $expunge2, $uidexpunge2, $dry,
+        $delete1, $delete2, $delete2duplicates,
+        $expunge1, $expunge2, $uidexpunge2,
         $justfoldersizes,
         $authmd5, $authmd51, $authmd52,
         $subscribed, $subscribe, $subscribeall,
         $version, $help,
         $justconnect, $justfolders, $justbanner,
         $fast,
-
-        $total_bytes_transferred,
         $total_bytes_skipped,
         $total_bytes_error,
-        $nb_msg_transferred,
         $nb_msg_skipped,
         $nb_msg_skipped_dry_mode,
         $h1_nb_msg_duplicate,
@@ -867,18 +841,14 @@ my(
         $h2_nb_msg_end, $h2_bytes_end,
 
         $timeout,
-        $timestart_int, $timeend,
+        $timestart_int,
         $timebefore,
-        $ssl1, $ssl2,
-        $ssl1_ssl_version, $ssl2_ssl_version,
-        $tls1, $tls2,
         $uid1, $uid2,
         $authuser1, $authuser2,
         $proxyauth1, $proxyauth2,
         $authmech1, $authmech2,
         $split1, $split2,
         $reconnectretry1, $reconnectretry2,
-        $tests, $test_builder, $testsdebug, $testslive,
         $justlogin,
         $tmpdir,
         $releasecheck,
@@ -891,8 +861,6 @@ my(
         %h1, %h2,
         $checkselectable, $checkmessageexists,
         $expungeaftereach,
-        $abletosearch,
-        $showpasswords,
         $fixslash2,
         $messageidnodomain,
         $fixInboxINBOX,
@@ -901,24 +869,36 @@ my(
         $uidnext_default,
         $fixcolonbug,
         $create_folder_old,
-        $maxmessagespersecond,
-        $maxbytespersecond,
         $skipcrossduplicates, $debugcrossduplicates,
         $disarmreadreceipts,
         $mixfolders, $skipemptyfolders,
-	$fetch_hash_set,
+        $fetch_hash_set,
 );
 
-# main program
 
+# main program
 # global variables initialisation
 
-$rcs = q{$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $} ;
+# Currently removing all global variables except $sync
+# passing each of them under $sync->{variable_name}
 
-$total_bytes_transferred   = 0;
+$sync->{timestart} = time ; # Is a float because of use Time::HiRres
+
+$sync->{rcs} = q{$Id: imapsync,v 1.836 2017/09/05 16:14:53 gilles Exp gilles $} ;
+
+
+
+my @loadavg = loadavg(  ) ;
+$sync->{cpu_number} = cpu_number(  ) ;
+$sync->{loaddelay} = load_and_delay( $sync->{cpu_number}, @loadavg ) ;
+$sync->{loadavg} = join( q{ }, @loadavg ) . " on $sync->{cpu_number} cores." ;
+
+
+
+$sync->{total_bytes_transferred} = 0 ;
 $total_bytes_skipped = 0;
 $total_bytes_error   = 0;
-$nb_msg_transferred = 0;
+$sync->{nb_msg_transferred} = 0;
 $nb_msg_skipped = $nb_msg_skipped_dry_mode = 0;
 $h1_nb_msg_deleted = $h2_nb_msg_deleted = 0;
 $h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0;
@@ -952,30 +932,54 @@ my %month_abrev = (
 );
 
 
+my $cgidir ;
 
+# CGI environment in case
+cgibegin( $sync ) ;
+# In cgi context, printing must start by the header so we delay other prints by using output() storage
+my $options_good = get_options( $sync, @ARGV ) ;
+docker_context( $sync ) ;
+cgibuildheader( $sync ) ;
+myprint( output( $sync ) ) ;
+output_reset_with( $sync ) ;
 
-# @ARGV will be eat by get_options()
-my @argv_copy = @ARGV;
+# Can break here if load is too heavy
+cgiload( $sync ) ;
 
-my $cgi_dir = '/var/tmp/imapsync_cgi' ;
+# don't go on if options are not all known.
+if ( ! defined $options_good ) { exit $EX_USAGE ; }
 
-# Under CGI environment
-if ( $ENV{SERVER_SOFTWARE} ) {
-        myprint( "\n" ) ;
-        myprint( "<pre>\n" ) ;
-        -d $cgi_dir or mkpath $cgi_dir or die "Can not create $cgi_dir: $!\n" ;
-        chdir  $cgi_dir or die "Can not cd to $cgi_dir: $!\n" ;
+# just the version
+myprint( imapsync_version( $sync ), "\n" ) and exit 0 if ( $version ) ;
+
+$sync->{debugenv} and printenv( $sync ) ; # if option --debugenv
+load_modules(  ) ;
+
+# after_get_options call usage and exit if --help or options were not well got
+after_get_options( $options_good ) ;
+
+# Under CGI environment, fix caveat emptor potentiel issues
+cgisetcontext( $sync ) ;
+
+easyany( $sync ) ;
+
+$tmpdir ||= File::Spec->tmpdir(  ) ;
+
+# Unit tests
+testsexit( $sync ) ;
+
+# init live varaiables
+testslive( $sync ) if ( $sync->{testslive} ) ;
+testslive6( $sync ) if ( $sync->{testslive6} )  ;
+
+#
+$sync->{pidfile} =  defined  $sync->{pidfile}  ? $sync->{pidfile} : $tmpdir . '/imapsync.pid' ;
+$sync->{pidfilelocking} = defined  $sync->{pidfilelocking}  ? $sync->{pidfilelocking} : 0 ;
+
+if ( $sync->{abort} ) {
+	abort( $sync ) ;
 }
 
-get_options(  ) ;
-unsetunsafe(  ) if ( $ENV{SERVER_SOFTWARE} ) ;
-
-# Under CGI environment
-if ( $ENV{SERVER_SOFTWARE} ) {
-        myprint( 'Current directory is ' . getcwd(  ) . "\n" ) ;
-        myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
-        myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
-}
 
 local $SIG{ INT } = sub {
         my $signame = shift ;
@@ -983,18 +987,15 @@ local $SIG{ INT } = sub {
 } ;
 
 local $SIG{ QUIT } = local $SIG{ TERM } = sub {
-	my $signame = shift ;
+        my $signame = shift ;
         catch_exit( $sync, $signame ) ;
 } ;
 
 
-$sync->{timestart} = $BASETIME ; # Never too let reading books and perlvar
-
 $sync->{log}        = defined $sync->{log}        ? $sync->{log}        :  1 ;
 $sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} :  1 ;
 $sync->{errorsmax}  = defined $sync->{errorsmax}  ? $sync->{errorsmax}  : $ERRORS_MAX ;
 
-$sync->{user2} = $user2 ;
 
 if ( $sync->{log} ) {
         setlogfile( $sync ) ;
@@ -1008,23 +1009,23 @@ my $timestart_str = localtime( $sync->{timestart} ) ;
 myprint( "Transfer started at $timestart_str\n" ) ;
 myprint( "PID is $PROCESS_ID\n" ) ;
 myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ;
+myprint( "Load is " . ( join( q{ }, loadavg(  ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ;
+myprint( 'Current directory is ' . getcwd(  ) . "\n" ) ;
+myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
+myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
+
 $modulesversion = defined  $modulesversion  ? $modulesversion : 1 ;
 
 # If you want releasecheck not to be done by default (like the github maintainer),
-# then uncomment the first "$releasecheck =" line, the line ending with "0 ;".
-# The second line (ending with "1 ;") can stay active or be commented,
-# the result will be the same: no releasecheck by default.
+# then just uncomment the first "$releasecheck =" line, the line ending with "0 ;",
+# the second line (ending with "1 ;") can then stay active or be commented,
+# the result will be the same: no releasecheck by default (because 0 is then defined value).
 
 $releasecheck = defined  $releasecheck  ? $releasecheck : 0 ;
 #$releasecheck = defined  $releasecheck  ? $releasecheck : 1 ;
 
 my $warn_release = ( $releasecheck ) ? check_last_release(  ) : q{} ;
 
-# default values
-
-$sync->{pidfile} =  defined  $sync->{pidfile}  ? $sync->{pidfile} : $tmpdir . '/imapsync.pid' ;
-
-$sync->{pidfilelocking} = defined  $sync->{pidfilelocking}  ? $sync->{pidfilelocking} : 0 ;
 
 $wholeheaderifneeded  = defined  $wholeheaderifneeded   ? $wholeheaderifneeded  : 1;
 
@@ -1047,9 +1048,15 @@ $cacheaftercopy = 1 if ( $usecache and ( ! defined  $cacheaftercopy  ) ) ;
 $checkselectable    = defined  $checkselectable  ? $checkselectable : 1 ;
 $checkmessageexists = defined  $checkmessageexists  ? $checkmessageexists : 0 ;
 $expungeaftereach   = defined  $expungeaftereach  ? $expungeaftereach : 1 ;
-$abletosearch       = defined  $abletosearch  ? $abletosearch : 1 ;
-$checkmessageexists = 0 if ( not $abletosearch ) ;
-$showpasswords      = defined  $showpasswords  ? $showpasswords : 0 ;
+
+# abletosearch is on by default
+$sync->{abletosearch}    = defined  $sync->{abletosearch}   ? $sync->{abletosearch} : 1 ;
+$sync->{abletosearch1}   = defined  $sync->{abletosearch1}  ? $sync->{abletosearch1} : $sync->{abletosearch} ;
+$sync->{abletosearch2}   = defined  $sync->{abletosearch2}  ? $sync->{abletosearch2} : $sync->{abletosearch} ;
+$checkmessageexists      = 0 if ( not $sync->{abletosearch1} ) ;
+
+
+$sync->{showpasswords}   = defined  $sync->{showpasswords}  ? $sync->{showpasswords} : 0 ;
 $fixslash2          = defined  $fixslash2  ? $fixslash2 : 1 ;
 $fixInboxINBOX      = defined  $fixInboxINBOX  ? $fixInboxINBOX : 1 ;
 $create_folder_old  = defined  $create_folder_old  ? $create_folder_old : 0 ;
@@ -1058,62 +1065,49 @@ $sync->{automap}    = defined  $sync->{automap}  ? $sync->{automap} : 0 ;
 
 $delete2duplicates = 1 if ( $delete2 and ( ! defined  $delete2duplicates  ) ) ;
 
-$maxmessagespersecond = defined  $maxmessagespersecond  ? $maxmessagespersecond : 0 ;
-$maxbytespersecond    = defined  $maxbytespersecond     ? $maxbytespersecond    : 0 ;
+$sync->{maxmessagespersecond} = defined  $sync->{maxmessagespersecond}  ? $sync->{maxmessagespersecond} : 0 ;
+$sync->{maxbytespersecond}    = defined  $sync->{maxbytespersecond}     ? $sync->{maxbytespersecond}    : 0 ;
 
-myprint( banner_imapsync( @argv_copy ) ) ;
+$sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ;
+
+myprint( banner_imapsync( @ARGV ) ) ;
 
 myprint( "Temp directory is $tmpdir  ( to change it use --tmpdir dirpath )\n") ;
+myprint( output( $sync ) ) ;
 
-is_valid_directory( $tmpdir ) || croak "Error creating tmpdir $tmpdir : $!" ;
+do_valid_directory( $tmpdir ) || croak "Error creating tmpdir $tmpdir : $OS_ERROR" ;
 
 if ( $sync->{pidfile} ) {
         write_pidfile( $sync->{pidfile}, $sync->{pidfilelocking} ) ;
 }
 
+if ( $sync->{simulong} ) { simulong( $sync->{simulong} ) ; }
+
+
+
 $fixcolonbug = defined  $fixcolonbug  ? $fixcolonbug : 1 ;
 
 if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug(  ) } ;
 
 $modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ;
 
-my $DEFAULT_SSL_VERIFY ;
-my %SSL_VERIFY_STR ;
-
-if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
-        Readonly $DEFAULT_SSL_VERIFY => IO::Socket::SSL::SSL_VERIFY_NONE(  ) ;
-        Readonly %SSL_VERIFY_STR => (
-                IO::Socket::SSL::SSL_VERIFY_NONE(  ) => 'SSL_VERIFY_NONE' ,
-                IO::Socket::SSL::SSL_VERIFY_PEER(  ) => 'SSL_VERIFY_PEER' ,
-        ) ;
-        $IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ;
-        myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ;
-}
-
-if ( $ssl1 ) {
-        myprint( 'Host1: SSL default mode is like --sslargs1 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host1 (do not check the certificate server)\n" ) ;
-        myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host1\n" ) ;
-}
-if ( $ssl2 ) {
-        myprint( 'Host2: SSL default mode is like --sslargs2 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host2 (do not check the certificate server)\n" ) ;
-        myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host2\n" ) ;
-}
-
 
 check_lib_version(  ) or
   croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
 
 exit_clean( $sync, $EX_OK ) if ( $justbanner ) ;
 
+sslcheck( $sync ) ;
+
 
 $split1 ||= $SPLIT ;
 $split2 ||= $SPLIT ;
 
-$host1 || missing_option( '--host1' ) ;
-$port1 ||= ( $ssl1 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
+$sync->{host1} || missing_option( '--host1' ) ;
+$sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
 
-$host2 || missing_option( '--host2' ) ;
-$port2 ||= ( $ssl2 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
+$sync->{host2} || missing_option( '--host2' ) ;
+$sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
 
 $debugimap1 = $debugimap2 = 1 if ( $debugimap ) ;
 $debug = 1 if ( $debugimap1 or $debugimap2 ) ;
@@ -1129,30 +1123,60 @@ $subscribe = defined $subscribe ? $subscribe : 1;
 # Allow size mismatch by default
 $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
 
-$delete2folders = 1
-    if ( defined  $delete2foldersbutnot  or defined  $delete2foldersonly  ) ;
 
-if ( $justconnect ) {
-	justconnect(  ) ;
-	exit_clean( $sync, $EX_OK ) ;
+if ( defined  $delete2foldersbutnot  or defined  $delete2foldersonly  ) {
+	$delete2folders = 1 ;
 }
 
-$user1 || missing_option( '--user1' ) ;
-$user2 || missing_option( '--user2' ) ;
+my $DEFAULT_SSL_VERIFY ;
+my %SSL_VERIFY_STR ;
+
+Readonly $DEFAULT_SSL_VERIFY => IO::Socket::SSL::SSL_VERIFY_NONE(  ) ;
+Readonly %SSL_VERIFY_STR => (
+	IO::Socket::SSL::SSL_VERIFY_NONE(  ) => 'SSL_VERIFY_NONE' ,
+	IO::Socket::SSL::SSL_VERIFY_PEER(  ) => 'SSL_VERIFY_PEER' ,
+) ;
+$IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ;
+
+
+
+if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) {
+        myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ;
+}
+
+if ( $sync->{ssl1} ) {
+        myprint( 'Host1: SSL default mode is like --sslargs1 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host1 (do not check the certificate server)\n" ) ;
+        myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host1\n" ) ;
+}
+
+if ( $sync->{ssl2} ) {
+        myprint( 'Host2: SSL default mode is like --sslargs2 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host2 (do not check the certificate server)\n" ) ;
+        myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host2\n" ) ;
+}
+
+
+
+if ( $justconnect ) {
+        justconnect(  ) ;
+        exit_clean( $sync, $EX_OK ) ;
+}
+
+$sync->{user1} || missing_option( '--user1' ) ;
+$sync->{user2} || missing_option( '--user2' ) ;
 
 $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
 
-# Turn on expunge if there is not explicit option --noexpunge and option
-# --delete is given.
-# Done because --delete --noexpunge is very dangerous on the second run:
-# the Deleted flag is then synced to all previously transfered messages.
-# So --delete implies --expunge is a better usability default behaviour.
-if ( $delete ) {
-	if ( ! defined  $expunge  ) {
-		myprint( "Info: turning on --expunge1 because --delete --noexpunge1 is very dangerous on the second run.\n" ) ;
-		$expunge = 1 ;
-	}
-		myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
+# Turn on expunge if there is not explicit option --noexpunge1 and option
+# --delete1 is given.
+# Done because --delete1 --noexpunge1 is very dangerous on the second run:
+# the Deleted flag is then synced to all previously transferred messages.
+# So --delete1 implies --expunge1 is a better usability default behavior.
+if ( $delete1 ) {
+        if ( ! defined  $expunge1  ) {
+                myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ;
+                $expunge1 = 1 ;
+        }
+                myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
 }
 
 if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
@@ -1163,48 +1187,48 @@ if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
 if ( ( $delete2 or $delete2duplicates ) and not defined  $uidexpunge2  ) {
         if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
                 myprint( "Info: will act as --uidexpunge2\n" ) ;
-		$uidexpunge2 = 1 ;
+                $uidexpunge2 = 1 ;
         }elsif ( not defined  $expunge2  ) {
                  myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
                 $expunge2 = 1 ;
         }
 }
 
-if ( $delete and $delete2 ) {
-	myprint( "Warning: using --delete and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ;
-	exit_clean( $sync, $EX_USAGE ) ;
+if ( $delete1 and $delete2 ) {
+        myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ;
+        exit_clean( $sync, $EX_USAGE ) ;
 }
 
 if ( $idatefromheader ) {
-	myprint( 'Turned ON idatefromheader, ',
-	      "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
-	$syncinternaldates = 0 ;
+        myprint( 'Turned ON idatefromheader, ',
+              "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
+        $syncinternaldates = 0 ;
 }
 
 if ( $syncinternaldates ) {
-	myprint( 'Info: turned ON syncinternaldates, ',
-	      "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
+        myprint( 'Info: turned ON syncinternaldates, ',
+              "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
 }else{
         myprint( "Info: turned OFF syncinternaldates\n" ) ;
 }
 
 if ( defined $authmd5 and $authmd5 ) {
-	$authmd51 = 1 ;
-	$authmd52 = 1 ;
+        $authmd51 = 1 ;
+        $authmd52 = 1 ;
 }
 
 if ( defined $authmd51 and $authmd51 ) {
-	$authmech1 ||= 'CRAM-MD5';
+        $authmech1 ||= 'CRAM-MD5';
 }
 else{
-	$authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
+        $authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
 }
 
 if ( defined $authmd52 and $authmd52 ) {
-	$authmech2 ||= 'CRAM-MD5';
+        $authmech2 ||= 'CRAM-MD5';
 }
 else{
-	$authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
+        $authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
 }
 
 $authmech1 = uc $authmech1;
@@ -1218,8 +1242,8 @@ if (defined $proxyauth2 && !$authuser2) {
         missing_option( 'With --proxyauth2, --authuser2' ) ;
 }
 
-$authuser1 ||= $user1;
-$authuser2 ||= $user2;
+$authuser1 ||= $sync->{user1};
+$authuser2 ||= $sync->{user2};
 
 myprint( "Host1: will try to use $authmech1 authentication on host1\n") ;
 myprint( "Host2: will try to use $authmech2 authentication on host2\n") ;
@@ -1249,7 +1273,7 @@ $reconnectretry2 = defined  $reconnectretry2  ? $reconnectretry2 : $DEFAULT_NB_R
 # then $uidnext_default is never used. So I have to remove it.
 $uidnext_default = $DEFAULT_UIDNEXT ;
 
-@useheader = qw( Message-Id Received ) unless ( @useheader ) ;
+if ( ! @useheader ) { @useheader = qw( Message-Id Received )  ; }
 
 my %useheader ;
 
@@ -1259,44 +1283,18 @@ for ( @useheader ) { $useheader{ uc  $_  } = undef } ;
 #myprint( Data::Dumper->Dump( [ \%useheader ] )  ) ;
 #exit ;
 
-myprint( "Host1: IMAP server [$host1] port [$port1] user [$user1]\n" ) ;
-myprint( "Host2: IMAP server [$host2] port [$port2] user [$user2]\n" ) ;
+myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ;
+myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ;
 
-$password1 || $passfile1 || 'PREAUTH' eq $authmech1 || 'EXTERNAL' eq $authmech1 || do {
-	myprint( << 'FIN_PASSFILE'  ) ;
-
-If you are afraid of giving password on the command line arguments, you can put the
-password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
-Then give this file restrictive permissions with the command "chmod 600 file1".
-FIN_PASSFILE
-
-	$password1 = ask_for_password( $authuser1 || $user1, $host1 ) ;
-} ;
-
-$password1 = ( defined  $passfile1  ) ? firstline ( $passfile1 ) : $password1 ;
+get_password1( $sync ) ;
+get_password2( $sync ) ;
 
 
-$password2 || $passfile2 || 'PREAUTH' eq $authmech2 || 'EXTERNAL' eq $authmech2 || do {
-	myprint( << 'FIN_PASSFILE'  ) ;
 
-If you are afraid of giving password on the command line arguments, you can put the
-password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
-Then give this file restrictive permissions with the command "chmod 600 file2".
-FIN_PASSFILE
-
-	$password2 = ask_for_password( $authuser2 || $user2, $host2 ) ;
-} ;
-
-$password2 = ( defined  $passfile2  ) ? firstline ( $passfile2 ) : $password2 ;
-
-
-# need clean up => write methods dry() and dry_message()
-$sync->{dry} = $dry ;
-my $dry_message = q{} ;
+$sync->{dry_message} = q{} ;
 if( $sync->{dry} ) {
-        $dry_message = "\t(not really since --dry mode)" ;
+        $sync->{dry_message} = "\t(not really since --dry mode)" ;
 }
-$sync->{dry_message} = $dry_message ;
 
 
 $search1 ||= $search if ( $search ) ;
@@ -1305,95 +1303,97 @@ $search2 ||= $search if ( $search ) ;
 
 
 if ( $disarmreadreceipts ) {
-	push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
+        push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ;
 }
 
 $pipemesscheck = ( defined  $pipemesscheck  ) ? $pipemesscheck : 1 ;
 
 if ( @pipemess and $pipemesscheck ) {
-	myprint( 'Checking each --pipemess command, ' 
-                . join( q{, }, @pipemess ) 
+        myprint( 'Checking each --pipemess command, '
+                . join( q{, }, @pipemess )
                 . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
-	my $string = pipemess( q{ }, @pipemess ) ;
+        my $string = pipemess( q{ }, @pipemess ) ;
         # string undef means something was bad.
         if ( not ( defined  $string  ) ) {
-        	die_clean( "Error: one of --pipemess command is bad, check it\n" ) ;
+                die_clean( "Error: one of --pipemess command is bad, check it\n" ) ;
         }
-	myprint( "Ok with each --pipemess @pipemess\n"  ) ;
+        myprint( "Ok with each --pipemess @pipemess\n"  ) ;
 }
 
 if ( $maxlinelengthcmd ) {
-	myprint( "Checking  --maxlinelengthcmd command,  $maxlinelengthcmd, with an space string.\n"  ) ;
-	my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
+        myprint( "Checking  --maxlinelengthcmd command,  $maxlinelengthcmd, with an space string.\n"  ) ;
+        my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
         # string undef means something was bad.
         if ( not ( defined  $string  ) ) {
-        	die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
+                die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
         }
-	myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n"  ) ;
+        myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n"  ) ;
 }
 
 if ( @regexmess ) {
-	my $string = regexmess( q{ } ) ;
-	myprint( "Checking each --regexmess command with an space string.\n"  ) ;
+        my $string = regexmess( q{ } ) ;
+        myprint( "Checking each --regexmess command with an space string.\n"  ) ;
         # string undef means one of the eval regex was bad.
         if ( not ( defined  $string  ) ) {
-        	die_clean( 'Error: one of --regexmess option is bad, check it' ) ;
+                die_clean( 'Error: one of --regexmess option is bad, check it' ) ;
         }
-	myprint( "Ok with each --regexmess\n"  ) ;
+        myprint( "Ok with each --regexmess\n"  ) ;
 }
 
 if ( @skipmess ) {
-	myprint( "Checking each --skipmess command with an space string.\n"  ) ;
-	my $match = skipmess( q{ } ) ;
+        myprint( "Checking each --skipmess command with an space string.\n"  ) ;
+        my $match = skipmess( q{ } ) ;
         # match undef means one of the eval regex was bad.
         if ( not ( defined  $match  ) ) {
-        	die_clean( 'Error: one of --skipmess option is bad, check it' ) ;
+                die_clean( 'Error: one of --skipmess option is bad, check it' ) ;
         }
-	myprint( "Ok with each --skipmess\n"  ) ;
+        myprint( "Ok with each --skipmess\n"  ) ;
 }
 
 if ( @regexflag ) {
-	myprint( "Checking each --regexflag command with an space string.\n"  ) ;
-	my $string = flags_regex( q{ } ) ;
-	# string undef means one of the eval regex was bad.
-	if ( not ( defined  $string  ) ) {
-		die_clean( 'Error: one of --regexflag option is bad, check it' ) ;
-	}
-	myprint( "Ok with each --regexflag\n"  ) ;
+        myprint( "Checking each --regexflag command with an space string.\n"  ) ;
+        my $string = flags_regex( q{ } ) ;
+        # string undef means one of the eval regex was bad.
+        if ( not ( defined  $string  ) ) {
+                die_clean( 'Error: one of --regexflag option is bad, check it' ) ;
+        }
+        myprint( "Ok with each --regexflag\n"  ) ;
 }
 
-$sync->{imap1} = my $imap1 = login_imap($host1, $port1, $user1, $domain1, $password1,
-		   $debugimap1, $sync->{h1}->{timeout}, $fastio1, $ssl1, $tls1,
-		   $authmech1, $authuser1, $reconnectretry1,
-		   $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1} ) ;
+$sync->{imap1} = my $imap1 = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $domain1, $sync->{password1},
+                   $debugimap1, $sync->{h1}->{timeout}, $fastio1, $sync->{ssl1}, $sync->{tls1},
+                   $authmech1, $authuser1, $reconnectretry1,
+                   $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1}, $sync ) ;
 
-$sync->{imap2} = my $imap2 = login_imap($host2, $port2, $user2, $domain2, $password2,
-		 $debugimap2, $sync->{h2}->{timeout}, $fastio2, $ssl2, $tls2,
-		 $authmech2, $authuser2, $reconnectretry2,
-		 $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2} ) ;
+$sync->{imap2} = my $imap2 = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $domain2, $sync->{password2},
+                 $debugimap2, $sync->{h2}->{timeout}, $fastio2, $sync->{ssl2}, $sync->{tls2},
+                 $authmech2, $authuser2, $reconnectretry2,
+                 $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2}, $sync ) ;
 
 
 $debug and myprint( 'Host1 Buffer I/O: ', $imap1->Buffer(), "\n" ) ;
 $debug and myprint( 'Host2 Buffer I/O: ', $imap2->Buffer(), "\n" ) ;
 
 
-die_clean( 'Not authenticated on host1' ) unless $imap1->IsAuthenticated( ) ;
+if ( ! $imap1->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host1' )  ; }
 myprint( "Host1: state Authenticated\n" ) ;
-die_clean( 'Not authenticated on host2' ) unless   $imap2->IsAuthenticated( ) ;
+if ( ! $imap2->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host2' )  ; }
 myprint( "Host2: state Authenticated\n" ) ;
 
-myprint( 'Host1 capability: ', join(q{ }, @{ $imap1->capability_update() || [] }), "\n" ) ;
-myprint( 'Host2 capability: ', join(q{ }, @{ $imap2->capability_update() || [] }), "\n" ) ;
+myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $imap1->capability() || [] }), "\n" ) ;
+myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $imap2->capability() || [] }), "\n" ) ;
 
+# ID on by default since 1.832
+$sync->{id} = defined  $sync->{id}  ? $sync->{id} : 1 ;
 imap_id_stuff( $sync ) ;
 
-#quota( $imap1, 'host1' ) ; # quota on host1 is useless and pollute host2 output.
-quota( $imap2, 'host2', $sync ) ;
+#quota( $imap1, 'h1', $sync ) ; # quota on host1 is useless and pollute host2 output.
+quota( $imap2, 'h2', $sync )  ;
 
 if ( $justlogin ) {
-	$imap1->logout(  ) ;
-	$imap2->logout(  ) ;
-	exit_clean( $sync, $EX_OK ) ;
+        $imap1->logout(  ) ;
+        $imap2->logout(  ) ;
+        exit_clean( $sync, $EX_OK ) ;
 }
 
 
@@ -1410,7 +1410,7 @@ my (
         %h2_folders_from_1_all ,
 ) ;
 
-my $h1_folders_wanted_nb = 0 ; 
+my $h1_folders_wanted_nb = 0 ;
 my $h1_folders_wanted_ct = 0 ; # counter of folders done.
 
 # All folders on host1 and host2
@@ -1421,11 +1421,13 @@ my $h1_folders_wanted_ct = 0 ; # counter of folders done.
 myprint( 'Host1: found ', scalar  @h1_folders_all , " folders.\n"  ) ;
 myprint( 'Host2: found ', scalar  @h2_folders_all , " folders.\n"  ) ;
 
-for ( @h1_folders_all ) { $h1_folders_all{ $_ } = 1 } ;
-for ( @h2_folders_all ) {
-	$h2_folders_all{ $_ } = 1 ;
-	$h2_folders_all_UPPER{ uc  $_  } = 1 ;
-} ;
+foreach my $f ( @h1_folders_all ) { 
+	$h1_folders_all{ $f } = 1 
+}
+foreach my $f ( @h2_folders_all ) {
+        $h2_folders_all{ $f } = 1 ;
+        $h2_folders_all_UPPER{ uc  $f  } = 1 ;
+}
 
 $sync->{h1_folders_all} = \%h1_folders_all ;
 $sync->{h2_folders_all} = \%h2_folders_all ;
@@ -1438,59 +1440,59 @@ for ( $imap2->subscribed(  ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
 
 
 if ( defined  $subfolder2  ) {
-	unshift @regextrans2,
-		q's,^${h2_prefix}(.*),${h2_prefix}${subfolder2}${h2_sep}$1,',
-		q's,^INBOX$,${h2_prefix}${subfolder2}${h2_sep}INBOX,' ;
+        unshift @regextrans2,
+                q(s,^${h2_prefix}(.*),${h2_prefix}${subfolder2}${h2_sep}$1,),
+                q(s,^INBOX$,${h2_prefix}${subfolder2}${h2_sep}INBOX,) ;
 
 }
 
 if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
-	push @regextrans2, $reg ;
+        push @regextrans2, $reg ;
 }
 
 if (scalar @folder or $subscribed or scalar @folderrec) {
-	# folders given by option --folder
-	if (scalar @folder) {
-		add_to_requested_folders(@folder);
-	}
+        # folders given by option --folder
+        if (scalar @folder) {
+                add_to_requested_folders(@folder);
+        }
 
-	# option --subscribed
-	if ( $subscribed ) {
-		add_to_requested_folders( keys  %h1_subscribed_folder  ) ;
-	}
+        # option --subscribed
+        if ( $subscribed ) {
+                add_to_requested_folders( keys  %h1_subscribed_folder  ) ;
+        }
 
-	# option --folderrec
-	if (scalar @folderrec) {
-		foreach my $folderrec (@folderrec) {
-			add_to_requested_folders($imap1->folders($folderrec));
-		}
-	}
+        # option --folderrec
+        if (scalar @folderrec) {
+                foreach my $folderrec (@folderrec) {
+                        add_to_requested_folders($imap1->folders($folderrec));
+                }
+        }
 }
 else {
-	# no include, no folder/subscribed/folderrec options => all folders
-	if (not scalar @include) {
-		myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n"  ) ;
-		add_to_requested_folders(@h1_folders_all);
-	}
+        # no include, no folder/subscribed/folderrec options => all folders
+        if (not scalar @include) {
+                myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n"  ) ;
+                add_to_requested_folders(@h1_folders_all);
+        }
 }
 
 
 # consider (optional) includes and excludes
 if ( scalar  @include  ) {
-	foreach my $include ( @include ) {
-		my @included_folders = grep { /$include/ } @h1_folders_all ;
-		add_to_requested_folders( @included_folders ) ;
-		myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders )  . "\n"  ) ;
-	}
+        foreach my $include ( @include ) {
+                my @included_folders = grep { /$include/ } @h1_folders_all ;
+                add_to_requested_folders( @included_folders ) ;
+                myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders )  . "\n"  ) ;
+        }
 }
 
 if ( scalar  @exclude  ) {
-	foreach my $exclude ( @exclude ) {
-		my @requested_folder = sort keys %requested_folder ;
-		my @excluded_folders = grep { /$exclude/ } @requested_folder ;
-		remove_from_requested_folders( @excluded_folders ) ;
-		myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n"  ) ;
-	}
+        foreach my $exclude ( @exclude ) {
+                my @requested_folder = sort keys %requested_folder ;
+                my @excluded_folders = grep { /$exclude/ } @requested_folder ;
+                remove_from_requested_folders( @excluded_folders ) ;
+                myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n"  ) ;
+        }
 }
 
 
@@ -1504,13 +1506,13 @@ if ( scalar  @exclude  ) {
 my @h1_folders_wanted_exist ;
 myprint( "Host1: checking all wanted folders exist.\n"  ) ;
 foreach my $folder ( @h1_folders_wanted ) {
-	( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n"  ) ;
-	if ( ! exists  $h1_folders_all{ $folder }  ) {
+        ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n"  ) ;
+        if ( ! exists  $h1_folders_all{ $folder }  ) {
                 myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
-		next ;
-	}else{
-		push  @h1_folders_wanted_exist, $folder  ;
-	}
+                next ;
+        }else{
+                push  @h1_folders_wanted_exist, $folder  ;
+        }
 }
 
 @h1_folders_wanted = @h1_folders_wanted_exist ;
@@ -1518,17 +1520,17 @@ foreach my $folder ( @h1_folders_wanted ) {
 
 
 $checkselectable and do {
-	my @h1_folders_wanted_selectable ;
+        my @h1_folders_wanted_selectable ;
         myprint( "Host1: checking all wanted folders are selectable. Use --nocheckselectable to avoid this check.\n"  ) ;
-	foreach my $folder ( @h1_folders_wanted ) {
-        	( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n"  ) ;
-        	if ( ! $imap1->selectable( $folder ) ) {
+        foreach my $folder ( @h1_folders_wanted ) {
+                ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n"  ) ;
+                if ( ! $imap1->selectable( $folder ) ) {
                                 myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ;
-        	}else{
-			push  @h1_folders_wanted_selectable, $folder  ;
-		}
-	}
-	@h1_folders_wanted = @h1_folders_wanted_selectable ;
+                }else{
+                        push  @h1_folders_wanted_selectable, $folder  ;
+                }
+        }
+        @h1_folders_wanted = @h1_folders_wanted_selectable ;
         ( $debug or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext(  ), " s\n"  ) ;
 } ;
 
@@ -1554,19 +1556,19 @@ automap( $sync ) ;
 
 
 foreach my $h1_fold ( @h1_folders_wanted ) {
-	my $h2_fold ;
-	$h2_fold = imap2_folder_name( $h1_fold ) ;
-	$h2_folders_from_1_wanted{ $h2_fold }++ ;
+        my $h2_fold ;
+        $h2_fold = imap2_folder_name( $h1_fold ) ;
+        $h2_folders_from_1_wanted{ $h2_fold }++ ;
         if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
-        	$h2_folders_from_1_several{ $h2_fold }++ ;
+                $h2_folders_from_1_several{ $h2_fold }++ ;
         }
 }
 @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
 
 foreach my $h1_fold ( @h1_folders_all ) {
-	my $h2_fold ;
-	$h2_fold = imap2_folder_name( $h1_fold ) ;
-	$h2_folders_from_1_all{ $h2_fold }++ ;
+        my $h2_fold ;
+        $h2_fold = imap2_folder_name( $h1_fold ) ;
+        $h2_folders_from_1_all{ $h2_fold }++ ;
 }
 
 
@@ -1583,12 +1585,17 @@ Y is the uft8 output just printed for convenience, to recognize it.
 END_LISTING
 
 print
-  "Host1 folders list:\n",
+  "Host1 folders list (first the raw imap format then the [X] = [Y]):\n",
+  $imap1->list(  ),
+  "\n",
   jux_utf8_list( @h1_folders_all ),
   "\n",
-  "Host2 folders list:\n",
+  "Host2 folders list (first the raw imap format then the [X] = [Y]):\n",
+  $imap2->list(  ),
+  "\n",
   jux_utf8_list( @h2_folders_all ),
-  "\n" ;
+  "\n", 
+  q{} ;
 
 print
   'Host1 subscribed folders list: ',
@@ -1599,31 +1606,31 @@ my @h2_folders_not_in_1;
 @h2_folders_not_in_1 = list_folders_in_2_not_in_1(  ) ;
 
 if ( @h2_folders_not_in_1 ) {
-	myprint( "Folders in host2 not in host1:\n",
-	jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
+        myprint( "Folders in host2 not in host1:\n",
+        jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
 }
 
 
-if ( defined  $sync->{f1f2auto}  ) {
-	myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n"  ) ;
-	foreach my $h1_fold ( keys %{$sync->{f1f2auto}} ) {
-        	my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
-		myprintf( "%-40s -> %-40s\n",
-		       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
+if ( keys %{ $sync->{f1f2auto} } ) {
+        myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n"  ) ;
+        foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) {
+                my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
+                myprintf( "%-40s -> %-40s\n",
+                       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
         }
         myprint( "\n"  ) ;
 }
 
-if ( defined  $sync->{f1f2}  ) {
-	myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n"  ) ;
-	foreach my $h1_fold ( keys %{$sync->{f1f2}} ) {
-        	my $h2_fold = $sync->{f1f2}{$h1_fold} ;
+if ( keys %{ $sync->{f1f2} } ) {
+        myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n"  ) ;
+        foreach my $h1_fold ( keys %{ $sync->{f1f2} } ) {
+                my $h2_fold = $sync->{f1f2}{$h1_fold} ;
                 my $warn = q{} ;
                 if ( not exists  $h1_folders_all{ $h1_fold }  ) {
                         $warn = "BUT $h1_fold does NOT exist on host1!" ;
                 }
-		myprintf( "%-40s -> %-40s %s\n",
-		       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
+                myprintf( "%-40s -> %-40s %s\n",
+                       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
         }
         myprint( "\n"  ) ;
 }
@@ -1653,7 +1660,7 @@ $h1_folders_wanted_nb = scalar  @h1_folders_wanted  ;
 
 myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
 
-my $begin_transfer_time = time ;
+$sync->{begin_transfer_time} = time ;
 
 my %uid_candidate_for_deletion ;
 my %uid_candidate_no_deletion ;
@@ -1662,234 +1669,235 @@ my %h2_folders_of_md5 = (  ) ;
 
 FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
 
-        last FOLDER if $imap1->IsUnconnected(  ) ;
-        last FOLDER if $imap2->IsUnconnected(  ) ;
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
-	my $h2_fold = imap2_folder_name( $h1_fold ) ;
+        my $h2_fold = imap2_folder_name( $h1_fold ) ;
 
-	$h1_folders_wanted_ct++ ;
-	myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
-		jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
+        $h1_folders_wanted_ct++ ;
+        myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
+                jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
         if ( $sync->{debugmemory} ) {
                 myprintf("FL: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
-	# host1 can not be fetched read only, select is needed because of expunge.
-	select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ;
+        # host1 can not be fetched read only, select is needed because of expunge.
+        select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ;
 
         debugsleep( $sync ) ;
 
-	my $h1_fold_nb_messages = count_from_select( $imap1->History ) ;
+        my $h1_fold_nb_messages = count_from_select( $imap1->History ) ;
         myprint( "Host1 folder [$h1_fold] has $h1_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
 
         if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) {
-        	myprint( "Skipping empty host1 folder [$h1_fold]\n"  ) ;
+                myprint( "Skipping empty host1 folder [$h1_fold]\n"  ) ;
                 next FOLDER ;
         }
 
-	if ( ! exists  $h2_folders_all{ $h2_fold }  ) {
-		create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ;
-	}
+        if ( ! exists  $h2_folders_all{ $h2_fold }  ) {
+                create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ;
+        }
 
-	acls_sync( $h1_fold, $h2_fold ) ;
+        acls_sync( $h1_fold, $h2_fold ) ;
 
         # Sometimes the folder on host2 is listed (it exists) but is
         # not selectable but becomes selectable by a create (Gmail)
-	select_folder( $imap2, $h2_fold, 'Host2' )
+        select_folder( $imap2, $h2_fold, 'Host2' )
         or ( create_folder( $imap2, $h2_fold, $h1_fold )
              and select_folder( $imap2, $h2_fold, 'Host2' ) )
         or next FOLDER ;
-	my @select_results = $imap2->Results(  ) ;
+        my @select_results = $imap2->Results(  ) ;
 
-	my $h2_fold_nb_messages = count_from_select( @select_results ) ;
+        my $h2_fold_nb_messages = count_from_select( @select_results ) ;
         myprint( "Host2 folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ;
 
-	my $permanentflags2 = permanentflags( @select_results ) ;
-	( $debug or $debugflags ) and myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n"  ) ;
+        my $permanentflags2 = permanentflags( @select_results ) ;
+        myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n"  ) ;
 
-	if ( $expunge or $expunge1 ){
-		myprint( "Host1: Expunging $h1_fold $dry_message\n"  ) ;
-		unless( $dry ) { $imap1->expunge(  ) } ;
-	}
+        if ( $expunge1 ){
+                myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n"  ) ;
+                if ( ! $sync->{dry} ) { $imap1->expunge(  ) } ;
+        }
 
-	if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
+        if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
              and not exists  $h2_subscribed_folder{ $h2_fold }  ) {
-		myprint( "Host2: Subscribing to folder $h2_fold\n"  ) ;
-		unless( $dry ) { $imap2->subscribe( $h2_fold ) } ;
-	}
+                myprint( "Host2: Subscribing to folder $h2_fold\n"  ) ;
+                if ( ! $sync->{dry} ) { $imap2->subscribe( $h2_fold ) } ;
+        }
 
-	next FOLDER if ( $justfolders ) ;
+        next FOLDER if ( $justfolders ) ;
 
-        last FOLDER if $imap1->IsUnconnected(  ) ;
-        last FOLDER if $imap2->IsUnconnected(  ) ;
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
         my $h1_msgs_all_hash_ref = {  } ;
-	my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $h1_fold );
-	last FOLDER if $imap1->IsUnconnected(  ) ;
+        my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $sync->{abletosearch1}, $h1_fold );
+
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
         my $h1_msgs_nb = scalar  @h1_msgs  ;
         $h1{ $h1_fold }{ 'messages_nb' } = $h1_msgs_nb ;
 
-	myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages\n"  ) ;
-	( $debug or $debuglist ) and myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
+        myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages\n"  ) ;
+        ( $debug or $debuglist ) and myprint( "Host1 folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ;
         $debug and myprint( "Host1 selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ;
 
         my $h2_msgs_all_hash_ref = {  } ;
-	my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $h2_fold ) ;
-	last FOLDER if $imap2->IsUnconnected(  ) ;
+        my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $sync->{abletosearch2}, $h2_fold ) ;
+
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
         my $h2_msgs_nb = scalar  @h2_msgs  ;
         $h2{ $h2_fold }{ 'messages_nb' } = $h2_msgs_nb ;
 
-	myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
-	( $debug or $debuglist ) and myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
+        myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ;
+        ( $debug or $debuglist ) and myprint( "Host2 folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ;
         $debug and myprint( "Host2 selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ;
 
-	my $cache_base = "$tmpdir/imapsync_cache/" ;
-	my $cache_dir = cache_folder( $cache_base, "$host1/$user1/$host2/$user2", $h1_fold, $h2_fold ) ;
-	my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
+        my $cache_base = "$tmpdir/imapsync_cache/" ;
+        my $cache_dir = cache_folder( $cache_base, "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ;
+        my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
 
-	my $h1_uidvalidity = $imap1->uidvalidity(  ) || q{} ;
-	my $h2_uidvalidity = $imap2->uidvalidity(  ) || q{} ;
+        my $h1_uidvalidity = $imap1->uidvalidity(  ) || q{} ;
+        my $h2_uidvalidity = $imap2->uidvalidity(  ) || q{} ;
 
-        last FOLDER if $imap1->IsUnconnected(  ) ;
-        last FOLDER if $imap2->IsUnconnected(  ) ;
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
-	if ( $usecache ) {
-		myprint( "cache directory: $cache_dir\n"  ) ;
-		mkpath( "$cache_dir" ) ;
-		( $cache_1_2_ref, $cache_2_1_ref )
+        if ( $usecache ) {
+                myprint( "cache directory: $cache_dir\n"  ) ;
+                mkpath( "$cache_dir" ) ;
+                ( $cache_1_2_ref, $cache_2_1_ref )
                 = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
-		myprint( 'CACHE h1 h2: ', scalar  keys %{ $cache_1_2_ref } , " files\n"  ) ;
-		$debug and myprint( '[',
-		    map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
-	}
-
-	my %h1_hash = () ;
-	my %h2_hash = () ;
-
-	my ( %h1_msgs, %h2_msgs ) ;
-	@h1_msgs{ @h1_msgs } = ();
-	@h2_msgs{ @h2_msgs } = ();
-
-	my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
-	my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
-
-	my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
-	%h1_msgs_not_in_cache = %h1_msgs ;
-	%h2_msgs_not_in_cache = %h2_msgs ;
-	delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
-	delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
-
-	my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
-	#myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n"  ) ;
-	my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
-
-	my @h2_msgs_delete2_not_in_cache = () ;
-	%h1_msgs_copy_by_uid = (  ) ;
-
-	if ( $useuid ) {
-		# use uid so we have to avoid getting header
-		@h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = (  ) ;
-		@h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
-		@h1_msgs_not_in_cache = (  ) ;
-		@h2_msgs_not_in_cache = (  ) ;
-
-		#myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
-	}
-
-	$debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ;
-
-	my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
-	$h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
-	$debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
-
-	@{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
-
-	$debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n"  ) ;
-        if ( $abletosearch ) {
-		$h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
-	  	if ( @h1_msgs ) ;
-        }else{
-		my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ;
-		my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
-		$h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
-		if ( @h1_msgs ) ;
+                myprint( 'CACHE h1 h2: ', scalar  keys %{ $cache_1_2_ref } , " files\n"  ) ;
+                $debug and myprint( '[',
+                    map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
         }
-	$debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n"  ) ;
-	unless ($h1_fir_ref) {
-		my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ",
-			scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ;
-		errors_incr( $sync, $error ) ;
-		next FOLDER ;
-	}
 
-	my @h1_msgs_duplicate;
-	foreach my $m (@h1_msgs_not_in_cache) {
-		my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash);
-		if (! defined $rc) {
-			my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
-			myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n"  ) ;
-			$total_bytes_skipped += $h1_size;
-			$nb_msg_skipped += 1;
-			$h1_nb_msg_noheader +=1;
+        my %h1_hash = () ;
+        my %h2_hash = () ;
+
+        my ( %h1_msgs, %h2_msgs ) ;
+        @h1_msgs{ @h1_msgs } = ();
+        @h2_msgs{ @h2_msgs } = ();
+
+        my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
+        my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
+
+        my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
+        %h1_msgs_not_in_cache = %h1_msgs ;
+        %h2_msgs_not_in_cache = %h2_msgs ;
+        delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
+        delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
+
+        my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
+        #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n"  ) ;
+        my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
+
+        my @h2_msgs_delete2_not_in_cache = () ;
+        %h1_msgs_copy_by_uid = (  ) ;
+
+        if ( $useuid ) {
+                # use uid so we have to avoid getting header
+                @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = (  ) ;
+                @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
+                @h1_msgs_not_in_cache = (  ) ;
+                @h2_msgs_not_in_cache = (  ) ;
+
+                #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
+        }
+
+        $debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ;
+
+        my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
+        $h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
+        $debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
+
+        @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
+
+        $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n"  ) ;
+        if ( $sync->{abletosearch1} ) {
+                $h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
+                if ( @h1_msgs ) ;
+        }else{
+                my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ;
+                my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+                $h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
+                if ( @h1_msgs ) ;
+        }
+        $debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n"  ) ;
+        if ( ! $h1_fir_ref ) {
+                my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ",
+                        scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ;
+                errors_incr( $sync, $error ) ;
+                next FOLDER ;
+        }
+
+        my @h1_msgs_duplicate;
+        foreach my $m (@h1_msgs_not_in_cache) {
+                my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash);
+                if ( ! defined $rc ) {
+                        my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
+                        myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n"  ) ;
+                        $total_bytes_skipped += $h1_size;
+                        $nb_msg_skipped += 1;
+                        $h1_nb_msg_noheader +=1;
                         $h1_nb_msg_processed +=1 ;
-		} elsif(0 == $rc) {
-			# duplicate
-			push @h1_msgs_duplicate, $m;
-			# duplicate, same id same size?
-			my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
-			$nb_msg_skipped += 1;
-			$h1_total_bytes_duplicate += $h1_size;
-			$h1_nb_msg_duplicate += 1;
+                } elsif(0 == $rc) {
+                        # duplicate
+                        push @h1_msgs_duplicate, $m;
+                        # duplicate, same id same size?
+                        my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
+                        $nb_msg_skipped += 1;
+                        $h1_total_bytes_duplicate += $h1_size;
+                        $h1_nb_msg_duplicate += 1;
                         $h1_nb_msg_processed +=1 ;
-		}
-	}
+                }
+        }
         my $h1_msgs_duplicate_nb = scalar  @h1_msgs_duplicate  ;
         $h1{ $h1_fold }{ 'duplicates_nb' } = $h1_msgs_duplicate_nb ;
 
         $debug and myprint( "Host1 selected: $h1_msgs_nb  duplicates: $h1_msgs_duplicate_nb\n"  ) ;
-	$debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n"  ) ;
-
-	$debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ;
-
-	my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
-	$h2_heads_ref =   $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
-	$debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
-
-	$debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n"  ) ;
-	@{ $h2_fir_ref }{@h2_msgs} = (  ); # fetch_hash can select by uid with last arg as ref
+        $debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n"  ) ;
+	# Getting headers and metada can be so long that host2 might be disconnected here
+        if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
 
-        if ( $abletosearch ) {
-		$h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref)
-		if (@h2_msgs) ;
+        $debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ;
+
+        my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
+        $h2_heads_ref =   $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
+        $debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
+
+        $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n"  ) ;
+        @{ $h2_fir_ref }{@h2_msgs} = (  ); # fetch_hash can select by uid with last arg as ref
+
+
+        if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) {
+                $h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref) ;
         }else{
-		my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ;
-		my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
-		$h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref )
-		if ( @h2_msgs ) ;
+                my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ;
+                my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+                $h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref )
+                if ( @h2_msgs ) ;
         }
 
-	$debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
+        $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
 
-	my @h2_msgs_duplicate;
-	foreach my $m (@h2_msgs_not_in_cache) {
-		my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash) ;
-		my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
-		if (! defined  $rc  ) {
+        my @h2_msgs_duplicate;
+        foreach my $m (@h2_msgs_not_in_cache) {
+                my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash) ;
+                my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
+                if (! defined  $rc  ) {
                         myprint( "Host2 $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n"  ) ;
-			$h2_nb_msg_noheader += 1 ;
-		} elsif( 0 == $rc ) {
-			# duplicate
-			$h2_nb_msg_duplicate += 1 ;
-			$h2_total_bytes_duplicate += $h2_size ;
-			push  @h2_msgs_duplicate, $m  ;
-		}
-	}
+                        $h2_nb_msg_noheader += 1 ;
+                } elsif( 0 == $rc ) {
+                        # duplicate
+                        $h2_nb_msg_duplicate += 1 ;
+                        $h2_total_bytes_duplicate += $h2_size ;
+                        push  @h2_msgs_duplicate, $m  ;
+                }
+        }
 
         # %h2_folders_of_md5
         foreach my $md5 (  keys  %h2_hash  ) {
-        	$h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ;
+                $h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ;
         }
 
 
@@ -1897,271 +1905,272 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
         $h2{ $h2_fold }{ 'duplicates_nb' } = $h2_msgs_duplicate_nb ;
 
         myprint( "Host2 folder $h2_fold selected: $h2_msgs_nb messages,  duplicates: $h2_msgs_duplicate_nb\n" )
-        	if ( $debug or $delete2duplicates or $h2_msgs_duplicate_nb ) ;
-	$debug and myprint( 'Host2 whole time parsing headers took ', timenext(  ), " s\n"  ) ;
+                if ( $debug or $delete2duplicates or $h2_msgs_duplicate_nb ) ;
+        $debug and myprint( 'Host2 whole time parsing headers took ', timenext(  ), " s\n"  ) ;
 
-	$debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
-	# messages in host1 that are not in host2
+        $debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
+        # messages in host1 that are not in host2
 
-	my @h1_hash_keys_sorted_by_uid
-	  = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
+        my @h1_hash_keys_sorted_by_uid
+          = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
 
-	#myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
+        #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
 
-	my @h2_hash_keys_sorted_by_uid
-	  = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
+        my @h2_hash_keys_sorted_by_uid
+          = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
 
 
-	if( $delete2duplicates and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
-		my @h2_expunge ;
+        if( $delete2duplicates and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+                my @h2_expunge ;
 
-		foreach my $h2_msg ( @h2_msgs_duplicate ) {
-			myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $dry_message\n"  ) ;
-			push  @h2_expunge, $h2_msg  if $uidexpunge2 ;
-			unless ( $dry ) {
-				$imap2->delete_message( $h2_msg ) ;
-				$h2_nb_msg_deleted += 1 ;
-			}
-		}
-		my $cnt = scalar @h2_expunge ;
-		if( @h2_expunge and not $expunge2 ) {
-			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
-			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
-		}
-        	if ( $expunge2 ){
-                	myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
-                	$imap2->expunge(  ) if ! $dry ;
-        	}
-	}
-
-	if( $delete2 and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
-        	# No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
-		my @h2_expunge;
-		foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
-			#myprint( "$m_id " ) ;
-			unless (exists $h1_hash{$m_id}) {
-				my $h2_msg  = $h2_hash{$m_id}{'m'};
-				my $h2_flags  = $h2_hash{$m_id}{'F'} || q{};
-				my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
-				myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $dry_message\n" )
-				  if ! $isdel;
-				push @h2_expunge, $h2_msg if $uidexpunge2;
-				unless ($dry or $isdel) {
-					$imap2->delete_message($h2_msg);
-					$h2_nb_msg_deleted += 1;
-				}
-			}
-		}
-		foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
-			myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $dry_message\n" ) ;
-                        push @h2_expunge, $h2_msg if $uidexpunge2;
-			unless ($dry) {
-				$imap2->delete_message($h2_msg);
-				$h2_nb_msg_deleted += 1;
-			}
-		}
-		my $cnt = scalar @h2_expunge ;
-
-		if( @h2_expunge and not $expunge2 ) {
-			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
-			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
-		}
-        	if ( $expunge2 ) {
-                	myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
-                	$imap2->expunge(  ) if ! $dry ;
-        	}
-	}
-
-	if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  ) {
-        	myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n"  ) ;
-		my @h2_expunge;
-		foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
-                	my $h2_msg  = $h2_hash{ $m_id }{ 'm' } ;
-			unless ( exists  $h1_hash{ $m_id }  ) {
-				my $h2_flags  = $h2_hash{ $m_id }{ 'F' } || q{} ;
-				my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
-				unless ( $isdel ) {
-                                	$debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n"  ) ;
-					$uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
-				}
-			}else{
-                        	$debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n"  ) ;
-                        	$uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                foreach my $h2_msg ( @h2_msgs_duplicate ) {
+                        myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n"  ) ;
+                        push  @h2_expunge, $h2_msg  if $uidexpunge2 ;
+                        if ( ! $sync->{dry} ) {
+                                $imap2->delete_message( $h2_msg ) ;
+                                $h2_nb_msg_deleted += 1 ;
                         }
-		}
-		foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
-			myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
-                        $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
-		}
+                }
+                my $cnt = scalar @h2_expunge ;
+                if( @h2_expunge and not $expunge2 ) {
+                        myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
+                }
+                if ( $expunge2 ){
+                        myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->expunge(  ) if ! $sync->{dry} ;
+                }
+        }
 
-		foreach my $h2_msg ( @h2_msgs_in_cache ) {
-			myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
+        if( $delete2 and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+                # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
+                my @h2_expunge;
+                foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
+                        #myprint( "$m_id " ) ;
+                        if ( ! exists $h1_hash{$m_id} ) {
+                                my $h2_msg  = $h2_hash{$m_id}{'m'};
+                                my $h2_flags  = $h2_hash{$m_id}{'F'} || q{};
+                                my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
+                                myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" )
+                                  if ! $isdel;
+                                push @h2_expunge, $h2_msg if $uidexpunge2;
+                                if ( ! ( $sync->{dry} or $isdel ) ) {
+                                        $imap2->delete_message($h2_msg);
+                                        $h2_nb_msg_deleted += 1;
+                                }
+                        }
+                }
+                foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
+                        myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ;
+                        push @h2_expunge, $h2_msg if $uidexpunge2;
+                        if ( ! $sync->{dry} ) {
+                                $imap2->delete_message($h2_msg);
+                                $h2_nb_msg_deleted += 1;
+                        }
+                }
+                my $cnt = scalar @h2_expunge ;
+
+                if( @h2_expunge and not $expunge2 ) {
+                        myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
+                }
+                if ( $expunge2 ) {
+                        myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->expunge(  ) if ! $sync->{dry} ;
+                }
+        }
+
+        if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+                myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n"  ) ;
+                my @h2_expunge;
+                foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
+                        my $h2_msg  = $h2_hash{ $m_id }{ 'm' } ;
+                        if ( ! exists  $h1_hash{ $m_id }  ) {
+                                my $h2_flags  = $h2_hash{ $m_id }{ 'F' } || q{} ;
+                                my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
+                                if ( ! $isdel ) {
+                                        $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n"  ) ;
+                                        $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
+                                }
+                        }else{
+                                $debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n"  ) ;
+                                $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                        }
+                }
+                foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
+                        myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
+                        $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
+                }
+
+                foreach my $h2_msg ( @h2_msgs_in_cache ) {
+                        myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
                         $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
-		}
+                }
 
 
                 if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
-                	# last host1 folder going to $h2_fold
+                        # last host1 folder going to $h2_fold
                         myprint( "Last host1 folder going to $h2_fold\n"  ) ;
                         foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) {
-                        	$debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n"  ) ;
+                                $debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n"  ) ;
                                 if ( exists  $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }  ) {
-                                	$debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n"  ) ;
+                                        $debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n"  ) ;
                                 }else{
-                                	myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $dry_message\n" ) ;
+                                        myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ;
                                         push  @h2_expunge, $h2_msg  if $uidexpunge2 ;
-                                        unless ( $dry ) {
-                                        	$imap2->delete_message( $h2_msg ) ;
-                                        	$h2_nb_msg_deleted += 1 ;
+                                        if ( ! $sync->{dry} ) {
+                                                $imap2->delete_message( $h2_msg ) ;
+                                                $h2_nb_msg_deleted += 1 ;
                                         }
                                 }
                         }
                 }
 
-		my $cnt = scalar @h2_expunge ;
-		if( @h2_expunge and not $expunge2 ) {
-			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
-			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
-		}
-        	if ( $expunge2 ) {
-                	myprint( "Host2: Expunging host2 folder $h2_fold $dry_message\n"  ) ;
-                	$imap2->expunge(  ) if ! $dry ;
-        	}
+                my $cnt = scalar @h2_expunge ;
+                if( @h2_expunge and not $expunge2 ) {
+                        myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ;
+                }
+                if ( $expunge2 ) {
+                        myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n"  ) ;
+                        $imap2->expunge(  ) if ! $sync->{dry} ;
+                }
 
                 $h2_folders_from_1_several{ $h2_fold }-- ;
-	}
+        }
 
-
-	my $h2_uidnext = $imap2->uidnext( $h2_fold ) ;
+        my $h2_uidnext = $imap2->uidnext( $h2_fold ) ;
         $debug and myprint( "Host2 uidnext: $h2_uidnext\n"  ) ;
-	$h2_uidguess = $h2_uidnext ;
-	MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
-        	last FOLDER if $imap1->IsUnconnected(  ) ;
-                last FOLDER if $imap2->IsUnconnected(  ) ;
+        $h2_uidguess = $h2_uidnext ;
 
-		#myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n"  ) ;
-		my $h1_size  = $h1_hash{$m_id}{'s'};
-		my $h1_msg   = $h1_hash{$m_id}{'m'};
-		my $h1_idate = $h1_hash{$m_id}{'D'};
+	# Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here
+        if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
-		if ( ( not exists  $h2_hash{ $m_id }  )
-                	and ( not ( exists $h2_folders_of_md5{ $m_id } )
+        MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
+		if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
+
+                #myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n"  ) ;
+                my $h1_size  = $h1_hash{$m_id}{'s'};
+                my $h1_msg   = $h1_hash{$m_id}{'m'};
+                my $h1_idate = $h1_hash{$m_id}{'D'};
+
+                if ( ( not exists  $h2_hash{ $m_id }  )
+                        and ( not ( exists $h2_folders_of_md5{ $m_id } )
                               or not $skipcrossduplicates ) ) {
-			# copy
-			my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
+                        # copy
+                        my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
                         $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ;
                         if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
-                        	myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
-	                        $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                                myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
+                                $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
                         }
                         last FOLDER if total_bytes_max_reached(  ) ;
-			next MESS;
-		}
-		else{
-		        # already on host2
+                        next MESS;
+                }
+                else{
+                        # already on host2
                         if ( exists  $h2_hash{ $m_id }  ) {
-				my $h2_msg   = $h2_hash{$m_id}{'m'} ;
-				$debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n"  ) ;
+                                my $h2_msg   = $h2_hash{$m_id}{'m'} ;
+                                $debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n"  ) ;
                                 if ( $usecache ) {
-                                	$debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n"  ) ;
-                                	touch( "$cache_dir/${h1_msg}_$h2_msg" )
+                                        $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n"  ) ;
+                                        touch( "$cache_dir/${h1_msg}_$h2_msg" )
                                         or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ;
                                 }
                         }elsif( exists  $h2_folders_of_md5{ $m_id }  ) {
-                        	my @folders_dup = keys  %{ $h2_folders_of_md5{ $m_id } }  ;
-                        	( $debug or $debugcrossduplicates ) and myprint( "Host1 found msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n"  ) ;
+                                my @folders_dup = keys  %{ $h2_folders_of_md5{ $m_id } }  ;
+                                ( $debug or $debugcrossduplicates ) and myprint( "Host1 found msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n"  ) ;
                         }
-			$total_bytes_skipped += $h1_size ;
-			$nb_msg_skipped += 1 ;
+                        $total_bytes_skipped += $h1_size ;
+                        $nb_msg_skipped += 1 ;
                         $h1_nb_msg_processed +=1 ;
                 }
 
                 if ( exists  $h2_hash{ $m_id }  ) {
-			#$debug and myprint( "MESSAGE $m_id\n" ) ;
-			my $h2_msg  = $h2_hash{$m_id}{'m'};
+                        #$debug and myprint( "MESSAGE $m_id\n" ) ;
+                        my $h2_msg  = $h2_hash{$m_id}{'m'};
 
-                	sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
-	    		# Good
-			my $h2_size = $h2_hash{$m_id}{'s'};
-			$debug and myprint(
-			"Host1 size  msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
-		}
-                last FOLDER if $imap2->IsUnconnected(  ) ;
-
-		if ( $delete ) {
-			delete_message_on_host1( $h1_msg, $h1_fold ) ;
-		}
-	}
-	# END MESS: loop
-        last FOLDER if $imap1->IsUnconnected(  ) ;
-        last FOLDER if $imap2->IsUnconnected(  ) ;
-	MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) {
-		my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
-		$debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
-		sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
-		my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
-		$total_bytes_skipped += $h1_size;
-		$nb_msg_skipped += 1;
-                $h1_nb_msg_processed +=1 ;
-                last FOLDER if $imap2->IsUnconnected(  ) ;
-	}
-
-	#myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n"  ) ;
-	MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) {
-		#
-		$debug and myprint( "Copy by uid $h1_fold/$h1_msg\n"  ) ;
-                last FOLDER if $imap1->IsUnconnected(  ) ;
-                last FOLDER if $imap2->IsUnconnected(  ) ;
-		my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
-                if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  and $h2_msg ) {
-                	myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
-	                $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                        sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
+                        # Good
+                        my $h2_size = $h2_hash{$m_id}{'s'};
+                        $debug and myprint(
+                        "Host1 size  msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
                 }
-		last FOLDER if total_bytes_max_reached(  ) ;
-	}
 
-	if ( $expunge or $expunge1 ){
-		myprint( "Host1: Expunging folder $h1_fold $dry_message\n"  ) ;
-		unless( $dry ) { $imap1->expunge(  ) } ;
-	}
-	if ( $expunge2 ){
-		myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
-		unless( $dry ) { $imap2->expunge(  ) } ;
-	}
-	$debug and myprint( 'Time: ', timenext(  ), " s\n"  ) ;
-}
+		if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
 
-sub total_bytes_max_reached {
+                if ( $delete1 ) {
+                        delete_message_on_host1( $h1_msg, $h1_fold ) ;
+                }
+        }
+        # END MESS: loop
+	if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
+        MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) {
+                my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
+                $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
+                sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
+                my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
+                $total_bytes_skipped += $h1_size;
+                $nb_msg_skipped += 1;
+                $h1_nb_msg_processed +=1 ;
+                if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
 
-	return( 0 ) if not $exitwhenover ;
-	if ( $total_bytes_transferred >= $exitwhenover ) {
-        	myprint( "Maximum bytes transferred reached, $total_bytes_transferred >= $exitwhenover, ending sync\n"  ) ;
-        	return( 1 ) ;
         }
 
+        #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n"  ) ;
+        MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) {
+                #
+                $debug and myprint( "Copy by uid $h1_fold/$h1_msg\n"  ) ;
+                if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; }
+
+                my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
+                if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  and $h2_msg ) {
+                        myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
+                        $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                }
+                last FOLDER if total_bytes_max_reached(  ) ;
+        }
+
+        if ( $expunge1 ){
+                myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n"  ) ;
+                if ( ! $sync->{dry} ) { $imap1->expunge(  ) } ;
+        }
+        if ( $expunge2 ){
+                myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n"  ) ;
+                if ( ! $sync->{dry} ) { $imap2->expunge(  ) } ;
+        }
+        $debug and myprint( 'Time: ', timenext(  ), " s\n"  ) ;
 }
 
+
 myprint( "++++ End looping on each folder\n"  ) ;
+
+if ( $delete1 and $sync->{'delete1emptyfolders'} ) {
+	delete1emptyfolders( $sync ) ;
+}
+
 ( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext(  ), " s\n"  ) ;
 
 
 if ( $foldersizesatend ) {
-	myprint( << 'END_SIZE'  ) ;
+        myprint( << 'END_SIZE'  ) ;
 
 Folders sizes after the synchronization.
 You can remove this foldersizes listing by using  "--nofoldersizesatend"
 END_SIZE
 
-	foldersizesatend(  ) ;
+        foldersizesatend(  ) ;
 }
 
-$imap1->logout(  ) unless lost_connection( $imap1, "for host1 [$host1]" ) ;
-$imap2->logout(  ) unless lost_connection( $imap2, "for host2 [$host2]" ) ;
+if ( ! lost_connection( $imap1, "for host1 [$sync->{host1}]" ) ) { $imap1->logout(  )  ; }
+if ( ! lost_connection( $imap2, "for host2 [$sync->{host2}]" ) ) { $imap2->logout(  )  ; }
 
 stats( $sync ) ;
 myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ;
-tests_live_result( $sync->{nb_errors} ) if ( $testslive ) ;
+tests_live_result( $sync->{nb_errors} ) if ( $sync->{testslive} or $sync->{testslive6} ) ;
+
+
 exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ;
 exit_clean( $sync, $EX_OK ) ;
 
@@ -2169,28 +2178,403 @@ exit_clean( $sync, $EX_OK ) ;
 
 
 # subroutines
-sub  myprint  { return print  @ARG ; } 
-sub  myprintf { return printf @ARG ; } 
+sub myprint  { return print  @ARG ; }
+sub myprintf { return printf @ARG ; }
 
 sub mysprintf {
         my( $format, @list ) = @ARG ;
-        return sprintf $format, @list ; 
+        return sprintf $format, @list ;
 }
 
-sub unsetunsafe {
-        # Remove all content in unsafe evalued options
-        @regextrans2 = (  ) ;
-        @regexflag = (  ) ;
-        @regexmess = (  ) ;
-        @skipmess = (  ) ;
-        @pipemess = (  ) ;
-        $delete2foldersonly = undef ;
-        $delete2foldersbutnot = undef ;
-        return ;
+sub output_start {
+	my $mysync = shift @ARG ;
+
+	if ( not $mysync ) { return ; }
+
+	my @output = @ARG ;
+	$mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ;
+	return $mysync->{ output } ;
 }
 
+sub tests_output_start {
+	note( 'Entering tests_output_start()' ) ;
+
+	my $mysync = { } ;
+
+	is( undef, output_start(  ), 'output_start: no args => undef' ) ;
+	is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ;
+	is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ;
+	is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ;
+	is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ;
+	is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ;
+
+	note( 'Leaving  tests_output_start()' ) ;
+	return ;
+}
+
+sub tests_output {
+	note( 'Entering tests_output()' ) ;
+
+	my $mysync = { } ;
+
+	is( undef, output(  ), 'output: no args => undef' ) ;
+	is( q{}, output( $mysync ), 'output: one arg => ""' ) ;
+	is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ;
+	is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ;
+	is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ;
+	is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ;
+
+	note( 'Leaving  tests_output()' ) ;
+	return ;
+}
+
+sub output {
+	my $mysync = shift @ARG ;
+
+	if ( not $mysync ) { return ; }
+
+	my @output = @ARG ;
+	$mysync->{ output } .= join( q{}, @output ) ;
+	return $mysync->{ output } ;
+}
+
+
+
+sub tests_output_reset_with {
+	note( 'Entering tests_output_reset_with()' ) ;
+
+	my $mysync = { } ;
+
+	is( undef,  output_reset_with(  ), 'output_reset_with: no args => undef' ) ;
+	is( q{},    output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ;
+	is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ;
+	is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ;
+	is( "\n",   output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ;
+
+	note( 'Leaving  tests_output_reset_with()' ) ;
+	return ;
+}
+
+sub output_reset_with {
+	my $mysync = shift @ARG ;
+
+	if ( not $mysync ) { return ; }
+
+	my @output = @ARG ;
+	$mysync->{ output } = join( q{}, @output ) ;
+	return $mysync->{ output } ;
+}
+
+
+
+sub abort  {
+	my $mysync = shift @ARG ;
+	if ( ! -r $sync->{pidfile} ) {
+		myprint( "Can not read pidfile $sync->{pidfile}. Exiting.\n" ) ;
+		exit $EX_OK ;
+	}
+	my $pidtokill = firstline( $sync->{pidfile} ) ;
+	if ( ! $pidtokill ) {
+		myprint( "No process to abort. Exiting.\n" ) ;
+		exit $EX_OK ;
+	}
+	# First ask for suicide
+	if ( kill 'ZERO', $pidtokill ) {
+		myprint( "Sending signal QUIT to PID $pidtokill \n" ) ;
+		kill 'QUIT', $pidtokill ;
+		sleep 1 ;
+	}else{
+		myprint( "Can not send signal to PID $pidtokill. Exiting.\n" ) ;
+		exit $EX_OK ;
+	}
+	# Then murder
+	if ( kill 'ZERO', $pidtokill ) {
+		myprint( "Sending signal KILL to PID $pidtokill \n" ) ;
+		kill 'KILL', $pidtokill ;
+		sleep 1 ;
+	}else{
+		myprint( "Process PID $pidtokill ended. Exiting.\n" ) ;
+		exit $EX_OK ;
+	}
+	# Well ...
+	if ( kill 'ZERO', $pidtokill ) {
+		myprint( "Process PID $pidtokill still there. Can not do much. Exiting.\n" ) ;
+		exit $EX_OK ;
+	}
+
+	return ;
+}
+
+
+
+sub docker_context {
+	my $mysync = shift ;
+	-e '/.dockerenv'  || return ;
+	myprint( "Docker context detected with /.dockerenv\n" ) ;
+	# No pidfile
+	$mysync->{pidfile} = q{} ;
+	# No log
+	$mysync->{log} = 0 ;
+	# In case
+	myprint( "Changing current directory to /var/tmp/\n" ) ;
+	chdir '/var/tmp/' ;
+	
+	return ;
+}
+
+sub cgibegin {
+	if ( ! under_cgi_context(  ) ) { return ; }
+	my $mysync = shift ;
+	require CGI ;
+	CGI->import( qw( -no_debug ) ) ;
+	require CGI::Carp ;
+	CGI::Carp->import( qw( fatalsToBrowser ) ) ;
+	$mysync->{cgi} = CGI->new( ) ;
+	return ;
+}
+
+sub tests_under_cgi_context {
+	note( 'Entering tests_under_cgi_context()' ) ;
+	# $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
+	do {
+		# Not in cgi context
+		delete local $ENV{SERVER_SOFTWARE} ;
+		is( undef, under_cgi_context(  ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
+	} ;
+	do {
+		# In cgi context
+		local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
+		is( 1, under_cgi_context(  ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
+	} ;
+	do {
+		# Not in cgi context
+		delete local $ENV{SERVER_SOFTWARE} ;
+		is( undef, under_cgi_context(  ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ;
+	} ;
+	do {
+		# In cgi context
+		local $ENV{SERVER_SOFTWARE} = 'under imapsync' ;
+		is( 1, under_cgi_context(  ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ;
+	} ;
+	note( 'Leaving  tests_under_cgi_context()' ) ;
+	return ;
+}
+
+
+sub under_cgi_context {
+
+	# Under cgi context
+	if ( $ENV{SERVER_SOFTWARE} ) {
+		return 1 ;
+	}
+	# Not in cgi context
+	return ;
+}
+
+sub cgibuildheader {
+	if ( ! under_cgi_context(  ) ) { return ; }
+	my $mysync = shift ;
+
+	my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ;
+	my $cookie = $mysync->{cgi}->cookie(
+			-name => 'imapsync_runs',
+			-value => 1 + $imapsync_runs,
+			-expires => '+20y',
+			-path    => '/cgi-bin/imapsync',
+		) ;
+	my $httpheader ;
+	if ( $mysync->{ abort } ) {
+		$httpheader = $mysync->{cgi}->header(
+			-type   => 'text/plain',
+			-status => '200 OK to stop playing IMAP mailboxes' . ". Load is $mysync->{ loadavg }",
+		) ;
+	}elsif( $mysync->{ loaddelay } ) {
+# https://tools.ietf.org/html/rfc2616#section-10.5.4
+# 503 Service Unavailable
+# The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
+		$httpheader = $mysync->{cgi}->header(
+			-type   => 'text/plain',
+			-status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }",
+		) ;
+	}else{
+		$httpheader = $mysync->{cgi}->header(
+		-type   => 'text/plain',
+		-status => '200 OK to play IMAP mailboxes' . ". Load is $mysync->{ loadavg }",
+		-cookie => $cookie,
+		) ;
+	}
+	output_start( $mysync, $httpheader ) ;
+
+	return ;
+}
+
+sub cgiload {
+	if ( ! under_cgi_context(  ) ) { return ; }
+	my $mysync = shift ;
+	if ( $mysync->{ abort } ) { return ; } # keep going to abort
+	if ( $mysync->{ loaddelay } ) {
+		myprint( "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n") ;
+		exit_clean( $mysync, $EX_UNAVAILABLE ) ;
+	}
+	return ;
+}
+
+sub tests_set_umask {
+	note( 'Entering tests_set_umask()' ) ;
+	my $save_umask = umask ;
+
+	my $mysync = {} ;
+	if ( 'MSWin32' eq $OSNAME ) {
+		is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ;
+	}else{
+		is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ;
+	}
+
+	umask $save_umask ;
+	note( 'Leaving  tests_set_umask()' ) ;
+	return ;
+}
+
+sub set_umask {
+	my $mysync = shift ;
+	my $previous_umask = umask_str(  ) ;
+	my $new_umask = umask_str( $UMASK_PARANO ) ;
+	output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ;
+	if ( $new_umask eq $UMASK_PARANO ) {
+		return 1 ;
+	}
+	return ;
+}
+
+sub tests_umask_str {
+	note( 'Entering tests_umask_str()' ) ;
+	my $save_umask = umask ;
+
+	is( umask_str(  ), umask_str(  ),  'umask_str: no parameters => idopotent' ) ;
+	is( my $save_umask_str = umask_str(  ), umask_str(  ),  'umask_str: no parameters => idopotent + save' ) ;
+	is( '0000', umask_str(    q{ } ),  'umask_str:  q{ } => 0000' ) ;
+	is( '0000', umask_str(     q{} ),  'umask_str:   q{} => 0000' ) ;
+	is( '0000', umask_str(  '0000' ),  'umask_str:  0000 => 0000' ) ;
+	is( '0000', umask_str(     '0' ),  'umask_str:     0 => 0000' ) ;
+	is( '0200', umask_str(  '0200' ),  'umask_str:  0200 => 0200' ) ;
+	is( '0400', umask_str(  '0400' ),  'umask_str:  0400 => 0400' ) ;
+	is( '0600', umask_str(  '0600' ),  'umask_str:  0600 => 0600' ) ;
+
+	SKIP: {
+	if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; }
+	is( '0100', umask_str(  '0100' ),  'umask_str:  0100 => 0100' ) ;
+	is( '0001', umask_str(  '0001' ),  'umask_str:  0001 => 0001' ) ;
+	is( '0777', umask_str(  '0777' ),  'umask_str:  0777 => 0777' ) ;
+	is( '0777', umask_str( '00777' ),  'umask_str: 00777 => 0777' ) ;
+	is( '0777', umask_str( ' 777 ' ),  'umask_str:  777  => 0777' ) ;
+	is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ),   "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ;
+	}
+
+	is( $save_umask_str, umask_str( $save_umask_str ),  'umask_str: restore with str' ) ;
+	is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ;
+	is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ;
+	is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ;
+
+	note( 'Leaving  tests_umask_str()' ) ;
+	return ;
+}
+
+sub umask_str {
+	my $value = shift ;
+
+	if ( defined $value ) {
+		umask oct( $value ) ;
+	}
+	my $current = umask ;
+
+	return( sprintf( '%#04o', $current ) ) ;
+}
+
+sub tests_umask {
+	note( 'Entering tests_umask()' ) ;
+	my $save_umask ;
+	is( umask, umask, 'umask: umask is umask' ) ;
+	is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ;
+	is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ;
+	is( oct(0000), umask, 'umask: umask is now 0000' ) ;
+	is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ;
+
+	SKIP: {
+	if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; }
+	is( oct(777), umask, 'umask: umask is now  0777' ) ;
+	is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ;
+	}
+
+	ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ;
+	is( $save_umask, umask, 'umask: umask is umask restored' ) ;
+	note( 'Leaving  tests_umask()' ) ;
+
+	return ;
+}
+
+sub cgisetcontext {
+	if ( ! under_cgi_context(  ) ) { return ; }
+
+	my $mysync = shift @ARG ;
+	output( $mysync, "Under cgi context\n" ) ;
+	set_umask( $mysync ) ;
+
+        # Remove all content in unsafe evaled options
+        @regextrans2 = (  ) ;
+        @regexflag   = (  ) ;
+        @regexmess   = (  ) ;
+        @skipmess    = (  ) ;
+        @pipemess    = (  ) ;
+        $delete2foldersonly   = undef ;
+        $delete2foldersbutnot = undef ;
+	$maxlinelengthcmd     = undef ;
+
+	# Set safe default values (I hope...)
+
+	$mysync->{pidfile} =  'imapsync.pid' ;
+	$mysync->{pidfilelocking} = 1 ;
+	$mysync->{errorsmax} = $ERRORS_MAX_CGI ;
+	$modulesversion = 0 ;
+	$releasecheck = 1 ;
+	$usecache = 0 ;
+	$mysync->{showpasswords} = 0 ;
+	$debugimap1 = $debugimap2 = $debugimap = 0 ;
+	$reconnectretry1 = $reconnectretry2 = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
+	$pipemesscheck = 0 ;
+
+	$mysync->{hashfile} = $CGI_HASHFILE ;
+	my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ;
+	$cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ;
+
+        -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ;
+        chdir  $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ;
+	$tmpdir = $cgidir ;
+	cgioutputenvcontext( $mysync ) ;
+        $debug and output( $mysync, 'Current directory is ' . getcwd(  ) . "\n" ) ;
+        $debug and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
+        $debug and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
+
+	return ;
+}
+
+sub cgioutputenvcontext {
+	my $mysync = shift @ARG ;
+
+	for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT HTTP_COOKIE ) ) {
+
+		my $envval = $ENV{ $envvar } || q{} ;
+		if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ;
+	}
+
+	return ;
+}
+
+
+
+
+
 sub debugsleep {
-        my $mysync = shift ;
+        my $mysync = shift @ARG ;
         if ( defined $mysync->{debugsleep} ) {
                 myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
                 sleep $mysync->{debugsleep} ;
@@ -2199,15 +2583,15 @@ sub debugsleep {
 }
 
 sub foldersizes_on_h1h2 {
-	myprint( << 'END_SIZE'  ) ;
+        myprint( << 'END_SIZE'  ) ;
 
 Folders sizes before the synchronization.
 You can remove foldersizes listings by using "--nofoldersizes" and  "--nofoldersizesatend"
 but then you will also loose the ETA (Estimation Time of Arrival) given after each message copy.
 END_SIZE
 
-	( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted        ) ;
-	( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
+        ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted        ) ;
+        ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
 
         if ( not all_defined( $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start ) ) {
                 my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ;
@@ -2216,16 +2600,29 @@ END_SIZE
                 $foldersizesatend = 0 ;
                 return ;
         }
-        
-        my $h2_bytes_limit = $sync->{host2}->{quota_limit_bytes} || 0 ;
+
+        my $h2_bytes_limit = $sync->{h2}->{quota_limit_bytes} || 0 ;
         if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) {
-        	my $quota_percent = mysprintf( '%.0f', $h1_bytes_start/$h2_bytes_limit ) ;
+                my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $h1_bytes_start / $h2_bytes_limit ) ;
                 my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ;
                 errors_incr( $sync, $error ) ;
         }
         return ;
 }
 
+
+sub total_bytes_max_reached {
+
+        return( 0 ) if not $exitwhenover ;
+        if ( $sync->{total_bytes_transferred} >= $exitwhenover ) {
+                myprint( "Maximum bytes transferred reached, $sync->{total_bytes_transferred} >= $exitwhenover, ending sync\n"  ) ;
+                return( 1 ) ;
+        }
+
+}
+
+
+
 sub all_defined {
         if ( not @ARG ) {
                 return 0 ;
@@ -2239,6 +2636,8 @@ sub all_defined {
 }
 
 sub tests_all_defined {
+	note( 'Entering tests_all_defined()' ) ;
+
         is( 0, all_defined(  ),             'all_defined: no param  => 0' ) ;
         is( 0, all_defined( () ),           'all_defined: void list => 0' ) ;
         is( 0, all_defined( undef ),        'all_defined: undef     => 0' ) ;
@@ -2247,171 +2646,346 @@ sub tests_all_defined {
         is( 0, all_defined( undef, 1 ),     'all_defined: undef 1   => 0' ) ;
         is( 1, all_defined( 1, 1 ),         'all_defined: 1 1   => 1' ) ;
         is( 1, all_defined( (1, 1) ),       'all_defined: (1 1) => 1' ) ;
+
+	note( 'Leaving  tests_all_defined()' ) ;
         return ;
 }
 
 
-sub imap_id_stuff {
-	my $sync = shift ;
+sub tests_hashsynclocal {
+	note( 'Entering tests_hashsynclocal()' ) ;
 
-	if ( not $sync->{id} ) { return ; } ;
+	my $mysync = {
+		host1 => '',
+		user1 => '',
+		password1 => '',
+		host2 => '',
+		user2 => '',
+		password2 => '',
+	} ;
 
-	$sync->{h1_imap_id} = imap_id( $sync->{imap1}, 'Host1' ) ;
-	#myprint( 'Host1: ' . $sync->{h1_imap_id}  ) ;
-	$sync->{h2_imap_id} = imap_id( $sync->{imap2}, 'Host2' ) ;
-	#myprint( 'Host2: ' . $sync->{h2_imap_id}  ) ;
+	is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ;
 
+	$mysync->{ hashfile } = '' ;
+	is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ;
+
+	$mysync->{ hashfile } = './noexist/rrr' ;
+	is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ;
+
+	SKIP: {
+		if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', 1 ) ; }
+		$mysync->{ hashfile } = '/rrr' ;
+		is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ;
+	}
+	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ;
+	$mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ;
+
+	ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ;
+	ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ;
+	is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ;
+	# A second time now
+	is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ;
+
+	note( 'Leaving  tests_hashsynclocal()' ) ;
 	return ;
 }
 
+sub hashsynclocal {
+	my $mysync = shift ;
+	my $hashkey = shift ; # Optional, only there for tests
+	my $hashfile = $mysync->{ hashfile } ;
+	$hashfile = createhashfileifneeded( $hashfile, $hashkey ) ;
+	if ( ! $hashfile ) {
+		return ;
+	}
+	$hashkey = firstline( $hashfile ) ;
+	if ( ! $hashkey ) {
+		myprint( "No hashkey!\n" ) ;
+		return ;
+	}
+	my $hashsynclocal = hashsync( $mysync, $hashkey ) ;
+	return( $hashsynclocal ) ;
+
+}
+
+sub tests_hashsync {
+	note( 'Entering tests_hashsync()' ) ;
+
+
+	is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ;
+	my $mysync ;
+	$mysync->{ host1 } = 'zzz' ;
+	is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
+	is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ;
+	$mysync->{ host2 } = 'zzz' ;
+	is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ;
+	is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ;
+
+	note( 'Leaving  tests_hashsync()' ) ;
+	return ;
+}
+
+sub hashsync {
+	my $mysync  = shift ;
+	my $hashkey = shift ;
+
+	my $mystring = join( q{},
+		$mysync->{ host1 }     || q{},
+		$mysync->{ user1 }     || q{},
+		$mysync->{ password1 } || q{},
+		$mysync->{ host2 }     || q{},
+		$mysync->{ user2 }     || q{},
+		$mysync->{ password2 } || q{},
+	) ;
+	my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ;
+	#myprint( "$hashsync\n" ) ;
+	return( $hashsync ) ;
+}
+
+
+sub tests_createhashfileifneeded {
+	note( 'Entering tests_createhashfileifneeded()' ) ;
+
+	is( undef, createhashfileifneeded(  ), 'createhashfileifneeded: no parameters => undef' ) ;
+
+	note( 'Leaving  tests_createhashfileifneeded()' ) ;
+	return ;
+}
+
+sub createhashfileifneeded {
+	my $hashfile = shift ;
+	my $hashkey  = shift || rand32(  ) ;
+
+	# no name
+	if ( ! $hashfile ) {
+		return ;
+	}
+	# already there
+	if ( -e -r $hashfile ) {
+		return $hashfile ;
+	}
+	# not creatable
+	if ( ! -w dirname( $hashfile ) ) {
+		return ;
+	}
+	# creatable
+	open my $FILE_HANDLE, '>', $hashfile
+                or do {
+                        myprint( "Could not open $hashfile for writing. Check permissions or disk space."  ) ;
+                return ;
+        } ;
+        myprint( "Writing random hashkey in $hashfile, once for all times\n"  ) ;
+        print $FILE_HANDLE $hashkey ;
+        close $FILE_HANDLE ;
+	# Should be there now
+	if ( -e -r $hashfile ) {
+		return $hashfile ;
+	}
+	# unknown failure
+	return ;
+}
+
+sub tests_rand32 {
+	note( 'Entering tests_rand32()' ) ;
+
+	my $string = rand32(  ) ;
+	print "$string\n" ;
+	is( 32, length( $string ),    'rand32: 32 characters long' ) ;
+	is( 32, length( rand32(  ) ), 'rand32: 32 characters long, another one' ) ;
+
+	note( 'Leaving  tests_rand32()' ) ;
+	return ;
+}
+
+sub rand32 {
+	my @chars = ( "a".."z" ) ;
+	my $string;
+	$string .= $chars[rand @chars] for 1..32 ;
+	return $string ;
+}
+
+sub imap_id_stuff {
+        my $mysync = shift ;
+
+        if ( not $mysync->{id} ) { return ; } ;
+
+        $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ;
+        #myprint( 'Host1: ' . $mysync->{h1_imap_id}  ) ;
+        $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ;
+        #myprint( 'Host2: ' . $mysync->{h2_imap_id}  ) ;
+
+        return ;
+}
+
 sub imap_id {
-	my ( $imap, $Side ) = @_ ;
+        my ( $mysync, $imap, $Side ) = @_ ;
 
-	$Side ||= q{} ;
-	my $imap_id_response = q{} ;
+        $Side ||= q{} ;
+        my $imap_id_response = q{} ;
 
-	if ( not $imap->has_capability( 'ID' ) ) {
-		 $imap_id_response = 'No ID capability' ;
+        if ( not $imap->has_capability( 'ID' ) ) {
+                 $imap_id_response = 'No ID capability' ;
                  myprint( "$Side: No ID capability\n"  ) ;
-	}else{
-		my $id_inp = imapsync_id( { side => lc $Side } ) ;
-		myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
+        }else{
+                my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ;
+                myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
                 . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
-		my $debug_before = $imap->Debug(  ) ;
-		$imap->Debug( 1 ) ;
-		my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
-		#my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
+                my $debug_before = $imap->Debug(  ) ;
+                $imap->Debug( 1 ) ;
+                my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
+                #my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
                 myprint( "\n"  ) ;
-		$imap->Debug( $debug_before ) ;
-		#$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
-	}
-	return( $imap_id_response ) ;
+                $imap->Debug( $debug_before ) ;
+                #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
+        }
+        return( $imap_id_response ) ;
 }
 
 sub imapsync_id {
-	my $overhashref = shift ;
-	# See http://tools.ietf.org/html/rfc2971.html
+        my $mysync = shift ;
+        my $overhashref = shift ;
+        # See http://tools.ietf.org/html/rfc2971.html
 
-	my $imapsync_id = { } ;
+        my $imapsync_id = { } ;
 
-	my $imapsync_id_lamiral = {
-		name          => 'imapsync',
-		version       => imapsync_version(  ),
-		os            => $OSNAME,
-		vendor        => 'Gilles LAMIRAL',
-		'support-url' => 'http://imapsync.lamiral.info/',
-		# Example of date-time:  19-Sep-2015 08:56:07
-		date          => date_from_rcs( q{$Date: 2016/08/19 10:30:36 $ } ),
-	} ;
+        my $imapsync_id_lamiral = {
+                name          => 'imapsync',
+                version       => imapsync_version( $mysync ),
+                os            => $OSNAME,
+                vendor        => 'Gilles LAMIRAL',
+                'support-url' => 'http://imapsync.lamiral.info/',
+                # Example of date-time:  19-Sep-2015 08:56:07
+                date          => date_from_rcs( q{$Date: 2017/09/05 16:14:53 $ } ),
+        } ;
 
-	my $imapsync_id_github  = {
-		name          => 'imapsync',
-		version       => imapsync_version(  ),
-		os            => $OSNAME,
-		vendor        => 'github',
-		'support-url' => 'https://github.com/imapsync/imapsync',
-		date          => date_from_rcs( q{$Date: 2016/08/19 10:30:36 $ } ),
-	} ;
+        my $imapsync_id_github  = {
+                name          => 'imapsync',
+                version       => imapsync_version( $mysync ),
+                os            => $OSNAME,
+                vendor        => 'github',
+                'support-url' => 'https://github.com/imapsync/imapsync',
+                date          => date_from_rcs( q{$Date: 2017/09/05 16:14:53 $ } ),
+        } ;
 
-	$imapsync_id = $imapsync_id_lamiral ;
-	#$imapsync_id = $imapsync_id_github ;
-	my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
-	my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
-	#myprint( "$imapsync_id_str\n"  ) ;
-	return( $imapsync_id_str ) ;
+        $imapsync_id = $imapsync_id_lamiral ;
+        #$imapsync_id = $imapsync_id_github ;
+        my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
+        my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
+        #myprint( "$imapsync_id_str\n"  ) ;
+        return( $imapsync_id_str ) ;
 }
 
 sub tests_imapsync_id {
-	ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "http://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
-	eq imapsync_id( {
-		version => 111,
-		os => 'beurk',
-		date => '22-12-1968',
-		side => 'host1' } ),
-	'tests_imapsync_id override' ) ;
+	note( 'Entering tests_imapsync_id()' ) ;
 
-	return ;
+        my $mysync ;
+        ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "http://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
+                eq imapsync_id( $mysync,
+                        {
+                        version => 111,
+                        os => 'beurk',
+                        date => '22-12-1968',
+                        side => 'host1' 
+                        }
+                ),
+                'tests_imapsync_id override' 
+        ) ;
+
+	note( 'Leaving  tests_imapsync_id()' ) ;
+        return ;
 }
 
 sub format_for_imap_arg {
-	my $ref = shift ;
+        my $ref = shift ;
 
-	my $string = q{} ;
-	my %terms = %{ $ref } ;
-	my @terms = (  ) ;
-	if ( not ( %terms ) ) { return( 'NIL' ) } ;
-	# sort like in RFC then add extra key/values
-	foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
-		if ( $terms{ $key } ) {
-			push  @terms, $key, $terms{ $key }  ;
-			delete $terms{ $key } ;
-		}
-	}
-	push  @terms, %terms  ;
-	$string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms )  . ')' ;
-	return( $string ) ;
+        my $string = q{} ;
+        my %terms = %{ $ref } ;
+        my @terms = (  ) ;
+        if ( not ( %terms ) ) { return( 'NIL' ) } ;
+        # sort like in RFC then add extra key/values
+        foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
+                if ( $terms{ $key } ) {
+                        push  @terms, $key, $terms{ $key }  ;
+                        delete $terms{ $key } ;
+                }
+        }
+        push  @terms, %terms  ;
+        $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms )  . ')' ;
+        return( $string ) ;
 }
 
 
 
 sub tests_format_for_imap_arg {
-	ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
-	ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
-	ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
-	return ;
+	note( 'Entering tests_format_for_imap_arg()' ) ;
+
+        ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
+        ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
+        ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
+
+	note( 'Leaving  tests_format_for_imap_arg()' ) ;
+        return ;
 }
 
 sub quota {
-	my ( $imap, $side, $sync ) = @_ ;
+        my ( $imap, $side, $mysync ) = @_ ;
 
-        my $Side = ucfirst $side ;
-	my $debug_before = $imap->Debug(  ) ;
-	$imap->Debug( 1 ) ;
-	if ( not $imap->has_capability( 'QUOTA' ) ) {
-        	$imap->Debug( $debug_before ) ;
-        	return ;
+	my %side = (
+		h1 => 'Host1',
+		h2 => 'Host2',
+	) ;
+        my $Side = $side{ $side } ;
+        my $debug_before = $imap->Debug(  ) ;
+        $imap->Debug( 1 ) ;
+        if ( not $imap->has_capability( 'QUOTA' ) ) {
+                $imap->Debug( $debug_before ) ;
+                return ;
         } ;
-	myprint( "\n$Side: found quota, presented in raw IMAP\n"  ) ;
-	my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
+        myprint( "\n$Side: found quota, presented in raw IMAP\n"  ) ;
+        my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
         # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
         #$imap->quota( 'ROOT' ) ;
         #$imap->quota( '""' ) ;
-	myprint( "\n"  ) ;
-	$imap->Debug( $debug_before ) ;
+        myprint( "\n"  ) ;
+        $imap->Debug( $debug_before ) ;
         my $quota_limit_bytes   = quota_extract_storage_limit_in_bytes( $getquotaroot ) ;
         my $quota_current_bytes = quota_extract_storage_current_in_bytes( $getquotaroot ) ;
-        $sync->{$side}->{quota_limit_bytes}   = $quota_limit_bytes ;
-        $sync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
+        $mysync->{$side}->{quota_limit_bytes}   = $quota_limit_bytes ;
+        $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
         my $quota_percent ;
         if ( $quota_limit_bytes > 0 ) {
-        	$quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
+                $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
         }else{
-        	$quota_percent = 0 ;
+                $quota_percent = 0 ;
         }
         myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n"  ) ;
         if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
-        	my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
-                errors_incr( $sync, $error ) ;
+                my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
+                errors_incr( $mysync, $error ) ;
         }
-	return ;
+        return ;
 }
 
 sub tests_quota_extract_storage_limit_in_bytes {
-	my $imap_output = [
-	'* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
+	note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ;
+
+        my $imap_output = [
+        '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
         '* QUOTA "Storage quota" (STORAGE 1 104857600)',
         '* QUOTA "Messages quota" (MESSAGE 2 100000)',
         '5 OK Getquotaroot completed.'
-	] ;
-        ok( $NUMBER_104857600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
+        ] ;
+        ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
+
+	note( 'Leaving  tests_quota_extract_storage_limit_in_bytes()' ) ;
         return ;
 }
 
 sub quota_extract_storage_limit_in_bytes {
-	my $imap_output = shift ;
+        my $imap_output = shift ;
 
         my $limit_kb ;
-        $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
+        $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
         $limit_kb ||= 0 ;
         $debug and myprint( "storage_limit_kb = $limit_kb\n"  ) ;
         return( $KIBI * $limit_kb ) ;
@@ -2419,21 +2993,26 @@ sub quota_extract_storage_limit_in_bytes {
 
 
 sub tests_quota_extract_storage_current_in_bytes {
-	my $imap_output = [
-	'* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
+	note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ;
+
+
+        my $imap_output = [
+        '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
         '* QUOTA "Storage quota" (STORAGE 1 104857600)',
         '* QUOTA "Messages quota" (MESSAGE 2 100000)',
         '5 OK Getquotaroot completed.'
-	] ;
+        ] ;
         ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
+
+	note( 'Leaving  tests_quota_extract_storage_current_in_bytes()' ) ;
         return ;
 }
 
 sub quota_extract_storage_current_in_bytes {
-	my $imap_output = shift ;
+        my $imap_output = shift ;
 
         my $current_kb ;
-        $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
+        $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ;
         $current_kb ||= 0 ;
         $debug and myprint( "storage_current_kb = $current_kb\n"  ) ;
         return( $KIBI * $current_kb ) ;
@@ -2442,52 +3021,52 @@ sub quota_extract_storage_current_in_bytes {
 
 
 sub automap {
-	my ( $sync ) = @_ ;
+        my ( $mysync ) = @_ ;
 
-	if ( $sync->{automap} ) {
-		myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n"  ) ;
-	}else{
-		myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n"  ) ;
-		return ;
-	}
+        if ( $mysync->{automap} ) {
+                myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n"  ) ;
+        }else{
+                myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n"  ) ;
+                return ;
+        }
 
-        $sync->{h1_special} = special_from_folders_hash( $sync->{imap1}, 'Host1' ) ;
-        $sync->{h2_special} = special_from_folders_hash( $sync->{imap2}, 'Host2' ) ;
+        $mysync->{h1_special} = special_from_folders_hash( $mysync->{imap1}, 'Host1' ) ;
+        $mysync->{h2_special} = special_from_folders_hash( $mysync->{imap2}, 'Host2' ) ;
 
-	build_possible_special( $sync ) ;
-        build_guess_special(  $sync ) ;
-	build_automap( $sync ) ;
+        build_possible_special( $mysync ) ;
+        build_guess_special(  $mysync ) ;
+        build_automap( $mysync ) ;
 
-	return ;
+        return ;
 }
 
 
 
 
 sub build_guess_special {
-	my ( $sync ) = shift ;
+        my ( $mysync ) = shift ;
 
-        foreach my $h1_fold ( sort keys  %{ $sync->{h1_folders_all} }  ) {
-        	my $special = guess_special( $h1_fold, $sync->{possible_special}, $sync->{h1_prefix} ) ;
-        	if ( $special ) {
-                	$sync->{h1_special_guessed}{$h1_fold} = $special ;
-                        my $already_guessed = $sync->{h1_special_guessed}{$special} ;
+        foreach my $h1_fold ( sort keys  %{ $mysync->{h1_folders_all} }  ) {
+                my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ;
+                if ( $special ) {
+                        $mysync->{h1_special_guessed}{$h1_fold} = $special ;
+                        my $already_guessed = $mysync->{h1_special_guessed}{$special} ;
                         if ( $already_guessed ) {
-                        	myprint( "Host1: $h1_fold not $special because set to $already_guessed\n"  ) ;
+                                myprint( "Host1: $h1_fold not $special because set to $already_guessed\n"  ) ;
                         }else{
-	                        $sync->{h1_special_guessed}{$special} = $h1_fold ;
+                                $mysync->{h1_special_guessed}{$special} = $h1_fold ;
                         }
                 }
         }
-        foreach my $h2_fold ( sort keys  %{ $sync->{h2_folders_all} }  ) {
-        	my $special = guess_special( $h2_fold, $sync->{possible_special}, $sync->{h2_prefix} ) ;
-        	if ( $special ) {
-                	$sync->{h2_special_guessed}{$h2_fold} = $special ;
-                        my $already_guessed = $sync->{h2_special_guessed}{$special} ;
+        foreach my $h2_fold ( sort keys  %{ $mysync->{h2_folders_all} }  ) {
+                my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ;
+                if ( $special ) {
+                        $mysync->{h2_special_guessed}{$h2_fold} = $special ;
+                        my $already_guessed = $mysync->{h2_special_guessed}{$special} ;
                         if ( $already_guessed ) {
-                        	myprint( "Host2: $h2_fold not $special because set to $already_guessed\n"  ) ;
+                                myprint( "Host2: $h2_fold not $special because set to $already_guessed\n"  ) ;
                         }else{
-	                        $sync->{h2_special_guessed}{$special} = $h2_fold ;
+                                $mysync->{h2_special_guessed}{$special} = $h2_fold ;
                         }
                 }
         }
@@ -2495,153 +3074,179 @@ sub build_guess_special {
 }
 
 sub guess_special {
-	my( $folder, $possible_special_ref, $prefix ) = @_ ;
+        my( $folder, $possible_special_ref, $prefix ) = @_ ;
 
         my $folder_no_prefix = $folder ;
-        $folder_no_prefix =~ s/${prefix}// ;
+        $folder_no_prefix =~ s/\Q${prefix}\E//xms ;
         #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n"  ) ;
 
         my $guess_special = $possible_special_ref->{ $folder }
-        	|| $possible_special_ref->{ $folder_no_prefix }
-        	|| q{} ;
+                || $possible_special_ref->{ $folder_no_prefix }
+                || q{} ;
 
         return( $guess_special ) ;
 }
 
 sub tests_guess_special {
-	my $possible_special_ref = build_possible_special( my $sync ) ;
+	note( 'Entering tests_guess_special()' ) ;
+
+        my $possible_special_ref = build_possible_special( my $mysync ) ;
         ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
         ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
         ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
-	return ;
+        ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ;
+
+	note( 'Leaving  tests_guess_special()' ) ;
+        return ;
 }
 
 sub build_automap {
-	my ( $sync ) = @_ ;
+        my $mysync = shift ;
+	$debug and myprint( "Entering build_automap\n" ) ;
+        foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) {
+                my $h2_fold ;
+                my $h1_special = $mysync->{h1_special}{$h1_fold} ;
+                my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ;
 
-	foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
-		my $h2_fold ;
-		my $h1_special = $sync->{h1_special}{$h1_fold} ;
-                my $h1_special_guessed = $sync->{h1_special_guessed}{$h1_fold} ;
-
-		# Case 1: special on both sides.
-		if ( $h1_special
-                     and exists  $sync->{h2_special}{$h1_special}  ) {
-			$h2_fold = $sync->{h2_special}{$h1_special} ;
-			$sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
-			next ;
-		}
-		# Case 2: special on host1, not on host2
-		if ( $h1_special
-                     and ( not exists  $sync->{h2_special}{$h1_special}  )
-                     and ( exists  $sync->{h2_special_guessed}{$h1_special}  )
+                # Case 1: special on both sides.
+                if ( $h1_special
+                     and exists  $mysync->{h2_special}{$h1_special}  ) {
+                        $h2_fold = $mysync->{h2_special}{$h1_special} ;
+                        $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+                        next ;
+                }
+                # Case 2: special on host1, not on host2
+                if ( $h1_special
+                     and ( not exists  $mysync->{h2_special}{$h1_special}  )
+                     and ( exists  $mysync->{h2_special_guessed}{$h1_special}  )
                    ) {
-			# special_guessed on host2
-                        $h2_fold = $sync->{h2_special_guessed}{$h1_special} ;
-                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
-			next ;
-		}
-		# Case 3: no special on host1, special on host2
+                        # special_guessed on host2
+                        $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ;
+                        $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+                        next ;
+                }
+                # Case 3: no special on host1, special on host2
                 if ( ( not $h1_special )
                      and ( $h1_special_guessed )
-                     and ( exists  $sync->{h2_special}{$h1_special_guessed}  )
+                     and ( exists  $mysync->{h2_special}{$h1_special_guessed}  )
                 ) {
-                	$h2_fold = $sync->{h2_special}{$h1_special_guessed} ;
-                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
-			next ;
+                        $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ;
+                        $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+                        next ;
                 }
                 # Case 4: no special on both sides.
                 if ( ( not $h1_special )
                      and ( $h1_special_guessed )
-                     and ( not exists  $sync->{h2_special}{$h1_special_guessed}  )
-                     and ( exists  $sync->{h2_special_guessed}{$h1_special_guessed}  )
+                     and ( not exists  $mysync->{h2_special}{$h1_special_guessed}  )
+                     and ( exists  $mysync->{h2_special_guessed}{$h1_special_guessed}  )
                 ) {
-                	$h2_fold = $sync->{h2_special_guessed}{$h1_special_guessed} ;
-                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
-			next ;
+                        $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ;
+                        $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+                        next ;
                 }
-	}
-	return( $sync->{f1f2auto} ) ;
+        }
+        return( $mysync->{f1f2auto} ) ;
 }
 
 # I willll probably add what there is at:
 # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
 sub build_possible_special {
-	my $sync = shift ;
-	my $possible_special = { } ;
-	# All|Archive|Drafts|Flagged|Junk|Sent|Trash
+        my $mysync = shift ;
+        my $possible_special = { } ;
+        # All|Archive|Drafts|Flagged|Junk|Sent|Trash
 
-	$possible_special->{'\All'}     = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
-	$possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
-	$possible_special->{'\Drafts'}  = [ 'Drafts', '&BCcENQRABD0EPgQyBDgEOgQ4-' ] ;
-	$possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
-	$possible_special->{'\Junk'}    = [ 'Junk', 'Spam', '&BCEEPwQwBDw-' ] ;
-	$possible_special->{'\Sent'}    = [ 'Sent', 'Sent Messages', 'Sent Items',
+        $possible_special->{'\All'}     = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
+        $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
+        $possible_special->{'\Drafts'}  = [ 'Drafts', '&BCcENQRABD0EPgQyBDgEOgQ4-' ] ;
+        $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
+        $possible_special->{'\Junk'}    = [ 'Junk', 'Spam', '&BCEEPwQwBDw-' ] ;
+        $possible_special->{'\Sent'}    = [ 'Sent', 'Sent Messages', 'Sent Items',
                                             'Gesendete Elemente', 'Gesendete Objekte',
-                                            '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-',
+                                            '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-', 'Objets envoy&AOk-s',
                                             'Elementos enviados',
                                             '&kAFP4W4IMH8wojCkMMYw4A-',
                                             '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-'] ;
-	$possible_special->{'\Trash'}   = [ 'Trash', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-' ] ;
+        $possible_special->{'\Trash'}   = [ 'Trash', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-' ] ;
 
-	foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
-		foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
-			$possible_special->{ $possible_folder } = $special ;
-		} ;
-	}
-        $sync->{possible_special} = $possible_special ;
-	$debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] )  ) ;
+        foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
+                foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
+                        $possible_special->{ $possible_folder } = $special ;
+                } ;
+        }
+        $mysync->{possible_special} = $possible_special ;
+        $debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] )  ) ;
         return( $possible_special ) ;
 }
 
 sub special_from_folders_hash {
-	my ( $imap, $side ) = @_ ;
-	my %special = (  ) ;
-        if ( not( Mail::IMAPClient->can( 'folders_hash' ) ) ) {
-        	my $error =  "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
+        my ( $imap, $side ) = @_ ;
+        my %special = (  ) ;
+
+        if ( ! defined $imap  ) { return ; }
+        $side = defined $side ? $side : 'Host?' ;
+
+        if ( ! $imap->can( 'folders_hash' ) ) {
+                my $error =  "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
                 errors_incr( $sync, $error ) ;
                 return( \%special ) ; # empty hash ref
         }
-	my $folders_hash = $imap->folders_hash(  ) ;
-	foreach my $fhash (@{ $folders_hash } ) {
-			my @special =  grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/ } @{ $fhash->{attrs} }  ;
-			if ( @special ) {
-				my $special = $special[0] ; # keep first one. Could be not very good.
-				if ( exists  $special{ $special }  ) {
-					myprintf( "%s: special %-20s = %s already asigned to %s\n",
-					        $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
-				}else{
-					myprintf( "%s: special %-20s = %s\n",
-					        $side, $fhash->{name}, join( q{ }, @special ) ) ;
-					$special{ $special } = $fhash->{name} ;
-					$special{ $fhash->{name} } = $special ; # double entry value => key
-				}
-			}
-		}
+        my $folders_hash = $imap->folders_hash(  ) ;
+        foreach my $fhash (@{ $folders_hash } ) {
+                        my @special =  grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} }  ;
+                        if ( @special ) {
+                                my $special = $special[0] ; # keep first one. Could be not very good.
+                                if ( exists  $special{ $special }  ) {
+                                        myprintf( "%s: special %-20s = %s already assigned to %s\n",
+                                                $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
+                                }else{
+                                        myprintf( "%s: special %-20s = %s\n",
+                                                $side, $fhash->{name}, join( q{ }, @special ) ) ;
+                                        $special{ $special } = $fhash->{name} ;
+                                        $special{ $fhash->{name} } = $special ; # double entry value => key
+                                }
+                        }
+                }
         myprint( "\n" ) if ( %special ) ;
-	return( \%special ) ;
+        return( \%special ) ;
+}
+
+sub tests_special_from_folders_hash {
+	note( 'Entering tests_special_from_folders_hash()' ) ;
+
+
+        require Test::MockObject ;
+        my $imapT = Test::MockObject->new(  ) ;
+
+        is( undef, special_from_folders_hash(  ), 'special_from_folders_hash: no args' ) ;
+        is_deeply( {}, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap void' ) ;
+
+        $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ;
+        is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' }, special_from_folders_hash( $imapT ), 'special_from_folders_hash: $imap \Sent' ) ;
+
+	note( 'Leaving  tests_special_from_folders_hash()' ) ;
+        return(  ) ;
 }
 
 sub errors_incr {
-	my ( $mysync, @error ) = @ARG ;
-	$sync->{nb_errors}++ ;
-        
+        my ( $mysync, @error ) = @ARG ;
+        $sync->{nb_errors}++ ;
+
         if ( @error ) {
-		errors_log( $mysync, @error ) ;
+                errors_log( $mysync, @error ) ;
                 myprint( @error ) ;
         }
-        
+
         $mysync->{errorsmax} ||= $ERRORS_MAX ;
-	if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) {
-		myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
+        if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) {
+                myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
                 if ( $mysync->{errorsdump} ) {
                         myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ;
                         # again since errorsdump(  ) can be very verbose and masq previous warning
-		        myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
-		}
+                        myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
+                }
                 exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
-	}
-	return ;
+        }
+        return ;
 }
 
 sub errors_log {
@@ -2652,7 +3257,7 @@ sub errors_log {
         }
 
         if ( @error ) {
-		push  @{ $mysync->{errors_log} }, join( q{}, @error  ) ;
+                push  @{ $mysync->{errors_log} }, join( q{}, @error  ) ;
         }
         if ( @{ $mysync->{errors_log} } ) {
                 return @{ $mysync->{errors_log} } ;
@@ -2663,126 +3268,132 @@ sub errors_log {
 }
 
 sub tests_errors_log {
+	note( 'Entering tests_errors_log()' ) ;
 
 
+	note( 'Leaving  tests_errors_log()' ) ;
+	return ;
 }
 
 
 sub errorsdump {
         my( $nb_errors, @errors_log ) = @ARG ;
-	my $error_num = 0 ;
-	my $errors_list = q{} ;
-	if ( @errors_log ) {
-		$errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
-		foreach my $error ( @errors_log ) {
-			$error_num++ ;
-			$errors_list .= "Err $error_num/$nb_errors: $error" ;
-		}
-	}
-	return( $errors_list ) ;
+        my $error_num = 0 ;
+        my $errors_list = q{} ;
+        if ( @errors_log ) {
+                $errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
+                foreach my $error ( @errors_log ) {
+                        $error_num++ ;
+                        $errors_list .= "Err $error_num/$nb_errors: $error" ;
+                }
+        }
+        return( $errors_list ) ;
 }
 
 
 sub tests_live_result {
-	my $nb_errors = shift ;
-	if ( $nb_errors  ) {
-		myprint( "Live tests failed with $nb_errors errors\n"  ) ;
-	} else {
-		myprint( "Live tests ended successfully\n"  ) ;
-	}
-	return ;
+	note( 'Entering tests_live_result()' ) ;
+
+        my $nb_errors = shift ;
+        if ( $nb_errors  ) {
+                myprint( "Live tests failed with $nb_errors errors\n"  ) ;
+        } else {
+                myprint( "Live tests ended successfully\n"  ) ;
+        }
+	note( 'Leaving  tests_live_result()' ) ;
+        return ;
 }
 
 sub foldersizesatend {
-	timenext(  ) ;
-	return if ( $imap1->IsUnconnected(  ) ) ;
-	return if ( $imap2->IsUnconnected(  ) ) ;
-	# Get all folders on host2 again since new were created
-	@h2_folders_all = sort $imap2->folders();
-	for ( @h2_folders_all ) {
-        	$h2_folders_all{ $_ } = 1 ;
-        	$h2_folders_all_UPPER{ uc  $_  } = 1 ;
+        timenext(  ) ;
+        return if ( $imap1->IsUnconnected(  ) ) ;
+        return if ( $imap2->IsUnconnected(  ) ) ;
+        # Get all folders on host2 again since new were created
+        @h2_folders_all = sort $imap2->folders();
+        for ( @h2_folders_all ) {
+                $h2_folders_all{ $_ } = 1 ;
+                $h2_folders_all_UPPER{ uc  $_  } = 1 ;
         } ;
-	( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted ) ;
-	( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
+        ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, $sync->{abletosearch1}, @h1_folders_wanted ) ;
+        ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ;
         if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
                 my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
                 errors_incr( $sync, $error ) ;
         }
-	return ;
+        return ;
 }
 
 sub size_filtered_flag {
-	my $h1_size = shift ;
+        my $h1_size = shift ;
 
-	if (defined $maxsize and $h1_size >= $maxsize) {
-		return( 1 ) ;
-	}
-	if (defined $minsize and $h1_size <= $minsize) {
-		return( 1 ) ;
-	}
-	return( 0 ) ;
+        if (defined $maxsize and $h1_size >= $maxsize) {
+                return( 1 ) ;
+        }
+        if (defined $minsize and $h1_size <= $minsize) {
+                return( 1 ) ;
+        }
+        return( 0 ) ;
 }
 
 sub sync_flags_fir {
-	my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
+        my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
 
-	if ( not defined  $h1_msg  ) { return } ;
-	if ( not defined  $h2_msg  ) { return } ;
+        if ( not defined  $h1_msg  ) { return } ;
+        if ( not defined  $h2_msg  ) { return } ;
 
-	my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
-	return if size_filtered_flag( $h1_size ) ;
+        my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ;
+        return if size_filtered_flag( $h1_size ) ;
 
-	# used cached flag values for efficiency
-	my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
-	my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
+        # used cached flag values for efficiency
+        my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ;
+        my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ;
 
-	sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
+        sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
 
         return ;
 }
 
 sub sync_flags_after_copy {
-	my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
+        my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
 
         my @h2_flags = $imap2->flags( $h2_msg ) ;
         my $h2_flags = "@h2_flags" ;
         ( $debug or $debugflags ) and myprint( "Host2 flags before resync by STORE on msg $h2_msg: $h2_flags\n"  ) ;
-	sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
+        sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
         return ;
 }
 
 sub sync_flags {
-	my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
+        my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ;
 
-	( $debug or $debugflags ) and
+        ( $debug or $debugflags ) and
         myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n"  ) ;
 
-	$h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
+        $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
 
-	$h2_flags = flagscase( $h2_flags ) ;
+        $h2_flags = flagscase( $h2_flags ) ;
 
-	( $debug or $debugflags ) and
+        ( $debug or $debugflags ) and
         myprint( "Host1 flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 $h2_fold/$h2_msg flags( $h2_flags )\n"  ) ;
 
 
-	# compare flags - set flags if there a difference
-	my @h1_flags = sort split(q{ }, $h1_flags );
-	my @h2_flags = sort split(q{ }, $h2_flags );
-	my $diff = compare_lists( \@h1_flags, \@h2_flags );
+        # compare flags - set flags if there a difference
+        my @h1_flags = sort split(q{ }, $h1_flags );
+        my @h2_flags = sort split(q{ }, $h2_flags );
+        my $diff = compare_lists( \@h1_flags, \@h2_flags );
 
-	$diff and ( $debug or $debugflags )
-		and     myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
-	# This sets flags so flags can be removed with this
-	# When you remove a \Seen flag on host1 you want to it
-	# to be removed on host2. Just add flags is not what
-	# we need most of the time.
+        $diff and ( $debug or $debugflags )
+                and     myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
+        # This sets flags so flags can be removed with this
+        # When you remove a \Seen flag on host1 you want to it
+        # to be removed on host2. Just add flags is not what
+        # we need most of the time.
 
-	if ( not $dry and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
-		my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
-		  $imap2->LastError || q{}, "\n" ;
-		errors_incr( $sync, $error_msg ) ;
-	}
+        if ( not $sync->{dry} and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
+                my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
+                  $imap2->LastError || q{}, "\n" ;
+                errors_incr( $sync, $error_msg ) ;
+        }
 
         return ;
 }
@@ -2790,7 +3401,7 @@ sub sync_flags {
 
 
 sub _filter {
-	my $str = shift or return q{} ;
+        my $str = shift or return q{} ;
         my $sz  = $SIZE_MAX_STR ;
         my $len = length $str ;
         if ( not $debug and $len > $sz*2 ) {
@@ -2805,7 +3416,7 @@ sub _filter {
 
 
 sub lost_connection {
-	my( $imap, $error_message ) = @_;
+        my( $imap, $error_message ) = @_;
         if ( $imap->IsUnconnected(  ) ) {
                 $sync->{nb_errors}++ ;
                 my $lcomm = $imap->LastIMAPCommand || q{} ;
@@ -2819,203 +3430,298 @@ sub lost_connection {
                 return( 1 ) ;
         }
         else{
-        	return( 0 ) ;
+                return( 0 ) ;
         }
 }
 
 sub max {
-	my @list = @_ ;
-	return( undef ) if ( 0 == scalar  @list  ) ;
-	my @sorted = sort { $a <=> $b } @list ;
-	return( pop @sorted ) ;
+        my @list = @_ ;
+        return( undef ) if ( 0 == scalar  @list  ) ;
+        
+        no warnings 'numeric' ;
+        no warnings 'uninitialized' ;
+
+        my @sorted = sort { $a <=> $b || $a cmp $b } @list ;
+        return( pop @sorted ) ;
 }
 
 sub tests_max {
-	ok( 0  == max( 0 ),  'max 0' ) ;
-	ok( 1  == max( 1 ),  'max 1' ) ;
-	ok( $MINUS_ONE == max( $MINUS_ONE ), 'max -1') ;
-	ok( not ( defined max(  ) ), 'max no arg' ) ;
-	ok( $NUMBER_100 == max( 1, $NUMBER_100 ), 'max 1 100' ) ;
-	ok( $NUMBER_100 == max( $NUMBER_100, 1 ), 'max 100 1' ) ;
-	ok( $NUMBER_100 == max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1' ) ;
-	ok( $NUMBER_100 == max( $NUMBER_100, '42', 1 ), 'max 100 42 1' ) ;
-	ok( $NUMBER_100 == max( '100', '42', 1 ), 'max 100 42 1' ) ;
-	#ok( 100 == max( 100, 'haha', 1 ), 'max 100 42 1') ;
+	note( 'Entering tests_max()' ) ;
+        is( 0, max( 0 ),  'max 0 => 0' ) ;
+        is( 1, max( 1 ),  'max 1 => 1' ) ;
+        is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ;
+        is( undef, max(  ), 'max no arg => undef' ) ;
+        is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ;
+        is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ;
+        is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ;
+        is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ;
+        is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ;
+        is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ;
+	is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ;
+	is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ;
+	is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ;
+        is( 'haha', max( 'haha' ), 'max haha => haha') ;
+        is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ;
+        is( 'bb', max( 'bb', 'aa' ), 'max bb aa bb => bb') ;
+        is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ;
+	note( 'Leaving  tests_max()' ) ;
+        return ;
+}
+
+sub min {
+        my @list = @_ ;
+        return( undef ) if ( 0 == scalar  @list  ) ;
+        no warnings 'numeric' ;
+        no warnings 'uninitialized' ;
+        my @sorted = sort { $a <=> $b || $a cmp $b } @list ;
+        return( shift @sorted ) ;
+}
+
+sub tests_min {
+	note( 'Entering tests_min()' ) ;
+
+        is( 0, min( 0 ),  'min 0 => 0' ) ;
+        is( 1, min( 1 ),  'min 1 => 1' ) ;
+        is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ;
+        is( undef, min(  ), 'min no arg => undef' ) ;
+        is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ;
+        is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ;
+        is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ;
+        is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ;
+        is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ;
+        is( 'haha', min( 100, 'haha', 1 ), 'min 100 haha 1 => haha') ;
+	is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ;
+	
+	is( undef, min( undef, 1 ), 'min undef 1 => undef' ) ;
+	is( undef, min( undef, 0 ), 'min undef 0 => undef' ) ;
+
+        is( 'haha', min( 'haha' ), 'min haha => haha') ;
+        is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ;
+        is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ;
+        is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ;
+
+	note( 'Leaving  tests_min()' ) ;
         return ;
 }
 
 
 sub check_lib_version {
-	$debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n"  ) ;
-	if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
-		myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n"  ) ;
-		return 0 ;
-	}
-	else{
-		# 3.x.x is no longer buggy with imapsync.
+        $debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n"  ) ;
+        if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
+                myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n"  ) ;
+                return 0 ;
+        }
+        else{
+                # 3.x.x is no longer buggy with imapsync.
                 # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
-		return 1 ;
-	}
+                return 1 ;
+        }
         return ;
 }
 
 sub module_version_str {
-	my( $module_name, $module_version ) = @_ ;
-	my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
+        my( $module_name, $module_version ) = @_ ;
+        my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
         return( $str ) ;
 }
 
 sub modulesversion {
 
-	my @list_version;
+        my @list_version;
 
-	my $v ;
-	eval { require Mail::IMAPClient; $v = $Mail::IMAPClient::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Mail::IMAPClient', $v )  ;
+        my %modulesversion = (
+                'Authen::NTLM'          => sub { $Authen::NTLM::VERSION },
+                'Compress::Zlib'        => sub { $Compress::Zlib::VERSION },
+                'Crypt::OpenSSL::RSA'   => sub { $Crypt::OpenSSL::RSA::VERSION },
+                'Data::Uniqid'          => sub { $Data::Uniqid::VERSION },
+                'Digest::HMAC_MD5'      => sub { $Digest::HMAC_MD5::VERSION },
+                'Digest::HMAC_SHA1'     => sub { $Digest::HMAC_SHA1::VERSION },
+                'Digest::MD5'           => sub { $Digest::MD5::VERSION },
+                'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION },
+                'File::Spec'            => sub { $File::Spec::VERSION },
+                'Getopt::Long'          => sub { $Getopt::Long::VERSION },
+                'HTML::Entities'        => sub { $HTML::Entities::VERSION },
+                'IO::Socket::INET6'     => sub { $IO::Socket::INET6::VERSION },
+                'IO::Socket::INET'      => sub { $IO::Socket::INET::VERSION },
+                'IO::Socket::SSL'       => sub { $IO::Socket::SSL::VERSION },
+                'IO::Socket'            => sub { $IO::Socket::VERSION },
+                'IO::Tee'               => sub { $IO::Tee::VERSION },
+                'JSON'                  => sub { $JSON::VERSION },
+                'JSON::WebToken'        => sub { $JSON::WebToken::VERSION },
+                'LWP'                   => sub { $LWP::VERSION },
+                'Mail::IMAPClient'      => sub { $Mail::IMAPClient::VERSION },
+                'Net::Ping'             => sub { $Net::Ping::VERSION },
+                'Net::SSLeay'           => sub { $Net::SSLeay::VERSION },
+                'Term::ReadKey'         => sub { $Term::ReadKey::VERSION },
+                'Test::MockObject'      => sub { $Test::MockObject::VERSION },
+                'Time::HiRes'           => sub { $Time::HiRes::VERSION },
+                'Unicode::String'       => sub { $Unicode::String::VERSION },
+                'URI::Escape'           => sub { $URI::Escape::VERSION },
+                #'Lalala'                => sub { $Lalala::VERSION },
+        ) ;
 
-	eval { require IO::Socket; $v = $IO::Socket::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'IO::Socket', $v )  ;
+        foreach my $module_name ( sort keys %modulesversion ) {
+                # trick from http://www.perlmonks.org/?node_id=152122
+                my $file_name = $module_name . '.pm' ;
+                $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm
+                my $v ;
+                eval {
+                        require $file_name ;
+                        $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ;
+                } or $v = q{Not installed} ;
 
-	eval { require IO::Socket::INET; $v = $IO::Socket::INET::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'IO::Socket::INET', $v )  ;
+                push  @list_version, module_version_str( $module_name, $v )  ;
+        }
 
-	eval { require IO::Socket::INET6; $v = $IO::Socket::INET6::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'IO::Socket::INET6', $v )  ;
-
-	eval { require IO::Socket::SSL ; $v = $IO::Socket::SSL::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'IO::Socket::SSL ', $v )  ;
-
-	eval { require Net::SSLeay ; $v = $Net::SSLeay::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Net::SSLeay ', $v )  ;
-
-	eval { require Compress::Zlib; $v = $Compress::Zlib::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Compress::Zlib', $v )  ;
-
-	eval { require Digest::MD5; $v = $Digest::MD5::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Digest::MD5', $v )  ;
-
-	eval { require Digest::HMAC_MD5; $v = $Digest::HMAC_MD5::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Digest::HMAC_MD5', $v )  ;
-
-	eval { require Digest::HMAC_SHA1; $v = $Digest::HMAC_SHA1::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Digest::HMAC_SHA1', $v )  ;
-
-	eval { require Term::ReadKey; $v = $Term::ReadKey::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Term::ReadKey', $v )  ;
-
-	eval { require File::Spec; $v = $File::Spec::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'File::Spec', $v )  ;
-
-	eval { require Time::HiRes; $v = $Time::HiRes::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Time::HiRes', $v )  ;
-
-	eval { require Unicode::String; $v = $Unicode::String::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Unicode::String', $v )  ;
-
-	eval { require IO::Tee; $v = $IO::Tee::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'IO::Tee', $v )  ;
-
-	eval { require File::Copy::Recursive; $v = $File::Copy::Recursive::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'File::Copy::Recursive', $v )  ;
-
-	eval { require Authen::NTLM; $v = $Authen::NTLM::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Authen::NTLM', $v )  ;
-
-	eval { require URI::Escape; $v = $URI::Escape::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'URI::Escape', $v )  ;
-
-	eval { require Data::Uniqid; $v = $Data::Uniqid::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Data::Uniqid', $v )  ;
-
-	eval { require JSON; $v = $JSON::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'JSON', $v )  ;
-
-	eval { require JSON::WebToken; $v = $JSON::WebToken::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'JSON::WebToken', $v )  ;
-
-	eval { require Crypt::OpenSSL::RSA; $v = $Crypt::OpenSSL::RSA::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Crypt::OpenSSL::RSA', $v )  ;
-
-	eval { require LWP; $v = $LWP::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'LWP', $v )  ;
-
-	eval { require HTML::Entities; $v = $HTML::Entities::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'HTML::Entities', $v )  ;
-
-	#eval { require Filesys::DfPortable; $v = $Filesys::DfPortable::VERSION } or $v = q{?} ;
-	#push  @list_version, module_version_str( 'Filesys::DfPortable', $v )  ;
-
-	eval { require Getopt::Long; $v = $Getopt::Long::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Getopt::Long', $v )  ;
-
-	eval { require Test::MockObject; $v = $Test::MockObject::VERSION } or $v = q{?} ;
-	push  @list_version, module_version_str( 'Test::MockObject', $v )  ;
-
-	return( @list_version ) ;
+        return( @list_version ) ;
 }
 
 
 # Construct a command line copy with passwords replaced by MASKED.
 sub command_line_nopassword {
-	my @argv = @_ ;
-	my @argv_nopassword ;
+        my @argv = @_ ;
+        my @argv_nopassword ;
 
-        return( "@argv" ) if $showpasswords ;
-	while ( @argv ) {
-		my $arg = shift @argv ; # option name or value
-		if ( $arg =~ m/-password[12]/x ) {
-			shift @argv ; # password value
-			push  @argv_nopassword, $arg, 'MASKED'  ; # option name and fake value
-		}else{
-			push  @argv_nopassword, $arg ; # same option or value
-		}
-	}
-	return("@argv_nopassword") ;
+        return( "@argv" ) if $sync->{showpasswords} ;
+        while ( @argv ) {
+                my $arg = shift @argv ; # option name or value
+                if ( $arg =~ m/-password[12]/x ) {
+                        shift @argv ; # password value
+                        push  @argv_nopassword, $arg, 'MASKED'  ; # option name and fake value
+                }else{
+                        push  @argv_nopassword, $arg ; # same option or value
+                }
+        }
+        return("@argv_nopassword") ;
 }
 
 sub tests_command_line_nopassword {
+	note( 'Entering tests_command_line_nopassword()' ) ;
 
-	ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
-	ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
-	#myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
-	ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
-	ok('--blabla --password1 MASKED --blibli'
-	eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
-	$showpasswords = 1 ;
-	ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
-	ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
-	#myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
-	ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
-	ok('--blabla --password1 secret1 --blibli'
-	eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+        ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
+        ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
+        #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
+        ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
+        ok('--blabla --password1 MASKED --blibli'
+        eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+        $sync->{showpasswords} = 1 ;
+        ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
+        ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
+        #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
+        ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
+        ok('--blabla --password1 secret1 --blibli'
+        eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+
+	note( 'Leaving  tests_command_line_nopassword()' ) ;
         return ;
 }
 
-sub ask_for_password {
-	my ( $user, $host ) = @_ ;
-	myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) "  ) ;
-	Term::ReadKey::ReadMode( 2 ) ;
-	my $password = <> ;
-	chomp $password ;
-	myprint( "\nGot it\n" ) ;
-	Term::ReadKey::ReadMode( 0 ) ;
-	return $password ;
+sub ask_for_password  {
+        my ( $user, $host ) = @ARG ;
+        myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) "  ) ;
+        Term::ReadKey::ReadMode( 2 ) ;
+        my $password = <STDIN> ;
+        chomp $password ;
+        myprint( "\nGot it\n" ) ;
+        Term::ReadKey::ReadMode( 0 ) ;
+        return $password ;
 }
 
+# Have to refactor get_password1() get_password2()
+# to have only get_password() and two calls
+sub get_password1 {
+
+	my $mysync = shift ;
+
+	$mysync->{password1}
+	|| $passfile1
+	|| 'PREAUTH' eq $authmech1
+	|| 'EXTERNAL' eq $authmech1
+	|| $ENV{IMAPSYNC_PASSWORD1}
+	|| do {
+        myprint( << 'FIN_PASSFILE'  ) ;
+
+If you are afraid of giving password on the command line arguments, you can put the
+password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
+Then give this file restrictive permissions with the command "chmod 600 file1".
+An other solution is to set the environment variable IMAPSYNC_PASSWORD1
+FIN_PASSFILE
+
+		$mysync->{password1} = ask_for_password( $authuser1 || $mysync->{user1}, $mysync->{host1} ) ;
+	} ;
+
+	if ( defined  $passfile1  ) {
+		if ( ! -e -r $passfile1 ) {
+			myprint( "Failure: file from parameter --passfile1 $passfile1 does not exist or is not readable\n" ) ;
+			exit_clean( $mysync, $EX_NOINPUT ) ;
+		}
+		# passfile1 readable
+		$mysync->{password1} = firstline ( $passfile1 ) ;
+		return ;
+	}
+	if ( $ENV{IMAPSYNC_PASSWORD1} ) {
+		$mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ;
+		return ;
+	}
+	return ;
+}
+
+sub get_password2 {
+
+	my $mysync = shift ;
+
+	$mysync->{password2}
+	|| $passfile2
+	|| 'PREAUTH' eq $authmech2
+	|| 'EXTERNAL' eq $authmech2
+	|| $ENV{IMAPSYNC_PASSWORD2}
+	|| do {
+        myprint( << 'FIN_PASSFILE'  ) ;
+
+If you are afraid of giving password on the command line arguments, you can put the
+password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
+Then give this file restrictive permissions with the command "chmod 600 file2".
+An other solution is to set the environment variable IMAPSYNC_PASSWORD2
+FIN_PASSFILE
+
+		$mysync->{password2} = ask_for_password( $authuser2 || $mysync->{user2}, $mysync->{host2} ) ;
+	} ;
+
+
+	if ( defined  $passfile2  ) {
+		if ( ! -e -r $passfile2 ) {
+			myprint( "Failure: file from parameter --passfile2 $passfile2 does not exist or is not readable\n" ) ;
+			exit_clean( $mysync, $EX_NOINPUT ) ;
+		}
+		# passfile2 readable
+		$mysync->{password2} = firstline ( $passfile2 ) ;
+		return ;
+	}
+	if ( $ENV{IMAPSYNC_PASSWORD2} ) {
+		$mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ;
+		return ;
+	}
+	return ;
+}
+
+
+
+
+
 sub catch_exit {
         my $mysync = shift ;
         my $signame = shift ;
         if ( $signame ) {
                 myprint( "\nGot a signal $signame\n" ) ;
         }
-	stats( $mysync ) ;
+        stats( $mysync ) ;
         myprint( "Ended by a signal\n" ) ;
-	exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
+        exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
         return ;
 }
 
 sub catch_reconnect {
-	my $mysync = shift ;
+        my $mysync = shift ;
         my $signame = shift ;
         myprint( "\nGot a signal $signame\n",
                 "Hit 2 ctr-c within 2 seconds to exit the program\n",
@@ -3031,24 +3737,109 @@ sub catch_reconnect {
 
         if ( ! defined $mysync->{imap1} ) { return ; }
         if ( ! defined $mysync->{imap2} ) { return ; }
-        
+
 
         myprint( "Info: reconnecting to host1 imap server\n" ) ;
         $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ;
+	$mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
         $mysync->{imap1}->reconnect(  ) ;
         myprint( "Info: reconnecting to host2 imap server\n" ) ;
         $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
+	$mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
         $mysync->{imap2}->reconnect(  ) ;
         myprint( "Info: reconnected to both imap servers\n" ) ;
         return ;
 }
 
+sub tests_reconnect_12_if_needed {
+	note( 'Entering tests_reconnect_12_if_needed()' ) ;
+
+	my $mysync ;
+
+	$mysync->{imap1} = Mail::IMAPClient->new(  ) ;
+	$mysync->{imap2} = Mail::IMAPClient->new(  ) ;
+	$mysync->{imap1}->Server( 'test1.lamiral.info' ) ;
+	$mysync->{imap2}->Server( 'test2.lamiral.info' ) ;
+	is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ;
+	is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
+	is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
+
+	note( 'Leaving  tests_reconnect_12_if_needed()' ) ;
+	return ;
+}
+
+sub reconnect_12_if_needed {
+        my $mysync = shift ;
+	#return 2 ;
+	if ( ! reconnect_if_needed( $mysync->{imap1} ) ) {
+		return ;
+	}
+	if ( ! reconnect_if_needed( $mysync->{imap2} ) ) {
+		return ;
+	}
+	# both were good
+	return 2 ;
+}
+
+
+sub tests_reconnect_if_needed {
+	note( 'Entering tests_reconnect_if_needed()' ) ;
+
+
+	my $myimap ;
+
+	is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ;
+	is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ;
+
+	$myimap = Mail::IMAPClient->new(  ) ;
+        $myimap->Debug( 1 ) ;
+	is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ;
+	$myimap->Server( 'test.lamiral.info' ) ;
+	is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ;
+	is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ;
+
+	note( 'Leaving  tests_reconnect_if_needed()' ) ;
+	return ;
+}
+
+sub reconnect_if_needed {
+	# return undef upon failure.
+	# return 1 upon connection success, with or without reconnection.
+
+        my $imap = shift ;
+
+	if ( ! defined $imap ) { return ; }
+	if ( ! $imap->Server(  ) ) { return ; }
+
+	if ( $imap->IsUnconnected(  ) ) {
+		$imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
+		if ( $imap->reconnect( ) )  {
+			return 1 ;
+		}
+	}else{
+		return 1 ;
+	}
+
+	# A last forced one
+	$imap->State( Mail::IMAPClient::Unconnected ) ;
+	$imap->reconnect(  ) ;
+	$imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ;
+	if ( $imap->noop ) {
+		# NOOP is ok
+		return 1 ;
+	}
+
+	return ;
+}
+
+
+
 sub here_twice {
         my $mysync = shift ;
         my $now = time ;
         my $previous = $mysync->{lastcatch} || 0 ;
         $mysync->{lastcatch} = $now ;
-        
+
         if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
                 return $TRUE ;
         }else{
@@ -3057,40 +3848,219 @@ sub here_twice {
 }
 
 
-
-
 sub justconnect {
 
-	$imap1 = connect_imap( $host1, $port1, $debugimap1, $ssl1, $tls1, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ;
-	myprint( 'Host1 banner: ', $imap1->Banner(  )  ) ;
-	myprint( 'Host1 capability: ', join(q{ }, $imap1->capability(  ) ), "\n"  ) ;
-	$imap2 = connect_imap( $host2, $port2, $debugimap2, $ssl2, $tls2, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ;
-	myprint( 'Host2 banner: ', $imap2->Banner(  )  ) ;
-	myprint( 'Host2 capability: ', join(q{ }, $imap2->capability(  ) ), "\n"  ) ;
-	$imap1->logout(  ) ;
-	$imap2->logout(  ) ;
+        $imap1 = connect_imap( $sync->{host1}, $sync->{port1}, $debugimap1, $sync->{ssl1}, $sync->{tls1}, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ;
+        $imap2 = connect_imap( $sync->{host2}, $sync->{port2}, $debugimap2, $sync->{ssl2}, $sync->{tls2}, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ;
+        $imap1->logout(  ) ;
+        $imap2->logout(  ) ;
         return ;
 }
 
+
+sub tests_mailimapclient_connect {
+        note( 'Entering tests_mailimapclient_connect()' ) ;
+        my $imap ;
+        # ipv4 
+        ok( $imap = Mail::IMAPClient->new(  ), 'mailimapclient_connect ipv4: new' ) ;
+        is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ;
+        SKIP: {
+
+        if ( 'macosx' eq hostname() ) { skip( 'Tests avoided on macosx get stuck', 1 ) ; }
+        is( undef, $imap->connect(  ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ;
+        }
+
+        is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ;
+        is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ;
+        is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ;
+        is( 3, $imap->Timeout( 3 ), 'mailimapclient_connect ipv4: setting Timout( 30 )' ) ;
+        like( ref( $imap->connect(  ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ;
+        like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ;
+        is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ;
+
+        # ipv4 + ssl
+        ok( $imap = Mail::IMAPClient->new(  ), 'mailimapclient_connect ipv4 + ssl: new' ) ;
+        is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ;
+        is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ;
+        ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
+        is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ;
+        like( ref( $imap->connect(  ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ;
+        is( $imap->logout( ), undef, 'mailimapclient_connect ipv4 + ssl: logout in ssl causes failure' ) ;
+        is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ;
+        
+        # ipv6 + ssl
+        ok( $imap = Mail::IMAPClient->new(  ), 'mailimapclient_connect ipv6 + ssl: new' ) ;
+        is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server(ks2ipv6.lamiral.info)' ) ;
+        ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ;
+        is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ;
+        SKIP: {
+        if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 2 ) ; }
+        like( ref( $imap->connect(  ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to ks2ipv6.lamiral.info' ) ;
+        is( $imap->logout( ), undef, 'mailimapclient_connect ipv6 + ssl: logout in ssl causes failure' ) ;
+        }
+        is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ;
+
+
+        note( 'Leaving  tests_mailimapclient_connect()' ) ;
+        return ;
+}
+
+sub tests_mailimapclient_connect_bug {
+        note( 'Entering tests_mailimapclient_connect_bug()' ) ;
+        my $imap ;
+
+        # ipv6
+        ok( $imap = Mail::IMAPClient->new(  ), 'mailimapclient_connect ipv6: new' ) ;
+        is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect ipv6: setting Server(ks2ipv6.lamiral.info)' ) ;        
+        is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv6: setting Port( 993 )' ) ;
+        
+        SKIP: {
+        if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE can not do ipv6', 1 ) ; }
+        like( ref( $imap->connect(  ) ), qr/IO::Socket::INET/, 'mailimapclient_connect ipv6: connect to ks2ipv6.lamiral.info' ) 
+        or diag( 'mailimapclient_connect ipv6: ', $imap->LastError(  ), $!,  ) ;
+        }
+        #is( $imap->logout( ), undef, 'mailimapclient_connect ipv6: logout in ssl causes failure' ) ;
+        is( undef, undef $imap, 'mailimapclient_connect ipv6: free variable' ) ;
+
+        note( 'Leaving  tests_mailimapclient_connect_bug()' ) ;
+        return ;
+}
+
+sub mailimapclient_connect  {
+
+
+        return ;
+}
+
+
+      
+sub tests_connect_socket {
+        note( 'Entering tests_connect_socket()' ) ;
+        
+	is( undef, connect_socket(  ), 'connect_socket: no args' ) ;
+
+        my $socket ;
+        my $imap ;
+        SKIP: {
+                if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 2 ) ; }
+                
+	$socket = IO::Socket::INET6->new( 
+		PeerAddr => 'ks2ipv6.lamiral.info',
+		PeerPort => 143,
+	) ;
+
+	
+	ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 143 IO::Socket::INET6' ) ;
+        #$imap->Debug( 1 ) ;
+        #print $imap->capability(  ) ;
+	if ( $imap ) { 
+                $imap->logout(  ) ;
+        }
+	
+        #$IO::Socket::SSL::DEBUG = 4 ;
+	$socket = IO::Socket::SSL->new( 
+		PeerHost => 'ks2ipv6.lamiral.info',
+		PeerPort => 993,
+                SSL_verify_mode => SSL_VERIFY_NONE,
+	) ;
+	#print $socket ;
+	ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ;
+        #$imap->Debug( 1 ) ;
+        #print $imap->capability(  ) ;
+        $socket->close(  ) ;
+	if ( $imap ) { 
+                $socket->close(  ) ;
+        }
+        #$socket->close(SSL_no_shutdown => 1) ;
+        #$imap->logout(  ) ;
+        #print "\n" ;
+	#$imap->logout(  ) ;
+        }
+
+        note( 'Leaving  tests_connect_socket()' ) ;
+        return ;
+}
+
+sub connect_socket {
+	my( $socket ) = @ARG ;
+
+	if ( ! defined $socket ) { return ; }
+	
+	my $host = $socket->peerhost(  ) ;
+	my $port = $socket->peerport(  ) ;
+	#print "socket->peerhost: ", $socket->peerhost(  ), "\n" ;
+	#print "socket->peerport: ", $socket->peerport(  ), "\n" ;
+	my $imap = Mail::IMAPClient->new(  ) ;
+	$imap->Socket( $socket ) ;
+	my $banner = $imap->Results()->[0] ;
+	#myprint( "banner: $banner"  ) ;
+	return $imap ;
+}
+
+
+sub tests_probe_imapssl {
+        note( 'Entering tests_probe_imapssl()' ) ;
+
+        is( undef, probe_imapssl(  ),          'probe_imapssl: no args => undef' ) ;
+        is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ;
+
+	SKIP: {
+                if ( 'CUILLERE' eq hostname() ) { skip( 'Tests avoided on CUILLERE cannot do ipv6', 1 ) ; }
+                like( probe_imapssl( 'ks2ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks2ipv6.lamiral.info matches "* OK"' ) ;
+        } ;
+
+        like( probe_imapssl( 'test1.lamiral.info' ),   qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ;
+        like( probe_imapssl( 'imap.gmail.com' ),       qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ;
+
+        note( 'Leaving  tests_probe_imapssl()' ) ;
+        return ;
+}
+
+sub probe_imapssl {
+        my $host = shift ;
+        
+        if ( ! $host ) { return ; }
+        
+  	my $socket = IO::Socket::SSL->new( 
+		PeerHost => $host,
+		PeerPort => $IMAP_SSL_PORT,
+                SSL_verify_mode => SSL_VERIFY_NONE,
+	) ;
+        #print "$socket\n" ;
+        if ( ! $socket ) { return ; }
+        
+        my $banner ;
+        $socket->sysread( $banner, 65_536 ) ;
+        #print "$banner" ;
+        $socket->close(  ) ;
+        return $banner ;
+
+}
+
 sub connect_imap {
-	my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
-	my $imap = Mail::IMAPClient->new() ;
-	if ( $ssl ) { set_ssl( $imap, $h ) }
-	if ( $tls ) { $imap->Tls( 1 ) }
-	$imap->Server( $host ) ;
-	$imap->Port( $port ) ;
-	$imap->Debug( $mydebugimap ) ;
+        my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
+        my $imap = Mail::IMAPClient->new(  ) ;
+        if ( $ssl ) { set_ssl( $imap, $h ) }
+        $imap->Server( $host ) ;
+        $imap->Port( $port ) ;
+        $imap->Debug( $mydebugimap ) ;
         $imap->Timeout( $mytimeout ) ;
-	$imap->connect(  )
-	  or die_clean( "$Side: Can not open imap connection on [$host]: $@\n" ) ;
 
+        my $side = lc $Side ;
+        myprint( "$Side: connecting on $side [$host] port [$port]\n"  ) ;
+
+        $imap->connect(  )
+          or die_clean( "$Side: Can not open imap connection on [$host]: " . $imap->LastError . " $OS_ERROR\n" ) ;
+		myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n"  ) ;
         my $banner = $imap->Results()->[0] ;
-        $imap->Banner( $banner ) ;
 
-        if ( $imap->Tls(  ) ) {
-        	set_tls( $imap, $h ) ;
-        	$imap->starttls(  )
-                or die_clean("$Side: Can not go to tls encryption on [$host]:", $imap->LastError, "\n" ) ;
+        myprint( "$Side banner: $banner"  ) ;
+        myprint( "$Side capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
+
+        if ( $tls ) {
+                set_tls( $imap, $h ) ;
+                $imap->starttls(  )
+                or die_clean("$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
                 myprint( "$Side: Socket successfuly converted to SSL\n"  ) ;
         }
         return( $imap ) ;
@@ -3099,57 +4069,63 @@ sub connect_imap {
 
 sub login_imap {
 
-	my @allargs = @_ ;
-	my(
-		$host, $port, $user, $domain, $password,
-		$mydebugimap, $mytimeout, $fastio,
-		$ssl, $tls, $authmech, $authuser, $reconnectretry,
-		$proxyauth, $uid, $split, $Side, $h ) = @allargs ;
+        my @allargs = @_ ;
+        my(
+                $host, $port, $user, $domain, $password,
+                $mydebugimap, $mytimeout, $fastio,
+                $ssl, $tls, $authmech, $authuser, $reconnectretry,
+                $proxyauth, $uid, $split, $Side, $h, $mysync ) = @allargs ;
 
-	my $side = lc $Side ;
-	myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n"  ) ;
+        my $side = lc $Side ;
+        myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n"  ) ;
 
-	my $imap = init_imap( @allargs ) ;
-
-	$imap->connect()
-	  or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: $@\n") ;
+        my $imap = init_imap( @allargs ) ;
 
+        $imap->connect()
+          or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: " . $imap->LastError . " $OS_ERROR\n" ) ;
+		myprint( "$Side IP address: ", $imap->Socket->peerhost(), "\n"  ) ;
         my $banner = $imap->Results()->[0] ;
-        $imap->Banner( $banner ) ;
-	myprint( "$Side banner: $banner"  ) ;
+
+        myprint( "$Side banner: $banner"  ) ;
+	myprint( "$Side capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ;
+
+	if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) {
+		myprint( "$Side: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ;
+		$tls = 1 ;
+	}
 
         if ( $authmech eq 'PREAUTH' ) {
-        	if ( $imap->IsAuthenticated( ) ) {
-        		$imap->Socket ;
-			myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
-        	}else{
-                	die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
+                if ( $imap->IsAuthenticated( ) ) {
+                        $imap->Socket ;
+                        myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
+                }else{
+                        die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
                 }
         }
 
-        if ( $imap->Tls(  ) ) {
-		set_tls( $imap, $h ) ;
-        	$imap->starttls(  )
+        if ( $tls ) {
+                set_tls( $imap, $h ) ;
+                $imap->starttls(  )
                 or die_clean("$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
                 myprint( "$Side: Socket successfuly converted to SSL\n"  ) ;
         }
 
         authenticate_imap( $imap, @allargs ) ;
 
-	myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n"  ) ;
-	return( $imap ) ;
+        myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n"  ) ;
+        return( $imap ) ;
 }
 
 
 sub authenticate_imap {
 
-	my($imap,
+        my($imap,
            $host, $port, $user, $domain, $password,
-	   $mydebugimap, $mytimeout, $fastio,
-	   $ssl, $tls, $authmech, $authuser, $reconnectretry,
-	   $proxyauth, $uid, $split, $Side, $h ) = @_ ;
+           $mydebugimap, $mytimeout, $fastio,
+           $ssl, $tls, $authmech, $authuser, $reconnectretry,
+           $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
 
-	check_capability( $imap, $authmech, $Side ) ;
+        check_capability( $imap, $authmech, $Side ) ;
 
         if ( $proxyauth ) {
                 $imap->Authmechanism(q{}) ;
@@ -3159,29 +4135,29 @@ sub authenticate_imap {
                 $imap->User($user) ;
         }
 
-	$imap->Authcallback(\&xoauth)  if ( 'XOAUTH'  eq $authmech ) ;
-	$imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ;
-	$imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech )  ) ;
+        $imap->Authcallback(\&xoauth)  if ( 'XOAUTH'  eq $authmech ) ;
+        $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $authmech ) ;
+        $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $authmech ) or ( 'EXTERNAL' eq $authmech )  ) ;
 
         $imap->Domain($domain) if (defined $domain) ;
         $imap->Authuser($authuser) ;
         $imap->Password($password) ;
 
-	unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) {
-		my $info  = "$Side failure: Error login on [$host] with user [$user] auth" ;
-		my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
-		chomp $einfo ;
-		my $error = "$info [$authmech]: $einfo\n" ;
+        unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) {
+                my $info  = "$Side failure: Error login on [$host] with user [$user] auth" ;
+                my $einfo = $imap->LastError || @{$imap->History}[$LAST] ;
+                chomp $einfo ;
+                my $error = "$info [$authmech]: $einfo\n" ;
                 if ( $authmech eq 'LOGIN' or $imap->IsUnconnected(  ) or $authuser ) {
-                	die_clean( $error ) ;
+                        die_clean( $error ) ;
                 }else{
-			myprint( $error  ) ;
+                        myprint( $error  ) ;
                 }
-		myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n"  ) ;
-		$imap->Authmechanism(q{}) ;
-		$imap->login() or
-		  die_clean("$info [LOGIN]: ", $imap->LastError, "\n") ;
-	}
+                myprint( "$Side info: trying LOGIN Auth mechanism on [$host] with user [$user]\n"  ) ;
+                $imap->Authmechanism(q{}) ;
+                $imap->login() or
+                  die_clean("$info [LOGIN]: ", $imap->LastError, "\n") ;
+        }
 
         if ( $proxyauth ) {
                 if ( ! $imap->proxyauth( $user ) ) {
@@ -3192,41 +4168,50 @@ sub authenticate_imap {
                 }
         }
 
-	return ;
+        return ;
 }
 
-sub check_capability {
+sub check_capability  {
 
-	my( $imap, $authmech, $Side ) = @_ ;
+        my( $imap, $authmech, $Side ) = @_ ;
 
-	if ($imap->has_capability("AUTH=$authmech")
-	    or $imap->has_capability($authmech)
-	   ) {
-		myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
-		       $Side, $imap->Server, $authmech);
+
+        if ($imap->has_capability( "AUTH=$authmech" )
+            or $imap->has_capability( $authmech ) ) {
+                myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
+                       $Side, $imap->Server, $authmech) ;
+		return ;
+        }
+
+	if ( $authmech eq 'LOGIN' ) {
+		# Well, the warning is so common and useless that I prefer to remove it
+		# No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN"
+		return ;
 	}
-	else {
-		myprintf("%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
-		       $Side, $imap->Server, $authmech);
-		if ($authmech eq 'PLAIN') {
-			myprint( "$Side: frequently PLAIN is only supported with SSL, ",
-			  "try --ssl or --tls options\n" ) ;
-		}
-	}
-	return ;
+
+
+	myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
+                       $Side, $imap->Server, $authmech ) ;
+
+		       if ($authmech eq 'PLAIN') {
+		myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ;
+        }
+
+        return ;
 }
 
 sub set_ssl {
-	my ( $imap, $h ) = @_ ;
+        my ( $imap, $h ) = @_ ;
         # SSL_version can be
         #    SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953)
         #
 
         my $sslargs_hash = $h->{sslargs} ;
 
-	my $sslargs_default = {
-		SSL_verify_mode => $DEFAULT_SSL_VERIFY,
-        	SSL_verifycn_scheme => 'imap',
+        my $sslargs_default = {
+                SSL_verify_mode => $DEFAULT_SSL_VERIFY,
+                SSL_verifycn_scheme => 'imap',
+		SSL_cipher_list => 'DEFAULT:!DH',
         } ;
 
         # initiate with default values
@@ -3241,16 +4226,17 @@ sub set_ssl {
         my @sslargs_mix = %sslargs_mix ;
         #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] )  ) ;
         $imap->Ssl( \@sslargs_mix ) ;
-	return ;
+        return ;
 }
 
 sub set_tls {
-	my ( $imap, $h ) = @_ ;
+        my ( $imap, $h ) = @_ ;
 
         my $sslargs_hash = $h->{sslargs} ;
 
-	my $sslargs_default = {
-		SSL_verify_mode => $DEFAULT_SSL_VERIFY,
+        my $sslargs_default = {
+                SSL_verify_mode => $DEFAULT_SSL_VERIFY,
+		SSL_cipher_list => 'DEFAULT:!DH',
         } ;
 
         # initiate with default values
@@ -3265,42 +4251,47 @@ sub set_tls {
         my @sslargs_mix = %sslargs_mix ;
 
         $imap->Starttls( \@sslargs_mix ) ;
-	return ;
+        return ;
 }
 
 
 
 
 sub init_imap {
-	my(
-	   $host, $port, $user, $domain, $password,
-	   $mydebugimap, $mytimeout, $fastio,
-	   $ssl, $tls, $authmech, $authuser, $reconnectretry,
-	   $proxyauth, $uid, $split, $Side, $h ) = @_ ;
+        my(
+           $host, $port, $user, $domain, $password,
+           $mydebugimap, $mytimeout, $fastio,
+           $ssl, $tls, $authmech, $authuser, $reconnectretry,
+           $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ;
 
-	my ( $imap ) ;
+        my ( $imap ) ;
 
-	$imap = Mail::IMAPClient->new() ;
+        $imap = Mail::IMAPClient->new() ;
 
-	if ( $ssl ) { set_ssl( $imap, $h ) }
-	if ( $tls ) { $imap->Tls( 1 ) } # can not do set_tls() here because connect() will directly do a STARTTLS
-	$imap->Clear(1);
-	$imap->Server($host);
-	$imap->Port($port);
-	$imap->Fast_io($fastio);
-	$imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
-	$imap->Uid($uid);
-
-	$imap->Peek(1);
-	$imap->Debug($mydebugimap);
-	defined  $mytimeout  and $imap->Timeout( $mytimeout ) ;
-
-	$imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
-	$imap->Ignoresizeerrors( $allowsizemismatch ) ;
-	$split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
+        if ( $ssl ) { set_ssl( $imap, $h ) }
+        if ( $tls ) {  } # can not do set_tls() here because connect() will directly do a STARTTLS
+        $imap->Clear(1);
+        $imap->Server($host);
+        $imap->Port($port);
+        $imap->Fast_io($fastio);
+        $imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
+        $imap->Uid($uid);
 
 
-	return( $imap ) ;
+        $imap->Peek(1);
+        $imap->Debug($mydebugimap);
+	if ( $mysync->{ showpasswords } ) {
+		$imap->Showcredentials( 1 ) ;
+	}
+        defined  $mytimeout  and $imap->Timeout( $mytimeout ) ;
+
+        $imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
+	$imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ;
+        $imap->Ignoresizeerrors( $allowsizemismatch ) ;
+        $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
+
+
+        return( $imap ) ;
 
 }
 
@@ -3341,12 +4332,12 @@ sub plainauth {
 # If the password arg ends in .json, it will assume this new json method, otherwise it
 # will fallback to the "oauth client id;.p12" format it was previously using.
 sub xoauth2 {
-	require JSON::WebToken ;
-	require LWP::UserAgent ;
-	require HTML::Entities ;
-	require JSON ;
-	require JSON::WebToken::Crypt::RSA ;
-	require Crypt::OpenSSL::RSA ;
+        require JSON::WebToken ;
+        require LWP::UserAgent ;
+        require HTML::Entities ;
+        require JSON ;
+        require JSON::WebToken::Crypt::RSA ;
+        require Crypt::OpenSSL::RSA ;
         require Encode::Byte ;
         require IO::Socket::SSL ;
 
@@ -3355,11 +4346,11 @@ sub xoauth2 {
 
         my ($iss,$key);
 
-        if( $imap->Password =~ /^(.*\.json)$/ ) {
+        if( $imap->Password =~ /^(.*\.json)$/x ) {
             my $json = JSON->new( ) ;
             my $filename = $1;
             $debug and myprint( "XOAUTH2 json file: $filename\n" ) ;
-            open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $! " ) ;
+            open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $OS_ERROR " ) ;
             my $jsonfile = $json->decode( join q{}, <$FILE> ) ;
             close $FILE ;
 
@@ -3370,7 +4361,7 @@ sub xoauth2 {
         }
         else {
             # Get iss (service account address), keyfile name, and keypassword if necessary
-            ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/ ;
+            ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ;
 
             # Assume key password is google default if not provided
             $keypass = 'notasecret' if not $keypass;
@@ -3494,211 +4485,224 @@ sub xoauth {
         return encode_base64("$string", q{});
 }
 
-sub server_banner {
-	my $imap = shift;
-	my $banner = $imap->Banner() ||  "No banner\n";
-	return $banner;
- }
 
 
 sub banner_imapsync {
 
-	my @argv = @_ ;
+        my @argv = @_ ;
 
-	my $banner_imapsync = join q{},
-		q{$RCSfile: imapsync,v $ },
-		q{$Revision: 1.727 $ },
-		q{$Date: 2016/08/19 10:30:36 $ },
-		"\n", localhost_info(), "\n",
-		"Command line used:\n",
-		"$0 ", command_line_nopassword( @argv ), "\n" ;
+        my $banner_imapsync = join q{},
+                q{$RCSfile: imapsync,v $ },
+                q{$Revision: 1.836 $ },
+                q{$Date: 2017/09/05 16:14:53 $ },
+                "\n", localhost_info(), "\n",
+                "Command line used:\n",
+                "$PROGRAM_NAME ", command_line_nopassword( @argv ), "\n" ;
 
         return( $banner_imapsync ) ;
 }
 
-sub is_valid_directory {
-	my $dir = shift;
+sub do_valid_directory {
+        my $dir = shift;
 
-	# all good => return ok.
-	return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
+        # all good => return ok.
+        return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
 
-	# exist but bad
-	if ( -e $dir and not -d _ ) {
-		myprint( "Error: $dir exists but is not a directory\n"  ) ;
-		return( 0 ) ;
-	}
-	if ( -e $dir and not -w _ ) {
-		my $sb = stat $dir ;
-		myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
-		         $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid(  ) ) ;
-		return( 0 ) ;
-	}
-	# Trying to create it
-	myprint( "Creating directory $dir\n"  ) ;
-	eval { mkpath( $dir ) } ;
-	myprint( "$@" ) if ( $@ )  ;
-	return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
-	return( 0 ) ;
+        # exist but bad
+        if ( -e $dir and not -d _ ) {
+                myprint( "Error: $dir exists but is not a directory\n"  ) ;
+                return( 0 ) ;
+        }
+        if ( -e $dir and not -w _ ) {
+                my $sb = stat $dir ;
+                myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
+                         $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid(  ) ) ;
+                return( 0 ) ;
+        }
+        # Trying to create it
+        myprint( "Creating directory $dir\n"  ) ;
+        if ( ! eval { mkpath( $dir ) } ) {
+                myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR )  ;
+        }
+        return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
+        return( 0 ) ;
 }
 
-sub tests_is_valid_directory {
-        Readonly my $NB_UNIX_tests_is_valid_directory => 4 ;
-	SKIP: {
-		skip( 'Tests only for Unix', $NB_UNIX_tests_is_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
-		ok( 1 == is_valid_directory( '.'), 'is_valid_directory: . good' ) ;
-		ok( 1 == is_valid_directory( './tmp/tests/valid/sub'), 'is_valid_directory: ./tmp/tests/valid/sub good' ) ;
-		diag( 'Error / not writable is on purpose' ) ;
-		ok( 0 == is_valid_directory( '/'), 'is_valid_directory: / bad' ) ;
-		diag( 'Error permission denied on /noway is on purpose' ) ;
-		ok( 0 == is_valid_directory( '/noway'), 'is_valid_directory: /noway bad' ) ;
-	}
-	return ;
+sub tests_do_valid_directory {
+	note( 'Entering tests_do_valid_directory()' ) ;
+
+        Readonly my $NB_UNIX_tests_do_valid_directory => 4 ;
+        SKIP: {
+                skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
+                ok( 1 == do_valid_directory( '.'), 'do_valid_directory: . good' ) ;
+                ok( 1 == do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ;
+                diag( 'Error / not writable is on purpose' ) ;
+                ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ;
+                diag( 'Error permission denied on /noway is on purpose' ) ;
+                ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ;
+        }
+	note( 'Leaving  tests_do_valid_directory()' ) ;
+        return ;
 }
 
 sub write_pidfile {
-	my $pid_filename = shift ;
+        my $pid_filename = shift ;
         my $lock = shift ;
-        
-	myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
-	if ( -e $pid_filename and $lock ) {
-		myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n"  ) ;
+
+        myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
+        if ( -e $pid_filename and $lock ) {
+                myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n"  ) ;
                 exit $EXIT_PID_FILE_ALREADY_EXIST ;
-	}
-	if ( -e $pid_filename ) {
-		myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n"  ) ;
-	}
+        }
+        if ( -e $pid_filename ) {
+                myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n"  ) ;
+        }
 
-	open my $FILE_HANDLE, '>', $pid_filename
-        	or do {
-			myprint( "Could not open $pid_filename for writing. Check permissions or disk space."  ) ;
-		return ;
-	} ;
-        myprint( "Wrinting my PID $PROCESS_ID in $pid_filename\n"  ) ;
-	print $FILE_HANDLE $PROCESS_ID ;
-	close $FILE_HANDLE ;
+        open my $FILE_HANDLE, '>', $pid_filename
+                or do {
+                        myprint( "Could not open $pid_filename for writing. Check permissions or disk space."  ) ;
+                return ;
+        } ;
+        myprint( "Writing my PID $PROCESS_ID in $pid_filename\n"  ) ;
+        print $FILE_HANDLE $PROCESS_ID ;
+        close $FILE_HANDLE ;
 
-	return( $PROCESS_ID ) ;
+        return( $PROCESS_ID ) ;
 }
 
 sub remove_tmp_files {
-        my $mysync = shift ;
-	unlink $mysync->{pidfile} ;
-	return ;
+        my $mysync = shift or return ;
+	$mysync->{pidfile} or return ;
+        if ( -e $mysync->{pidfile} ) {
+		unlink $mysync->{pidfile} ;
+	}
+        return ;
 }
 
 
 sub exit_clean {
         my $mysync = shift ;
-	my $status = shift ;
-	$status = defined  $status  ? $status : $EXIT_UNKNOWN ;
+        my $status = shift ;
+        $status = defined  $status  ? $status : $EXIT_UNKNOWN ;
         remove_tmp_files( $mysync ) ;
         myprint( "Exiting with return value $status\n" ) ;
         if ( $mysync->{log} ) {
                 myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ;
                 close $mysync->{logfile_handle} ;
         }
-	exit $status ;
+        exit $status ;
 }
 
 sub die_clean {
-	my @messages = @_ ;
+        my @messages = @_ ;
         remove_tmp_files( $sync ) ;
-	die @messages ;
+        myprint( @messages ) ;
+	exit 255 ;
 }
 
 sub missing_option {
-	my ( $option ) = @_ ;
-	die_clean( "$option option is mandatory, for help run $0 --help\n" ) ;
-	return ;
+        my ( $option ) = @_ ;
+        die_clean( "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ;
+        return ;
 }
 
 
 sub fix_Inbox_INBOX_mapping {
-	my( $h1_all, $h2_all ) = @_ ;
+        my( $h1_all, $h2_all ) = @_ ;
 
-	my $regex = q{} ;
-	SWITCH: {
-		if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{INBOX}  ) { $regex = q{} ; last SWITCH ; } ;
-		if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{Inbox}  ) { $regex = q{} ; last SWITCH ; } ;
-		if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{Inbox}  ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
-		if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{INBOX}  ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
-	} ;
+        my $regex = q{} ;
+        SWITCH: {
+                if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{INBOX}  ) { $regex = q{} ; last SWITCH ; } ;
+                if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{Inbox}  ) { $regex = q{} ; last SWITCH ; } ;
+                if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{Inbox}  ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
+                if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{INBOX}  ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
+        } ;
         return( $regex ) ;
 }
 
 sub tests_fix_Inbox_INBOX_mapping {
+	note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ;
 
-	my( $h1_all, $h2_all ) ;
 
-	$h1_all = { 'INBOX' => q{} } ;
-	$h2_all = { 'INBOX' => q{} } ;
-	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
+        my( $h1_all, $h2_all ) ;
 
-	$h1_all = { 'Inbox' => q{} } ;
-	$h2_all = { 'Inbox' => q{} } ;
-	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
+        $h1_all = { 'INBOX' => q{} } ;
+        $h2_all = { 'INBOX' => q{} } ;
+        ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
 
-	$h1_all = { 'INBOX' => q{} } ;
-	$h2_all = { 'Inbox' => q{} } ;
-	ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
+        $h1_all = { 'Inbox' => q{} } ;
+        $h2_all = { 'Inbox' => q{} } ;
+        ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
 
-	$h1_all = { 'Inbox' => q{} } ;
-	$h2_all = { 'INBOX' => q{} } ;
-	ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
+        $h1_all = { 'INBOX' => q{} } ;
+        $h2_all = { 'Inbox' => q{} } ;
+        ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
 
-	$h1_all = { 'INBOX' => q{} } ;
-	$h2_all = { 'rrrrr' => q{} } ;
-	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
+        $h1_all = { 'Inbox' => q{} } ;
+        $h2_all = { 'INBOX' => q{} } ;
+        ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
 
-	$h1_all = { 'rrrrr' => q{} } ;
-	$h2_all = { 'Inbox' => q{} } ;
-	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
+        $h1_all = { 'INBOX' => q{} } ;
+        $h2_all = { 'rrrrr' => q{} } ;
+        ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
 
-	return ;
+        $h1_all = { 'rrrrr' => q{} } ;
+        $h2_all = { 'Inbox' => q{} } ;
+        ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
+
+	note( 'Leaving  tests_fix_Inbox_INBOX_mapping()' ) ;
+        return ;
 }
 
 
 sub jux_utf8_list {
-	my @s_inp = @_ ;
-	my $s_out = q{} ;
-	foreach my $s ( @s_inp ) {
-		$s_out .= jux_utf8( $s ) . "\n" ;
-	}
-	return( $s_out ) ;
+        my @s_inp = @_ ;
+        my $s_out = q{} ;
+        foreach my $s ( @s_inp ) {
+                $s_out .= jux_utf8( $s ) . "\n" ;
+        }
+        return( $s_out ) ;
 }
 
 sub tests_jux_utf8_list {
-	ok( q{} eq jux_utf8_list(  ), 'jux_utf8_list: void' ) ;
-	ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
-	ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
-	ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
-	return( 0 ) ;
+	note( 'Entering tests_jux_utf8_list()' ) ;
+
+        ok( q{} eq jux_utf8_list(  ), 'jux_utf8_list: void' ) ;
+        ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
+        ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
+        ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
+
+	note( 'Leaving  tests_jux_utf8_list()' ) ;
+        return( 0 ) ;
 }
 
 sub jux_utf8 {
-	# juxtapose utf8 at the right if different
+        # juxtapose utf8 at the right if different
         my ( $s_utf7 ) =  shift ;
         my ( $s_utf8 ) =  imap_utf7_decode( $s_utf7 ) ;
 
         if ( $s_utf7 eq $s_utf8 ) {
-        	#myprint( "[$s_utf7]\n"  ) ;
-        	return( "[$s_utf7]" ) ;
+                #myprint( "[$s_utf7]\n"  ) ;
+                return( "[$s_utf7]" ) ;
         }else{
-        	#myprint( "[$s_utf7] = [$s_utf8]\n"  ) ;
-        	return( "[$s_utf7] = [$s_utf8]" ) ;
+                #myprint( "[$s_utf7] = [$s_utf8]\n"  ) ;
+                return( "[$s_utf7] = [$s_utf8]" ) ;
         }
 }
 
 # editing utf8 can be tricky without an utf8 editor
 sub tests_jux_utf8 {
-	ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
-	ok( '[&ZTZO9nux-] = [收件箱]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
-	ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
+	note( 'Entering tests_jux_utf8()' ) ;
+
+        ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
+        ok( '[&ZTZO9nux-] = [收件箱]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
+        ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
         ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
         ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
         ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]'      eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ),    'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
 
-	return( 0 ) ;
+	note( 'Leaving  tests_jux_utf8()' ) ;
+        return ;
 }
 
 # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
@@ -3711,64 +4715,64 @@ sub imap_utf7_decode {
         # On remplace , par / dans les BASE 64 (, entre & et -)
         # On remplace les &, non suivi d'un - par +
         # On remplace les &- par &
-        $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/g ;
-        $s =~ s/&(?!\-)/\+/g ;
-        $s =~ s/&\-/&/g ;
+        $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
+        $s =~ s/&(?!\-)/\+/xg ;
+        $s =~ s/&\-/&/xg ;
         return( Unicode::String::utf7( $s )->utf8 ) ;
 }
 
 sub imap_utf7_encode {
-	my ( $s ) = @_ ;
+        my ( $s ) = @_ ;
 
-	$s = Unicode::String::utf8( $s )->utf7 ;
+        $s = Unicode::String::utf8( $s )->utf7 ;
 
-	$s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/g ;
-	$s =~ s/&/&\-/g ;
-	$s =~ s/\+([^+\-]+)?\-/&$1\-/g ;
-	return( $s ) ;
+        $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ;
+        $s =~ s/&/&\-/xg ;
+        $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ;
+        return( $s ) ;
 }
 
 
 
 
 sub select_folder {
-	my ( $imap, $folder, $hostside ) = @_ ;
-	if ( ! $imap->select( $folder ) ) {
-		my $error = join q{},
-			"$hostside folder $folder: Could not select: ",
-			$imap->LastError,  "\n" ;
-		errors_incr( $sync, $error ) ;
-		return( 0 ) ;
-	}else{
-		# ok select succeeded
-		return( 1 ) ;
-	}
+        my ( $imap, $folder, $hostside ) = @_ ;
+        if ( ! $imap->select( $folder ) ) {
+                my $error = join q{},
+                        "$hostside folder $folder: Could not select: ",
+                        $imap->LastError,  "\n" ;
+                errors_incr( $sync, $error ) ;
+                return( 0 ) ;
+        }else{
+                # ok select succeeded
+                return( 1 ) ;
+        }
 }
 
 sub examine_folder {
-	my ( $imap, $folder, $hostside ) = @_ ;
-	if ( ! $imap->examine( $folder ) ) {
-		my $error = join q{},
-			"$hostside folder $folder: Could not examine: ",
-			$imap->LastError,  "\n" ;
-		errors_incr( $sync, $error ) ;
-		return( 0 ) ;
-	}else{
-		# ok select succeeded
-		return( 1 ) ;
-	}
+        my ( $imap, $folder, $hostside ) = @_ ;
+        if ( ! $imap->examine( $folder ) ) {
+                my $error = join q{},
+                        "$hostside folder $folder: Could not examine: ",
+                        $imap->LastError,  "\n" ;
+                errors_incr( $sync, $error ) ;
+                return( 0 ) ;
+        }else{
+                # ok select succeeded
+                return( 1 ) ;
+        }
 }
 
 
 
 
 sub count_from_select {
-	my @lines = @_ ;
+        my @lines = @_ ;
         my $count ;
         foreach my $line ( @lines ) {
-        	#myprint( "line = [$line]\n"  ) ;
-                if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/ ) {
-                	$count = $1 ;
+                #myprint( "line = [$line]\n"  ) ;
+                if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) {
+                        $count = $1 ;
                         return( $count ) ;
                 }
         }
@@ -3793,34 +4797,34 @@ sub count_from_select {
 
 
 sub create_folder_old {
-	my( $imap, $h2_fold, $h1_fold ) = @_ ;
+        my( $imap, $h2_fold, $h1_fold ) = @_ ;
 
-	myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
+        myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
         if ( ( 'INBOX' eq uc  $h2_fold )
          and ( $imap->exists( $h2_fold ) ) ) {
                 myprint( "Folder [$h2_fold] already exists\n"  ) ;
                 return( 1 ) ;
         }
-	if ( ! $dry ){
-		if ( ! $imap->create( $h2_fold ) ) {
-			my $error = join q{},
-				"Could not create folder [$h2_fold] from [$h1_fold]: ",
-				$imap->LastError(  ), "\n" ;
-			errors_incr( $sync, $error ) ;
+        if ( ! $sync->{dry} ){
+                if ( ! $imap->create( $h2_fold ) ) {
+                        my $error = join q{},
+                                "Could not create folder [$h2_fold] from [$h1_fold]: ",
+                                $imap->LastError(  ), "\n" ;
+                        errors_incr( $sync, $error ) ;
                         # success if folder exists ("already exists" error)
                         return( 1 ) if $imap->exists( $h2_fold ) ;
                         # failure since create failed
-			return( 0 ) ;
-		}else{
-			#create succeeded
+                        return( 0 ) ;
+                }else{
+                        #create succeeded
                         myprint( "Created ( the old way ) folder [$h2_fold] on host2\n"  ) ;
-			return( 1 ) ;
-		}
-	}else{
-		# dry mode, no folder so many imap will fail, assuming failure
-                myprint( "Created ( the old way ) folder [$h2_fold] on host2 $dry_message\n"  ) ;
-		return( 0 ) ;
-	}
+                        return( 1 ) ;
+                }
+        }else{
+                # dry mode, no folder so many imap will fail, assuming failure
+                myprint( "Created ( the old way ) folder [$h2_fold] on host2 $sync->{dry_message}\n"  ) ;
+                return( 0 ) ;
+        }
 }
 
 
@@ -3833,9 +4837,9 @@ sub create_folder {
                 return( 0 ) ;
         }
 
-	if ( $create_folder_old ) {
-        	return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ;
-	}
+        if ( $create_folder_old ) {
+                return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ;
+        }
         myprint( "Creating folder [$h2_fold] on host2\n"  ) ;
         if ( ( 'INBOX' eq uc  $h2_fold  )
          and ( $imap2->exists( $h2_fold ) ) ) {
@@ -3854,20 +4858,20 @@ sub create_folder {
                 return( 0 ) ;
         }
 
-        @parts = split /\Q$h2_sep\E/, $h2_fold ;
+        @parts = split /\Q$h2_sep\E/x, $h2_fold ;
         pop @parts ;
         $parent = join $h2_sep, @parts ;
-        $parent =~ s/^\s+|\s+$//g ;
+        $parent =~ s/^\s+|\s+$//xg ;
         if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) {
                 create_folder( $imap2 , $parent , $h1_fold ) ;
         }
 
-        if ( ! $dry ) {
+        if ( ! $sync->{dry} ) {
                 if ( ! $imap2->create( $h2_fold ) ) {
-			my $error = join q{},
-				"Could not create folder [$h2_fold] from [$h1_fold]: " ,
-				$imap2->LastError(  ), "\n" ;
-			errors_incr( $sync, $error ) ;
+                        my $error = join q{},
+                                "Could not create folder [$h2_fold] from [$h1_fold]: " ,
+                                $imap2->LastError(  ), "\n" ;
+                        errors_incr( $sync, $error ) ;
                         # success if folder exists ("already exists" error)
                         return( 1 ) if $imap2->exists( $h2_fold ) ;
                         # failure since create failed
@@ -3879,395 +4883,412 @@ sub create_folder {
                 }
         }else{
                 # dry mode, no folder so many imap will fail, assuming failure
-                myprint( "Created  folder [$h2_fold] on host2 $dry_message\n"  ) ;
+                myprint( "Created  folder [$h2_fold] on host2 $sync->{dry_message}\n"  ) ;
                 if ( ! $justfolders ) {
-			myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
-			. "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
+                        myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
+                        . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
                 }
-		return( 0 ) ;
+                return( 0 ) ;
         }
 }
 
 
 
 sub tests_folder_routines {
-	ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1'               );
-	ok(  add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo'       );
-	ok(  is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2'               );
-	ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST'       );
-	ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo'                   );
-	ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3'               );
-	my @f ;
-	ok(  @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f"        );
-	ok(  is_requested_folder('folder_bar'), 'is_requested_folder 4'                          );
-	ok(  is_requested_folder('folder_toto'), 'is_requested_folder 5'                         );
-	ok(  remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: '       );
-	ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6'                         );
-	ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ;
+	note( 'Entering tests_folder_routines()' ) ;
+
+        ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1'               );
+        ok(  add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo'       );
+        ok(  is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2'               );
+        ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST'       );
+        ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo'                   );
+        ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3'               );
+        my @f ;
+        ok(  @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f"        );
+        ok(  is_requested_folder('folder_bar'), 'is_requested_folder 4'                          );
+        ok(  is_requested_folder('folder_toto'), 'is_requested_folder 5'                         );
+        ok(  remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: '       );
+        ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6'                         );
+        ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ;
 
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [] ), 'sort_requested_folders: all empty' ) ;
-	ok(  add_to_requested_folders('M_55'), 'add_to_requested_folders M_55'       );
+        ok(  add_to_requested_folders('M_55'), 'add_to_requested_folders M_55'       );
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [ 'M_55' ] ), 'sort_requested_folders: middle' ) ;
-	@folderfirst = ( 'Z_11' ) ;
+        @folderfirst = ( 'Z_11' ) ;
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
-	@folderlast = ( 'A_99' ) ;
+        @folderlast = ( 'A_99' ) ;
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ;
 
-	ok(  add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44'       );
+        ok(  add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44'       );
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 2' ) ;
-	@folderfirst = qw( Z_22  Z_11 ) ;
-	@folderlast  = qw( A_99  A_88 ) ;
+        @folderfirst = qw( Z_22  Z_11 ) ;
+        @folderlast  = qw( A_99  A_88 ) ;
         ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [  'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ;
 
-	return ;
-}
-
-
-sub sort_requested_folders {
-	my @requested_folders_sorted = () ;
-
-	foreach my $folder ( @folderfirst ) {
-        	remove_from_requested_folders( $folder ) ;
-        }
-
-	foreach my $folder ( @folderlast ) {
-        	remove_from_requested_folders( $folder ) ;
-        }
-
-	my @middle = sort keys %requested_folder ;
-
-        @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ;
-
-	return( @requested_folders_sorted ) ;
-}
-
-sub is_requested_folder {
-	my ( $folder ) = @_;
-
-	return( defined  $requested_folder{ $folder }  ) ;
-}
-
-
-sub add_to_requested_folders {
-	my @wanted_folders = @_ ;
-
-	foreach my $folder ( @wanted_folders ) {
-	 	++$requested_folder{ $folder } ;
-	}
-	return( keys  %requested_folder  ) ;
-}
-
-sub remove_from_requested_folders {
-	my @wanted_folders = @_ ;
-
-	foreach my $folder ( @wanted_folders ) {
-	 	delete $requested_folder{ $folder } ;
-	}
-	return( keys %requested_folder ) ;
-}
-
-sub compare_lists {
-	my ($list_1_ref, $list_2_ref) = @_;
-
-	return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
-	return(0)  if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
-	return(1)  if (not defined $list_2_ref); # end if only one list
-
-	if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
-	if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
-
-
-	my $last_used_indice = $MINUS_ONE;
-
-
-	ELEMENT:
-	foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
-		$last_used_indice = $indice ;
-
-		# End of list_2
-		return 1 if ($indice > $#{ $list_2_ref } ) ;
-
-		my $element_list_1 = $list_1_ref->[$indice] ;
-		my $element_list_2 = $list_2_ref->[$indice] ;
-		my $balance = $element_list_1 cmp $element_list_2 ;
-		next ELEMENT if ($balance == 0) ;
-		return $balance ;
-	}
-	# each element equal until last indice of list_1
-	return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
-
-	# same size, each element equal
-	return 0 ;
-}
-
-sub tests_compare_lists {
-
-
-	my $empty_list_ref = [];
-
-	ok( 0 == compare_lists()               , 'compare_lists, no args');
-	ok( 0 == compare_lists(undef)          , 'compare_lists, undef = nothing');
-	ok( 0 == compare_lists(undef, undef)   , 'compare_lists, undef = undef');
-	ok($MINUS_ONE == compare_lists(undef , [])     , 'compare_lists, undef < []');
-	ok($MINUS_ONE == compare_lists(undef , [1])    , 'compare_lists, undef < [1]');
-	ok($MINUS_ONE == compare_lists(undef , [0])    , 'compare_lists, undef < [0]');
-      	ok(+1 == compare_lists([])             , 'compare_lists, [] > nothing');
-        ok(+1 == compare_lists([], undef)      , 'compare_lists, [] > undef');
-	ok( 0 == compare_lists([] , [])        , 'compare_lists, [] = []');
-
-	ok($MINUS_ONE == compare_lists([] , [1])        , 'compare_lists, [] < [1]');
-	ok(+1 == compare_lists([1] , [])        , 'compare_lists, [1] > []');
-
-
-	ok( 0 == compare_lists([1],  1 )          , 'compare_lists, [1] =  1 ') ;
-	ok( 0 == compare_lists( 1 , [1])          , 'compare_lists,  1  = [1]') ;
-	ok( 0 == compare_lists( 1 ,  1 )          , 'compare_lists,  1  =  1 ') ;
-	ok($MINUS_ONE == compare_lists( 0 ,  1 )          , 'compare_lists,  0  <  1 ') ;
-	ok($MINUS_ONE == compare_lists($MINUS_ONE ,  0 )          , 'compare_lists, -1  <  0 ') ;
-	ok($MINUS_ONE == compare_lists( 1 ,  2 )          , 'compare_lists,  1  <  2 ') ;
-	ok(+1 == compare_lists( 2 ,  1 )          , 'compare_lists,  2  >  1 ') ;
-
-
-	ok( 0 == compare_lists([1,2], [1,2])   , 'compare_lists,  [1,2] = [1,2]' ) ;
-	ok($MINUS_ONE == compare_lists([1], [1,2])     , 'compare_lists,    [1] < [1,2]' ) ;
-	ok(+1 == compare_lists([2], [1,2])     , 'compare_lists,    [2] > [1,2]' ) ;
-	ok($MINUS_ONE == compare_lists([1], [1,1])     , 'compare_lists,    [1] < [1,1]' ) ;
-	ok(+1 == compare_lists([1, 1], [1])    , 'compare_lists, [1, 1] >   [1]' ) ;
-	ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
-                                               , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
-	ok($MINUS_ONE == compare_lists([1], [2])       , 'compare_lists, [1] < [2]') ;
-	ok( 0 == compare_lists([2], [2])       , 'compare_lists, [0] = [2]') ;
-	ok(+1 == compare_lists([2], [1])       , 'compare_lists, [2] > [1]') ;
-
-	ok($MINUS_ONE == compare_lists(['a'],  ['b'])   , 'compare_lists, ["a"] < ["b"]') ;
-	ok( 0 == compare_lists(['a'],  ['a'])   , 'compare_lists, ["a"] = ["a"]') ;
-	ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
-	ok(+1 == compare_lists(['b'],  ['a'])   , 'compare_lists, ["b"] > ["a"]') ;
-	ok($MINUS_ONE == compare_lists(['a'],  ['aa'])  , 'compare_lists, ["a"] < ["aa"]') ;
-	ok($MINUS_ONE == compare_lists(['a'],  ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
-	ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
-	ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
+	note( 'Leaving  tests_folder_routines()' ) ;
         return ;
 }
 
 
-sub guess_prefix {
-	my @foldernames = @_ ;
+sub sort_requested_folders {
+        my @requested_folders_sorted = () ;
 
-	return( undef ) unless ( @foldernames ) ;
+        foreach my $folder ( @folderfirst ) {
+                remove_from_requested_folders( $folder ) ;
+        }
 
-	my $prefix_guessed = q{} ;
-	foreach my $folder ( @foldernames ) {
-		next if ( $folder =~ m{^INBOX$}i ) ; # no guessing from INBOX
-		if ( $folder !~ m{^INBOX}i ) {
-			$prefix_guessed = q{} ; # prefix empty guessed
-			last ;
-		}
-		if ( $folder =~ m{^(INBOX(?:\.|\/))}i ) {
-			$prefix_guessed = $1 ;  # prefix Inbox/ or INBOX. guessed
-		}
-	}
-	return( $prefix_guessed ) ;
+        foreach my $folder ( @folderlast ) {
+                remove_from_requested_folders( $folder ) ;
+        }
+
+        my @middle = sort keys %requested_folder ;
+
+        @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ;
+
+        return( @requested_folders_sorted ) ;
+}
+
+sub is_requested_folder {
+        my ( $folder ) = @_;
+
+        return( defined  $requested_folder{ $folder }  ) ;
+}
+
+
+sub add_to_requested_folders {
+        my @wanted_folders = @_ ;
+
+        foreach my $folder ( @wanted_folders ) {
+                ++$requested_folder{ $folder } ;
+        }
+        return( keys  %requested_folder  ) ;
+}
+
+sub remove_from_requested_folders {
+        my @wanted_folders = @_ ;
+
+        foreach my $folder ( @wanted_folders ) {
+                delete $requested_folder{ $folder } ;
+        }
+        return( keys %requested_folder ) ;
+}
+
+sub compare_lists {
+        my ($list_1_ref, $list_2_ref) = @_;
+
+        return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
+        return(0)  if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
+        return(1)  if (not defined $list_2_ref); # end if only one list
+
+        if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
+        if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
+
+
+        my $last_used_indice = $MINUS_ONE;
+
+
+        ELEMENT:
+        foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
+                $last_used_indice = $indice ;
+
+                # End of list_2
+                return 1 if ($indice > $#{ $list_2_ref } ) ;
+
+                my $element_list_1 = $list_1_ref->[$indice] ;
+                my $element_list_2 = $list_2_ref->[$indice] ;
+                my $balance = $element_list_1 cmp $element_list_2 ;
+                next ELEMENT if ($balance == 0) ;
+                return $balance ;
+        }
+        # each element equal until last indice of list_1
+        return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
+
+        # same size, each element equal
+        return 0 ;
+}
+
+sub tests_compare_lists {
+	note( 'Entering tests_compare_lists()' ) ;
+
+        my $empty_list_ref = [];
+
+        ok( 0 == compare_lists()               , 'compare_lists, no args');
+        ok( 0 == compare_lists(undef)          , 'compare_lists, undef = nothing');
+        ok( 0 == compare_lists(undef, undef)   , 'compare_lists, undef = undef');
+        ok($MINUS_ONE == compare_lists(undef , [])     , 'compare_lists, undef < []');
+        ok($MINUS_ONE == compare_lists(undef , [1])    , 'compare_lists, undef < [1]');
+        ok($MINUS_ONE == compare_lists(undef , [0])    , 'compare_lists, undef < [0]');
+        ok(+1 == compare_lists([])             , 'compare_lists, [] > nothing');
+        ok(+1 == compare_lists([], undef)      , 'compare_lists, [] > undef');
+        ok( 0 == compare_lists([] , [])        , 'compare_lists, [] = []');
+
+        ok($MINUS_ONE == compare_lists([] , [1])        , 'compare_lists, [] < [1]');
+        ok(+1 == compare_lists([1] , [])        , 'compare_lists, [1] > []');
+
+
+        ok( 0 == compare_lists([1],  1 )          , 'compare_lists, [1] =  1 ') ;
+        ok( 0 == compare_lists( 1 , [1])          , 'compare_lists,  1  = [1]') ;
+        ok( 0 == compare_lists( 1 ,  1 )          , 'compare_lists,  1  =  1 ') ;
+        ok($MINUS_ONE == compare_lists( 0 ,  1 )          , 'compare_lists,  0  <  1 ') ;
+        ok($MINUS_ONE == compare_lists($MINUS_ONE ,  0 )          , 'compare_lists, -1  <  0 ') ;
+        ok($MINUS_ONE == compare_lists( 1 ,  2 )          , 'compare_lists,  1  <  2 ') ;
+        ok(+1 == compare_lists( 2 ,  1 )          , 'compare_lists,  2  >  1 ') ;
+
+
+        ok( 0 == compare_lists([1,2], [1,2])   , 'compare_lists,  [1,2] = [1,2]' ) ;
+        ok($MINUS_ONE == compare_lists([1], [1,2])     , 'compare_lists,    [1] < [1,2]' ) ;
+        ok(+1 == compare_lists([2], [1,2])     , 'compare_lists,    [2] > [1,2]' ) ;
+        ok($MINUS_ONE == compare_lists([1], [1,1])     , 'compare_lists,    [1] < [1,1]' ) ;
+        ok(+1 == compare_lists([1, 1], [1])    , 'compare_lists, [1, 1] >   [1]' ) ;
+        ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
+                                               , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
+        ok($MINUS_ONE == compare_lists([1], [2])       , 'compare_lists, [1] < [2]') ;
+        ok( 0 == compare_lists([2], [2])       , 'compare_lists, [0] = [2]') ;
+        ok(+1 == compare_lists([2], [1])       , 'compare_lists, [2] > [1]') ;
+
+        ok($MINUS_ONE == compare_lists(['a'],  ['b'])   , 'compare_lists, ["a"] < ["b"]') ;
+        ok( 0 == compare_lists(['a'],  ['a'])   , 'compare_lists, ["a"] = ["a"]') ;
+        ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
+        ok(+1 == compare_lists(['b'],  ['a'])   , 'compare_lists, ["b"] > ["a"]') ;
+        ok($MINUS_ONE == compare_lists(['a'],  ['aa'])  , 'compare_lists, ["a"] < ["aa"]') ;
+        ok($MINUS_ONE == compare_lists(['a'],  ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
+        ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
+        ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
+
+	note( 'Leaving  tests_compare_lists()' ) ;
+        return ;
+}
+
+
+sub guess_prefix  {
+        my @foldernames = @_ ;
+
+        my $prefix_guessed = q{} ;
+        foreach my $folder ( @foldernames ) {
+                next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX
+                if ( $folder !~ m{^INBOX}xi ) {
+                        $prefix_guessed = q{} ; # prefix empty guessed
+                        last ;
+                }
+                if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) {
+                        $prefix_guessed = $1 ;  # prefix Inbox/ or INBOX. guessed
+                }
+        }
+        return( $prefix_guessed ) ;
 }
 
 sub tests_guess_prefix {
+	note( 'Entering tests_guess_prefix()' ) ;
 
-	ok( not( defined guess_prefix(  ) ), 'guess_prefix: no args' ) ;
-	ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
-	ok( q{} eq guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
-	ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
-	ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
-	ok( 'INBOX.' eq guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
-	ok( 'Inbox/' eq guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
-	ok( 'Inbox.' eq guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
-	ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
-	ok( q{} eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
-	ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
-	ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
+        is( guess_prefix(  ), q{}, 'guess_prefix: no args => empty string' ) ;
+        is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
+        is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
+        is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
+        is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
+        is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
+        is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
+        is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
+        is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
+        is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
+        is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
+        is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
 
-	return ;
+	note( 'Leaving  tests_guess_prefix()' ) ;
+        return ;
 }
 
 sub get_prefix {
-	my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
-	my( $prefix_out, $prefix_guessed ) ;
+        my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ;
+        my( $prefix_out, $prefix_guessed ) ;
 
-	( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n"  ) ;
-	$prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
-	myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n"  ) ;
-	( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n"  ) ;
-	if ( $imap->has_capability( 'namespace' ) ) {
-		my $r_namespace = $imap->namespace(  ) ;
-		$prefix_out = $r_namespace->[0][0][0] ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n"  ) ;
+        $prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
+        myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n"  ) ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n"  ) ;
+        if ( $imap->has_capability( 'namespace' ) ) {
+                my $r_namespace = $imap->namespace(  ) ;
+                $prefix_out = $r_namespace->[0][0][0] ;
                 myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n"  ) ;
-		if ( defined  $prefix_in  ) {
-                	myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n"  ) ;
-                	$prefix_out = $prefix_in ;
-                	return( $prefix_out ) ;
+                if ( defined  $prefix_in  ) {
+                        myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n"  ) ;
+                        $prefix_out = $prefix_in ;
+                        return( $prefix_out ) ;
                 }else{
-                	# all good
-	                return( $prefix_out ) ;
+                        # all good
+                        return( $prefix_out ) ;
                 }
-	}
-	else{
-        	if ( defined  $prefix_in  ) {
-                	myprint( "$Side: using [$prefix_in] given by $prefix_opt\n"  ) ;
-                	$prefix_out = $prefix_in ;
-                	return( $prefix_out ) ;
+        }
+        else{
+                if ( defined  $prefix_in  ) {
+                        myprint( "$Side: using [$prefix_in] given by $prefix_opt\n"  ) ;
+                        $prefix_out = $prefix_in ;
+                        return( $prefix_out ) ;
                 }else{
-			myprint(
-			  "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
-			  help_to_guess_prefix( $imap, $prefix_opt ) ) ;
-			return( $prefix_guessed ) ;
+                        myprint(
+                          "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
+                          help_to_guess_prefix( $imap, $prefix_opt ) ) ;
+                        return( $prefix_guessed ) ;
                 }
-	}
+        }
         return ;
 }
 
 
 sub guess_separator {
-	my @foldernames = @_ ;
+        my @foldernames = @_ ;
 
-	#return( undef ) unless ( @foldernames ) ;
+        #return( undef ) unless ( @foldernames ) ;
 
-	my $sep_guessed ;
-	my %counter ;
-	foreach my $folder ( @foldernames ) {
-		$counter{'/'}++  while ( $folder =~ m{/}g ) ;  # count /
-		$counter{'.'}++  while ( $folder =~ m{\.}g ) ; # count .
-		$counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}g ) ; # count \\
-	}
-	my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys  %counter  ;
-	#myprint( "@race_sorted\n"  ) ;
-	$sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
-	return( $sep_guessed ) ;
+        my $sep_guessed ;
+        my %counter ;
+        foreach my $folder ( @foldernames ) {
+                $counter{'/'}++  while ( $folder =~ m{/}xg ) ;  # count /
+                $counter{'.'}++  while ( $folder =~ m{\.}xg ) ; # count .
+                $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\
+                $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \
+        }
+        my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys  %counter  ;
+        $debug and myprint( "@foldernames\n@race_sorted\n", %counter, "\n"  ) ;
+        $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
+        return( $sep_guessed ) ;
 }
 
 sub tests_guess_separator {
-	ok( '/' eq  guess_separator(  ), 'guess_separator: no args' ) ;
-	ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
-	ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
-	ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
-	ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
-	return ;
+	note( 'Entering tests_guess_separator()' ) ;
+
+        ok( '/' eq  guess_separator(  ), 'guess_separator: no args' ) ;
+        ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
+        ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
+        ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
+        ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
+        ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ;
+        ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ;
+        ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ;
+
+	note( 'Leaving  tests_guess_separator()' ) ;
+        return ;
 }
 
 sub get_separator {
-	my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
-	my( $sep_out, $sep_guessed ) ;
+        my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ;
+        my( $sep_out, $sep_guessed ) ;
 
-	( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n"  ) ;
-	$sep_guessed = guess_separator( @{ $folders_ref } ) ;
-	myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n"  ) ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n"  ) ;
+        $sep_guessed = guess_separator( @{ $folders_ref } ) ;
+        myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n"  ) ;
 
-	( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n"  ) ;
-	if ( $imap->has_capability( 'namespace' ) ) {
-		$sep_out = $imap->separator(  ) ;
-		if ( defined  $sep_out  ) {
-                	myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n"  ) ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n"  ) ;
+        if ( $imap->has_capability( 'namespace' ) ) {
+                $sep_out = $imap->separator(  ) ;
+                if ( defined  $sep_out  ) {
+                        myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n"  ) ;
                         if ( defined  $sep_in  ) {
-                		myprint( "$Side: but using [$sep_in] given by $sep_opt\n"  ) ;
-                        	$sep_out = $sep_in ;
-                        	return( $sep_out ) ;
+                                myprint( "$Side: but using [$sep_in] given by $sep_opt\n"  ) ;
+                                $sep_out = $sep_in ;
+                                return( $sep_out ) ;
                         }else{
-                        	return( $sep_out ) ;
+                                return( $sep_out ) ;
                         }
-		}else{
-                	if ( defined  $sep_in  ) {
-                        	myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n"  ) ;
-                        	$sep_out = $sep_in ;
-                        	return( $sep_out ) ;
-                        }else{
-				myprint(
-		  		"$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
-                  		help_to_guess_sep( $imap, $sep_opt ) ) ;
-				return( $sep_guessed ) ;
-                        }
-                }
-	}
-	else{
-        	if ( defined  $sep_in  ) {
-                	myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n"  ) ;
-                	$sep_out = $sep_in ;
-                	return( $sep_out ) ;
                 }else{
-			myprint(
-		  	"$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
-		      	help_to_guess_sep( $imap, $sep_opt ) ) ;
-			return( $sep_guessed ) ;
+                        if ( defined  $sep_in  ) {
+                                myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n"  ) ;
+                                $sep_out = $sep_in ;
+                                return( $sep_out ) ;
+                        }else{
+                                myprint(
+                                "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
+                                help_to_guess_sep( $imap, $sep_opt ) ) ;
+                                return( $sep_guessed ) ;
+                        }
                 }
-	}
+        }
+        else{
+                if ( defined  $sep_in  ) {
+                        myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n"  ) ;
+                        $sep_out = $sep_in ;
+                        return( $sep_out ) ;
+                }else{
+                        myprint(
+                        "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
+                        help_to_guess_sep( $imap, $sep_opt ) ) ;
+                        return( $sep_guessed ) ;
+                }
+        }
         return ;
 }
 
 sub help_to_guess_sep {
-	my( $imap, $sep_opt ) = @_ ;
+        my( $imap, $sep_opt ) = @_ ;
 
-	my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
-	. "the complete listing of folders may help you to find it\n"
-	. folders_list_to_help( $imap ) ;
+        my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n"
+        . "the complete listing of folders may help you to find it\n"
+        . folders_list_to_help( $imap ) ;
 
-	return( $help_to_guess_sep ) ;
+        return( $help_to_guess_sep ) ;
 }
 
 sub help_to_guess_prefix {
-	my( $imap, $prefix_opt ) = @_ ;
+        my( $imap, $prefix_opt ) = @_ ;
 
-	my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
-	. "the folowing listing of folders may help you to find it:\n"
-	. folders_list_to_help( $imap ) ;
+        my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n"
+        . "the folowing listing of folders may help you to find it:\n"
+        . folders_list_to_help( $imap ) ;
 
-	return( $help_to_guess_prefix ) ;
+        return( $help_to_guess_prefix ) ;
 }
 
 
 sub folders_list_to_help {
-	my($imap) = @_ ;
+        my($imap) = @_ ;
 
-	my @folders = $imap->folders ;
-	my $listing = join q{}, map { "[$_]\n" } @folders ;
-	return( $listing ) ;
+        my @folders = $imap->folders ;
+        my $listing = join q{}, map { "[$_]\n" } @folders ;
+        return( $listing ) ;
 }
 
 
 sub tests_separator_invert {
-	$fixslash2 = 0 ;
-	ok( not( defined separator_invert(  )  ), 'separator_invert: no args' ) ;
-	ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
-	ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
+	note( 'Entering tests_separator_invert()' ) ;
 
-	ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
-	ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
-	ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
-	ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
-	ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
-	ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
-	ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
+        $fixslash2 = 0 ;
+        ok( not( defined separator_invert(  )  ), 'separator_invert: no args' ) ;
+        ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
+        ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
 
-	ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+        ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
+        ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
+        ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
+        ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
+        ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+        ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
+        ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
+
+        ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
         $fixslash2 = 1 ;
-	ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+        ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
 
-	return ;
+	note( 'Leaving  tests_separator_invert()' ) ;
+        return ;
 }
 
 sub separator_invert {
-	my( $h1_fold, $h1_separator, $h2_separator ) = @_ ;
+        my( $h1_fold, $h1_separator, $h2_separator ) = @_ ;
 
-	return( undef ) if ( not defined  $h1_fold  or not defined  $h1_separator  or not defined  $h2_separator  ) ;
-	# The separator we hope we'll never encounter: 00000000 == 0x00
-	my $o_sep = "\000" ;
+        return( undef ) if ( not defined  $h1_fold  or not defined  $h1_separator  or not defined  $h2_separator  ) ;
+        # The separator we hope we'll never encounter: 00000000 == 0x00
+        my $o_sep = "\000" ;
 
-	my $h2_fold = $h1_fold ;
-	$h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
-	$h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
-	$h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
+        my $h2_fold = $h1_fold ;
+        $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
+        $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
+        $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
         $h2_fold =~ s,/,_,xg if( $fixslash2 and '/' ne $h2_separator and '/' eq $h1_separator ) ;
-	return( $h2_fold ) ;
+        return( $h2_fold ) ;
 }
 
 
 sub tests_imap2_folder_name {
+	note( 'Entering tests_imap2_folder_name()' ) ;
 
 $h1_prefix = $h2_prefix = q{};
 $h1_sep = '/';
@@ -4344,459 +5365,480 @@ ok( 'TEST/TEST/TEST/TEST' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), '
 ok( 'test/test/test/test' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ;
 
 
-return ;
+	note( 'Leaving  tests_imap2_folder_name()' ) ;
+	return ;
 
 }
 
 sub imap2_folder_name {
-	my ( $h1_fold ) = @_ ;
-	my ( $h2_fold ) ;
-	if ( $sync->{f1f2}{ $h1_fold } ) {
-		$h2_fold = $sync->{f1f2}{ $h1_fold } ;
-		( $debug or $sync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n"  ) ;
-		return( $h2_fold ) ;
-	}
-	if ( $sync->{f1f2auto}{ $h1_fold } ) {
-		$h2_fold = $sync->{f1f2auto}{ $h1_fold } ;
-		( $debug or $sync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n"  ) ;
-		return( $h2_fold ) ;
-	}
+        my ( $h1_fold ) = @_ ;
+        my ( $h2_fold ) ;
+        if ( $sync->{f1f2}{ $h1_fold } ) {
+                $h2_fold = $sync->{f1f2}{ $h1_fold } ;
+                ( $debug or $sync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n"  ) ;
+                return( $h2_fold ) ;
+        }
+        if ( $sync->{f1f2auto}{ $h1_fold } ) {
+                $h2_fold = $sync->{f1f2auto}{ $h1_fold } ;
+                ( $debug or $sync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n"  ) ;
+                return( $h2_fold ) ;
+        }
 
-	$h2_fold = prefix_seperator_invertion( $h1_fold ) ;
-	$h2_fold = regextrans2( $h2_fold ) ;
-	return( $h2_fold ) ;
+        $h2_fold = prefix_seperator_invertion( $h1_fold ) ;
+        $h2_fold = regextrans2( $h2_fold ) ;
+        return( $h2_fold ) ;
 }
 
 sub prefix_seperator_invertion {
-	my ( $h1_fold ) = @_ ;
-	my ( $h2_fold ) ;
+        my ( $h1_fold ) = @_ ;
+        my ( $h2_fold ) ;
 
-	# first we remove the prefix
-	$h1_fold =~ s/^\Q$h1_prefix\E//x ;
-	( $debug or $sync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n"  ) ;
-	$h2_fold = separator_invert( $h1_fold, $h1_sep, $h2_sep ) ;
-	( $debug or $sync->{debugfolders} ) and myprint( "inverted  separators: [$h2_fold]\n"  ) ;
-	# Adding the prefix supplied by namespace or the --prefix2 option
-	$h2_fold = $h2_prefix . $h2_fold
-	  unless( ( $h2_prefix eq 'INBOX' . $h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ;
-	( $debug or $sync->{debugfolders} ) and myprint( "added   host2 prefix: [$h2_fold]\n"  ) ;
-	return( $h2_fold ) ;
+        # first we remove the prefix
+        $h1_fold =~ s/^\Q$h1_prefix\E//x ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n"  ) ;
+        $h2_fold = separator_invert( $h1_fold, $h1_sep, $h2_sep ) ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "inverted  separators: [$h2_fold]\n"  ) ;
+        # Adding the prefix supplied by namespace or the --prefix2 option
+        $h2_fold = $h2_prefix . $h2_fold
+          unless( ( $h2_prefix eq 'INBOX' . $h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ;
+        ( $debug or $sync->{debugfolders} ) and myprint( "added   host2 prefix: [$h2_fold]\n"  ) ;
+        return( $h2_fold ) ;
 }
 
 sub regextrans2 {
-	my( $h2_fold ) = @_ ;
-	# Transforming the folder name by the --regextrans2 option(s)
-	foreach my $regextrans2 ( @regextrans2 ) {
-	        my $h2_fold_before = $h2_fold ;
-		my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
-		( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n"  ) ;
-                if ( not ( defined  $ret  ) or $@ ) {
-			die_clean( "error: eval regextrans2 '$regextrans2': $@\n" ) ;
+        my( $h2_fold ) = @_ ;
+        # Transforming the folder name by the --regextrans2 option(s)
+        foreach my $regextrans2 ( @regextrans2 ) {
+                my $h2_fold_before = $h2_fold ;
+                my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
+                ( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n"  ) ;
+                if ( not ( defined  $ret  ) or $EVAL_ERROR ) {
+                        die_clean( "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ;
                 }
-	}
-	return( $h2_fold ) ;
+        }
+        return( $h2_fold ) ;
 }
 
 
 sub tests_decompose_regex {
-	ok( 1, 'decompose_regex 1' ) ;
-	ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
-	ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
-	return ;
+	note( 'Entering tests_decompose_regex()' ) ;
+
+        ok( 1, 'decompose_regex 1' ) ;
+        ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
+        ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
+
+	note( 'Leaving  tests_decompose_regex()' ) ;
+        return ;
 }
 
 sub decompose_regex {
-	my $regex = shift ;
-	my( $left_part, $right_part ) ;
+        my $regex = shift ;
+        my( $left_part, $right_part ) ;
 
-	( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
+        ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
         return( q{}, q{} ) if not $left_part ;
-	return( $left_part, $right_part ) ;
+        return( $left_part, $right_part ) ;
 }
 
 
 sub foldersizes {
 
-	my ( $side, $imap, $search_cmd, @folders ) = @_ ;
-	my $total_size = 0 ;
-	my $total_nb = 0 ;
-	my $biggest_in_all = 0 ;
+        my ( $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ;
+        my $total_size = 0 ;
+        my $total_nb = 0 ;
+        my $biggest_in_all = 0 ;
 
-	my $nb_folders = scalar  @folders  ;
-	my $ct_folders = 0 ; # folder counter.
-	myprint( "++++ Calculating sizes of $nb_folders folders on $side\n"  ) ;
-	foreach my $folder ( @folders )     {
-		my $stot = 0 ;
-		my $nb_msgs = 0 ;
-		$ct_folders++ ;
-		myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
+        my $nb_folders = scalar  @folders  ;
+        my $ct_folders = 0 ; # folder counter.
+        myprint( "++++ Calculating sizes of $nb_folders folders on $side\n"  ) ;
+        foreach my $folder ( @folders )     {
+                my $stot = 0 ;
+                my $nb_msgs = 0 ;
+                $ct_folders++ ;
+                myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
                 if ( 'Host2' eq $side and not exists  $h2_folders_all_UPPER{ uc  $folder  }  ) {
-		        myprint( " does not exist yet\n") ;
-			next ;
-		}
+                        myprint( " does not exist yet\n") ;
+                        next ;
+                }
                 if ( 'Host1' eq $side and not exists  $h1_folders_all{ $folder }  ) {
-		        myprint( " does not exist\n" ) ;
-			next ;
-		}
+                        myprint( " does not exist\n" ) ;
+                        next ;
+                }
 
-		last if $imap->IsUnconnected(  ) ;
-		# FTGate is RFC buggy with EXAMINE it does not act as SELECT
-		#unless ( $imap->examine( $folder ) ) {
-		unless ( $imap->select( $folder ) ) {
-			my $error = join q{},
-				"$side Folder $folder: Could not select: ",
-				$imap->LastError,  "\n"  ;
-			errors_incr( $sync, $error ) ;
-			next ;
-		}
-		last if $imap->IsUnconnected(  ) ;
+                last if $imap->IsUnconnected(  ) ;
+                # FTGate is RFC buggy with EXAMINE it does not act as SELECT
+                #unless ( $imap->examine( $folder ) ) {
+                unless ( $imap->select( $folder ) ) {
+                        my $error = join q{},
+                                "$side Folder $folder: Could not select: ",
+                                $imap->LastError,  "\n"  ;
+                        errors_incr( $sync, $error ) ;
+                        next ;
+                }
+                last if $imap->IsUnconnected(  ) ;
 
-		my $hash_ref = { } ;
-		my @msgs = select_msgs( $imap, undef, $search_cmd, $folder ) ;
-		$nb_msgs = scalar  @msgs  ;
-		my $biggest_in_folder = 0 ;
-		@{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
+                my $hash_ref = { } ;
+                my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ;
+                $nb_msgs = scalar  @msgs  ;
+                my $biggest_in_folder = 0 ;
+                @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
 
-		last if $imap->IsUnconnected(  ) ;
-		if ( $nb_msgs > 0 and @msgs ) {
-                	if ( $abletosearch ) {
-				if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
-                                        my $error = "$side failure with fetch_hash: $@" ;
+                last if $imap->IsUnconnected(  ) ;
+                if ( $nb_msgs > 0 and @msgs ) {
+                        if ( $abletosearch ) {
+                                if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
+                                        my $error = "$side failure with fetch_hash: $EVAL_ERROR" ;
                                         errors_incr( $sync, $error ) ;
                                         return ;
                                 }
                         }else{
-				my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
-				my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
-				if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
-                                        my $error = "$side failure with fetch_hash: $@" ;
+                                my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
+                                my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+                                if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
+                                        my $error = "$side failure with fetch_hash: $EVAL_ERROR" ;
                                         errors_incr( $sync, $error ) ;
                                         return ;
                                 }
                         }
-			for ( keys %{ $hash_ref } ) {
-                        	my $size =  $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
-                        	$stot    += $size ;
+                        for ( keys %{ $hash_ref } ) {
+                                my $size =  $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
+                                $stot    += $size ;
                                 $biggest_in_folder =  max( $biggest_in_folder, $size ) ;
                         }
-		}
+                }
 
-		myprintf( ' Size: %9s', $stot ) ;
-		myprintf( ' Messages: %5s', $nb_msgs ) ;
-		myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
-		$total_size += $stot ;
-		$total_nb += $nb_msgs ;
+                myprintf( ' Size: %9s', $stot ) ;
+                myprintf( ' Messages: %5s', $nb_msgs ) ;
+                myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
+                $total_size += $stot ;
+                $total_nb += $nb_msgs ;
                 $biggest_in_all =  max( $biggest_in_all, $biggest_in_folder ) ;
-	}
-	myprintf( "%s Nb folders:      %11s folders\n",    $side, $nb_folders ) ;
-	myprintf( "%s Nb messages:     %11s messages\n",   $side, $total_nb ) ;
-	myprintf( "%s Total size:      %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
-	myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
-	myprintf( "%s Time spent:      %11.1f seconds\n",  $side, timenext(  ) ) ;
+        }
+        myprintf( "%s Nb folders:      %11s folders\n",    $side, $nb_folders ) ;
+        myprintf( "%s Nb messages:     %11s messages\n",   $side, $total_nb ) ;
+        myprintf( "%s Total size:      %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
+        myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
+        myprintf( "%s Time spent:      %11.1f seconds\n",  $side, timenext(  ) ) ;
         return( $total_nb, $total_size ) ;
 }
 
 sub timenext {
-	my ( $timenow, $timediff ) ;
-	# $timebefore is global, beurk !
-	$timenow    = time ;
-	$timediff   = $timenow - $timebefore ;
-	$timebefore = $timenow ;
-	return( $timediff ) ;
+        my ( $timenow, $timediff ) ;
+        # $timebefore is global, beurk !
+        $timenow    = time ;
+        $timediff   = $timenow - $timebefore ;
+        $timebefore = $timenow ;
+        return( $timediff ) ;
 }
 
 sub timesince {
-	my $timeinit = shift ;
-	my ( $timenow, $timediff ) ;
-	$timenow    = time ;
-	$timediff   = $timenow - $timeinit ;
-	return( $timediff ) ;
+        my $timeinit = shift || 0 ;
+        my ( $timenow, $timediff ) ;
+        $timenow    = time ;
+        $timediff   = $timenow - $timeinit ;
+	# Often used in a division so no 0
+        return( max( 1, $timediff) ) ;
 }
 
 
 
 
 sub tests_flags_regex {
+	note( 'Entering tests_flags_regex()' ) ;
 
-	ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
-	ok( q'\Seen NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), 'flags_regex, nothing to do');
+        ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
+        ok( q{\Seen NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, nothing to do} ) ;
 
-	@regexflag = ('I am BAD' ) ;
+        @regexflag = ('I am BAD' ) ;
         ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ;
 
-	@regexflag = ( 's/NonJunk//g' ) ;
-	ok( q'\Seen  $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
-	@regexflag = ( q's/\$Spam//g' ) ;
-	ok( '\Seen NonJunk ' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
+        @regexflag = ( 's/NonJunk//g' ) ;
+        ok( q{\Seen  $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
+        @regexflag = ( q's/\$Spam//g' ) ;
+        ok( q{\Seen NonJunk } eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
 
-	@regexflag = ( 's/\\\\Seen//g' ) ;
+        @regexflag = ( 's/\\\\Seen//g' ) ;
 
-	ok( q' NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
+        ok( q{ NonJunk $Spam} eq flags_regex( q{\Seen NonJunk $Spam} ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
 
-	@regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
-	ok( '\Seen \Middle \End'   eq flags_regex( q'\Seen NonJunk \Middle $Spam \End' ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
-	ok( ' \Seen \Middle \End1' eq flags_regex( q'Begin \Seen NonJunk \Middle $Spam \End1 End' ), 
-                     q'flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End' ) ;
+        @regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
+        ok( q{\Seen \Middle \End}   eq flags_regex( q{\Seen NonJunk \Middle $Spam \End} ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
+        ok( q{ \Seen \Middle \End1} eq flags_regex( q{Begin \Seen NonJunk \Middle $Spam \End1 End} ),
+                     q{flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ;
 
-	@regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g' ) ;
-	ok('Keep1 Keep2  ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
-	
-	ok('Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
-	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
-	ok('Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM  Keep2'), 'Keep only regex' ) ;
-	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
-	ok('Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
+        @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ;
+        ok( 'Keep1 Keep2  ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
 
-	@regexflag = ( q's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
-	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
-	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM  REM'), 'Keep only regex' ) ;
-	ok('Keep2 ' eq flags_regex('Keep2 REM REM  REM'), 'Keep only regex' ) ;
-	
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM  Keep2'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
+        ok( 'Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
 
-	@regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g',
-	   's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g');
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex');
-	ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
-	ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
-	ok('Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex');
-	ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
-
-	@regexflag = ('s/(.*)/$1 jrdH8u/');
-	ok('REM  REM  REM REM REM jrdH8u' eq flags_regex('REM  REM  REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ;
-	@regexflag = ('s/jrdH8u *//');
-	ok('REM  REM  REM REM REM ' eq flags_regex('REM  REM  REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ;
-
-	@regexflag = (
-	's/(.*)/$1 jrdH8u/',
-	's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g',
-	's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g',
-	's/jrdH8u *//'
-	);
-
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ;
-	ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
-	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
-	ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
-	ok('Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex');
-	ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
-	ok(q{} eq flags_regex('REM  REM REM REM REM'), 'Keep only regex');
-
-	@regexflag = (
-	's/(.*)/$1 jrdH8u/',
-	's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g',
-	's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g',
-	's/jrdH8u *//'
-	);
-
-	ok('\\Deleted \\Answered '
-	    eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ;
-	ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ;
-	ok( q{}
-	   eq flags_regex('Blabla $Junk  machin  truc'), 'Keep only regex: Exchange case, no accepted flags' ) ;
-	ok( '\\Deleted \\Answered \\Draft \\Flagged '
-	    eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ;
+        @regexflag = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM  REM'), 'Keep only regex' ) ;
+        ok( 'Keep2 ' eq flags_regex('Keep2 REM REM  REM'), 'Keep only regex' ) ;
 
 
-	@regexflag = (
-	's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
-	);
+        @regexflag = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g},
+           's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex' ) ;
+        ok( 'Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex' ) ;
+        ok( 'Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex' ) ;
 
-	ok( '\\Deleted \\Answered '
-	eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
-	'Keep only regex: Exchange case (Phil)' ) ;
+        @regexflag = ( 's/(.*)/$1 jrdH8u/' ) ;
+        ok('REM  REM  REM REM REM jrdH8u' eq flags_regex('REM  REM  REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ;
+        @regexflag = ('s/jrdH8u *//');
+        ok('REM  REM  REM REM REM ' eq flags_regex('REM  REM  REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ;
 
-	ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
+        @regexflag = (
+        's/(.*)/$1 jrdH8u/',
+        's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g',
+        's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g',
+        's/jrdH8u *//'
+        );
 
-	ok( q{}
-	eq flags_regex('Blabla $Junk  machin  truc'),
-	'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
+        ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ;
+        ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
+        ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
+        ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
+        ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
+        ok('Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex');
+        ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
+        ok(q{} eq flags_regex('REM  REM REM REM REM'), 'Keep only regex');
 
-	ok('\\Deleted \\Answered \\Draft \\Flagged '
-	eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '),
-	'Keep only regex: Exchange case (Phil)' ) ;
+        @regexflag = (
+        's/(.*)/$1 jrdH8u/',
+        's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g',
+        's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g',
+        's/jrdH8u *//'
+        );
 
-	return ;
+        ok('\\Deleted \\Answered '
+            eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ;
+        ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ;
+        ok( q{}
+           eq flags_regex('Blabla $Junk  machin  truc'), 'Keep only regex: Exchange case, no accepted flags' ) ;
+        ok( '\\Deleted \\Answered \\Draft \\Flagged '
+            eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ;
+
+
+        @regexflag = (
+        's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
+        );
+
+        ok( '\\Deleted \\Answered '
+        eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
+        'Keep only regex: Exchange case (Phil)' ) ;
+
+        ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
+
+        ok( q{}
+        eq flags_regex('Blabla $Junk  machin  truc'),
+        'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
+
+        ok('\\Deleted \\Answered \\Draft \\Flagged '
+        eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '),
+        'Keep only regex: Exchange case (Phil)' ) ;
+
+	note( 'Leaving  tests_flags_regex()' ) ;
+        return ;
 }
 
 sub flags_regex {
-	my ( $h1_flags ) = @_ ;
-	foreach my $regexflag ( @regexflag ) {
-		my $h1_flags_orig = $h1_flags ;
-		$debugflags and myprint( "eval \$h1_flags =~ $regexflag\n"  ) ;
-		my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
-		$debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n"  ) ;
-                if( not ( defined $ret ) or $@ ) {
-			myprint( "Error: eval regexflag '$regexflag': $@\n"  ) ;
+        my ( $h1_flags ) = @_ ;
+        foreach my $regexflag ( @regexflag ) {
+                my $h1_flags_orig = $h1_flags ;
+                $debugflags and myprint( "eval \$h1_flags =~ $regexflag\n"  ) ;
+                my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
+                $debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n"  ) ;
+                if( not ( defined $ret ) or $EVAL_ERROR ) {
+                        myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n"  ) ;
                         return( undef ) ;
                 }
-	}
-	return( $h1_flags ) ;
+        }
+        return( $h1_flags ) ;
 }
 
 sub acls_sync {
-	my($h1_fold, $h2_fold) = @_ ;
-	if ( $syncacls ) {
-		my $h1_hash = $imap1->getacl($h1_fold)
-		  or myprint( "Could not getacl for $h1_fold: $@\n" ) ;
-		my $h2_hash = $imap2->getacl($h2_fold)
-		  or myprint( "Could not getacl for $h2_fold: $@\n" ) ;
-		my %users = map { ($_, 1) } ( keys  %{ $h1_hash} , keys %{ $h2_hash }  ) ;
-		foreach my $user (sort keys %users ) {
-			my $acl = $h1_hash->{$user} || 'none' ;
-			myprint( "acl $user: [$acl]\n" ) ;
-			next if ($h1_hash->{$user} && $h2_hash->{$user} &&
-				 $h1_hash->{$user} eq $h2_hash->{$user});
-			unless ($dry) {
-				myprint( "setting acl $h2_fold $user $acl\n" ) ;
-				$imap2->setacl($h2_fold, $user, $acl)
-				  or myprint( "Could not set acl: $@\n" ) ;
-			}
-		}
-	}
+        my($h1_fold, $h2_fold) = @_ ;
+        if ( $syncacls ) {
+                my $h1_hash = $imap1->getacl($h1_fold)
+                  or myprint( "Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ;
+                my $h2_hash = $imap2->getacl($h2_fold)
+                  or myprint( "Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ;
+                my %users = map { ($_, 1) } ( keys  %{ $h1_hash} , keys %{ $h2_hash }  ) ;
+                foreach my $user (sort keys %users ) {
+                        my $acl = $h1_hash->{$user} || 'none' ;
+                        myprint( "acl $user: [$acl]\n" ) ;
+                        next if ($h1_hash->{$user} && $h2_hash->{$user} &&
+                                 $h1_hash->{$user} eq $h2_hash->{$user});
+                        unless ($sync->{dry}) {
+                                myprint( "setting acl $h2_fold $user $acl\n" ) ;
+                                $imap2->setacl($h2_fold, $user, $acl)
+                                  or myprint( "Could not set acl: $EVAL_ERROR\n" ) ;
+                        }
+                }
+        }
         return ;
 }
 
 
 sub tests_permanentflags {
+	note( 'Entering tests_permanentflags()' ) ;
 
-	my $string;
-	ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
-	   'permanentflags \*');
-	ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
-	   'permanentflags \Draft \Answered');
-	ok('\Draft \Answered'
-	   eq permanentflags('Blabla',
-	                     ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
-			     'Blabla'),
-	   'permanentflags \Draft \Answered'
-	);
-	ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
+        my $string;
+        ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
+           'permanentflags \*');
+        ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
+           'permanentflags \Draft \Answered');
+        ok('\Draft \Answered'
+           eq permanentflags('Blabla',
+                             ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
+                             'Blabla'),
+           'permanentflags \Draft \Answered'
+        );
+        ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
+
+	note( 'Leaving  tests_permanentflags()' ) ;
         return ;
 }
 
 sub permanentflags {
-	my @lines = @_ ;
+        my @lines = @_ ;
 
-	foreach my $line (@lines) {
-		if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
-			( $debugflags or $debug ) and myprint( "permanentflags: $line"  ) ;
-			my $permanentflags = $1 ;
-			if ( $permanentflags =~ m{\\\*}x ) {
-				$permanentflags = q{} ;
-			}
-			return($permanentflags) ;
-		} ;
-	}
+        foreach my $line (@lines) {
+                if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
+                        ( $debugflags or $debug ) and myprint( "permanentflags: $line"  ) ;
+                        my $permanentflags = $1 ;
+                        if ( $permanentflags =~ m{\\\*}x ) {
+                                $permanentflags = q{} ;
+                        }
+                        return($permanentflags) ;
+                } ;
+        }
         return( q{} ) ;
 }
 
 sub tests_flags_filter {
+	note( 'Entering tests_flags_filter()' ) ;
 
-	ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
-	ok( q{} eq flags_filter('\Seen', '\Draft  \Answered'), 'flags_filter ' );
-	ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
-	ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
-	ok( '\Seen \Draft'
-	   eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
-	ok( '\Seen \Draft'
-	   eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
-        return ;
+        ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
+        ok( q{} eq flags_filter('\Seen', '\Draft  \Answered'), 'flags_filter ' );
+        ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
+        ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
+        ok( '\Seen \Draft'
+           eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
+        ok( '\Seen \Draft'
+           eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
+
+	note( 'Leaving  tests_flags_filter()' ) ;
+	return ;
 }
 
 sub flags_filter {
-	my( $flags, $allowed_flags ) = @_ ;
+        my( $flags, $allowed_flags ) = @_ ;
 
-	my @flags = split  /\s+/x, $flags ;
-	my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
-	my @flags_out     = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
+        my @flags = split  /\s+/x, $flags ;
+        my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
+        my @flags_out     = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
 
-	my $flags_out = join q{ }, @flags_out ;
+        my $flags_out = join q{ }, @flags_out ;
 
-	return( $flags_out ) ;
+        return( $flags_out ) ;
 }
 
 sub flagscase {
-	my $flags = shift ;
+        my $flags = shift ;
 
-	my @flags = split /\s+/x, $flags ;
-	my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
-	my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
+        my @flags = split /\s+/x, $flags ;
+        my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
+        my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
 
-	my $flags_out = join q{ }, @flags_out ;
+        my $flags_out = join q{ }, @flags_out ;
 
-	return( $flags_out ) ;
+        return( $flags_out ) ;
 }
 
 sub tests_flagscase {
-	ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
-	ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
+	note( 'Entering tests_flagscase()' ) ;
 
-	ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
-	ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
+        ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
+        ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
 
-	ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT  LALA \SEEN' ), 'flagscase: \DRAFT  LALA \SEEN -> \Draft LALA \Seen' ) ;
-	ok( '\Draft lala \Seen' eq flagscase( '\DRAFT  lala \SEEN' ), 'flagscase: \DRAFT  lala \SEEN -> \Draft lala \Seen' ) ;
+        ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
+        ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
+
+        ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT  LALA \SEEN' ), 'flagscase: \DRAFT  LALA \SEEN -> \Draft LALA \Seen' ) ;
+        ok( '\Draft lala \Seen' eq flagscase( '\DRAFT  lala \SEEN' ), 'flagscase: \DRAFT  lala \SEEN -> \Draft lala \Seen' ) ;
+
+	note( 'Leaving  tests_flagscase()' ) ;
         return ;
 }
 
 
 
 sub ucsecond {
-	my $string = shift ;
-	my $output ;
+        my $string = shift ;
+        my $output ;
 
-	return( $string )  if ( 1 >= length $string ) ;
-	
-	$output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
-	#myprint( "UUU $string -> $output\n"  ) ;
-	return( $output ) ;
+        return( $string )  if ( 1 >= length $string ) ;
+
+        $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
+        #myprint( "UUU $string -> $output\n"  ) ;
+        return( $output ) ;
 }
 
 
 sub tests_ucsecond {
-	ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
-	ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE'  ) ;
-	ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE'  ) ;
-	ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde'  ) ;
-	ok( 'A'     eq ucsecond( 'A' ),     'ucsecond: A  -> A'  ) ;
-	ok( 'AB'    eq ucsecond( 'Ab' ),    'ucsecond: Ab -> AB' ) ;
-	ok( '\B'    eq ucsecond( '\b' ),    'ucsecond: \b -> \B' ) ;
-	ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
+	note( 'Entering tests_ucsecond()' ) ;
+        ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
+        ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE'  ) ;
+        ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE'  ) ;
+        ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde'  ) ;
+        ok( 'A'     eq ucsecond( 'A' ),     'ucsecond: A  -> A'  ) ;
+        ok( 'AB'    eq ucsecond( 'Ab' ),    'ucsecond: Ab -> AB' ) ;
+        ok( '\B'    eq ucsecond( '\b' ),    'ucsecond: \b -> \B' ) ;
+        ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
+
+	note( 'Leaving  tests_ucsecond()' ) ;
         return ;
 }
 
 
 sub select_msgs {
-	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
-	my ( @msgs ) ;
+        my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ;
+        my ( @msgs ) ;
 
-	if ( $abletosearch ) {
-		@msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
-	}else{
-		@msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
-	}
-	return(  @msgs ) ;
+        if ( $abletosearch ) {
+                @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
+        }else{
+                @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
+        }
+        return(  @msgs ) ;
 
 }
 
 sub select_msgs_by_search {
-	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
-	my ( @msgs, @msgs_all ) ;
+        my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
+        my ( @msgs, @msgs_all ) ;
 
         # Need to have the whole list in msgs_all_hash_ref
         # without calling messages() several times.
         # Need all messages list to avoid deleting useful cache part
         # in case of --search or --minage or --maxage
 
-	if ( ( defined  $msgs_all_hash_ref  and $usecache )
+        if ( ( defined  $msgs_all_hash_ref  and $usecache )
         or ( not defined  $maxage  and not defined  $minage  and not defined  $search_cmd  )
         ) {
 
-       		$debugdev and myprint( "Calling messages()\n"  ) ;
-		@msgs_all = $imap->messages(  ) ;
+                $debugdev and myprint( "Calling messages()\n"  ) ;
+                @msgs_all = $imap->messages(  ) ;
 
                 return if ( $#msgs_all == 0 && !defined  $msgs_all[0]  ) ;
 
@@ -4807,22 +5849,22 @@ sub select_msgs_by_search {
                 if ( not defined  $maxage  and not defined  $minage  and not defined  $search_cmd  ) {
                         return( @msgs_all ) ;
                 }
-	}
+        }
 
         if ( defined  $search_cmd  ) {
-        	@msgs = $imap->search( $search_cmd ) ;
+                @msgs = $imap->search( $search_cmd ) ;
                 return( @msgs ) ;
         }
 
-	# we are here only if $maxage or $minage is defined
+        # we are here only if $maxage or $minage is defined
         @msgs = select_msgs_by_age( $imap ) ;
-	return( @msgs );
+        return( @msgs );
 }
 
 
 sub select_msgs_by_fetch {
-	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
-	my ( @msgs, @msgs_all, %fetch ) ;
+        my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
+        my ( @msgs, @msgs_all, %fetch ) ;
 
         # Need to have the whole list in msgs_all_hash_ref
         # without calling messages() several times.
@@ -4830,10 +5872,10 @@ sub select_msgs_by_fetch {
         # in case of --search or --minage or --maxage
 
 
-	$debugdev and myprint( "Calling fetch_hash()\n"  ) ;
-	my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
-	my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
-	%fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
+        $debugdev and myprint( "Calling fetch_hash()\n"  ) ;
+        my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
+        my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+        %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
 
         @msgs_all = sort { $a <=> $b } keys  %fetch  ;
         $debugdev and myprint( "Done fetch_hash()\n"  ) ;
@@ -4849,138 +5891,142 @@ sub select_msgs_by_fetch {
         }
 
         if ( defined  $search_cmd  ) {
-		myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n"  ) ;
-        	@msgs = $imap->search( $search_cmd ) ;
+                myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n"  ) ;
+                @msgs = $imap->search( $search_cmd ) ;
                 return( @msgs ) ;
         }
 
-	# we are here only if $maxage or $minage is defined
-	my( @max, @min, $maxage_epoch, $minage_epoch ) ;
-	if ( defined  $maxage  ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
-	if ( defined  $minage  ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
-	foreach my $msg ( @msgs_all ) {
-		my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
-		#myprint( "$idate\n"  ) ;
-		if ( defined  $maxage  and ( epoch( $idate ) >= $maxage_epoch ) ) {
-			push  @max, $msg  ;
-		}
-		if ( defined  $minage  and ( epoch( $idate ) <= $minage_epoch ) ) {
-			push  @min, $msg  ;
-		}
-	}
+        # we are here only if $maxage or $minage is defined
+        my( @max, @min, $maxage_epoch, $minage_epoch ) ;
+        if ( defined  $maxage  ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
+        if ( defined  $minage  ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
+        foreach my $msg ( @msgs_all ) {
+                my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
+                #myprint( "$idate\n"  ) ;
+                if ( defined  $maxage  and ( epoch( $idate ) >= $maxage_epoch ) ) {
+                        push  @max, $msg  ;
+                }
+                if ( defined  $minage  and ( epoch( $idate ) <= $minage_epoch ) ) {
+                        push  @min, $msg  ;
+                }
+        }
         @msgs = msgs_from_maxmin( \@max, \@min ) ;
-	return( @msgs ) ;
+        return( @msgs ) ;
 }
 
 sub select_msgs_by_age {
-	my( $imap ) = @_ ;
+        my( $imap ) = @_ ;
 
-	my( @max, @min, @msgs, @inter, @union ) ;
+        my( @max, @min, @msgs, @inter, @union ) ;
 
-	if ( defined  $maxage  ) {
-		@max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
-	}
-	if ( defined  $minage  ) {
-		@min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
-	}
+        if ( defined  $maxage  ) {
+                @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
+        }
+        if ( defined  $minage  ) {
+                @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
+        }
 
-	@msgs = msgs_from_maxmin( \@max, \@min ) ;
-	return( @msgs ) ;
+        @msgs = msgs_from_maxmin( \@max, \@min ) ;
+        return( @msgs ) ;
 }
 
 sub msgs_from_maxmin {
-	my( $max_ref, $min_ref ) = @_ ;
-	my( @max, @min, @msgs, @inter, @union ) ;
+        my( $max_ref, $min_ref ) = @_ ;
+        my( @max, @min, @msgs, @inter, @union ) ;
 
-	@max = @{ $max_ref } ;
-	@min = @{ $min_ref } ;
+        @max = @{ $max_ref } ;
+        @min = @{ $min_ref } ;
 
-	SWITCH: {
-		unless( defined  $minage  ) { @msgs = @max ; last SWITCH } ;
-		unless( defined  $maxage  ) { @msgs = @min ; last SWITCH } ;
-		my ( %union, %inter ) ;
-		foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
-		@inter = sort { $a <=> $b } keys  %inter  ;
-		@union = sort { $a <=> $b } keys  %union  ;
-		# normal case
-		if ( $minage <= $maxage )  { @msgs = @inter ; last SWITCH } ;
-		# just exclude messages between
-		if ( $minage > $maxage )  { @msgs = @union ; last SWITCH } ;
+        SWITCH: {
+                unless( defined  $minage  ) { @msgs = @max ; last SWITCH } ;
+                unless( defined  $maxage  ) { @msgs = @min ; last SWITCH } ;
+                my ( %union, %inter ) ;
+                foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
+                @inter = sort { $a <=> $b } keys  %inter  ;
+                @union = sort { $a <=> $b } keys  %union  ;
+                # normal case
+                if ( $minage <= $maxage )  { @msgs = @inter ; last SWITCH } ;
+                # just exclude messages between
+                if ( $minage > $maxage )  { @msgs = @union ; last SWITCH } ;
 
-	}
-	return( @msgs ) ;
+        }
+        return( @msgs ) ;
 }
 
 sub tests_msgs_from_maxmin {
-	my @msgs ;
-	$maxage = $NUMBER_200 ;
-	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
-	ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
-	$minage = $NUMBER_100 ;
-	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
-	ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin:  -maxage++minage-' ) ;
-	$minage = $NUMBER_300 ;
-	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
-	ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++maxage-minage++' ) ;
-	$maxage = undef ;
-	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
-	ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++minage-' ) ;
-	return ;
+	note( 'Entering tests_msgs_from_maxmin()' ) ;
+
+        my @msgs ;
+        $maxage = $NUMBER_200 ;
+        @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+        ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
+        $minage = $NUMBER_100 ;
+        @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+        ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin:  -maxage++minage-' ) ;
+        $minage = $NUMBER_300 ;
+        @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+        ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++maxage-minage++' ) ;
+        $maxage = undef ;
+        @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+        ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++minage-' ) ;
+
+	note( 'Leaving  tests_msgs_from_maxmin()' ) ;
+        return ;
 }
 
 
 sub lastuid {
-	my $imap   = shift ;
-	my $folder = shift ;
-	my $lastuid_guess  = shift ;
-	my $lastuid ;
+        my $imap   = shift ;
+        my $folder = shift ;
+        my $lastuid_guess  = shift ;
+        my $lastuid ;
 
-	# rfc3501: The only reliable way to identify recent messages is to
-	#          look at message flags to see which have the \Recent flag
-	#          set, or to do a SEARCH RECENT.
-	# SEARCH RECENT doesn't work this way on courrier.
+        # rfc3501: The only reliable way to identify recent messages is to
+        #          look at message flags to see which have the \Recent flag
+        #          set, or to do a SEARCH RECENT.
+        # SEARCH RECENT doesn't work this way on courrier.
 
-	my @recent_messages ;
-	# SEARCH RECENT for each transfer can be expensive with a big folder
-	# Call commented for now
-	#@recent_messages = $imap->recent(  ) ;
-	#myprint( "Recent: @recent_messages\n" ) ;
+        my @recent_messages ;
+        # SEARCH RECENT for each transfer can be expensive with a big folder
+        # Call commented for now
+        #@recent_messages = $imap->recent(  ) ;
+        #myprint( "Recent: @recent_messages\n" ) ;
 
-	my $max_recent ;
-	$max_recent = max( @recent_messages ) ;
+        my $max_recent ;
+        $max_recent = max( @recent_messages ) ;
 
-	if ( defined  $max_recent  and ($lastuid_guess <= $max_recent ) ) {
-		$lastuid = $max_recent ;
-	}else{
-		$lastuid = $lastuid_guess
-	}
-	return( $lastuid ) ;
+        if ( defined  $max_recent  and ($lastuid_guess <= $max_recent ) ) {
+                $lastuid = $max_recent ;
+        }else{
+                $lastuid = $lastuid_guess
+        }
+        return( $lastuid ) ;
 }
 
 sub size_filtered {
-	my( $h1_size, $h1_msg, $h1_fold, $h2_fold  ) = @_ ;
+        my( $h1_size, $h1_msg, $h1_fold, $h2_fold  ) = @_ ;
 
         $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
-	if (defined $maxsize and $h1_size > $maxsize) {
-		myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ;
-		$total_bytes_skipped += $h1_size;
-		$nb_msg_skipped += 1;
-		return( 1 ) ;
-	}
-	if (defined $minsize and $h1_size <= $minsize) {
-		myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
-		$total_bytes_skipped += $h1_size;
-		$nb_msg_skipped += 1;
-		return( 1 ) ;
-	}
-	return( 0 ) ;
+        if (defined $maxsize and $h1_size > $maxsize) {
+                myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ;
+                $total_bytes_skipped += $h1_size;
+                $nb_msg_skipped += 1;
+                return( 1 ) ;
+        }
+        if (defined $minsize and $h1_size <= $minsize) {
+                myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
+                $total_bytes_skipped += $h1_size;
+                $nb_msg_skipped += 1;
+                return( 1 ) ;
+        }
+        return( 0 ) ;
 }
 
 sub message_exists {
-	my( $imap, $msg ) = @_ ;
-	return( 1 ) if not $imap->Uid(  ) ;
+        my( $imap, $msg ) = @_ ;
+        return( 1 ) if not $imap->Uid(  ) ;
 
-	my $search_uid ;
+        my $search_uid ;
         ( $search_uid ) = $imap->search( "UID $msg" ) ;
         #myprint( "$search ? $msg\n"  ) ;
         return( 1 ) if ( $search_uid eq $msg ) ;
@@ -4988,82 +6034,82 @@ sub message_exists {
 }
 
 sub copy_message {
-	# copy
+        # copy
 
-	my ( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
-	( $debug or $dry) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $dry_message\n" ) ;
+        my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
+        ( $debug or $mysync->{dry}) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message}\n" ) ;
 
-	my $h1_size  = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'}  || 0 ;
-	my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'}        || q{} ;
-	my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
+        my $h1_size  = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'}  || 0 ;
+        my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'}        || q{} ;
+        my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
 
 
         if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold  ) ) {
-        	$h1_nb_msg_processed +=1 ;
+                $h1_nb_msg_processed +=1 ;
                 return ;
         }
 
-	debugsleep( $sync ) ;
-	myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size )   ;
+        debugsleep( $mysync ) ;
+        myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size )   ;
 
 
         if ( $checkmessageexists and not message_exists( $imap1, $h1_msg ) ) {
-		$total_bytes_skipped += $h1_size;
-		$nb_msg_skipped += 1;
-        	$h1_nb_msg_processed +=1 ;
+                $total_bytes_skipped += $h1_size;
+                $nb_msg_skipped += 1;
+                $h1_nb_msg_processed +=1 ;
                 return ;
         }
-        if ( $sync->{debugmemory} ) {
+        if ( $mysync->{debugmemory} ) {
                 myprintf("C1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
-	my ( $string, $string_len ) ;
-        ( $string_len ) = message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
+        my ( $string, $string_len ) ;
+        ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
 
-        if ( $sync->{debugmemory} ) {
+        if ( $mysync->{debugmemory} ) {
                 myprintf("C2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
         # not defined or empty $string
-        if ( ( not $string ) and ( not $string_len ) ) {
-		myprint( "- msg $h1_fold/$h1_msg skipped.\n"  ) ;
-		$total_bytes_skipped += $h1_size;
-		$nb_msg_skipped += 1;
-                $h1_nb_msg_processed +=1 ;
+        if ( ( not $string ) or ( not $string_len ) ) {
+                myprint( "- msg $h1_fold/$h1_msg skipped.\n"  ) ;
+                $total_bytes_skipped += $h1_size;
+                $nb_msg_skipped += 1;
+                $h1_nb_msg_processed += 1 ;
                 return ;
         }
 
         # Lines too long (or not enough) => do no copy or fix
         if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
-		$string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
-		if ( not defined  $string  ) {
-			$h1_nb_msg_processed +=1 ;
-			$total_bytes_skipped += $h1_size ;
-			$nb_msg_skipped += 1 ;
-			return ;
-		}
-	}
-
-	my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
-
-	( $debug or $debugflags ) and
-        myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
-
-	$h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
-
-	( $debug or $debugflags ) and
-        myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
-
-	$h1_date = undef if ($h1_date eq q{});
-
-	my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
-
-	if ( $new_id and $syncflagsaftercopy ) {
-        	sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
+                $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
+                if ( not defined  $string  ) {
+                        $h1_nb_msg_processed +=1 ;
+                        $total_bytes_skipped += $h1_size ;
+                        $nb_msg_skipped += 1 ;
+                        return ;
+                }
         }
 
-	if ( $sync->{debugmemory} ) {
-        	myprintf("C3: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
+
+        ( $debug or $debugflags ) and
+        myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
+
+        $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
+
+        ( $debug or $debugflags ) and
+        myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
+
+        $h1_date = undef if ($h1_date eq q{});
+
+        my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
+
+        if ( $new_id and $syncflagsaftercopy ) {
+                sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
+        }
+
+        if ( $mysync->{debugmemory} ) {
+                myprintf("C3: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
         return $new_id ;
@@ -5072,51 +6118,51 @@ sub copy_message {
 
 
 sub linelengthstuff {
-	my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate  ) = @_ ;
-	my $maxlinelength_string = max_line_length( $string ) ;
+        my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate  ) = @_ ;
+        my $maxlinelength_string = max_line_length( $string ) ;
         $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n"  ) ;
 
         if ( ( defined $minmaxlinelength )  and ( $maxlinelength_string <= $minmaxlinelength ) ) {
-		my $subject = subject( $string ) ;
-         	$debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
-                      	. "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
-         	return ;
+                my $subject = subject( $string ) ;
+                $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
+                        . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
+                return ;
         }
 
         if ( ( defined $maxlinelength )  and ( $maxlinelength_string > $maxlinelength ) ) {
-         	my $subject = subject( $string ) ;
-		if ( $maxlinelengthcmd ) {
-			$string = pipemess( $string, $maxlinelengthcmd ) ;
-			# string undef means something was bad.
-			if ( not ( defined  $string  ) ) {
-				myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
-				      . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
-				return ;
-			}else{
-				return $string ;
-			}
-		}
-         	myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
+                my $subject = subject( $string ) ;
+                if ( $maxlinelengthcmd ) {
+                        $string = pipemess( $string, $maxlinelengthcmd ) ;
+                        # string undef means something was bad.
+                        if ( not ( defined  $string  ) ) {
+                                myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
+                                      . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
+                                return ;
+                        }else{
+                                return $string ;
+                        }
+                }
+                myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
                       . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
-		return ;
-	}
-	return $string ;
+                return ;
+        }
+        return $string ;
 }
 
 
 sub message_for_host2 {
 
-# global variable list: 
+# global variable list:
 # @skipmess
 # @regexmess
 # @pipemess
 # $addheader
 # $debugcontent
 # $debug
-# 
+#
 # API current
 #
-# at failure: 
+# at failure:
 #   * return nothing ( will then be undef or () )
 #   * $string_ref content is undef or empty
 # at success:
@@ -5124,78 +6170,78 @@ sub message_for_host2 {
 #   * $string_ref content filled with message
 
 # API future
-# 
-# 
-	my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
+#
+#
+        my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
 
         # abort when missing a parameter
-        if ( (!$sync) or  (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!$h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) {
+        if ( (!$sync) or  (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!defined $h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) {
                 return ;
         }
 
-        if ( $sync->{debugmemory} ) {
+        if ( $mysync->{debugmemory} ) {
                 myprintf("M1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
-        my $imap1 = $sync->{imap1} ;
-	my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ;
+        my $imap1 = $mysync->{imap1} ;
+        my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ;
 
-        if ( $sync->{debugmemory} ) {
+        if ( $mysync->{debugmemory} ) {
                 myprintf("M2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
-	my $string_len = length_ref( $string_ref  ) ;
+        my $string_len = length_ref( $string_ref  ) ;
 
 
-	unless ( defined  $string_ok  and $string_len ) {
-		# undef or 0 length
-		my $error = join q{},
-			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
-			$imap1->LastError || q{}, "\n"  ;
-		errors_incr( $sync, $error ) ;
-		$total_bytes_error += $h1_size if ( $h1_size ) ;
+        unless ( defined  $string_ok  and $string_len ) {
+                # undef or 0 length
+                my $error = join q{},
+                        "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
+                        $imap1->LastError || q{}, "\n"  ;
+                errors_incr( $mysync, $error ) ;
+                $total_bytes_error += $h1_size if ( $h1_size ) ;
                 $h1_nb_msg_processed +=1 ;
-		return ;
-	}
+                return ;
+        }
 
-	if ( @skipmess ) {
-		my $match = skipmess( ${ $string_ref } ) ;
+        if ( @skipmess ) {
+                my $match = skipmess( ${ $string_ref } ) ;
                 # string undef means the eval regex was bad.
                 if ( not ( defined  $match  ) ) {
-                	myprint(
-			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        myprint(
+                        "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
                         . " could not be skipped by --skipmess option, bad regex\n" ) ;
-                	return ;
+                        return ;
                 }
                 if ( $match ) {
                         my $subject = subject( ${ $string_ref } ) ;
                         myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
                             . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
-                	return ;
+                        return ;
                 }
-	}
+        }
 
-	if ( @regexmess ) {
-		${ $string_ref } = regexmess( ${ $string_ref } ) ;
+        if ( @regexmess ) {
+                ${ $string_ref } = regexmess( ${ $string_ref } ) ;
                 # string undef means the eval regex was bad.
                 if ( not ( defined  ${ $string_ref }  ) ) {
-                	myprint(
-			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        myprint(
+                        "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
                         . " could not be transformed by --regexmess\n" ) ;
-                	return ;
+                        return ;
                 }
-	}
+        }
 
-	if ( @pipemess ) {
-		${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
+        if ( @pipemess ) {
+                ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
                 # string undef means something was bad.
                 if ( not ( defined  ${ $string_ref }  ) ) {
-                	myprint(
-			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        myprint(
+                        "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
                         . " could not be successfully transformed by --pipemess option\n" ) ;
-                	return ;
+                        return ;
                 }
-	}
+        }
 
         if ( $addheader and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) {
                 my $header = add_header( $h1_msg ) ;
@@ -5205,85 +6251,89 @@ sub message_for_host2 {
 
         $string_len = length_ref( $string_ref  ) ;
 
-	$debugcontent and myprint(
-		q{=} x $STD_CHAR_PER_LINE, "\n",
-		"F message content begin next line ($string_len characters long)\n",
-		${ $string_ref },
-		"F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
+        $debugcontent and myprint(
+                q{=} x $STD_CHAR_PER_LINE, "\n",
+                "F message content begin next line ($string_len characters long)\n",
+                ${ $string_ref },
+                "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
 
-        if ( $sync->{debugmemory} ) {
+        if ( $mysync->{debugmemory} ) {
                 myprintf("M3: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
-	return $string_len ;
+        return $string_len ;
 }
 
 sub tests_message_for_host2 {
-        
-        my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
-        
+	note( 'Entering tests_message_for_host2()' ) ;
+
+
+        my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
+
         is( undef, message_for_host2(  ), q{message_for_host2: no args} ) ;
-        is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ;
+        is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ;
 
         require Test::MockObject ;
         my $imapT = Test::MockObject->new(  ) ;
-        $sync->{imap1} = $imapT ;
+        $mysync->{imap1} = $imapT ;
         my $string ;
-        
+
         $h1_msg = 1 ;
         $h1_fold = 'FoldFoo';
-        $h1_size =  9 ; 
-        $h1_flags = '' ; 
+        $h1_size =  9 ;
+        $h1_flags = '' ;
         $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
         $h1_fir_ref = {} ;
         $string_ref = \$string ;
-        $imapT->mock( 'message_to_file',   
+        $imapT->mock( 'message_to_file',
                 sub {
-                        my ( $imap, $string_ref, $msg ) = @_ ;
-                        ${$string_ref} = 'blablabla' ;
-                        return length ${$string_ref} ;
+                        my ( $imap, $mystring_ref, $msg ) = @_ ;
+                        ${$mystring_ref} = 'blablabla' ;
+                        return length ${$mystring_ref} ;
                 }
         ) ;
-        is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+        is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
         q{message_for_host2: msg 1 == "blablabla", length} ) ;
         is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
- 
+
         # so far so good
         # now the --pipemess stuff
 
-	SKIP: {
+        SKIP: {
                 Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
-		skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
-		# Windows
-		# "type" command does not accept redirection of STDIN with <
-		# "sort" does
+                skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
+                # Windows
+                # "type" command does not accept redirection of STDIN with <
+                # "sort" does
 
-	} ;
+        } ;
 
-	SKIP: {
+        SKIP: {
                 Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
-		skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
-		# Unix
-                
+                skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
+                # Unix
+
                 # no change by cat
                 @pipemess = ( 'cat' ) ;
-                is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
                 q{message_for_host2: --pipemess 'cat', length} ) ;
                 is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
 
-                
+
                 # failure by false
                 @pipemess = ( 'false' ) ;
-                is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
                 q{message_for_host2: --pipemess 'false', length} ) ;
                 is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
 
                 # failure by true since no output
                 @pipemess = ( 'true' ) ;
-                is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ),
                 q{message_for_host2: --pipemess 'true', length} ) ;
                 is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
         }
+
+	note( 'Leaving  tests_message_for_host2()' ) ;
         return ;
 }
 
@@ -5294,6 +6344,8 @@ sub length_ref {
 }
 
 sub tests_length_ref {
+	note( 'Entering tests_length_ref()' ) ;
+
         my $notdefined ;
         is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
         my $notref ;
@@ -5302,200 +6354,257 @@ sub tests_length_ref {
         my $lala = 'lala' ;
         is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
         is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
+
+	note( 'Leaving  tests_length_ref()' ) ;
         return ;
 }
 
 sub date_for_host2 {
-	my( $h1_msg, $h1_idate ) = @_ ;
+        my( $h1_msg, $h1_idate ) = @_ ;
 
-	my $h1_date = q{} ;
+        my $h1_date = q{} ;
 
-	if ( $syncinternaldates ) {
-		$h1_date = $h1_idate ;
-		$debug and myprint( "internal date from host1: [$h1_date]\n"  ) ;
-		$h1_date = good_date( $h1_date ) ;
-		$debug and myprint( "internal date from host1: [$h1_date] (fixed)\n"  ) ;
-	}
+        if ( $syncinternaldates ) {
+                $h1_date = $h1_idate ;
+                $debug and myprint( "internal date from host1: [$h1_date]\n"  ) ;
+                $h1_date = good_date( $h1_date ) ;
+                $debug and myprint( "internal date from host1: [$h1_date] (fixed)\n"  ) ;
+        }
 
-	if ( $idatefromheader ) {
-		$h1_date = $imap1->get_header( $h1_msg, 'Date' ) ;
-		$debug and myprint( "header date from host1: [$h1_date]\n"  ) ;
-		$h1_date = good_date( $h1_date ) ;
-		$debug and myprint( "header date from host1: [$h1_date] (fixed)\n"  ) ;
-	}
+        if ( $idatefromheader ) {
+                $h1_date = $imap1->get_header( $h1_msg, 'Date' ) ;
+                $debug and myprint( "header date from host1: [$h1_date]\n"  ) ;
+                $h1_date = good_date( $h1_date ) ;
+                $debug and myprint( "header date from host1: [$h1_date] (fixed)\n"  ) ;
+        }
 
-	return( $h1_date ) ;
+        return( $h1_date ) ;
 }
 
 sub flags_for_host2 {
-	my( $h1_flags, $permanentflags2 ) = @_ ;
-	# RFC 2060: This flag can not be altered by any client
-	$h1_flags =~ s@\\Recent\s?@@xgi ;
+        my( $h1_flags, $permanentflags2 ) = @_ ;
+        # RFC 2060: This flag can not be altered by any client
+        $h1_flags =~ s@\\Recent\s?@@xgi ;
         my $h1_flags_re ;
         if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) {
                 $h1_flags = $h1_flags_re ;
         }
-	$h1_flags = flagscase( $h1_flags ) if $flagscase ;
+        $h1_flags = flagscase( $h1_flags ) if $flagscase ;
         $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ;
 
-	return( $h1_flags ) ;
+        return( $h1_flags ) ;
 }
 
 sub subject {
-	my $string = shift ;
-	my $subject = q{} ;
+        my $string = shift ;
+        my $subject = q{} ;
 
         my $header = extract_header( $string ) ;
 
         if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) {
-        	#myprint( "MMM[$1]\n"  ) ;
-        	$subject = $1 ;
+                #myprint( "MMM[$1]\n"  ) ;
+                $subject = $1 ;
         }
-	return( $subject ) ;
+        return( $subject ) ;
 }
 
 sub tests_subject {
-	ok( q{} eq subject( q{} ), 'subject: null') ;
-	ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
-	ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
-	ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
+	note( 'Entering tests_subject()' ) ;
+
+        ok( q{} eq subject( q{} ), 'subject: null') ;
+        ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
+        ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
+        ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
 
         my $MESS ;
-	$MESS = <<'EOF';
+        $MESS = <<'EOF';
 From: lalala
 Subject: toto le hero
 Date: zzzzzz
 
 Boogie boogie
 EOF
-	ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
+        ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
 
-	$MESS = <<'EOF';
+        $MESS = <<'EOF';
 Subject: toto le hero
 From: lalala
 Date: zzzzzz
 
 Boogie boogie
 EOF
-	ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
+        ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
 
 
-	$MESS = <<'EOF';
+        $MESS = <<'EOF';
 From: lalala
 Subject: cuicui
 Date: zzzzzz
 
 Subject: toto le hero
 EOF
-	ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
+        ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
 
-	$MESS = <<'EOF';
+        $MESS = <<'EOF';
 From: lalala
 Date: zzzzzz
 
 Subject: toto le hero
 EOF
-	ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
+        ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
 
-	return ;
+	note( 'Leaving  tests_subject()' ) ;
+        return ;
 }
 
 
 # GlobVar
-# $dry
+# $sync
 # $max_msg_size_in_bytes
 # $imap2
 # $imap1
 # $total_bytes_error
 # $h1_nb_msg_processed
 # $h2_uidguess
-# $total_bytes_transferred
-# $nb_msg_transferred
-# $begin_transfer_time
-# $time_spent
 # ...
 #
 #
 sub append_message_on_host2 {
-	my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
-	if ( $sync->{debugmemory} ) {
-        	myprintf("A1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
+        if ( $sync->{debugmemory} ) {
+                myprintf("A1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
         }
 
-	my $new_id ;
-	if ( ! $dry ) {
-		$max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
-		$new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
-	        if ( $sync->{debugmemory} ) {
-        	        myprintf("A2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        my $new_id ;
+        if ( ! $sync->{dry} ) {
+                $max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
+                $new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
+                if ( $sync->{debugmemory} ) {
+                        myprintf("A2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
                 }
-		if ( ! $new_id){
-                	my $subject = subject( ${ $string_ref } ) ;
+                if ( ! $new_id){
+                        my $subject = subject( ${ $string_ref } ) ;
                         my $error_imap = $imap2->LastError || q{} ;
-			my $error = "- msg $h1_fold/$h1_msg {$string_len} couldn't append  (Subject:[$subject]) to folder $h2_fold: $error_imap\n" ;
-			errors_incr( $sync, $error ) ;
-			$total_bytes_error += $h1_size;
+                        my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size] ) to folder $h2_fold: $error_imap\n" ;
+                        errors_incr( $sync, $error ) ;
+                        $total_bytes_error += $h1_size;
                         $h1_nb_msg_processed +=1 ;
-			return ;
-		}
-		else{
-			# good
-			# $new_id is an id if the IMAP server has the
-			# UIDPLUS capability else just a ref
-			if ( $new_id !~ m{^\d+$}x ) {
-				$new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ;
-			}
-			$h2_uidguess += 1 ;
-			$total_bytes_transferred += $h1_size ;
-			$nb_msg_transferred += 1 ;
+                        return ;
+                }
+                else{
+                        # good
+                        # $new_id is an id if the IMAP server has the
+                        # UIDPLUS capability else just a ref
+                        if ( $new_id !~ m{^\d+$}x ) {
+                                $new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ;
+                        }
+                        $h2_uidguess += 1 ;
+                        $sync->{total_bytes_transferred} += $h1_size ;
+                        $sync->{nb_msg_transferred} += 1 ;
                         $h1_nb_msg_processed +=1 ;
 
-                        my $time_spent = timesince( $begin_transfer_time ) ;
-                        my $rate = bytes_display_string( $total_bytes_transferred / $time_spent ) ;
+                        my $time_spent = timesince( $sync->{begin_transfer_time} ) ;
+                        my $rate = bytes_display_string( $sync->{total_bytes_transferred} / $time_spent ) ;
                         my $eta = eta( $time_spent,
-                                       $h1_nb_msg_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
-                        my $amount_transferred = bytes_display_string( $total_bytes_transferred ) ;
-			myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s  %s/s %s copied  %s\n",
-                        $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $nb_msg_transferred/$time_spent, $rate,
+                                       $h1_nb_msg_processed, $h1_nb_msg_start, $sync->{nb_msg_transferred} ) ;
+                        my $amount_transferred = bytes_display_string( $sync->{total_bytes_transferred} ) ;
+                        myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s  %s/s %s copied  %s\n",
+                        $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $sync->{nb_msg_transferred}/$time_spent, $rate,
                         $amount_transferred,
                         $eta );
-                        sleep_if_needed( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) ;
+                        sleep_if_needed( $sync ) ;
                         if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) {
-				$debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n"  ) ;
-				touch( "$cache_dir/${h1_msg}_$new_id" )
-                        	or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
+                                $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n"  ) ;
+                                touch( "$cache_dir/${h1_msg}_$new_id" )
+                                or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ;
                         }
-			if ( $delete ) {
-				delete_message_on_host1( $h1_msg, $h1_fold ) ;
-			}
-			#myprint( "PRESS ENTER" ) and my $a = <> ;
+                        if ( $delete1 ) {
+                                delete_message_on_host1( $h1_msg, $h1_fold ) ;
+                        }
+                        #myprint( "PRESS ENTER" ) and my $a = <> ;
                         return( $new_id ) ;
-		}
-	}
-	else{
-		# NOOP to avoid timeout on large folders.
-		$imap2->noop(  ) ;
-		$nb_msg_skipped_dry_mode += 1 ;
+                }
+        }
+        else{
+                $nb_msg_skipped_dry_mode += 1 ;
                 $h1_nb_msg_processed +=1 ;
-	}
+        }
 
+        return ;
+}
+
+sub tests_sleep_if_needed {
+	note( 'Entering tests_sleep_if_needed()' ) ;
+
+	is( undef, sleep_if_needed(  ), 'sleep_if_needed: no args => undef' ) ;
+	my $mysync ;
+	is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ;
+
+	$mysync->{maxbytespersecond} = 1000 ;
+	is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ;
+	$mysync->{begin_transfer_time} = time ; # now
+	is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ;
+	$mysync->{begin_transfer_time} = time - 2 ; # 2 s before
+	is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ;
+
+	$mysync->{total_bytes_transferred} = 2200 ;
+	$mysync->{begin_transfer_time} = time - 2 ; # 2 s before
+	is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ;
+	is( '0',  sleep_if_needed( $mysync ),   'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ;
+
+	$mysync->{maxsleep} = 0.1 ;
+	$mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
+	is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ;
+	
+	$mysync->{maxbytesafter} = 4000 ;
+	$mysync->{begin_transfer_time} = time - 2 ; # 2 s before again
+	is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ;
+
+	note( 'Leaving  tests_sleep_if_needed()' ) ;
 	return ;
 }
 
+
 sub sleep_if_needed {
-	my( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) = @_ ;
-        my $sleep_max_messages = sleep_max_messages( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) ;
-        my $sleep_max_bytes = sleep_max_bytes( $total_bytes_transferred, $time_spent, $maxbytespersecond  ) ;
-        my $sleep_max = max( $sleep_max_messages, $sleep_max_bytes ) ;
+        my( $mysync ) = shift ;
+
+	if ( ! $mysync ) {
+		return ;
+	}
+	# No need to go further if there is no limit set
+	if (  not ( $mysync->{maxmessagespersecond}
+		or $mysync->{maxbytespersecond} )
+	) {
+		return ;
+	}
+	
+	$mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ;
+	
+	my $time_spent = timesince( $mysync->{begin_transfer_time} ) ;
+        my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ;
+
+	my $maxbytesafter = $mysync->{maxbytesafter} || 0 ;
+	my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ;
+	my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ;
+
+	#myprint( "maxbytesafter:$maxbytesafter\n" ) ;
+	#myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
+
+        my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond}  ) ;
+        my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ;
+	$sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals.
         if ( $sleep_max > 0 ) {
-        	myprintf( "sleeping %.2f s\n", $sleep_max ) ;
+                myprint( "sleeping $sleep_max s\n" ) ;
                 sleep $sleep_max ;
+		# Slept
+		return $sleep_max ;
         }
-	return ;
+	# No sleep
+        return 0 ;
 }
 
 sub sleep_max_messages {
-	# how long we have to sleep to go under max_messages_per_second
+        # how long we have to sleep to go under max_messages_per_second
         my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ;
         if ( ( not defined  $maxmessagespersecond  ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ;
         my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
@@ -5505,417 +6614,454 @@ sub sleep_max_messages {
 
 
 sub tests_sleep_max_messages {
-	ok( 0 == sleep_max_messages( 4, 2, undef ),  'sleep_max_messages: maxmessagespersecond = undef') ;
-	ok( 0 == sleep_max_messages( 4, 2, 0 ),  'sleep_max_messages: maxmessagespersecond = 0') ;
-	ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
-	ok( 0 == sleep_max_messages( 4, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
-	ok( 2 == sleep_max_messages( 8, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max over') ;
-	ok( 0 == sleep_max_messages( 2, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
-	return ;
+	note( 'Entering tests_sleep_max_messages()' ) ;
+
+        ok( 0 == sleep_max_messages( 4, 2, undef ),  'sleep_max_messages: maxmessagespersecond = undef') ;
+        ok( 0 == sleep_max_messages( 4, 2, 0 ),  'sleep_max_messages: maxmessagespersecond = 0') ;
+        ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
+        ok( 0 == sleep_max_messages( 4, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
+        ok( 2 == sleep_max_messages( 8, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max over') ;
+        ok( 0 == sleep_max_messages( 2, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
+
+	note( 'Leaving  tests_sleep_max_messages()' ) ;
+        return ;
 }
 
 
 sub sleep_max_bytes {
-	# how long we have to sleep to go under max_bytes_per_second
-        my( $total_bytes_transferred, $time_spent, $maxbytespersecond ) = @_ ;
+        # how long we have to sleep to go under max_bytes_per_second
+        my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ;
+	$total_bytes_to_consider ||= 0 ;
+	$time_spent ||= 0 ;
+
         if ( ( not defined  $maxbytespersecond  ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
-        my $sleep = ( $total_bytes_transferred / $maxbytespersecond ) - $time_spent ;
+	#myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ;
+        my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ;
         # the sleep must be positive
         return( max( 0, $sleep ) ) ;
 }
 
 
 sub tests_sleep_max_bytes {
-	ok( 0 == sleep_max_bytes( 4000, 2, undef ),  'sleep_max_bytes: maxbytespersecond = undef') ;
-	ok( 0 == sleep_max_bytes( 4000, 2, 0 ),  'sleep_max_bytes: maxbytespersecond = 0') ;
-	ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1') ;
-	ok( 0 == sleep_max_bytes( 4000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max reached') ;
-	ok( 2 == sleep_max_bytes( 8000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max over') ;
-	ok( 0 == sleep_max_bytes( 2000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max not reached') ;
-	return ;
+	note( 'Entering tests_sleep_max_bytes()' ) ;
+
+        ok( 0 == sleep_max_bytes( 4000, 2, undef ),  'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ;
+        ok( 0 == sleep_max_bytes( 4000, 2, 0 ),  'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ;
+        ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ;
+        ok( 0 == sleep_max_bytes( 4000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ;
+        ok( 2 == sleep_max_bytes( 8000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ;
+        ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
+        ok( 0 == sleep_max_bytes( 2000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ;
+        ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ;
+
+	note( 'Leaving  tests_sleep_max_bytes()' ) ;
+        return ;
 }
 
 
 
 
-# 6 GlobVar: $dry_message $dry $imap1 $h1_nb_msg_deleted $expunge $expunge1
+# 6 GlobVar: $sync $imap1 $h1_nb_msg_deleted  $expunge1
 sub delete_message_on_host1  {
-	my( $h1_msg, $h1_fold ) = @_ ;
-	my $expunge_message = q{} ;
-	$expunge_message = 'and expunged' if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
-	myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $dry_message\n"  ) ;
-        if ( ! $dry ) {
-        	$imap1->delete_message( $h1_msg ) ;
-        	$h1_nb_msg_deleted += 1 ;
-        	$imap1->expunge(  ) if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
+        my( $h1_msg, $h1_fold ) = @_ ;
+        my $expunge_message = q{} ;
+        $expunge_message = 'and expunged' if ( $expungeaftereach and $expunge1 ) ;
+        myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $sync->{dry_message}\n"  ) ;
+        if ( ! $sync->{dry} ) {
+                $imap1->delete_message( $h1_msg ) ;
+                $h1_nb_msg_deleted += 1 ;
+                $imap1->expunge(  ) if ( $expungeaftereach and $expunge1 ) ;
         }
         return ;
 }
 
 
 sub eta {
-	my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
-	return( q{} ) if not $foldersizes ;
+        my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
+        return( q{} ) if not $foldersizes ;
 
-        my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) ;
-        my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
+        my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) ;
+        my $nb_msg_remaining = $my_h1_nb_msg_start - $h1_nb_processed ;
         my $eta_date = localtime( time + $time_remaining ) ;
-        return( mysprintf( 'ETA: %s  %1.0f s  %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
+        return( mysprintf( 'ETA: %s  %1.0f s  %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $my_h1_nb_msg_start ) ) ;
 }
 
 sub time_remaining {
 
-	my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
+        my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ;
 
-	my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
-	return( $time_remaining ) ;
+        my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $my_h1_nb_msg_start - $h1_nb_processed ) ;
+        return( $time_remaining ) ;
 }
 
 
 sub tests_time_remaining {
+	note( 'Entering tests_time_remaining()' ) ;
 
-	ok( 1 == time_remaining( 1, 1,  2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1'  ) ;
-	ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
-	ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
-	return ;
+
+        ok( 1 == time_remaining( 1, 1,  2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1'  ) ;
+        ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
+        ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
+
+	note( 'Leaving  tests_time_remaining()' ) ;
+        return ;
 }
 
 
 sub cache_map {
-	my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
-	my ( %map1_2, %map2_1, %done2 ) ;
+        my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
+        my ( %map1_2, %map2_1, %done2 ) ;
 
-	my $h1_msgs_hash_ref = {  } ;
-	my $h2_msgs_hash_ref = {  } ;
+        my $h1_msgs_hash_ref = {  } ;
+        my $h2_msgs_hash_ref = {  } ;
 
-	@{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = (  ) ;
-	@{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = (  ) ;
+        @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = (  ) ;
+        @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = (  ) ;
 
-	foreach my $file ( sort @{ $cache_files_ref } ) {
-		$debugcache and myprint( "C12: $file\n"  ) ;
-		( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
+        foreach my $file ( sort @{ $cache_files_ref } ) {
+                $debugcache and myprint( "C12: $file\n"  ) ;
+                ( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
 
-		if (  exists( $h1_msgs_hash_ref->{ defined  $uid1  ? $uid1 : q{} } )
-		  and exists( $h2_msgs_hash_ref->{ defined  $uid2  ? $uid2 : q{} } ) ) {
-		  	# keep only the greatest uid2
-			# 130_2301 and
-			# 130_231  => keep only 130 -> 2301
+                if (  exists( $h1_msgs_hash_ref->{ defined  $uid1  ? $uid1 : q{} } )
+                  and exists( $h2_msgs_hash_ref->{ defined  $uid2  ? $uid2 : q{} } ) ) {
+                        # keep only the greatest uid2
+                        # 130_2301 and
+                        # 130_231  => keep only 130 -> 2301
 
-			# keep only the greatest uid1
-			# 1601_260 and
-			#  161_260 => keep only 1601 -> 260
-		  	my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
-			if ( exists $done2{ $max_uid2 } ) {
-				if ( $done2{ $max_uid2 } < $uid1 )  {
-					$map1_2{ $uid1 } = $max_uid2 ;
-					delete $map1_2{ $done2{ $max_uid2 } } ;
-					$done2{ $max_uid2 } = $uid1 ;
-				}
-			}else{
-				$map1_2{ $uid1 } = $max_uid2 ;
-				$done2{ $max_uid2 } = $uid1 ;
-			}
-		};
+                        # keep only the greatest uid1
+                        # 1601_260 and
+                        #  161_260 => keep only 1601 -> 260
+                        my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
+                        if ( exists $done2{ $max_uid2 } ) {
+                                if ( $done2{ $max_uid2 } < $uid1 )  {
+                                        $map1_2{ $uid1 } = $max_uid2 ;
+                                        delete $map1_2{ $done2{ $max_uid2 } } ;
+                                        $done2{ $max_uid2 } = $uid1 ;
+                                }
+                        }else{
+                                $map1_2{ $uid1 } = $max_uid2 ;
+                                $done2{ $max_uid2 } = $uid1 ;
+                        }
+                };
 
-	}
-	%map2_1 = reverse %map1_2 ;
-	return( \%map1_2, \%map2_1) ;
+        }
+        %map2_1 = reverse %map1_2 ;
+        return( \%map1_2, \%map2_1) ;
 }
 
 sub tests_cache_map {
-	#$debugcache = 1 ;
-	my @cache_files = qw (
-	100_200
-	101_201
-	120_220
-	142_242
-	143_243
-	177_277
-	177_278
-	177_279
-	155_255
-	180_280
-	181_280
-	182_280
-	130_231
-	130_2301
-	161_260
-	1601_260
-	) ;
+	note( 'Entering tests_cache_map()' ) ;
 
-	my $msgs_1 = [120, 142, 143, 144, 161, 1601,           177,      182, 130 ];
-	my $msgs_2 = [     242, 243,       260,      299, 377, 279, 255, 280, 231, 2301 ];
+        #$debugcache = 1 ;
+        my @cache_files = qw (
+        100_200
+        101_201
+        120_220
+        142_242
+        143_243
+        177_277
+        177_278
+        177_279
+        155_255
+        180_280
+        181_280
+        182_280
+        130_231
+        130_2301
+        161_260
+        1601_260
+        ) ;
 
-	my( $c12, $c21 ) ;
-	ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
-	my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
-	my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
-	ok( 0 == compare_lists( [ 130, 142, 143,      177, 182, 1601      ], $a1 ), 'cache_map: 03' );
-	ok( 0 == compare_lists( [      242, 243, 260, 279, 280,      2301 ], $a2 ), 'cache_map: 04' );
-	ok( ! $c12->{161},        'cache_map: ! 161 ->  260' );
-	ok( 260  == $c12->{1601}, 'cache_map:  1601 ->  260' );
-	ok( 2301 == $c12->{130},  'cache_map:   130 -> 2301' );
-	#myprint( $c12->{1601}, "\n" ) ;
-	return ;
+        my $msgs_1 = [120, 142, 143, 144, 161, 1601,           177,      182, 130 ];
+        my $msgs_2 = [     242, 243,       260,      299, 377, 279, 255, 280, 231, 2301 ];
+
+        my( $c12, $c21 ) ;
+        ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
+        my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+        my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+        ok( 0 == compare_lists( [ 130, 142, 143,      177, 182, 1601      ], $a1 ), 'cache_map: 03' );
+        ok( 0 == compare_lists( [      242, 243, 260, 279, 280,      2301 ], $a2 ), 'cache_map: 04' );
+        ok( ! $c12->{161},        'cache_map: ! 161 ->  260' );
+        ok( 260  == $c12->{1601}, 'cache_map:  1601 ->  260' );
+        ok( 2301 == $c12->{130},  'cache_map:   130 -> 2301' );
+        #myprint( $c12->{1601}, "\n" ) ;
+
+	note( 'Leaving  tests_cache_map()' ) ;
+        return ;
 
 }
 
 sub cache_dir_fix {
-	my $cache_dir = shift ;
+        my $cache_dir = shift ;
         $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ;
         #myprint( "cache_dir_fix: $cache_dir\n"  ) ;
-	return( $cache_dir ) ;
+        return( $cache_dir ) ;
 }
 
 sub tests_cache_dir_fix {
-	ok( 'lalala' eq  cache_dir_fix('lalala'),  'cache_dir_fix: lalala -> lalala' );
-	ok( 'ii\\\\ii' eq  cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
-	ok( 'ii@ii' eq  cache_dir_fix('ii@ii'),  'cache_dir_fix: ii@ii -> ii@ii' );
-	ok( 'ii@ii\\:ii' eq  cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
-	ok( 'i\\\\i\\\\ii' eq  cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
-	ok( 'i\\\\ii' eq  cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
-	ok( '\\\\ ' eq  cache_dir_fix('\\ '), 'cache_dir_fix: \\  -> \\\\\ ' );
-	ok( '\\\\ ' eq  cache_dir_fix('\ '), 'cache_dir_fix: \  -> \\\\\ ' );
-	ok( '\[bracket\]' eq  cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
-	return ;
+	note( 'Entering tests_cache_dir_fix()' ) ;
+
+        ok( 'lalala' eq  cache_dir_fix('lalala'),  'cache_dir_fix: lalala -> lalala' );
+        ok( 'ii\\\\ii' eq  cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
+        ok( 'ii@ii' eq  cache_dir_fix('ii@ii'),  'cache_dir_fix: ii@ii -> ii@ii' );
+        ok( 'ii@ii\\:ii' eq  cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
+        ok( 'i\\\\i\\\\ii' eq  cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
+        ok( 'i\\\\ii' eq  cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
+        ok( '\\\\ ' eq  cache_dir_fix('\\ '), 'cache_dir_fix: \\  -> \\\\\ ' );
+        ok( '\\\\ ' eq  cache_dir_fix('\ '), 'cache_dir_fix: \  -> \\\\\ ' );
+        ok( '\[bracket\]' eq  cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
+
+	note( 'Leaving  tests_cache_dir_fix()' ) ;
+        return ;
 }
 
 sub cache_dir_fix_win {
-	my $cache_dir = shift ;
+        my $cache_dir = shift ;
         $cache_dir =~ s/(\[|\])/[$1]/xg ;
         #myprint( "cache_dir_fix_win: $cache_dir\n"  ) ;
-	return( $cache_dir ) ;
+        return( $cache_dir ) ;
 }
 
 sub tests_cache_dir_fix_win {
-	ok( 'lalala' eq  cache_dir_fix_win('lalala'),  'cache_dir_fix_win: lalala -> lalala' );
-	ok( '[[]bracket[]]' eq  cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
-	return ;
+	note( 'Entering tests_cache_dir_fix_win()' ) ;
+
+        ok( 'lalala' eq  cache_dir_fix_win('lalala'),  'cache_dir_fix_win: lalala -> lalala' );
+        ok( '[[]bracket[]]' eq  cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
+
+	note( 'Leaving  tests_cache_dir_fix_win()' ) ;
+        return ;
 }
 
 
 
 
 sub get_cache {
-	my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
+        my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_;
 
-	$debugcache and myprint( "Entering get_cache\n" ) ;
+        $debugcache and myprint( "Entering get_cache\n" ) ;
 
-	-d $cache_dir or return( undef ); # exit if cache directory doesn't exist
-	$debugcache and myprint( "cache_dir    : $cache_dir\n" ) ;
+        -d $cache_dir or return( undef ); # exit if cache directory doesn't exist
+        $debugcache and myprint( "cache_dir    : $cache_dir\n" ) ;
 
 
         if ( 'MSWin32' ne $OSNAME ) {
-        	$cache_dir = cache_dir_fix( $cache_dir ) ;
+                $cache_dir = cache_dir_fix( $cache_dir ) ;
         }else{
-        	$cache_dir = cache_dir_fix_win( $cache_dir ) ;
+                $cache_dir = cache_dir_fix_win( $cache_dir ) ;
         }
 
-	$debugcache and myprint( "cache_dir_fix: $cache_dir\n"  ) ;
+        $debugcache and myprint( "cache_dir_fix: $cache_dir\n"  ) ;
 
-	my @cache_files = bsd_glob( "$cache_dir/*" ) ;
-	#$debugcache and myprint( "cache_files: [@cache_files]\n"  ) ;
+        my @cache_files = bsd_glob( "$cache_dir/*" ) ;
+        #$debugcache and myprint( "cache_files: [@cache_files]\n"  ) ;
 
-	$debugcache and myprint( 'cache_files: ', scalar  @cache_files , " files found\n" ) ;
+        $debugcache and myprint( 'cache_files: ', scalar  @cache_files , " files found\n" ) ;
 
-	my( $cache_1_2_ref, $cache_2_1_ref )
-	  = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
+        my( $cache_1_2_ref, $cache_2_1_ref )
+          = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
 
-	clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
+        clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
 
-	$debugcache and myprint( "Exiting get_cache\n" ) ;
-	return( $cache_1_2_ref, $cache_2_1_ref ) ;
+        $debugcache and myprint( "Exiting get_cache\n" ) ;
+        return( $cache_1_2_ref, $cache_2_1_ref ) ;
 }
 
 
 sub tests_get_cache {
+	note( 'Entering tests_get_cache()' ) ;
 
-	ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
-	ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' )), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
-	ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
+        ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
+        ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
+        ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
 
-	my @test_files_cache = ( qw(
-	W/tmp/cache/F1/F2/100_200
-	W/tmp/cache/F1/F2/101_201
-	W/tmp/cache/F1/F2/120_220
-	W/tmp/cache/F1/F2/142_242
-	W/tmp/cache/F1/F2/143_243
-	W/tmp/cache/F1/F2/177_277
-	W/tmp/cache/F1/F2/177_377
-	W/tmp/cache/F1/F2/177_777
-	W/tmp/cache/F1/F2/155_255
-	) ) ;
-	ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
+        my @test_files_cache = ( qw(
+        W/tmp/cache/F1/F2/100_200
+        W/tmp/cache/F1/F2/101_201
+        W/tmp/cache/F1/F2/120_220
+        W/tmp/cache/F1/F2/142_242
+        W/tmp/cache/F1/F2/143_243
+        W/tmp/cache/F1/F2/177_277
+        W/tmp/cache/F1/F2/177_377
+        W/tmp/cache/F1/F2/177_777
+        W/tmp/cache/F1/F2/155_255
+        ) ) ;
+        ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
 
 
-	# on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
-	# on live:
-	my $msgs_1 = [120, 142, 143, 144,          177      ];
-	my $msgs_2 = [     242, 243,     299, 377, 777, 255 ];
+        # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
+        # on live:
+        my $msgs_1 = [120, 142, 143, 144,          177      ];
+        my $msgs_2 = [     242, 243,     299, 377, 777, 255 ];
 
         my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
         my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
 
-	my( $c12, $c21 ) ;
-	ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
-	my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
-	my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
-	ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
-	ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
-	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
-	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
-	ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
-	ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
+        my( $c12, $c21 ) ;
+        ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
+        my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+        my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+        ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
+        ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
+        ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
+        ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
+        ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
+        ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
 
-	# test clean_cache executed
-	$maxage = 2 ;
-	ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
-	ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
-	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
-	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
-	ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
-	ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
+        # test clean_cache executed
+        $maxage = 2 ;
+        ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
+        ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
+        ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
+        ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
+        ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
+        ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
 
 
-	# strange files
-	#$debugcache = 1 ;
-	$maxage = undef ;
-	ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
-	ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
+        # strange files
+        #$debugcache = 1 ;
+        $maxage = undef ;
+        ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
+        ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
 
-	@test_files_cache = ( qw(
-	W/tmp/cache/rr\uee/100_200
-	W/tmp/cache/rr\uee/101_201
-	W/tmp/cache/rr\uee/120_220
-	W/tmp/cache/rr\uee/142_242
-	W/tmp/cache/rr\uee/143_243
-	W/tmp/cache/rr\uee/177_277
-	W/tmp/cache/rr\uee/177_377
-	W/tmp/cache/rr\uee/177_777
-	W/tmp/cache/rr\uee/155_255
-	) ) ;
-	ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
+        @test_files_cache = ( qw(
+        W/tmp/cache/rr\uee/100_200
+        W/tmp/cache/rr\uee/101_201
+        W/tmp/cache/rr\uee/120_220
+        W/tmp/cache/rr\uee/142_242
+        W/tmp/cache/rr\uee/143_243
+        W/tmp/cache/rr\uee/177_277
+        W/tmp/cache/rr\uee/177_377
+        W/tmp/cache/rr\uee/177_777
+        W/tmp/cache/rr\uee/155_255
+        ) ) ;
+        ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
 
-	# on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
-	# on live:
-	$msgs_1 = [120, 142, 143, 144,          177      ] ;
-	$msgs_2 = [     242, 243,     299, 377, 777, 255 ] ;
+        # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
+        # on live:
+        $msgs_1 = [120, 142, 143, 144,          177      ] ;
+        $msgs_2 = [     242, 243,     299, 377, 777, 255 ] ;
 
         $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
         $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
 
-	ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/rr\uee', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2), 'get_cache: strange path 02' );
-	$a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
-	$a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
-	ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
-	ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
-	ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
-	ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
-	ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
-	ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
-	return ;
+        ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/rr\uee', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2), 'get_cache: strange path 02' );
+        $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+        $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+        ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
+        ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
+        ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
+        ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
+        ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
+        ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
+
+	note( 'Leaving  tests_get_cache()' ) ;
+        return ;
 }
 
 sub match_a_cache_file {
-	my $file = shift ;
-	my ( $cache_uid1, $cache_uid2 ) ;
+        my $file = shift ;
+        my ( $cache_uid1, $cache_uid2 ) ;
 
-	return( ( undef, undef ) ) if ( ! $file ) ;
-	if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
-		$cache_uid1 = $1 ;
-		$cache_uid2 = $2 ;
-	}
-	return( $cache_uid1, $cache_uid2 ) ;
+        return( ( undef, undef ) ) if ( ! $file ) ;
+        if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
+                $cache_uid1 = $1 ;
+                $cache_uid2 = $2 ;
+        }
+        return( $cache_uid1, $cache_uid2 ) ;
 }
 
 sub tests_match_a_cache_file {
-	my ( $tuid1, $tuid2 ) ;
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file(  ), 'match_a_cache_file: no arg' ) ;
-	ok( ! defined  $tuid1 , 'match_a_cache_file: no arg 1' ) ;
-	ok( ! defined  $tuid2 , 'match_a_cache_file: no arg 2' ) ;
+	note( 'Entering tests_match_a_cache_file()' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
-	ok( ! defined  $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
-	ok( ! defined  $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
+        my ( $tuid1, $tuid2 ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file(  ), 'match_a_cache_file: no arg' ) ;
+        ok( ! defined  $tuid1 , 'match_a_cache_file: no arg 1' ) ;
+        ok( ! defined  $tuid2 , 'match_a_cache_file: no arg 2' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
-	ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
-	ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
+        ok( ! defined  $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
+        ok( ! defined  $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
-	ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
-	ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
+        ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
+        ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
-	ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
-	ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
+        ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
+        ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
-	ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
-	ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
+        ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
+        ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
 
-	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
-	ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
-	ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
+        ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
+        ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
 
-	return ;
+        ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
+        ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
+        ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
+
+	note( 'Leaving  tests_match_a_cache_file()' ) ;
+        return ;
 }
 
 sub clean_cache {
-	my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref )  = @_ ;
+        my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref )  = @_ ;
 
-	$debugcache and myprint( "Entering clean_cache\n" ) ;
+        $debugcache and myprint( "Entering clean_cache\n" ) ;
 
-	$debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref }  ) ;
-	foreach my $file ( @{ $cache_files_ref } ) {
-		$debugcache and myprint( "$file\n"  ) ;
-		my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
-		$debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
-#		  or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
-#		  or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
-		if ( ( not defined  $cache_uid1  )
-		  or ( not defined  $cache_uid2  )
+        $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref }  ) ;
+        foreach my $file ( @{ $cache_files_ref } ) {
+                $debugcache and myprint( "$file\n"  ) ;
+                my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
+                $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
+#                 or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
+#                 or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
+                if ( ( not defined  $cache_uid1  )
+                  or ( not defined  $cache_uid2  )
                   or ( not exists  $h1_msgs_all_hash_ref->{ $cache_uid1 }  )
                   or ( not exists  $h2_msgs_all_hash_ref->{ $cache_uid2 }  )
                 ) {
-			$debugcache and myprint( "remove $file\n"  ) ;
-			unlink $file or myprint( "$!"  ) ;
-		}
-	}
+                        $debugcache and myprint( "remove $file\n"  ) ;
+                        unlink $file or myprint( "$OS_ERROR"  ) ;
+                }
+        }
 
-	$debugcache and myprint( "Exiting clean_cache\n" ) ;
-	return( 1 ) ;
+        $debugcache and myprint( "Exiting clean_cache\n" ) ;
+        return( 1 ) ;
 }
 
 sub tests_clean_cache {
+	note( 'Entering tests_clean_cache()' ) ;
 
-	ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
-	ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
+        ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
+        ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
 
-	my @test_files_cache = ( qw(
-	W/tmp/cache/G1/G2/100_200
-	W/tmp/cache/G1/G2/101_201
-	W/tmp/cache/G1/G2/120_220
-	W/tmp/cache/G1/G2/142_242
-	W/tmp/cache/G1/G2/143_243
-	W/tmp/cache/G1/G2/177_277
-	W/tmp/cache/G1/G2/177_377
-	W/tmp/cache/G1/G2/177_777
-	W/tmp/cache/G1/G2/155_255
-	) ) ;
-	ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
+        my @test_files_cache = ( qw(
+        W/tmp/cache/G1/G2/100_200
+        W/tmp/cache/G1/G2/101_201
+        W/tmp/cache/G1/G2/120_220
+        W/tmp/cache/G1/G2/142_242
+        W/tmp/cache/G1/G2/143_243
+        W/tmp/cache/G1/G2/177_277
+        W/tmp/cache/G1/G2/177_377
+        W/tmp/cache/G1/G2/177_777
+        W/tmp/cache/G1/G2/155_255
+        ) ) ;
+        ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
 
-	ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
-	ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
-	ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
+        ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
+        ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
+        ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
 
-	my $cache = {
-		142 => 242,
-		177 => 777,
-	} ;
+        my $cache = {
+                142 => 242,
+                177 => 777,
+        } ;
 
         my $all_1 = {
                 142 => q{},
@@ -5927,46 +7073,49 @@ sub tests_clean_cache {
                 242 => q{},
                 777 => q{},
         } ;
-	ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
+        ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
 
-	ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
-	ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
-	ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
-	return ;
+        ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
+        ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
+        ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
+
+	note( 'Leaving  tests_clean_cache()' ) ;
+        return ;
 }
 
 sub tests_clean_cache_2 {
+	note( 'Entering tests_clean_cache_2()' ) ;
 
-	ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
-	ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
+        ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
+        ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
 
-	my @test_files_cache = ( qw(
-	W/tmp/cache/G1/G2/100_200
-	W/tmp/cache/G1/G2/101_201
-	W/tmp/cache/G1/G2/120_220
-	W/tmp/cache/G1/G2/142_242
-	W/tmp/cache/G1/G2/143_243
-	W/tmp/cache/G1/G2/177_277
-	W/tmp/cache/G1/G2/177_377
-	W/tmp/cache/G1/G2/177_777
-	W/tmp/cache/G1/G2/155_255
-	) ) ;
-	ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
+        my @test_files_cache = ( qw(
+        W/tmp/cache/G1/G2/100_200
+        W/tmp/cache/G1/G2/101_201
+        W/tmp/cache/G1/G2/120_220
+        W/tmp/cache/G1/G2/142_242
+        W/tmp/cache/G1/G2/143_243
+        W/tmp/cache/G1/G2/177_277
+        W/tmp/cache/G1/G2/177_377
+        W/tmp/cache/G1/G2/177_777
+        W/tmp/cache/G1/G2/155_255
+        ) ) ;
+        ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
 
-	ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
-	ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
-	ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
-	ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
+        ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
+        ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
+        ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
+        ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
 
-	my $cache = {
-		142 => 242,
-		177 => 777,
-	} ;
+        my $cache = {
+                142 => 242,
+                177 => 777,
+        } ;
 
         my $all_1 = {
                 $NUMBER_100 => q{},
@@ -5982,105 +7131,131 @@ sub tests_clean_cache_2 {
 
 
 
-	ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
+        ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
 
-	ok(   -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
-	ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
-	ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
-	ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
-	return ;
+        ok(   -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
+        ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
+        ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
+        ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
+
+	note( 'Leaving  tests_clean_cache_2()' ) ;
+        return ;
 }
 
 
 
 sub tests_mkpath {
+	note( 'Entering tests_mkpath()' ) ;
 
-	ok( 1 == 1, 'tests_mkpath: 1 == 1' ) ;
+	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ;
+	
+        SKIP: {
+                skip( 'Tests only for Unix', 10   ) if ( 'MSWin32' eq $OSNAME ) ;
+                my $long_path_unix = '123456789/' x 30 ;
+                ok( ( -d "W/tmp/tests/long/$long_path_unix" or  mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ;
+		ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ;
+                ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ;
+		ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ;
+		
+		ok( ( -d 'W/tmp/tests/trailing_dots...' or  mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
+		ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
+		ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
+		ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
 
-	SKIP: {
-		skip( 'Tests only for Unix', 2   ) if ( 'MSWin32' eq $OSNAME ) ;
-		my $long_path_unix = '123456789/' x 30 ;
-		ok( (-d "W/tmp/tests/long/$long_path_unix" or  mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'tests_mkpath: mkpath > 300 char' ) ;
-		ok( (-d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'tests_mkpath: rmtree > 300 char' ) ;
+		eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ;
+		ok( 1, 'mkpath: still alive' ) ;
         } ;
 
-	SKIP: {
-		skip( 'Tests only for MSWin32', 6  ) if ( 'MSWin32' ne $OSNAME ) ;
-		my $long_path_2_prefix =  "$tmpdir\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests'  ;
-		myprint( "long_path_2_prefix: $long_path_2_prefix\n"  ) ;
+        SKIP: {
+                skip( 'Tests only for MSWin32', 13  ) if ( 'MSWin32' ne $OSNAME ) ;
+                my $long_path_2_prefix =  "$tmpdir\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests'  ;
+                myprint( "long_path_2_prefix: $long_path_2_prefix\n"  ) ;
 
-		my $long_path_2   = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
-		my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
+                my $long_path_100   = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
+                my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
 
-		myprint( "$long_path_2\n"  ) ;
+                #myprint( "$long_path_100\n"  ) ;
 
-		#ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
-		#ok( ( -d $long_path_2_prefix or mkpath( "\\\\\?\\E:\\\\TEMP\\imapsync_tests" ) ), 'tests_mkpath: -d  small path 1' ) ;
+                ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ;
+                ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ;
+                ok( ( -d $long_path_100        or mkpath( $long_path_100 ) ),        'mkpath: mkpath > 100 char' ) ;
+                ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ;
+                ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ;
+                ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ;
 
-		ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'tests_mkpath: -d mkpath small path' ) ;
-		ok( ( -d $long_path_2_prefix ), 'tests_mkpath: -d mkpath small path done' ) ;
-		ok( ( -d $long_path_2        or mkpath( $long_path_2 ) ),        'tests_mkpath: mkpath > 200 char' ) ;
-		ok( ( -d $long_path_2 ), 'tests_mkpath: -d mkpath > 200 char done' ) ;
-		ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
-		ok( (! -d $long_path_2_prefix ), 'tests_mkpath: ! -d rmtree done' ) ;
+                # Without the eval the following mkpath 300 just kill the whole process without a whisper
+                #myprint( "$long_path_300\n"  ) ;
+                eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ),  'mkpath: create a path with 300 characters' ) ; } 
+			or ok( 1, 'mkpath: can not create a path with 300 characters' ) ;
+                ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ;
+		ok( 1, 'mkpath: still alive' ) ;
 
-		myprint( "$long_path_300\n"  ) ;
-		# This one just kill the whole process without a whisper:
-		#ok( ( -d $long_path_300        or mkpath( $long_path_300 ) ),        'tests_mkpath: mkpath fails > 300 char' ) ;
-		#ok( ( -d $long_path_300 and rmtree( $long_path_300 ) ), 'tests_mkpath: rmtree \ > 300 char' ) ;
-	} ;
+		ok( ( -d 'W/tmp/tests/trailing_dots...' or  mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ;
+		ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ;
+		ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ;
+		ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ;
+		
+		
+        } ;
 
-	return 1 ;
+	note( 'Leaving  tests_mkpath()' ) ;
+	# Keep this because of the eval used by the caller (failed badly?)
+        return 1 ;
 }
 
 sub tests_touch {
+	note( 'Entering tests_touch()' ) ;
 
-	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' )), 'tests_touch: mkpath W/tmp/tests/' ) ;
-	ok( 1 == touch( 'W/tmp/tests/lala'), 'tests_touch: W/tmp/tests/lala') ;
-	ok( 1 == touch( 'W/tmp/tests/\y'), 'tests_touch: W/tmp/tests/\y') ;
-	ok( 0 == touch( '/no/no/no/aaa'), 'tests_touch: not /aaa') ;
-	ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'tests_touch: 2 files') ;
-	ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'tests_touch: 2 files, 1 fails' ) ;
-	return ;
+        ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ;
+        ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ;
+        ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ;
+        ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ;
+        ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ;
+        ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ;
+
+	note( 'Leaving  tests_touch()' ) ;
+        return ;
 }
 
 
 sub touch {
-	my @files = @_ ;
-	my $failures = 0 ;
+        my @files = @_ ;
+        my $failures = 0 ;
 
-	foreach my $file ( @files ) {
-		my  $fh = IO::File->new ;
-		if ( $fh->open(">> $file" ) ) {
-			$fh->close ;
-		}else{
-                	myprint( "Could not open file $file in write/append mode\n"  ) ;
-                	$failures++ ;
+        foreach my $file ( @files ) {
+                my  $fh = IO::File->new ;
+                if ( $fh->open(">> $file" ) ) {
+                        $fh->close ;
+                }else{
+                        myprint( "Could not open file $file in write/append mode\n"  ) ;
+                        $failures++ ;
                 }
-	}
-	return( ! $failures );
+        }
+        return( ! $failures );
 }
 
 
 sub tests_tmpdir_has_colon_bug {
+	note( 'Entering tests_tmpdir_has_colon_bug()' ) ;
 
-	ok( 0 == tmpdir_has_colon_bug( q{} ),        'tmpdir_has_colon_bug: ' ) ;
-	ok( 0 == tmpdir_has_colon_bug( '/tmp' ),    'tmpdir_has_colon_bug: /tmp' ) ;
-	ok( 1 == tmpdir_has_colon_bug( 'C:' ),      'tmpdir_has_colon_bug: C:' ) ;
-	ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
+        ok( 0 == tmpdir_has_colon_bug( q{} ),        'tmpdir_has_colon_bug: ' ) ;
+        ok( 0 == tmpdir_has_colon_bug( '/tmp' ),    'tmpdir_has_colon_bug: /tmp' ) ;
+        ok( 1 == tmpdir_has_colon_bug( 'C:' ),      'tmpdir_has_colon_bug: C:' ) ;
+        ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
 
-        return( 0 ) ;
+	note( 'Leaving  tests_tmpdir_has_colon_bug()' ) ;
+        return ;
 }
 
 sub tmpdir_has_colon_bug {
-	my $path = shift ;
+        my $path = shift ;
 
-	my $path_filtered = filter_forbidden_characters( $path ) ;
-	if ( $path_filtered ne $path ) {
-        	( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n"  ) ;
-        	return( 1 ) ;
+        my $path_filtered = filter_forbidden_characters( $path ) ;
+        if ( $path_filtered ne $path ) {
+                ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n"  ) ;
+                return( 1 ) ;
         }
         return( 0 ) ;
 }
@@ -6133,140 +7308,154 @@ sub tmpdir_fix_colon_bug {
 
 
 sub tests_cache_folder {
+	note( 'Entering tests_cache_folder()' ) ;
 
-	ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
-	ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
-	ok( '/_p_a__th/fol_d1/fold2' eq cache_folder( q{}, '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
 
-	ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
-	ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
-	ok( 'D:/_p_a__th/fol_d1/fold2' eq cache_folder( 'D:', '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
-	ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder:  -> //' ) ;
-	ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
-	return ;
+        ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
+        ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
+        ok( '/_p_a__th/fol_d1/fold2' eq cache_folder( q{}, '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
+
+        ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
+        ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
+        ok( 'D:/_p_a__th/fol_d1/fold2' eq cache_folder( 'D:', '/>p<a|*th', 'fol*d1', 'fold2'), 'cache_folder: />p<a|*th, fol*d1, fold2 -> /path/fol_d1/fold2' ) ;
+        ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder:  -> //' ) ;
+        ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
+
+	note( 'Leaving  tests_cache_folder()' ) ;
+        return ;
 }
 
 sub cache_folder {
-	my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
+        my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
 
-	my $sep_1 = $h1_sep || '/';
-	my $sep_2 = $h2_sep || '/';
+        my $sep_1 = $h1_sep || '/';
+        my $sep_2 = $h2_sep || '/';
 
-	#myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
-	$h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
-	$h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
+        #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
+        $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
+        $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
 
         my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
-	#myprint( "cache_folder [$cache_folder]\n"  ) ;
+        #myprint( "cache_folder [$cache_folder]\n"  ) ;
         return( $cache_folder ) ;
 }
 
 sub filter_forbidden_characters  {
-	my $string = shift ;
+        my $string = shift ;
+
+	if ( ! defined $string ) { return ; }
 
         if ( 'MSWin32' eq $OSNAME ) {
-        	# Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
-        	$string =~ s{\ (/|$)}{_$1}xg ;
+                # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
+                $string =~ s{\ (/|$)}{_$1}xg ;
         }
         $string =~ s{[\Q*|?:"<>\E]}{_}xg ;
         #myprint( "[$string]\n"  ) ;
-	return( $string ) ;
+        return( $string ) ;
 }
 
 sub tests_filter_forbidden_characters  {
+	note( 'Entering tests_filter_forbidden_characters()' ) ;
 
-	ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
-	ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
-	ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
-	ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
-	ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
 
-	SKIP: {
-		skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
-		ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
-	} ;
+        ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
+        ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
+        ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
+        ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
+        ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
 
-	SKIP: {
-		skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
-		ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
-		ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
+        SKIP: {
+                skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
+                ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
         } ;
 
-	return ;
+        SKIP: {
+                skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
+                ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
+                ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
+        } ;
+
+	note( 'Leaving  tests_filter_forbidden_characters()' ) ;
+        return ;
 }
 
 sub convert_sep_to_slash {
-	my ( $folder, $sep ) = @_ ;
+        my ( $folder, $sep ) = @_ ;
 
-	$folder =~ s{\Q$sep\E}{/}xg ;
-	return( $folder ) ;
+        $folder =~ s{\Q$sep\E}{/}xg ;
+        return( $folder ) ;
 }
 
 sub tests_convert_sep_to_slash {
+	note( 'Entering tests_convert_sep_to_slash()' ) ;
 
-	ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
-	ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
-	ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
-	ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
-	ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
-	ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
-	ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
-	return ;
+
+        ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
+        ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
+        ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
+        ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
+        ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
+        ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
+        ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
+
+	note( 'Leaving  tests_convert_sep_to_slash()' ) ;
+        return ;
 }
 
 
 sub tests_regexmess {
+	note( 'Entering tests_regexmess()' ) ;
 
-	ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
+        ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
 
-	@regexmess = ( 'lalala' ) ;
-	ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
+        @regexmess = ( 'lalala' ) ;
+        ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
 
-	@regexmess = ( 's/p/Z/g' ) ;
-	ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
+        @regexmess = ( 's/p/Z/g' ) ;
+        ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
 
-	@regexmess = ( 's{c}{C}gxms' ) ;
-	ok("H1: abC\nH2: Cde\n\nBody abC"
-		   eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
-	   'regexmess, c->C');
+        @regexmess = ( 's{c}{C}gxms' ) ;
+        ok("H1: abC\nH2: Cde\n\nBody abC"
+                   eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
+           'regexmess, c->C');
 
-	@regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
-	ok(          q{}
-	eq regexmess(q{}),
-	'From mbox 1 add colon blank');
+        @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
+        ok(          q{}
+        eq regexmess(q{}),
+        'From mbox 1 add colon blank');
 
-	ok(          'From:<tartanpion@machin.truc>'
-	eq regexmess('From <tartanpion@machin.truc>'),
-	'From mbox 2 add colo');
+        ok(          'From:<tartanpion@machin.truc>'
+        eq regexmess('From <tartanpion@machin.truc>'),
+        'From mbox 2 add colo');
 
-	ok(          "\n" . 'From <tartanpion@machin.truc>'
-	eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
-	'From mbox 3 add colo') ;
+        ok(          "\n" . 'From <tartanpion@machin.truc>'
+        eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
+        'From mbox 3 add colo') ;
 
-	ok(          "From: zzz\n" . 'From <tartanpion@machin.truc>'
-	eq regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'),
-	'From mbox 4 add colo') ;
+        ok(          "From: zzz\n" . 'From <tartanpion@machin.truc>'
+        eq regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'),
+        'From mbox 4 add colo') ;
 
-	@regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
-	ok(          q{}
-	eq regexmess(q{}),
-	'From mbox 1 remove, blank');
+        @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
+        ok(          q{}
+        eq regexmess(q{}),
+        'From mbox 1 remove, blank');
 
-	ok(          q{}
-	eq regexmess('From <tartanpion@machin.truc>'),
-	'From mbox 2 remove');
+        ok(          q{}
+        eq regexmess('From <tartanpion@machin.truc>'),
+        'From mbox 2 remove');
 
-	ok(          "\n" . 'From <tartanpion@machin.truc>'
-	eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
-	'From mbox 3 remove');
+        ok(          "\n" . 'From <tartanpion@machin.truc>'
+        eq regexmess("\n" . 'From <tartanpion@machin.truc>'),
+        'From mbox 3 remove');
 
-	#myprint( "[", regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
-	ok(          q{}            . 'From <tartanpion@machin.truc>'
-	eq regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'),
-	'From mbox 4 remove');
+        #myprint( "[", regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'), "]" ) ;
+        ok(          q{}            . 'From <tartanpion@machin.truc>'
+        eq regexmess("From  zzz\n" . 'From <tartanpion@machin.truc>'),
+        'From mbox 4 remove');
 
 
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6274,7 +7463,7 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 From  zzz
 Date: Sat, 10 Jul 2010 05:34:45 -0700
@@ -6287,7 +7476,7 @@ EOM
 
 
 @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6295,7 +7484,7 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
@@ -6304,10 +7493,10 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	),
-	'regexmess: 1 Delete header Disposition-Notification-To:');
+        ),
+        'regexmess: 1 Delete header Disposition-Notification-To:');
 
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6315,7 +7504,7 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6325,9 +7514,9 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 2 Delete header Disposition-Notification-To:');
+        'regexmess: 2 Delete header Disposition-Notification-To:');
 
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6335,7 +7524,7 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Date: Sat, 10 Jul 2010 05:34:45 -0700
@@ -6345,9 +7534,9 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 3 Delete header Disposition-Notification-To:');
+        'regexmess: 3 Delete header Disposition-Notification-To:');
 
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6355,7 +7544,7 @@ From:<tartanpion@machin.truc>
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Date: Sat, 10 Jul 2010 05:34:45 -0700
@@ -6365,10 +7554,10 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 4 Delete header Disposition-Notification-To:');
+        'regexmess: 4 Delete header Disposition-Notification-To:');
 
 
-	ok(
+        ok(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6376,7 +7565,7 @@ From:<tartanpion@machin.truc>
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6385,7 +7574,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 5 Delete header Disposition-Notification-To:');
+        'regexmess: 5 Delete header Disposition-Notification-To:');
 
 
 ok(
@@ -6397,7 +7586,7 @@ Hello,
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6407,7 +7596,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 6 Delete header Disposition-Notification-To:');
+        'regexmess: 6 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6419,7 +7608,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6430,7 +7619,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 7 Delete header Disposition-Notification-To:');
+        'regexmess: 7 Delete header Disposition-Notification-To:');
 
 
 ok(
@@ -6441,7 +7630,7 @@ From:<tartanpion@machin.truc>
 Hello,
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6450,7 +7639,7 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 8 Delete header Disposition-Notification-To:');
+        'regexmess: 8 Delete header Disposition-Notification-To:');
 
 
 ok(
@@ -6462,7 +7651,7 @@ Hello,
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6472,7 +7661,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 9 Delete header Disposition-Notification-To:');
+        'regexmess: 9 Delete header Disposition-Notification-To:');
 
 
 
@@ -6487,7 +7676,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6499,7 +7688,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 10 Delete header Disposition-Notification-To:');
+        'regexmess: 10 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6512,7 +7701,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6524,7 +7713,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 11 Delete header Disposition-Notification-To:');
+        'regexmess: 11 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6539,7 +7728,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6553,7 +7742,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 12 Delete header Disposition-Notification-To:');
+        'regexmess: 12 Delete header Disposition-Notification-To:');
 
 
 @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
@@ -6573,7 +7762,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6587,7 +7776,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 13 Delete header Disposition-Notification-To:');
+        'regexmess: 13 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6603,7 +7792,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
@@ -6618,7 +7807,7 @@ Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Bye.
 EOM
 ),
-	'regexmess: 14 Delete header Disposition-Notification-To:');
+        'regexmess: 14 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6630,7 +7819,7 @@ Hello,
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
@@ -6641,7 +7830,7 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 15 Delete header Disposition-Notification-To:');
+        'regexmess: 15 Delete header Disposition-Notification-To:');
 
 
 ok(
@@ -6654,7 +7843,7 @@ Hello,
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6665,7 +7854,7 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 16 Delete header Disposition-Notification-To:');
+        'regexmess: 16 Delete header Disposition-Notification-To:');
 
 ok(
 <<'EOM'
@@ -6677,7 +7866,7 @@ Hello,
 
 Bye.
 EOM
-	eq regexmess(
+        eq regexmess(
 <<'EOM'
 Disposition-Notification-To: Gilles LAMIRAL <gilles.lamiral@laposte.net>
 Date: Sat, 10 Jul 2010 05:34:45 -0700
@@ -6688,76 +7877,78 @@ Hello,
 Bye.
 EOM
 ),
-	'regexmess: 17 Delete header Disposition-Notification-To:');
+        'regexmess: 17 Delete header Disposition-Notification-To:');
 
 
 
 # regex to play with Date: from the FAQ
 #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
 
-return ;
+	note( 'Leaving  tests_regexmess()' ) ;
+	return ;
 
 }
 
 sub regexmess {
-	my ( $string ) = @_ ;
-	foreach my $regexmess ( @regexmess ) {
-		$debug and myprint( "eval \$string =~ $regexmess\n"  ) ;
-		my $ret = eval "\$string =~ $regexmess ; 1" ;
+        my ( $string ) = @_ ;
+        foreach my $regexmess ( @regexmess ) {
+                $debug and myprint( "eval \$string =~ $regexmess\n"  ) ;
+                my $ret = eval "\$string =~ $regexmess ; 1" ;
                 #myprint( "eval [$ret]\n"  ) ;
-                if ( ( not $ret ) or $@ ) {
-			myprint( "Error: eval regexmess '$regexmess': $@"  ) ;
+                if ( ( not $ret ) or $EVAL_ERROR ) {
+                        myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR"  ) ;
                         return( undef ) ;
                 }
-	}
+        }
         $debug and myprint( "$string\n" ) ;
-	return( $string ) ;
+        return( $string ) ;
 }
 
 
 sub tests_skipmess {
+	note( 'Entering tests_skipmess()' ) ;
 
-	ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
+        ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
 
-	@skipmess = ('[') ;
-	ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
+        @skipmess = ('[') ;
+        ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
 
-	@skipmess = ('lalala') ;
-	ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
+        @skipmess = ('lalala') ;
+        ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
 
-	@skipmess = ('/popopo/') ;
-	ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
+        @skipmess = ('/popopo/') ;
+        ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
 
-	@skipmess = ('/popopo/') ;
-	ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
+        @skipmess = ('/popopo/') ;
+        ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
 
-	@skipmess = ('m{^$}') ;
-	ok( 1 == skipmess( q{} ),    'skipmess: empty string yes' ) ;
-	ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
+        @skipmess = ('m{^$}') ;
+        ok( 1 == skipmess( q{} ),    'skipmess: empty string yes' ) ;
+        ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
 
-	@skipmess = ('m{i}') ;
-	ok( 1 == skipmess( 'Hi!' ),  'skipmess: i string yes' ) ;
-	ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
+        @skipmess = ('m{i}') ;
+        ok( 1 == skipmess( 'Hi!' ),  'skipmess: i string yes' ) ;
+        ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
 
-	@skipmess = ('m{[\x80-\xff]}') ;
-	ok( 0 == skipmess( 'Hi!' ),  'skipmess: i 8bit no' ) ;
-	ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
+        @skipmess = ('m{[\x80-\xff]}') ;
+        ok( 0 == skipmess( 'Hi!' ),  'skipmess: i 8bit no' ) ;
+        ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
 
-	@skipmess = ('m{A}', 'm{B}') ;
-	ok( 0 == skipmess( 'Hi!' ),  'skipmess: A or B no' ) ;
-	ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
-	ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
-	ok( 1 == skipmess( 'AB' ),   'skipmess: A or B yes' ) ;
-	ok( 1 == skipmess( 'BA' ),   'skipmess: A or B yes' ) ;
-	ok( 1 == skipmess( 'AA' ),   'skipmess: A or B yes' ) ;
-	ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
+        @skipmess = ('m{A}', 'm{B}') ;
+        ok( 0 == skipmess( 'Hi!' ),  'skipmess: A or B no' ) ;
+        ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
+        ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
+        ok( 1 == skipmess( 'AB' ),   'skipmess: A or B yes' ) ;
+        ok( 1 == skipmess( 'BA' ),   'skipmess: A or B yes' ) ;
+        ok( 1 == skipmess( 'AA' ),   'skipmess: A or B yes' ) ;
+        ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
 
 
-	@skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
+        @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
 
 
 
-	ok( 1 == skipmess(
+        ok( 1 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Content-Type: Message/Partial; blabla
@@ -6769,7 +7960,7 @@ EOM
 ),
     'skipmess: 1 match Content-Type: Message/Partial' ) ;
 
-	ok( 0 == skipmess(
+        ok( 0 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6781,7 +7972,7 @@ EOM
     'skipmess: 2 not match Content-Type: Message/Partial' ) ;
 
 
-	ok( 1 == skipmess(
+        ok( 1 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6793,7 +7984,7 @@ EOM
 ),
     'skipmess: 3 match Content-Type: Message/Partial' ) ;
 
-	ok( 0 == skipmess(
+        ok( 0 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6806,7 +7997,7 @@ EOM
     'skipmess: 4 not match Content-Type: Message/Partial' ) ;
 
 
-	ok( 0 == skipmess(
+        ok( 0 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 From:<tartanpion@machin.truc>
@@ -6820,7 +8011,7 @@ EOM
     'skipmess: 5 not match Content-Type: Message/Partial' ) ;
 
 
-	ok( 1 == skipmess(
+        ok( 1 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Content-Type: Message/Partial; blabla
@@ -6835,7 +8026,7 @@ EOM
 ),
     'skipmess: 6 match Content-Type: Message/Partial' ) ;
 
-	ok( 1 == skipmess(
+        ok( 1 == skipmess(
 <<'EOM'
 Date: Sat, 10 Jul 2010 05:34:45 -0700
 Content-Type: Message/Partial;
@@ -6847,14 +8038,14 @@ EOM
 ),
     'skipmess: 7 match Content-Type: Message/Partial' ) ;
 
-	ok( 1 == skipmess(
+        ok( 1 == skipmess(
 <<'EOM'
 Date: Wed, 2 Jul 2014 02:26:40 +0000
 MIME-Version: 1.0
 Content-Type: message/partial;
-	id="TAN_U_P<1404267997.00007489ed17>";
-	number=3;
-	total=3
+        id="TAN_U_P<1404267997.00007489ed17>";
+        number=3;
+        total=3
 
 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
 
@@ -6898,7 +8089,7 @@ Content-Type: message/partial;
 
 test: aethaecohngiexao
 EOM
-. "lalala\n" x 3000000
+. "lalala\n" x 3_000_000
 ),
     'skipmess: 10 match Content-Type: Message/Partial' ) ;
 
@@ -6909,7 +8100,7 @@ From: gilles@lamiral.info (Gilles LAMIRAL)
 
 test: aethaecohngiexao
 EOM
-. "lalala\n" x 3000000
+. "lalala\n" x 3_000_000
 ),
     'skipmess: 11 match Content-Type: Message/Partial' ) ;
 
@@ -6925,69 +8116,75 @@ Content-Type: text/plain; charset=iso-8859-1\r
 Content-Transfer-Encoding: 7bit\r
 \r
 EOM
-. qq{!#"$%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32730
+. qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730
 ),
     'skipmess: 12 not match Content-Type: Message/Partial' ) ;
         # Complex regular subexpression recursion limit (32766) exceeded with more lines
         # exit;
-	return ;
+
+	note( 'Leaving  tests_skipmess()' ) ;
+        return ;
 }
 
 sub skipmess {
-	my ( $string ) = @_ ;
-	my $match ;
-	#myprint( "$string\n"  ) ;
-	foreach my $skipmess ( @skipmess ) {
-		$debug and myprint( "eval \$match = \$string =~ $skipmess\n"  ) ;
-		my $ret = eval "\$match = \$string =~ $skipmess ; 1"  ;
-		#myprint( "eval [$ret]\n"  ) ;
-		$debug and myprint( "match [$match]\n"  ) ;
-		if ( ( not $ret ) or $@ ) {
-			myprint( "Error: eval skipmess '$skipmess': $@"  ) ;
-			return( undef ) ;
-		}
-		return( $match ) if ( $match ) ;
-	}
-	return( $match ) ;
+        my ( $string ) = @_ ;
+        my $match ;
+        #myprint( "$string\n"  ) ;
+        foreach my $skipmess ( @skipmess ) {
+                $debug and myprint( "eval \$match = \$string =~ $skipmess\n"  ) ;
+                my $ret = eval "\$match = \$string =~ $skipmess ; 1"  ;
+                #myprint( "eval [$ret]\n"  ) ;
+                $debug and myprint( "match [$match]\n"  ) ;
+                if ( ( not $ret ) or $EVAL_ERROR ) {
+                        myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR"  ) ;
+                        return( undef ) ;
+                }
+                return( $match ) if ( $match ) ;
+        }
+        return( $match ) ;
 }
 
 
 
 
 sub tests_bytes_display_string {
+	note( 'Entering tests_bytes_display_string()' ) ;
+
 
         is(    'NA', bytes_display_string(       ), 'bytes_display_string: no args => NA' ) ;
         is(    'NA', bytes_display_string( undef ), 'bytes_display_string: undef   => NA' ) ;
         is(    'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla   => NA' ) ;
-        
-	ok(    '0.000 KiB' eq bytes_display_string(       0 ), 'bytes_display_string:       0' ) ;
-	ok(    '0.001 KiB' eq bytes_display_string(       1 ), 'bytes_display_string:       1' ) ;
-	ok(    '0.010 KiB' eq bytes_display_string(      10 ), 'bytes_display_string:      10' ) ;
-	ok(    '1.000 MiB' eq bytes_display_string( 1048575 ), 'bytes_display_string: 1048575' ) ;
-	ok(    '1.000 MiB' eq bytes_display_string( 1048576 ), 'bytes_display_string: 1048576' ) ;
 
-	ok(    '1.000 GiB' eq bytes_display_string( 1073741823 ), 'bytes_display_string: 1073741823 ' ) ;
-	ok(    '1.000 GiB' eq bytes_display_string( 1073741824 ), 'bytes_display_string: 1073741824 ' ) ;
+        ok(    '0.000 KiB' eq bytes_display_string(       0 ), 'bytes_display_string:       0' ) ;
+        ok(    '0.001 KiB' eq bytes_display_string(       1 ), 'bytes_display_string:       1' ) ;
+        ok(    '0.010 KiB' eq bytes_display_string(      10 ), 'bytes_display_string:      10' ) ;
+        ok(    '1.000 MiB' eq bytes_display_string( 1_048_575 ), 'bytes_display_string: 1_048_575' ) ;
+        ok(    '1.000 MiB' eq bytes_display_string( 1_048_576 ), 'bytes_display_string: 1_048_576' ) ;
 
-	ok(    '1.000 TiB' eq bytes_display_string( 1099511627775 ), 'bytes_display_string: 1099511627775' ) ;
-	ok(    '1.000 TiB' eq bytes_display_string( 1099511627776 ), 'bytes_display_string: 1099511627776' ) ;
+        ok(    '1.000 GiB' eq bytes_display_string( 1_073_741_823 ), 'bytes_display_string: 1_073_741_823 ' ) ;
+        ok(    '1.000 GiB' eq bytes_display_string( 1_073_741_824 ), 'bytes_display_string: 1_073_741_824 ' ) ;
 
-	ok(    '1.000 PiB' eq bytes_display_string( 1125899906842623 ), 'bytes_display_string: 1125899906842623' ) ;
-	ok(    '1.000 PiB' eq bytes_display_string( 1125899906842624 ), 'bytes_display_string: 1125899906842624' ) ;
+        ok(    '1.000 TiB' eq bytes_display_string( 1_099_511_627_775 ), 'bytes_display_string: 1_099_511_627_775' ) ;
+        ok(    '1.000 TiB' eq bytes_display_string( 1_099_511_627_776 ), 'bytes_display_string: 1_099_511_627_776' ) ;
 
-	ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846975 ), 'bytes_display_string: 1152921504606846975' ) ;
-	ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846976 ), 'bytes_display_string: 1152921504606846976' ) ;
+        ok(    '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_623 ), 'bytes_display_string: 1_125_899_906_842_623' ) ;
+        ok(    '1.000 PiB' eq bytes_display_string( 1_125_899_906_842_624 ), 'bytes_display_string: 1_125_899_906_842_624' ) ;
 
-	ok( '1048576.000 PiB' eq bytes_display_string( 1180591620717411303424 ), 'bytes_display_string: 1180591620717411303424' ) ;
+        ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_975 ), 'bytes_display_string: 1_152_921_504_606_846_975' ) ;
+        ok( '1024.000 PiB' eq bytes_display_string( 1_152_921_504_606_846_976 ), 'bytes_display_string: 1_152_921_504_606_846_976' ) ;
 
-        #myprint(  bytes_display_string( 1180591620717411303424 ), "\n"  ) ;
-	return ;
+        ok( '1048576.000 PiB' eq bytes_display_string( 1_180_591_620_717_411_303_424 ), 'bytes_display_string: 1_180_591_620_717_411_303_424' ) ;
+
+        #myprint(  bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n"  ) ;
+	note( 'Leaving  tests_bytes_display_string()' ) ;
+
+        return ;
 }
 
 sub bytes_display_string {
-	my ( $bytes ) = @_ ;
+        my ( $bytes ) = @_ ;
 
-	my $readable_value = q{} ;
+        my $readable_value = q{} ;
 
         if ( ! defined( $bytes ) ) {
                 return( 'NA' ) ;
@@ -6997,71 +8194,69 @@ sub bytes_display_string {
                 return( 'NA' ) ;
         }
 
-        
 
-	SWITCH: {
-        	if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
-        		$readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
-                	last SWITCH ;
-        	}
-        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
-        		$readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
-        	        last SWITCH ;
-        	}
-        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
-			$readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
-        	        last SWITCH ;
-        	}
-        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
-			$readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
-        	        last SWITCH ;
-        	} else {
-			$readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
-        	}
-		# if you have exabytes (EiB) of email to transfer, you have too much email!
-	}
+
+        SWITCH: {
+                if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
+                        $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
+                        last SWITCH ;
+                }
+                if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
+                        $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
+                        last SWITCH ;
+                }
+                if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
+                        $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
+                        last SWITCH ;
+                }
+                if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
+                        $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
+                        last SWITCH ;
+                } else {
+                        $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
+                }
+                # if you have exabytes (EiB) of email to transfer, you have too much email!
+        }
         #myprint( "$bytes = $readable_value\n"  ) ;
         return( $readable_value ) ;
 }
 
 sub stats {
-        my $sync_loc = shift ;
+        my $mysync = shift ;
 
-        if ( ! $sync_loc->{stats} ) {
+        if ( ! $mysync->{stats} ) {
                 return ;
         }
-        
-	$timeend = time ;
-	my $timediff = $timeend - $sync_loc->{timestart} ;
 
-	my $timeend_str   = localtime $timeend ;
+        my $timeend = time ;
+        my $timediff = $timeend - $mysync->{timestart} ;
 
-	my $memory_consumption = 0 ;
+        my $timeend_str   = localtime $timeend ;
+
+        my $memory_consumption = 0 ;
         $memory_consumption = memory_consumption(  ) || 0 ;
-	my $memory_ratio = ($max_msg_size_in_bytes) ?
-		mysprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : 'NA' ;
+        my $memory_ratio = ($max_msg_size_in_bytes) ?
+                mysprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : 'NA' ;
 
-	my $host1_reconnect_count = $imap1->Reconnect_counter() || 0 ;
-	my $host2_reconnect_count = $imap2->Reconnect_counter() || 0 ;
 
-	myprint(  "++++ Statistics\n"  ) ;
-	myprint(  "Transfer started on               : $timestart_str\n"  ) ;
-	myprint(  "Transfer ended on                 : $timeend_str\n"  ) ;
-	myprintf( "Transfer time                     : %.1f sec\n", $timediff ) ;
-	myprint(  "Folders synced                    : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n"  ) ;
-	myprint(  "Messages transferred              : $nb_msg_transferred "  ) ;
-	myprint(  "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $dry ) ;
-	myprint(  "\n" ) ;
-	myprint(  "Messages skipped                  : $nb_msg_skipped\n"  ) ;
-	myprint(  "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n"  ) ;
-	myprint(  "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n"  ) ;
-	myprint(  "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n"  ) ;
-	myprint(  "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n"  ) ;
-	myprint(  "Messages deleted on host1         : $h1_nb_msg_deleted\n"  ) ;
-	myprint(  "Messages deleted on host2         : $h2_nb_msg_deleted\n"  ) ;
+        myprint(  "++++ Statistics\n"  ) ;
+        myprint(  "Transfer started on               : $timestart_str\n"  ) ;
+        myprint(  "Transfer ended on                 : $timeend_str\n"  ) ;
+        myprintf( "Transfer time                     : %.1f sec\n", $timediff ) ;
+        myprint(  "Folders synced                    : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n"  ) ;
+        myprint(  "Messages transferred              : $mysync->{nb_msg_transferred} "  ) ;
+        myprint(  "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ;
+        myprint(  "\n" ) ;
+        myprint(  "Messages skipped                  : $nb_msg_skipped\n"  ) ;
+        myprint(  "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n"  ) ;
+        myprint(  "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n"  ) ;
+        myprint(  "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n"  ) ;
+        myprint(  "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n"  ) ;
+        myprint(  "Messages deleted on host1         : $h1_nb_msg_deleted\n"  ) ;
+        myprint(  "Messages deleted on host2         : $h2_nb_msg_deleted\n"  ) ;
         myprintf( "Total bytes transferred           : %s (%s)\n",
-                $total_bytes_transferred,
-                bytes_display_string( $total_bytes_transferred ) ) ;
+                $mysync->{total_bytes_transferred},
+                bytes_display_string( $mysync->{total_bytes_transferred} ) ) ;
         myprintf( "Total bytes duplicate host1       : %s (%s)\n",
                 $h1_total_bytes_duplicate,
                 bytes_display_string( $h1_total_bytes_duplicate) ) ;
@@ -7074,62 +8269,62 @@ sub stats {
         myprintf( "Total bytes error                 : %s (%s)\n",
                 $total_bytes_error,
                 bytes_display_string( $total_bytes_error ) ) ;
-	$timediff ||= 1 ; # No division per 0
-	myprintf("Message rate                      : %.1f messages/s\n", $nb_msg_transferred / $timediff ) ;
-	myprintf("Average bandwidth rate            : %.1f KiB/s\n", $total_bytes_transferred / $KIBI / $timediff ) ;
-	#myprint(  "Reconnections to host1            : $host1_reconnect_count\n"  ) ;
-	#myprint(  "Reconnections to host2            : $host2_reconnect_count\n"  ) ;
-	myprintf("Memory consumption                : %.1f MiB\n", $memory_consumption / $KIBI / $KIBI ) ;
+        $timediff ||= 1 ; # No division per 0
+        myprintf("Message rate                      : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ;
+        myprintf("Average bandwidth rate            : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ;
+        myprint( "Reconnections to host1            : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n"  ) ;
+        myprint( "Reconnections to host2            : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n"  ) ;
+        myprintf("Memory consumption                : %.1f MiB\n", $memory_consumption / $KIBI / $KIBI ) ;
         myprintf("Biggest message                   : %s bytes (%s)\n",
                 $max_msg_size_in_bytes,
                 bytes_display_string( $max_msg_size_in_bytes) ) ;
-	myprint(  "Memory/biggest message ratio      : $memory_ratio\n"  ) ;
+        myprint(  "Memory/biggest message ratio      : $memory_ratio\n"  ) ;
         if ( $foldersizesatend and $foldersizes ) {
-        
+
 
         my $nb_msg_start_diff = diff_or_NA( $h2_nb_msg_start, $h1_nb_msg_start ) ;
         my $bytes_start_diff  = diff_or_NA( $h2_bytes_start,  $h1_bytes_start  ) ;
-        
-	myprintf("Start difference host2 - host1    : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
+
+        myprintf("Start difference host2 - host1    : %s messages, %s bytes (%s)\n", $nb_msg_start_diff,
                                                         $bytes_start_diff,
                                                         bytes_display_string( $bytes_start_diff ) ) ;
 
         my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ;
         my $bytes_end_diff  = diff_or_NA( $h2_bytes_end,  $h1_bytes_end  ) ;
-        
-	myprintf("Final difference host2 - host1    : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
+
+        myprintf("Final difference host2 - host1    : %s messages, %s bytes (%s)\n", $nb_msg_end_diff,
                                                         $bytes_end_diff,
                                                         bytes_display_string( $bytes_end_diff ) ) ;
         }
-	myprint(  "Detected $sync->{nb_errors} errors\n\n"  ) ;
+        myprint(  "Detected $mysync->{nb_errors} errors\n\n"  ) ;
 
-	myprint(  $warn_release, "\n"  ) ;
-	myprint(  thank_author(  )  ) ;
-	return ;
+        myprint(  $warn_release, "\n"  ) ;
+        myprint(  homepage(  ), "\n"  ) ;
+        return ;
 }
 
 sub diff_or_NA {
         my( $n1, $n2 ) = @ARG ;
-        
+
         if ( not defined $n1 or not defined $n2 ) {
                 return 'NA' ;
         }
-        
-        if ( not match_number( $n1 ) 
+
+        if ( not match_number( $n1 )
           or not match_number( $n2 ) ) {
                  return 'NA' ;
         }
-        
+
         return( $n1 - $n2 ) ;
 }
 
 sub match_number {
         my $n = shift @ARG ;
-        
+
         if ( not defined $n ) {
                 return 0 ;
         }
-        if ( $n =~  /[0-9]+\.?[0-9]?/ ) {
+        if ( $n =~  /[0-9]+\.?[0-9]?/x ) {
                 return 1 ;
         }
         else {
@@ -7139,6 +8334,8 @@ sub match_number {
 
 
 sub tests_match_number {
+	note( 'Entering tests_match_number()' ) ;
+
 
         is( 0, match_number(   ),        'match_number: no parameters => 0' ) ;
         is( 0, match_number( undef ),    'match_number:         undef => 0' ) ;
@@ -7147,12 +8344,16 @@ sub tests_match_number {
         is( 1, match_number( 1 ),        'match_number:             1 => 1' ) ;
         is( 1, match_number( 1.0 ),      'match_number:           1.0 => 1' ) ;
         is( 1, match_number( 0.0 ),      'match_number:           0.0 => 1' ) ;
+
+	note( 'Leaving  tests_match_number()' ) ;
         return ;
 }
 
 
 
 sub tests_diff_or_NA {
+	note( 'Entering tests_diff_or_NA()' ) ;
+
 
         is( 'NA', diff_or_NA(  ),             'diff_or_NA: no parameters => NA' ) ;
         is( 'NA', diff_or_NA( undef ),        'diff_or_NA: undef         => NA' ) ;
@@ -7167,127 +8368,120 @@ sub tests_diff_or_NA {
         is( 0, diff_or_NA( 1.0, 1 ),          'diff_or_NA: 1.0    1      =>  0' ) ;
         is( 1, diff_or_NA( 1.0, 0 ),          'diff_or_NA: 1.0    0      =>  1' ) ;
         is( -1, diff_or_NA( 0, 1.0 ),         'diff_or_NA: 0      1.0    => -1' ) ;
+
+	note( 'Leaving  tests_diff_or_NA()' ) ;
         return ;
 }
 
-sub thank_author {
-	return( "Homepage: http://imapsync.lamiral.info/\n" ) ;
+sub homepage {
+        return( 'Homepage: http://imapsync.lamiral.info/' ) ;
 }
 
 
 sub load_modules {
-
-	if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
-        	# not yet a "use" statement
-        	require IO::Socket::SSL ;
-		if ( $sync->{inet4} ) {
-		        IO::Socket::SSL->import( 'inet4' ) ;
-		}
-		if ( $sync->{inet6} ) {
-		        IO::Socket::SSL->import( 'inet6' ) ;
-		}
+        if ( $sync->{ssl1}
+	  or $sync->{ssl2}
+	  or $sync->{tls1}
+	  or $sync->{tls2}) {
+                if ( $sync->{inet4} ) {
+                        IO::Socket::SSL->import( 'inet4' ) ;
+                }
+                if ( $sync->{inet6} ) {
+                        IO::Socket::SSL->import( 'inet6' ) ;
+                }
         }
-
-       if ( ( ( not( $password1 or $passfile1 ) )
-	   or (not ( $password2 or $passfile2 ) )
-            )
-	and ( not $help ) ) {
-        	# now a "use" statement
-        	#require Term::ReadKey ;
-        }
-
-	return ;
+        return ;
 }
 
 
 
 sub parse_header_msg {
-	my ( $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
+        my ( $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
 
-	my $head = $s_heads->{$m_uid} ;
-	my $headnum =  scalar keys  %{ $head }   ;
-	$debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n"  ) ;
+        my $head = $s_heads->{$m_uid} ;
+        my $headnum =  scalar keys  %{ $head }   ;
+        $debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n"  ) ;
 
-	if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
-		myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n"  ) ;
-		$imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
-		my $whole_header = $imap->_transaction_literals ;
+        if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
+                myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n"  ) ;
+                $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
+                my $whole_header = $imap->_transaction_literals ;
 
                 #myprint( $whole_header ) ;
                 $head = decompose_header( $whole_header ) ;
 
                 $headnum =  scalar  keys  %{ $head }   ;
-	        $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ;
-	}
+                $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ;
+        }
 
         #myprint( Data::Dumper->Dump( [ $head, \%useheader ] )  ) ;
 
-	my $headstr ;
+        my $headstr ;
 
         $headstr = header_construct( $head, $side, $m_uid ) ;
 
-	if ( ( ! $headstr) and ( $addheader ) and ( $side eq 'Host1' ) ) {
-        	my $header = add_header( $m_uid ) ;
-		myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ;
-		$headstr .= uc  $header  ;
-		$s_fir->{$m_uid}->{NO_HEADER} = 1;
-	}
+        if ( ( ! $headstr) and ( $addheader ) and ( $side eq 'Host1' ) ) {
+                my $header = add_header( $m_uid ) ;
+                myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ;
+                $headstr .= uc  $header  ;
+                $s_fir->{$m_uid}->{NO_HEADER} = 1;
+        }
 
-	return if ( ! $headstr ) ;
+        return if ( ! $headstr ) ;
 
-	my $size  = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
-	my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
-	my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
-	$size = length $headstr  unless ( $size ) ;
-	my $m_md5 = md5_base64( $headstr ) ;
-	$debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n"  ) ;
-	my $key ;
+        my $size  = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
+        my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
+        my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
+        $size = length $headstr  unless ( $size ) ;
+        my $m_md5 = md5_base64( $headstr ) ;
+        $debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n"  ) ;
+        my $key ;
         if ($skipsize) {
                 $key = "$m_md5";
         }
-	else {
+        else {
                 $key = "$m_md5:$size";
         }
-	# 0 return code is used to identify duplicate message hash
-	return 0 if exists $s_hash->{"$key"};
-	$s_hash->{"$key"}{'5'} = $m_md5;
-	$s_hash->{"$key"}{'s'} = $size;
-	$s_hash->{"$key"}{'D'} = $idate;
-	$s_hash->{"$key"}{'F'} = $flags;
-	$s_hash->{"$key"}{'m'} = $m_uid;
+        # 0 return code is used to identify duplicate message hash
+        return 0 if exists $s_hash->{"$key"};
+        $s_hash->{"$key"}{'5'} = $m_md5;
+        $s_hash->{"$key"}{'s'} = $size;
+        $s_hash->{"$key"}{'D'} = $idate;
+        $s_hash->{"$key"}{'F'} = $flags;
+        $s_hash->{"$key"}{'m'} = $m_uid;
 
-	return( 1 ) ;
+        return( 1 ) ;
 }
 
 sub header_construct {
 
-	my( $head, $side, $m_uid ) = @_ ;
+        my( $head, $side, $m_uid ) = @_ ;
 
         my $headstr ;
-	foreach my $h ( sort keys  %{ $head }  ) {
+        foreach my $h ( sort keys  %{ $head }  ) {
                 next if ( not ( exists $useheader{ uc  $h  } )
                       and ( not exists  $useheader{ 'ALL' } )
                 ) ;
-		foreach my $val ( sort @{$head->{$h}} ) {
+                foreach my $val ( sort @{$head->{$h}} ) {
 
                         my $H = header_line_normalize( $h, $val ) ;
 
-			# show stuff in debug mode
-			$debug and myprint( "$side uid $m_uid header [$H]", "\n"  ) ;
+                        # show stuff in debug mode
+                        $debug and myprint( "$side uid $m_uid header [$H]", "\n"  ) ;
 
-			if ($skipheader and $H =~ m/$skipheader/xi) {
-				$debug and myprint( "$side uid $m_uid skipping header [$H]\n"  ) ;
-				next ;
-			}
-			$headstr .= "$H" ;
-		}
-	}
-	return( $headstr ) ;
+                        if ($skipheader and $H =~ m/$skipheader/xi) {
+                                $debug and myprint( "$side uid $m_uid skipping header [$H]\n"  ) ;
+                                next ;
+                        }
+                        $headstr .= "$H" ;
+                }
+        }
+        return( $headstr ) ;
 }
 
 
 sub header_line_normalize {
-	my( $header_key,  $header_val ) = @_ ;
+        my( $header_key,  $header_val ) = @_ ;
 
         # no 8-bit data in headers !
         $header_val =~ s/[\x80-\xff]/X/xog;
@@ -7312,20 +8506,23 @@ sub header_line_normalize {
 
         my $header_line = uc "$header_key: $header_val" ;
 
-	return( $header_line ) ;
+        return( $header_line ) ;
 }
 
 sub tests_header_line_normalize {
+	note( 'Entering tests_header_line_normalize()' ) ;
 
-	ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
-	ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
-	ok( 'HHH: VVV' eq header_line_normalize( 'hhh', '  vvv' ), 'header_line_normalize: remove first blancs' ) ;
-	ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa  bb   ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
-	ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa  bb   ccc   ' ), 'header_line_normalize: remove last blanks' ) ;
-	ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
-	ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
 
-	return ;
+        ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
+        ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
+        ok( 'HHH: VVV' eq header_line_normalize( 'hhh', '  vvv' ), 'header_line_normalize: remove first blancs' ) ;
+        ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa  bb   ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
+        ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa  bb   ccc   ' ), 'header_line_normalize: remove last blanks' ) ;
+        ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
+        ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
+
+	note( 'Leaving  tests_header_line_normalize()' ) ;
+        return ;
 }
 
 
@@ -7334,9 +8531,14 @@ sub firstline {
 
         my( $file ) = @_ ;
         my $line  = q{} ;
-        my $FILE ;
-        open $FILE, '<', $file or do {
-                myprint( "Error opening file $file : $!\n" ) ;
+
+	if ( ! -e $file ) {
+                myprint( "Cannot open file $file since it does not exist\n" ) ;
+                return ;
+	}
+
+        open my $FILE, '<', $file or do {
+                myprint( "Error opening file $file : $OS_ERROR\n" ) ;
                 return ;
         } ;
         $line = <$FILE> || q{} ;
@@ -7346,34 +8548,113 @@ sub firstline {
 }
 
 sub tests_firstline {
-        is( 1 , string_to_file( "blabla\n", 'tmp/firstline.txt' ), 'tests_firstline: put blabla in tmp/firstline.txt' ) ;
-        is( 'blabla' , firstline( 'tmp/firstline.txt' ), 'tests_firstline: get blabla from tmp/firstline.txt' ) ;
-        is( undef , firstline( 'tmp/noexist.txt' ), 'tests_firstline: get blabla from tmp/noexist.txt' ) ;
-        is( 1 , string_to_file( q{}, 'tmp/firstline2.txt' ), 'tests_firstline: put empty string in tmp/firstline2.txt' ) ;
-        is( q{} , firstline( 'tmp/firstline2.txt' ), 'tests_firstline: get empty string from tmp/firstline2.txt' ) ;
-        is( 1 , string_to_file( "\n", 'tmp/firstline3.txt' ), 'tests_firstline: put CR in tmp/firstline3.txt' ) ;
-        is( q{} , firstline( 'tmp/firstline3.txt' ), 'tests_firstline: get empty string from tmp/firstline3.txt' ) ;
+	note( 'Entering tests_firstline()' ) ;
 
+        is( undef , firstline( 'W/tmp/tests/noexist.txt' ), 'tests_firstline: not getting blabla from W/tmp/tests/noexist.txt' ) ;
+        is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'tests_firstline: put blabla in W/tmp/tests/firstline.txt' ) ;
+        is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'tests_firstline: get blabla from W/tmp/tests/firstline.txt' ) ;
+        is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: put empty string in W/tmp/tests/firstline2.txt' ) ;
+        is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline2.txt' ) ;
+        is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: put CR in W/tmp/tests/firstline3.txt' ) ;
+        is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'tests_firstline: get empty string from W/tmp/tests/firstline3.txt' ) ;
+
+	note( 'Leaving  tests_firstline()' ) ;
         return ;
 }
 
 
-sub file_to_string {
-	my( $file ) = @_ ;
-	my @string ;
-	open my $FILE, '<', $file or die_clean( "Error with file $file : $! " ) ;
-	@string = <$FILE> ;
-	close $FILE ;
-	return( join q{}, @string ) ;
+
+# Should be unit tested and then be used by file_to_string, refactoring file_to_string
+sub file_to_array {
+
+        my( $file ) = shift ;
+        my @string ;
+
+        open my $FILE, '<', $file or do {
+		myprint( "Error reading file $file : $OS_ERROR" ) ;
+		return ;
+	} ;
+        @string = <$FILE> ;
+        close $FILE ;
+        return( @string ) ;
 }
 
 
+sub tests_file_to_string {
+	note( 'Entering tests_file_to_string()' ) ;
+
+	is( undef, file_to_string(  ), 'file_to_string: no args => undef' ) ;
+	is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ;
+	is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ;
+	ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ;
+
+	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ;
+
+	is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ;
+	is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ;
+
+	is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ;
+	is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ;
+
+	note( 'Leaving  tests_file_to_string()' ) ;
+	return ;
+}
+
+sub file_to_string {
+        my  $file  = shift ;
+	if ( ! $file ) { return ; }
+	if ( ! -e $file ) { return ; }
+	if ( ! -f $file ) { return ; }
+	if ( ! -r $file ) { return ; }
+        my @string ;
+        if ( open my $FILE, '<', $file ) {
+		@string = <$FILE> ;
+		close $FILE ;
+		return( join q{}, @string ) ;
+	}else{
+		myprint( "Error reading file $file : $OS_ERROR\n" ) ;
+		return ;
+	}
+}
+
+
+sub tests_string_to_file {
+	note( 'Entering tests_string_to_file()' ) ;
+
+	is( undef, string_to_file(  ),         'string_to_file: no args => undef' ) ;
+	is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ;
+	is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ;
+	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ;
+	is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ;
+	is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ;
+
+	SKIP: {
+                Readonly my $NB_UNX_tests_string_to_file => 1 ;
+                skip( 'Not on Unix', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME) ;
+		is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ;
+	}
+
+	note( 'Leaving  tests_string_to_file()' ) ;
+	return ;
+}
+
 sub string_to_file {
-	my( $string, $file ) = @_ ;
-	sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) or die_clean( "$! $file" ) ;
-	print FILE $string ;
-	close FILE ;
-	return 1 ;
+        my( $string, $file ) = @_ ;
+	if( ! defined $string ) { return ; }
+	if( ! defined $file )   { return ; }
+
+	if ( ! -e $file && ! -w dirname( $file ) ) {
+		myprint( "string_to_file: directory of $file is not writable\n" ) ;
+		return ;
+	}
+
+        if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) {
+		myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ;
+		return ;
+	}
+        print FILE $string ;
+        close FILE ;
+        return $string ;
 }
 
 q^
@@ -7383,18 +8664,18 @@ Based on David Carter discussion, to do:
 * Now always "return( $string, $error )". Descriptions below.
 OK * Still    capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
 OK * Now also capture STDERR via "2> $error_tmpfile"  to finish in $error  and "return( $string, $error )"
-OK * in case of CHILD_ERROR, return( undef, $error ) 
+OK * in case of CHILD_ERROR, return( undef, $error )
   and print $error, with folder/UID/maybeSubject context,
   on console and at the end with the final error listing. Count this as a sync error.
 * in case of good command, take final $string as is, unless void. In case $error with value then print it.
 * in case of good command and final $string empty, consider it like CHILD_ERROR =>
   return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
-  on console and at the end with the final error listing. Count this as a sync error. 
+  on console and at the end with the final error listing. Count this as a sync error.
 ^ if 0 ; # End of multiline comment.
 
 sub pipemess {
-	my ( $string, @commands ) = @_ ;
-	my $error = q{} ;
+        my ( $string, @commands ) = @_ ;
+        my $error = q{} ;
         foreach my $command ( @commands ) {
                 my $input_tmpfile  = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
                 my $output_tmpfile = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
@@ -7404,22 +8685,22 @@ sub pipemess {
                 my $is_command_ko = $CHILD_ERROR ;
                 my $error_cmd = file_to_string( $error_tmpfile ) ;
                 chomp( $error_cmd ) ;
-		$string = file_to_string( $output_tmpfile ) ;
+                $string = file_to_string( $output_tmpfile ) ;
                 my $string_len = length( $string ) ;
                 unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
 
-		if ( $is_command_ko or ( ! $string_len ) ) {
-			my $cmd_exit_value = $CHILD_ERROR >> 8 ;
-			my $cmd_end_signal = $CHILD_ERROR & 127 ;
+                if ( $is_command_ko or ( ! $string_len ) ) {
+                        my $cmd_exit_value = $CHILD_ERROR >> 8 ;
+                        my $cmd_end_signal = $CHILD_ERROR & 127 ;
                         my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
                         my $error_log = qq{Failure: --pipemess command "$command" ended with$signal_log "$string_len" characters exit value "$cmd_exit_value" and STDERR "$error_cmd"\n} ;
-			myprint( $error_log ) ;
-			if ( wantarray ) {
+                        myprint( $error_log ) ;
+                        if ( wantarray ) {
                                 return @{ [ undef, $error_log ] }
                         }else{
                                 return ;
                         }
-		}
+                }
                 if ( $error_cmd ) {
                         $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
                         myprint(  qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
@@ -7436,39 +8717,41 @@ sub pipemess {
 
 
 sub tests_pipemess {
+	note( 'Entering tests_pipemess()' ) ;
 
-	SKIP: {
+
+        SKIP: {
                 Readonly my $NB_WIN_tests_pipemess => 3 ;
-		skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
-		# Windows
-		# "type" command does not accept redirection of STDIN with <
-		# "sort" does
-		ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
-		ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
-		# command not found
-		#diag( 'Warning and failure about cacaprout are on purpose' ) ;
-		ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
+                skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
+                # Windows
+                # "type" command does not accept redirection of STDIN with <
+                # "sort" does
+                ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
+                ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
+                # command not found
+                #diag( 'Warning and failure about cacaprout are on purpose' ) ;
+                ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
 
-	} ;
+        } ;
 
         my ( $stringT, $errorT ) ;
 
-	SKIP: {
+        SKIP: {
                 Readonly my $NB_UNX_tests_pipemess => 25 ;
-		skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
-		# Unix
-		ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
+                skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
+                # Unix
+                ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
 
-		ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
+                ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
 
-		ok( "     1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
-		ok( "     1\tnumberize\n     2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
+                ok( "     1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
+                ok( "     1\tnumberize\n     2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
 
-		ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
+                ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
 
-		# command not found
-		#diag( 'Warning and failure about cacaprout are on purpose' ) ;
-		is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
+                # command not found
+                #diag( 'Warning and failure about cacaprout are on purpose' ) ;
+                is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
 
                 # success with true but no output at all
                 is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
@@ -7476,376 +8759,714 @@ sub tests_pipemess {
                 # failure with false and no output at all
                 is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
 
-		# Failure since pipemess is not a real pipe, so first cat wait for standard input
-		is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
+                # Failure since pipemess is not a real pipe, so first cat wait for standard input
+                is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
 
 
                 ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
                 is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
                 is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
-                
+
                 ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
                 is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
-                like( $errorT, qr{Failure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""},  'pipemess: list context, true but no output, error' ) ;
+                like( $errorT, qr{\QFailure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""\E}xm,  'pipemess: list context, true but no output, error' ) ;
 
                 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
                 is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
-                like( $errorT, qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""},  'pipemess: list context, false and no output, error' ) ;
+                like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
+		'pipemess: list context, false and no output, error' ) ;
 
-                ( $stringT, $errorT ) = pipemess( 'dontcare', 'echo -n blablabla' ) ;
+                ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ;
                 is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
                 is( $errorT, q{},  'pipemess: list context, "echo blablabla", error' ) ;
 
-                
+
                 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
                 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
-                like( $errorT,  qr{blablabla"$},  'pipemess: list context, "no output STDERR blablabla", error' ) ;
+                like( $errorT,  qr{blablabla"}xm,  'pipemess: list context, "no output STDERR blablabla", error' ) ;
 
 
                 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
                 is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
-                like( $errorT,  qr{blablabla"$},  'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
+                like( $errorT,  qr{blablabla"}xm,  'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
 
                 ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
                 is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
-                like( $errorT,  qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""},  'pipemess: list context, "false then STDERR blablabla", error' ) ;
+                like( $errorT,  qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm,
+		'pipemess: list context, "false then STDERR blablabla", error' ) ;
 
                 ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
-                like( $stringT, qr{rrrrr}, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
-                like( $errorT,  qr{STDERR.*error_blablabla},  'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
+                like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
+                like( $errorT,  qr{STDERR.*error_blablabla}xm,  'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
 
-	}
+        }
 
         ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
         is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
-        like( $errorT, qr{Failure: --pipemess command "cacaprout" ended with "0" characters exit value.*}, 'pipemess: list context, cacaprout not found, error' ) ;
+        like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm,
+	'pipemess: list context, cacaprout not found, error' ) ;
 
-	return ;
+	note( 'Leaving  tests_pipemess()' ) ;
+        return ;
 }
 
+
+
 sub tests_is_a_release_number {
-	ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_1), 'is_a_release_number 1.351') ;
-	ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_2), 'is_a_release_number 42.4242') ;
-	ok(is_a_release_number(imapsync_version()), 'is_a_release_number imapsync_version()') ;
-	ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla') ;
-	return ;
+	note( 'Entering tests_is_a_release_number()' ) ;
+
+        ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_1), 'is_a_release_number 1.351') ;
+        ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_2), 'is_a_release_number 42.4242') ;
+        ok(is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version(  )') ;
+        ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla') ;
+
+	note( 'Leaving  tests_is_a_release_number()' ) ;
+        return ;
 }
 
 sub is_a_release_number {
-	my $number = shift;
-
-	return( $number =~ m{^\d+\.\d+$}xo ) ;
-}
-
-sub check_last_release {
-
-	my $public_release = not_long_imapsync_version_public(  ) ;
-	$debug and myprint( "check_last_release: [$public_release]\n"  ) ;
-	return('unknown') if ($public_release eq 'unknown') ;
-	return('timeout') if ($public_release eq 'timeout') ;
-	return('unknown') if (! is_a_release_number( $public_release ) ) ;
-
-	my $imapsync_here  = imapsync_version();
-
-	if ($public_release > $imapsync_here) {
-		return("New imapsync release $public_release available");
-	}else{
-		return( 'This imapsync is up to date') ;
-	}
-}
-
-sub imapsync_version  {
-	my $rcs_imapsync = '$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $ ' ;
-        my $imapsync_version ;
-
-	if ( $rcs_imapsync =~ m{,v\s+(\d+\.\d+)}xo ) {
-		$imapsync_version = $1
-        } else {
-                $imapsync_version = 'UNKNOWN' ;
-        }
-	return( $imapsync_version ) ;
-}
-
-sub tests_imapsync_basename {
-	ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
-	ok( 'blabla'   ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
-	return ;
-}
-
-sub imapsync_basename {
-
-	return basename($0);
+        my $number = shift;
 
+        return( $number =~ m{^\d+\.\d+$}xo ) ;
 }
 
 sub imapsync_version_public {
 
-	my $local_version = imapsync_version();
-	my $imapsync_basename = imapsync_basename();
-	my $agent_info = "$OSNAME system, perl "
-		. mysprintf( '%vd', $PERL_VERSION)
-		. ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
-		. " $imapsync_basename";
-	my $sock = IO::Socket::INET->new(
-		PeerAddr => 'imapsync.lamiral.info',
-		PeerPort => 80,
-		Proto    => 'tcp',
+        my $local_version = imapsync_version( $sync ) ;
+        my $imapsync_basename = imapsync_basename(  ) ;
+        my $agent_info = "$OSNAME system, perl "
+                . mysprintf( '%vd', $PERL_VERSION)
+                . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
+                . " $imapsync_basename" ;
+        my $sock = IO::Socket::INET->new(
+                PeerAddr => 'imapsync.lamiral.info',
+                PeerPort => 80,
+                Proto    => 'tcp',
                 ) ;
-	return( 'unknown' ) if not $sock ;
-	print $sock
-		"GET /prj/imapsync/VERSION HTTP/1.0\n",
-		"User-Agent: imapsync/$local_version ($agent_info)\n",
-		"Host: ks.lamiral.info\n\n";
-	my @line = <$sock>;
-	close $sock ;
-	my $last_release = $line[$LAST];
-	chomp $last_release ;
-	return($last_release) ;
+        return( 'unknown' ) if not $sock ;
+        print $sock
+                "GET /prj/imapsync/VERSION HTTP/1.0\r\n",
+                "User-Agent: imapsync/$local_version ($agent_info)\r\n",
+                "Host: ks.lamiral.info\r\n\r\n" ;
+        my @line = <$sock> ;
+        close $sock ;
+        my $last_release = $line[$LAST] ;
+        chomp $last_release ;
+        return( $last_release ) ;
 }
 
-sub not_long_imapsync_version_public {
-	#myprint( "Entering not_long_imapsync_version_public\n" ) ;
+sub not_long_imapsync_version_public  {
+        #myprint( "Entering not_long_imapsync_version_public\n" ) ;
 
-	my $val;
+	my $fake = shift ;
+	if ( $fake ) { return $fake }
 
-	# Doesn't work with gethostbyname (see perlipc)
-	#local $SIG{ALRM} = sub { die "alarm\n" };
+        my $val ;
 
-	if ('MSWin32' eq $OSNAME) {
-		local $SIG{ALRM} = sub { die "alarm\n" };
-	}else{
+        # Doesn't work with gethostbyname (see perlipc)
+        #local $SIG{ALRM} = sub { die "alarm\n" } ;
 
-        	POSIX::sigaction(SIGALRM,
+        if ('MSWin32' eq $OSNAME) {
+                local $SIG{ALRM} = sub { die "alarm\n" } ;
+        }else{
+
+                POSIX::sigaction(SIGALRM,
                          POSIX::SigAction->new(sub { croak 'alarm' } ) )
-        		or myprint( "Error setting SIGALRM handler: $!\n"  ) ;
-	}
+                        or myprint( "Error setting SIGALRM handler: $OS_ERROR\n"  ) ;
+        }
 
-	my $ret = eval {
-		alarm 3 ;
-		{
-			$val = imapsync_version_public(  ) ;
+        my $ret = eval {
+                alarm 3 ;
+                {
+                        $val = imapsync_version_public(  ) ;
                         #sleep 4 ;
-			#myprint( "End of imapsync_version_public\n"  ) ;
-		}
-		alarm 0 ;
+                        #myprint( "End of imapsync_version_public\n"  ) ;
+                }
+                alarm 0 ;
                 1 ;
-	} ;
+        } ;
         #myprint( "eval [$ret]\n"  ) ;
-	if ( ( not $ret ) or $@ ) {
-		#myprint( "$@" ) ;
-		if ($@ =~ /alarm/) {
-		# timed out
-			return('timeout');
-		}else{
-			alarm 0 ;
-			return('unknown'); # propagate unexpected errors
-		}
-	}else {
-	# Good!
-		return($val);
-	}
+        if ( ( not $ret ) or $EVAL_ERROR ) {
+                #myprint( "$EVAL_ERROR" ) ;
+                if ($EVAL_ERROR =~ /alarm/) {
+                # timed out
+                        return('timeout') ;
+                }else{
+                        alarm 0 ;
+                        return( 'unknown' ) ; # propagate unexpected errors
+                }
+        }else {
+        # Good!
+                return( $val ) ;
+        }
 }
 
+sub tests_not_long_imapsync_version_public {
+	note( 'Entering tests_not_long_imapsync_version_public()' ) ;
+
+
+	is( 1, is_a_release_number( not_long_imapsync_version_public(  ) ),
+		'not_long_imapsync_version_public: public release is a number' ) ;
+
+	note( 'Leaving  tests_not_long_imapsync_version_public()' ) ;
+	return ;
+}
+
+sub check_last_release {
+	my $fake = shift ;
+        my $public_release = not_long_imapsync_version_public( $fake ) ;
+        $debug and myprint( "check_last_release: [$public_release]\n"  ) ;
+        return( 'Imapsync public release is unknown' ) if ( $public_release eq 'unknown' ) ;
+        return( 'Imapsync public release is unknown (timeout)' ) if ( $public_release eq 'timeout' ) ;
+        return( "Imapsync public release is unknown ($public_release)" ) if ( ! is_a_release_number( $public_release ) ) ;
+
+        my $imapsync_here  = imapsync_version( $sync ) ;
+
+        if ( $public_release > $imapsync_here ) {
+                return(
+			"New imapsync release $public_release available to replace this $imapsync_here\n"
+			. "Get it at https://imapsync.lamiral.info/dist/") ;
+        }else{
+                return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )") ;
+        }
+
+	return('really unknown') ; # Should never arrive here
+}
+
+sub tests_check_last_release {
+	note( 'Entering tests_check_last_release()' ) ;
+
+	diag( check_last_release( 1.1 ) ) ;
+	like( check_last_release( 1.1 ), qr/\Qup to date\E/mxs, 'check_last_release: up to date' ) ;
+	like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ;
+	diag( check_last_release( 999.999 ) ) ;
+	like( check_last_release( 999.999 ), qr/available/mxs, 'check_last_release: update available' ) ;
+	like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: update available, include number' ) ;
+	diag( check_last_release(  ) ) ;
+	is( 'Imapsync public release is unknown', check_last_release( 'unknown' ), 'check_last_release: unknown' ) ;
+	is( 'Imapsync public release is unknown (timeout)', check_last_release( 'timeout' ), 'check_last_release: timeout' ) ;
+	is( 'Imapsync public release is unknown (lalala)', check_last_release( 'lalala' ), 'check_last_release: lalala' ) ;
+
+	note( 'Leaving  tests_check_last_release()' ) ;
+	return ;
+}
+
+sub imapsync_version  {
+        my $mysync = shift ;
+        my $rcs = $mysync->{rcs} ;
+        my $imapsync_version ;
+
+        $imapsync_version = version_from_rcs( $rcs ) ;
+        
+        return( $imapsync_version ) ;
+}
+
+
+sub tests_version_from_rcs {
+	note( 'Entering tests_version_from_rcs()' ) ;
+
+	is( undef, version_from_rcs(  ), 'version_from_rcs: no args => UNKNOWN' ) ;
+	is( 1.831, version_from_rcs( q{imapsync,v 1.831 2017/08/27} ), 'version_from_rcs: imapsync,v 1.831 2017/08/27 => 1.831' ) ;
+	is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs:  1.831  => UNKNOWN' ) ;
+
+	note( 'Leaving  tests_version_from_rcs()' ) ;
+	return ;
+}
+
+
+sub version_from_rcs {
+
+        my $rcs = shift ;
+        if ( ! $rcs ) { return ; }
+        
+        my $version = 'UNKNOWN' ;
+
+        if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) {
+                $version = $1
+        }
+        
+        return( $version ) ;
+}
+
+
+sub tests_imapsync_basename {
+	note( 'Entering tests_imapsync_basename()' ) ;
+
+        ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
+        ok( 'blabla'   ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
+
+	note( 'Leaving  tests_imapsync_basename()' ) ;
+        return ;
+}
+
+sub imapsync_basename  {
+
+        return basename( $PROGRAM_NAME ) ;
+
+}
+
+
 sub localhost_info {
 
-	my($infos) = join q{},
-	    "Here is a [$OSNAME] system (",
-	    join(q{ },
-	         uname(),
-	         ),
+        my( $infos ) = join q{},
+            "Here is " . hostname() . ", a " . memory_available(  ) . " [$OSNAME] system (",
+            join(q{ },
+                 uname(),
+                 ),
                  ")\n",
-	         'with Perl ',
-	         mysprintf( '%vd', $PERL_VERSION),
-	         " Mail::IMAPClient $Mail::IMAPClient::VERSION",
+                 'with Perl ',
+                 mysprintf( '%vd ', $PERL_VERSION),
+                 "and Mail::IMAPClient $Mail::IMAPClient::VERSION",
              ;
-	return($infos) ;
+        return( $infos ) ;
 }
 
+sub tests_cpu_number {
+	note( 'Entering tests_cpu_number()' ) ;
+
+	ok( 1 <= cpu_number(  ), "cpu_number: 1 or more" ) ;
+
+	note( 'Leaving  tests_cpu_number()' ) ;
+	return ;
+}
+
+sub cpu_number {
+	my @cpuinfo ;
+
+	# Well, here 1 is better than 0 or undef
+	my $cpu_number = 1 ; # Default value, erased if better found
+
+	if ( $ENV{"NUMBER_OF_PROCESSORS"} ) {
+		# might be under a Windows system
+		$cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ;
+		$debug and myprint( "Number of processors found by envvar NUMBER_OF_PROCESSORS: $cpu_number\n" ) ;
+		return $cpu_number ;
+	}
+
+	if ( 'darwin' eq $OSNAME ) {
+		$cpu_number = `sysctl -n hw.ncpu` ;
+		chomp( $cpu_number ) ;
+		return $cpu_number ;
+	}
+
+	if ( ! -e '/proc/cpuinfo' ) {
+		$debug and myprint( "Number of processors not found so use: $cpu_number\n" ) ;
+		return $cpu_number ;
+	}
+
+	@cpuinfo = file_to_array( '/proc/cpuinfo' ) ;
+	if ( @cpuinfo ) {
+		$cpu_number = grep { /^processor/mxs } @cpuinfo ;
+	}
+	$debug and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ;
+	return $cpu_number ;
+}
+
+
+
+sub tests_loadavg {
+	note( 'Entering tests_loadavg()' ) ;
+
+
+	SKIP: {
+		skip( 'Tests for darwin', 2 ) if ('darwin' ne $OSNAME) ;
+		is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
+		is_deeply( [ '0.11', '0.22', '0.33' ],
+		[ loadavg( 'W/t/loadavg.out' ) ],
+		'loadavg W/t/loadavg.out => 0.11 0.22 0.33' ) ;
+	} ;
+
+	SKIP: {
+		skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ;
+		is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ;
+		ok( loadavg( ), 'loadavg: no args' ) ;
+
+		is_deeply( [ '0.39', '0.30', '0.37', '1/602' ],
+		[ loadavg( '0.39 0.30 0.37 1/602 6073' ) ],
+		'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ;
+	} ;
+
+	SKIP: {
+		skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ;
+		is_deeply( [ 0 ],
+		[ loadavg( ) ],
+		'loadavg on MSWin32 => 0' ) ;
+
+	} ;
+
+	note( 'Leaving  tests_loadavg()' ) ;
+	return ;
+}
+
+
+sub loadavg {
+	if ( 'linux' eq $OSNAME ) {
+		return ( loadavg_linux( @ARG ) ) ;
+	}
+	if ( 'darwin' eq $OSNAME ) {
+		return ( loadavg_darwin( @ARG ) ) ;
+	}
+	if ( 'MSWin32' eq $OSNAME ) {
+		return ( loadavg_windows( @ARG ) ) ;
+	}
+	return( 'unknown' ) ;
+
+}
+
+sub loadavg_linux {
+	my $line = shift ;
+
+	if ( ! $line ) {
+                $line = firstline( '/proc/loadavg'  ) or return ;
+	}
+
+	my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ;
+	if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) {
+		$debug and print "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ;
+		return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ;
+	}
+	return ;
+}
+
+
+sub loadavg_darwin {
+	my $file = shift ;
+	# Example of output of command "sysctl vm.loadavg":
+	# vm.loadavg: { 0.15 0.08 0.08 }
+	my $loadavg ;
+
+	if ( ! defined $file ) {
+		eval {
+			$loadavg = `/usr/sbin/sysctl vm.loadavg` ;
+			#myprint( "LOADAVG DARWIN: $loadavg\n" ) ;
+		} ;
+		if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
+	}else{
+		$loadavg = firstline( $file ) or return ;
+	}
+
+	my ( $avg_1_min, $avg_5_min, $avg_15_min )
+	= $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ;
+	$debug and print "System load: $avg_1_min $avg_5_min $avg_15_min\n" ;
+	return ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
+}
+
+sub loadavg_windows {
+	my $file = shift ;
+	# Example of output of command "wmic cpu get loadpercentage":
+	# LoadPercentage
+        # 12
+	my $loadavg ;
+
+	if ( ! defined $file ) {
+		eval {
+			#$loadavg = `CMD wmic cpu get loadpercentage` ;
+			$loadavg = "LoadPercentage\n0\n" ;
+			#myprint( "LOADAVG WIN: $loadavg\n" ) ;
+		} ;
+		if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; }
+	}else{
+		$loadavg = file_to_string( $file ) or return ;
+		#myprint( "$loadavg" ) ;
+	}
+	$loadavg =~ /LoadPercentage\n(\d+)/xms ;
+	my $num = $1 ;
+	$num /= 100 ;
+
+	$debug and myprint( "System load: $num\n" ) ;
+	return ( $num ) ;
+}
+
+
+
+
+
+
+sub tests_load_and_delay {
+	note( 'Entering tests_load_and_delay()' ) ;
+
+	is( undef, load_and_delay(  ), 'load_and_delay: no args => undef ' ) ;
+	is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ;
+	is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ;
+	is(  0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ;
+	is(  0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ;
+	is(  0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m     is 2 => ok ' ) ;
+
+	is(  0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ;
+	is(  0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ;
+	is(  0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ;
+	is(  0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
+	is(  1, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
+	is(  1, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
+	is(  5, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
+	is( 15, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
+
+	is(  0, load_and_delay( 4, 0, 2, 2 ), 'load_and_delay: four core, load1m=0 load5m=2 load15m=2 => 0 ' ) ;
+	is(  1, load_and_delay( 4, 8, 0, 0 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=0 => 1 ' ) ;
+	is(  1, load_and_delay( 4, 8, 0, 2 ), 'load_and_delay: four core, load1m=2 load5m=0 load15m=2 => 1 ' ) ;
+	is(  5, load_and_delay( 4, 8, 8, 0 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=0 => 5 ' ) ;
+	is( 15, load_and_delay( 4, 8, 8, 8 ), 'load_and_delay: four core, load1m=2 load5m=2 load15m=2 => 15 ' ) ;
+	is( 15, load_and_delay( 4, 8, 8, 8, 'lalala' ), 'load_and_delay: five arguments is ok' ) ;
+
+	note( 'Leaving  tests_load_and_delay()' ) ;
+	return ;
+}
+
+sub load_and_delay {
+	# Basically return 0 if load is not heavy, ie <= 1 per processor
+
+	if ( 4 > scalar @ARG ) { return ; }
+
+	my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ;
+
+	if ( 0 == $cpu_num ) { return ; }
+
+	# Let divide by number of cores
+	( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ;
+	# One of avg ok => ok, for now it is a OR
+	if ( $avg_1_min <= 1 ) { return 0 ; }
+	if ( $avg_5_min <= 1 ) { return 1 ; } # Retry in 1 minute
+	if ( $avg_15_min <= 1 ) { return 5 ; } # Retry in 5 minutes
+	return 15 ; # Retry in 15 minutes
+}
+
+sub memory_available {
+	# / ( 1000 ** 3 )
+	return(
+		sprintf( "%.1f GiB", Sys::MemInfo::get("totalmem") / ( 1024 ** 3 ) )
+	) ;
+}
 sub memory_consumption {
-	# memory consumed by imapsync until now in bytes
-	return( ( memory_consumption_of_pids(  ) )[0] );
+        # memory consumed by imapsync until now in bytes
+        return( ( memory_consumption_of_pids(  ) )[0] );
 }
 
 sub tests_memory_consumption {
+	note( 'Entering tests_memory_consumption()' ) ;
 
-	like( memory_consumption(  ),  qr{\d+},'memory_consumption no args') ;
-	like( memory_consumption( 1 ), qr{\d+},'memory_consumption 1') ;
-	like( memory_consumption( $PROCESS_ID ), qr{\d+},"memory_consumption_of_pids $PROCESS_ID") ;
+        like( memory_consumption(  ),  qr{\d+}xms,'memory_consumption no args') ;
+        like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ;
+        like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ;
 
-	like( memory_consumption_ratio(), qr{\d+},   'memory_consumption_ratio' ) ;
-	like( memory_consumption_ratio(1), qr{\d+},  'memory_consumption_ratio 1' ) ;
-	like( memory_consumption_ratio(10), qr{\d+}, 'memory_consumption_ratio 10' ) ;
+        like( memory_consumption_ratio(), qr{\d+}xms,   'memory_consumption_ratio' ) ;
+        like( memory_consumption_ratio(1), qr{\d+}xms,  'memory_consumption_ratio 1' ) ;
+        like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ;
 
-	like( memory_consumption(), qr{\d+}, "memory_consumption\n" ) ;
-	return ;
+        like( memory_consumption(), qr{\d+}xms, "memory_consumption\n" ) ;
+
+	note( 'Leaving  tests_memory_consumption()' ) ;
+        return ;
 }
 
 
 
 sub memory_consumption_of_pids {
 
-	my @pid = @_;
-	@pid = (@pid) ? @pid : ($PROCESS_ID) ;
+        my @pid = @_;
+        @pid = (@pid) ? @pid : ($PROCESS_ID) ;
 
-	#myprint( "PIDs: @pid\n" ) ;
-	my @val;
-	if ('MSWin32' eq $OSNAME) {
-		@val = memory_consumption_of_pids_win32(@pid);
-	}else{
-		# Unix
-		my @ps = qx{ ps -o vsz -p @pid } ;
+        #myprint( "PIDs: @pid\n" ) ;
+        my @val;
+        if ('MSWin32' eq $OSNAME) {
+                @val = memory_consumption_of_pids_win32(@pid);
+        }else{
+                # Unix
+                my @ps = qx{ ps -o vsz -p @pid } ;
                 #myprint( @ps ) ;
                 #my @ps = backtick( "ps -o vsz -p @pid" ) ;
-		shift @ps; # First line is column name "VSZ"
-		chomp @ps;
-		# convert to octets
-                
-		@val = map { $_ * $KIBI } @ps;
-	}
-	return( @val ) ;
+                shift @ps; # First line is column name "VSZ"
+                chomp @ps;
+                # convert to octets
+
+                @val = map { $_ * $KIBI } @ps;
+        }
+        return( @val ) ;
 }
 
 sub memory_consumption_of_pids_win32 {
-	# Windows
-	my @PID = @_;
-	my %PID;
-	# hash of pids as key values
-	map { $PID{$_}++ } @PID;
+        # Windows
+        my @PID = @_;
+        my %PID;
+        # hash of pids as key values
+        map { $PID{$_}++ } @PID;
 
-	# Does not work but should reading the tasklist documentation
-	#@ps = qx{ tasklist /FI "PID eq @PID" };
+        # Does not work but should reading the tasklist documentation
+        #@ps = qx{ tasklist /FI "PID eq @PID" };
 
-	my @ps = qx{ tasklist /NH /FO CSV } ;
+        my @ps = qx{ tasklist /NH /FO CSV } ;
         #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
-	#myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
-	my @val;
-	foreach my $line (@ps) {
-		my($name, $pid, $mem) = (split ',', $line )[0,1,4];
-		next if (! $pid);
-		#myprint( "[$name][$pid][$mem]" ) ;
-		if ($PID{remove_qq($pid)}) {
-			#myprint( "MATCH !\n" ) ;
-			chomp $mem ;
-			$mem = remove_qq($mem);
-			$mem = remove_Ko($mem);
-			$mem = remove_not_num($mem);
-			#myprint( "[$mem]\n" ) ;
-			push @val, $mem * $KIBI;
-		}
-	}
-	return(@val);
+        #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
+        my @val;
+        foreach my $line (@ps) {
+                my($name, $pid, $mem) = (split ',', $line )[0,1,4];
+                next if (! $pid);
+                #myprint( "[$name][$pid][$mem]" ) ;
+                if ($PID{remove_qq($pid)}) {
+                        #myprint( "MATCH !\n" ) ;
+                        chomp $mem ;
+                        $mem = remove_qq($mem);
+                        $mem = remove_Ko($mem);
+                        $mem = remove_not_num($mem);
+                        #myprint( "[$mem]\n" ) ;
+                        push @val, $mem * $KIBI;
+                }
+        }
+        return(@val);
 }
 
 sub backtick {
-	my $command = shift ;
-	my ( $writer, $reader, $err ) ;
+        my $command = shift ;
+
+	if ( ! $command ) { return ; }
+
+        my ( $writer, $reader, $err ) ;
         my @output ;
-        open3( $writer, $reader, $err, $command ) ;
-        @output = <$reader>;  #Output here
+        my $pid ;
+	eval {
+		$pid = open3( $writer, $reader, $err, $command ) ;
+	} ;
+
+	if ( ! $pid  ) { return ; }
+	waitpid( $pid, 0 ) ;
+        @output = <$reader>;  # Output here
+	#
         #my @errors = <$err>;    #Errors here, instead of the console
+	if ( not @output ) { return ; }
         $debugdev and myprint( @output  ) ;
-        return( @output ) ;
+	if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; }
+	if ( wantarray ) {
+		return( @output ) ;
+	} else {
+		return( join( q{}, @output) ) ;
+	}
 }
 
 sub tests_backtick {
+	note( 'Entering tests_backtick()' ) ;
+
+	is( undef, backtick( ), 'backtick: no args' ) ;
+	is( undef, backtick( q{} ), 'backtick: empty command' ) ;
 
         SKIP: {
-		skip( 'Tests for MSWin32', 3 ) if ('MSWin32' ne $OSNAME) ;
-		my @output ;
-		@output = backtick( 'echo Hello World!' ) ;
-		# Add \r on Windows.
-		ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
-		$debug and myprint( "[@output]"  ) ;
-		@output = backtick( 'echo Hello & echo World!' ) ;
-		ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World!' ) ;
-		ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World!' ) ;
-		$debug and myprint( "[@output][$output[0]][$output[1]]"  ) ;
+                skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ;
+                my @output ;
+                @output = backtick( 'echo Hello World!' ) ;
+                # Add \r on Windows.
+                ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
+                $debug and myprint( "[@output]"  ) ;
+                @output = backtick( 'echo Hello & echo World!' ) ;
+                ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ;
+                ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ;
+                $debug and myprint( "[@output][$output[0]][$output[1]]"  ) ;
+		# Scalar context
+		ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ),
+		'backtick: echo Hello World! scalar' ) ;
+		ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ),
+		'backtick: echo Hello & echo World! scalar 2 lines' ) ;
         } ;
-	SKIP: {
-		skip( 'Tests for Unix', 3 ) if ('MSWin32' eq $OSNAME) ;
-		my @output ;
-		@output = backtick( 'echo Hello World!' ) ;
-		ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
-		$debug and myprint( "[@output]"  ) ;
-		@output = backtick( "echo Hello\necho World!" ) ;
-		ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World!' ) ;
-		ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World!' ) ;
-		$debug and myprint( "[@output]"  ) ;
-	}
+        SKIP: {
+                skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ;
+		is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ;
+		# Array context
+                my @output ;
+                @output = backtick( 'echo Hello World!' ) ;
+                ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
+                $debug and myprint( "[@output]"  ) ;
+                @output = backtick( "echo Hello\necho World!" ) ;
+                ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ;
+                ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ;
+                $debug and myprint( "[@output]"  ) ;
+		# Scalar context
+		ok( "Hello World!\n" eq backtick( 'echo Hello World!' ),
+		'backtick: echo Hello World! scalar' ) ;
+		ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ),
+		'backtick: echo Hello; echo World! scalar 2 lines' ) ;
+		# Return error positive value, that's ok
+		is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ;
+        }
+
+	note( 'Leaving  tests_backtick()' ) ;
         return ;
 }
 
 sub remove_not_num {
 
-	my $string = shift;
-	$string =~ tr/0-9//cd;
-	#myprint( "tr [$string]\n" ) ;
-	return($string);
+        my $string = shift ;
+        $string =~ tr/0-9//cd ;
+        #myprint( "tr [$string]\n" ) ;
+        return( $string ) ;
 }
 
 sub tests_remove_not_num {
+	note( 'Entering tests_remove_not_num()' ) ;
 
-	ok('123' eq remove_not_num(123), 'remove_not_num( 123 )' ) ;
-	ok('123' eq remove_not_num('123'), q{remove_not_num( '123' )} ) ;
-	ok('123' eq remove_not_num('12 3'), q{remove_not_num( '12 3' )} ) ;
-	ok('123' eq remove_not_num('a 12 3 Ko'), q{remove_not_num( 'a 12 3 Ko' )} ) ;
-	return ;
+        ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ;
+        ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ;
+        ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ;
+        ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ;
+
+	note( 'Leaving  tests_remove_not_num()' ) ;
+        return ;
 }
 
 sub remove_Ko {
-	my $string = shift;
-	if ($string =~ /^(.*)\sKo$/xo) {
-		return($1);
-	}else{
-		return($string);
-	}
+        my $string = shift;
+        if ($string =~ /^(.*)\sKo$/xo) {
+                return($1);
+        }else{
+                return($string);
+        }
 }
 
 sub remove_qq {
-	my $string = shift;
-	if ($string =~ /^"(.*)"$/xo) {
-		return($1);
-	}else{
-		return($string);
-	}
+        my $string = shift;
+        if ($string =~ /^"(.*)"$/xo) {
+                return($1);
+        }else{
+                return($string);
+        }
 }
 
 sub memory_consumption_ratio {
 
-	my ($base) = @_;
-	$base ||= 1;
-	my $consu = memory_consumption();
-	return($consu / $base);
+        my ($base) = @_;
+        $base ||= 1;
+        my $consu = memory_consumption();
+        return($consu / $base);
 }
 
 
 sub date_from_rcs {
-	my $d = shift ;
+        my $d = shift ;
 
-	my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
+        my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
         if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
                 # Handles the following format
                 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
-		#myprint( "$d\n"  ) ;
+                #myprint( "$d\n"  ) ;
                 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n"  ) ;
                 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
                 $month = $num2mon{$month} ;
                 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
-		#myprint( "$d\n"  ) ;
-	}
-	return( $d ) ;
+                #myprint( "$d\n"  ) ;
+        }
+        return( $d ) ;
 }
 
 sub tests_date_from_rcs {
-	ok('19-Sep-2015 16:11:07 +0000'
-	eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
-	return ;
+	note( 'Entering tests_date_from_rcs()' ) ;
+
+        ok('19-Sep-2015 16:11:07 +0000'
+        eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
+
+	note( 'Leaving  tests_date_from_rcs()' ) ;
+        return ;
 }
 
 sub good_date {
         # two incoming formats:
         # header    Tue, 24 Aug 2010 16:00:00 +0200
-	# internal       24-Aug-2010 16:00:00 +0200
+        # internal       24-Aug-2010 16:00:00 +0200
 
         # outgoing format: internal date format
         #   24-Aug-2010 16:00:00 +0200
@@ -7853,18 +9474,18 @@ sub good_date {
     my $d = shift ;
     return(q{}) if not defined $d;
 
-	SWITCH: {
-    	if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
-		#myprint( "internal: [$1][$2][$3][$4]\n"  ) ;
-		my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
-		$day_1 = '0' if ($day_1 eq q{}) ;
-		$zone  = ' +0000'  if not defined $zone ;
-		$d = $day_1 . $date_rest . $hour . $zone ;
+        SWITCH: {
+        if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
+                #myprint( "internal: [$1][$2][$3][$4]\n"  ) ;
+                my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
+                $day_1 = '0' if ($day_1 eq q{}) ;
+                $zone  = ' +0000'  if not defined $zone ;
+                $d = $day_1 . $date_rest . $hour . $zone ;
                 last SWITCH ;
         }
 
-	if ($d =~ m{(?:\w{3,},\s)?(\d{1,2}),?\s+(\w{3,})\s+(\d{2,4})\s+(\d{1,2})(?::|\.)(\d{1,2})(?:(?::|\.)(\d{1,2}))?\s*((?:\+|-)\d{4})?}xo ) {
-        	# Handles any combination of following formats
+        if ($d =~ m{(?:\w{3,},\s)?(\d{1,2}),?\s+(\w{3,})\s+(\d{2,4})\s+(\d{1,2})(?::|\.)(\d{1,2})(?:(?::|\.)(\d{1,2}))?\s*((?:\+|-)\d{4})?}xo ) {
+                # Handles any combination of following formats
                 # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
                 # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
                 # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
@@ -7886,11 +9507,11 @@ sub good_date {
                 $sec  = mysprintf( '%02d', $sec ) ;
                 $zone = '+0000' if not defined  $zone  ;
                 $d    = "$day-$month-$year $hour:$min:$sec $zone" ;
-		last SWITCH ;
-	}
+                last SWITCH ;
+        }
 
-	if ($d =~ m{(?:.{3})\s(...)\s+(\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})\s(?:\w{3})?\s?(\d{4})}xo ) {
-        	# Handles any combination of following formats
+        if ($d =~ m{(?:.{3})\s(...)\s+(\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})\s(?:\w{3})?\s?(\d{4})}xo ) {
+                # Handles any combination of following formats
                 # Sun Aug 20 11:55:09 2006
                 # Wed Jan 24 11:58:38 MST 2007
                 # Wed Jan  2 08:40:57 2008
@@ -7902,21 +9523,21 @@ sub good_date {
                 $min  = mysprintf( '%02d', $min  ) ;
                 $sec  = mysprintf( '%02d', $sec  ) ;
                 $d    = "$day-$month-$year $hour:$min:$sec +0000" ;
-		last SWITCH ;
-	}
+                last SWITCH ;
+        }
         my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
 
         if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
                 # Handles the following format
                 # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
-		#myprint( "$d\n"  ) ;
+                #myprint( "$d\n"  ) ;
                 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n"  ) ;
                 my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
                 $month = $num2mon{$month} ;
                 $d = "$day-$month-$year $hour:$min:$sec +0000" ;
-		#myprint( "$d\n"  ) ;
-		last SWITCH ;
-	}
+                #myprint( "$d\n"  ) ;
+                last SWITCH ;
+        }
 
         if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
                 # Handles the following format
@@ -7927,11 +9548,11 @@ sub good_date {
                 $year = '20' . $year;
                 $month = $num2mon{$month};
                 $d = "$day-$month-$year $hour:$min:$sec +0000";
-		last SWITCH ;
-	}
+                last SWITCH ;
+        }
 
-	if ($d =~ m{\w{6,},\s(\w{3})\w+\s+(\d{1,2}),\s(\d{4})\s(\d{2}):(\d{2})\s(AM|PM)}xo ) {
-        	# Handles the following format
+        if ($d =~ m{\w{6,},\s(\w{3})\w+\s+(\d{1,2}),\s(\d{4})\s(\d{2}):(\d{2})\s(AM|PM)}xo ) {
+                # Handles the following format
                 # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
 
                 my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
@@ -7940,10 +9561,10 @@ sub good_date {
                 $day = mysprintf( '%02d', $day ) ;
                 $d = "$day-$month-$year $hour:$min:00 +0000" ;
                 last SWITCH ;
-	}
+        }
 
-	if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
-        	# Handles the following format
+        if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
+                # Handles the following format
                 # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
 
                 my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
@@ -7951,20 +9572,20 @@ sub good_date {
                 $day = mysprintf( '%02d', $day ) ;
                 $d = "$day-$month-$year $hour:$min:$sec $zone";
                 last SWITCH ;
-	}
+        }
 
-	if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
-        	# Handles the following format
+        if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
+                # Handles the following format
                 # 21-Jun-2001 - register.com domain transfer email circa 2001
 
                 my ($day, $month, $year) = ($1,$2,$3);
                 $day = mysprintf( '%02d', $day);
                 $d = "$day-$month-$year 11:11:11 +0000";
-		last SWITCH ;
-	}
+                last SWITCH ;
+        }
 
-    	# unknown or unmatch => return same string
-    	return($d);
+        # unknown or unmatch => return same string
+        return($d);
     }
 
     $d = qq("$d") ;
@@ -7973,15 +9594,16 @@ sub good_date {
 
 
 sub tests_good_date {
+	note( 'Entering tests_good_date()' ) ;
 
-	ok(q{} eq good_date(), 'good_date no arg');
-	ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
-	ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
-	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
-	ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
-	ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
-	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200'), 'good_date header SP 1digit zone');
-	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200 (CEST)'), 'good_date header SP 1digit zone');
+        ok(q{} eq good_date(), 'good_date no arg');
+        ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
+        ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
+        ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
+        ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
+        ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
+        ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200'), 'good_date header SP 1digit zone');
+        ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200 (CEST)'), 'good_date header SP 1digit zone');
         ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
         ok('"02-Jan-2008 08:40:57 +0000"' eq good_date('Wed Jan  2 08:40:57 2008'), 'good_date header dice.com support 1digit day');
         ok('"20-Aug-2006 11:55:09 +0000"' eq good_date('Sun Aug 20 11:55:09 2006'), 'good_date header dice.com support 2digit day');
@@ -8002,104 +9624,220 @@ sub tests_good_date {
         ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders');
         ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer');
         ok('"18-Nov-2012 18:34:38 +0100"' eq good_date('Sun, 18 Nov 2012 18:34:38 +0100'), 'good_date pop2imap bug (Westeuropäische Normalzeit)');
-	ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
-	return ;
+        ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
+
+	note( 'Leaving  tests_good_date()' ) ;
+        return ;
 }
 
 
 sub tests_list_keys_in_2_not_in_1 {
+	note( 'Entering tests_list_keys_in_2_not_in_1()' ) ;
 
-	my @list;
-	ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
-	ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
-	ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}');
-	ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}');
-	ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}');
-	ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
-	ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
 
-	return ;
+        my @list;
+        ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
+        ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
+        ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}');
+        ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}');
+        ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}');
+        ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+        ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+
+	note( 'Leaving  tests_list_keys_in_2_not_in_1()' ) ;
+        return ;
 }
 
 sub list_keys_in_2_not_in_1 {
 
-	my $folders1_ref = shift;
-	my $folders2_ref = shift;
-	my @list;
+        my $folders1_ref = shift;
+        my $folders2_ref = shift;
+        my @list;
 
-	foreach my $folder ( sort keys %{ $folders2_ref } ) {
-		next if exists $folders1_ref->{$folder};
-		push @list, $folder;
-	}
-	return(@list);
+        foreach my $folder ( sort keys %{ $folders2_ref } ) {
+                next if exists $folders1_ref->{$folder};
+                push @list, $folder;
+        }
+        return(@list);
 }
 
 
 sub list_folders_in_2_not_in_1 {
 
-	my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ;
-	@h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ;
-	map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
-	@h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ;
+        my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ;
+        @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ;
+        map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
+        @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ;
 
-	return( reverse @h2_folders_not_in_h1 );
+        return( reverse @h2_folders_not_in_h1 );
 }
 
-sub delete_folders_in_2_not_in_1 {
 
-	foreach my $folder (@h2_folders_not_in_1) {
-		if ( defined  $delete2foldersonly  and eval "\$folder !~ $delete2foldersonly" ) {
-			myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n"  ) ;
-			next ;
-		}
-		if ( defined  $delete2foldersbutnot  and eval "\$folder =~ $delete2foldersbutnot" ) {
-			myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n"  ) ;
-			next ;
-		}
-		my $res = $dry ; # always success in dry mode!
-		$imap2->unsubscribe( $folder ) if ( ! $dry ) ;
-		$res = $imap2->delete( $folder ) if ( ! $dry ) ;
-		if ( $res ) {
-			myprint( "Deleted $folder", "$dry_message", "\n"  ) ;
-		}else{
-			myprint( "Deleting $folder failed", "\n"  ) ;
-		}
+sub tests_match {
+	note( 'Entering tests_match()' ) ;
+
+	# undef serie
+	is( undef, match(  ), 'match: no args => undef' ) ;
+	is( undef, match( 'lalala' ), 'match: one args => undef' ) ;
+
+	# This one gives 0 under a binary made by pp
+	# but 1 under "normal" Perl interpreter. So a PAR bug?
+	#is( 1, match( q{}, q{} ),                'match: q{}      =~ q{}      => 1' ) ;
+	
+	is( 1, match( 'lalala', 'lalala' ),      'match: lalala   =~ lalala => 1' ) ;
+	is( 1, match( 'lalala', '^lalala' ),     'match: lalala   =~ ^lalala  => 1' ) ;
+	is( 1, match( 'lalala',  'lalala$' ),    'match: lalala   =~ lalala$  => 1' ) ;
+	is( 1, match( 'lalala', '^lalala$' ),    'match: lalala   =~ ^lalala$ => 1' ) ;
+	is( 1, match( '_lalala_', 'lalala' ),    'match: _lalala_ =~ lalala   => 1' ) ;
+	is( 1, match( 'lalala', '.*' ),          'match: lalala   =~ .*       => 1' ) ;
+	is( 1, match( 'lalala', '.' ),           'match: lalala   =~ .        => 1' ) ;
+	is( 1, match( '/lalala/', '/lalala/' ),  'match: /lalala/ =~ /lalala/ => 1' ) ;
+
+
+	is( 0, match( 'lalala', 'ooo' ),         'match: lalala   =~ ooo      => 0' ) ;
+	is( 0, match( 'lalala', 'lal_ala' ),     'match: lalala   =~ lal_ala  => 0' ) ;
+	is( 0, match( 'lalala', '\.' ),          'match: lalala   =~ \.       => 0' ) ;
+	is( 0, match( 'lalalaX', '^lalala$' ),   'match: lalalaX  =~ ^lalala$ => 0' ) ;
+	is( 0, match( 'lalala', '/lalala/' ),    'match: lalala   =~ /lalala/ => 1' ) ;
+
+	is( 1, match( 'LALALA', '(?i:lalala)' ),           'match: LALALA   =~ (?i:lalala) => 1' ) ;
+
+	is( undef, match( 'LALALA', '(?{`ls /`})' ),       'match: LALALA   =~ (?{`ls /`})       => undef' ) ;
+	is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA   =~ (?{print "CACA"})  => undef' ) ;
+	is( undef, match( 'CACA', '(??{print "CACA"})' ),  'match: CACA     =~ (??{print "CACA"}) => undef' ) ;
+
+	note( 'Leaving  tests_match()' ) ;
+
+	return ;
+}
+
+sub match {
+	my( $var, $regex ) = @ARG ;
+
+	# undef cases
+	if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
+
+	# normal cases
+	if ( eval { $var =~ $regex } ) {
+		return 1 ;
+	}elsif ( $EVAL_ERROR ) {
+		print "Fatal regex $regex\n" ;
+		return ;
+	} else {
+		return 0 ;
 	}
 	return ;
 }
 
+
+sub tests_notmatch {
+	note( 'Entering tests_notmatch()' ) ;
+
+	# undef serie
+	is( undef, notmatch(  ), 'notmatch: no args => undef' ) ;
+	is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ;
+
+	is( 1, notmatch( 'lalala', '/lalala/' ),   'notmatch: lalala   !~ /lalala/ => 1' ) ;
+	is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ;
+	is( 1, notmatch( 'lalala', '/ooo/' ),      'notmatch: lalala   !~ /ooo/    => 1' ) ;
+
+	# This one gives 1 under a binary made by pp
+	# but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match .
+	#is( 0, notmatch( q{}, q{} ),             'notmatch: q{}      !~ q{}      => 0' ) ;
+
+	is( 0, notmatch( 'lalala', 'lalala' ),   'notmatch: lalala   !~ lalala   => 0' ) ;	
+	is( 0, notmatch( 'lalala', '^lalala' ),  'notmatch: lalala   !~ ^lalala  => 0' ) ;
+	is( 0, notmatch( 'lalala',  'lalala$' ), 'notmatch: lalala   !~ lalala$  => 0' ) ;
+	is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala   !~ ^lalala$ => 0' ) ;
+	is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala   => 0' ) ;
+	is( 0, notmatch( 'lalala', '.*' ),       'notmatch: lalala   !~ .*       => 0' ) ;
+	is( 0, notmatch( 'lalala', '.' ),        'notmatch: lalala   !~ .        => 0' ) ;
+
+
+	is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ;
+	is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ;
+	is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ;
+	is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ;
+
+	note( 'Leaving  tests_notmatch()' ) ;
+
+	return ;
+}
+
+sub notmatch {
+	my( $var, $regex ) = @ARG ;
+
+	# undef cases
+	if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; }
+
+	# normal cases
+	if ( eval { $var !~ $regex } ) {
+		return 1 ;
+	}elsif ( $EVAL_ERROR ) {
+		print "Fatal regex $regex\n" ;
+		return ;
+	}else{
+		return 0 ;
+	}
+	return ;
+}
+
+
+sub delete_folders_in_2_not_in_1 {
+
+        foreach my $folder (@h2_folders_not_in_1) {
+                if ( defined  $delete2foldersonly  and eval "\$folder !~ $delete2foldersonly" ) {
+                        myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n"  ) ;
+                        next ;
+                }
+                if ( defined  $delete2foldersbutnot  and eval "\$folder =~ $delete2foldersbutnot" ) {
+                        myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n"  ) ;
+                        next ;
+                }
+                my $res = $sync->{dry} ; # always success in dry mode!
+                $imap2->unsubscribe( $folder ) if ( ! $sync->{dry} ) ;
+                $res = $imap2->delete( $folder ) if ( ! $sync->{dry} ) ;
+                if ( $res ) {
+                        myprint( "Deleted $folder", "$sync->{dry_message}", "\n"  ) ;
+                }else{
+                        myprint( "Deleting $folder failed", "\n"  ) ;
+                }
+        }
+        return ;
+}
+
 sub delete_folder {
-        my ( $sync, $imap, $folder, $Side ) = @_ ;
-        if ( ! $sync )   { return ; }
+        my ( $mysync, $imap, $folder, $Side ) = @_ ;
+        if ( ! $mysync )   { return ; }
         if ( ! $imap )   { return ; }
         if ( ! $folder ) { return ; }
         $Side ||= 'HostX' ;
-        
-        my $res = $sync->{dry} ; # always success in dry mode!
-        if ( ! $sync->{dry} ) {
+
+        my $res = $mysync->{dry} ; # always success in dry mode!
+        if ( ! $mysync->{dry} ) {
                 $imap->unsubscribe( $folder ) ;
                 $res = $imap->delete( $folder ) ;
         }
         if ( $res ) {
-        	myprint( "$Side deleted $folder", $sync->{dry_message}, "\n"  ) ;
+                myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n"  ) ;
                 return 1 ;
         }else{
-        	myprint( "$Side deleting $folder failed", "\n"  ) ;
+                myprint( "$Side deleting $folder failed", "\n"  ) ;
                 return ;
         }
 }
 
 sub delete1emptyfolders {
-        my $sync = shift ;
-        if ( ! $sync ) { return ; } # abort if no parameter
-        if ( ! $sync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
-        my $imap = $sync->{imap1} ;
+        my $mysync = shift ;
+        if ( ! $mysync ) { return ; } # abort if no parameter
+        if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
+        my $imap = $mysync->{imap1} ;
         if ( ! $imap ) { return ; } # abort if no imap
         if ( $imap->IsUnconnected(  ) ) { return ; } # abort if diesconnected
-        
+
         my %folders_kept ;
         myprint( qq{Host1 deleting empty folders\n} ) ;
-        foreach my $folder ( reverse sort @{ $sync->{h1_folders_wanted} } ) {
+        foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) {
                 my $parenthood = $imap->is_parent( $folder ) ;
                 if ( defined $parenthood and $parenthood ) {
                         myprint( "Host1 folder $folder has subfolders\n" ) ;
@@ -8123,42 +9861,42 @@ sub delete1emptyfolders {
                 if ( uc $folder eq 'INBOX' ) {
                         myprint( "Host1 Not deleting $folder\n" ) ;
                         $folders_kept{ $folder }++ ;
-                        next ; 
+                        next ;
                 }
                 myprint( "Host1 deleting empty folder $folder\n" ) ;
                 # can not delete a SELECTed or EXAMINEd folder so closing it
                 # could changed be SELECT INBOX
-                $imap->close(  ) ; # close after examine does not expunge; anyway expunging an empty folder... 
-                if ( delete_folder( $sync, $imap, $folder, 'Host1' ) ) {
+                $imap->close(  ) ; # close after examine does not expunge; anyway expunging an empty folder...
+                if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) {
                         next ; # Deleted, good!
                 }else{
                         $folders_kept{ $folder }++ ;
                         next ; # Not deleted, bad!
                 }
         }
-        remove_deleted_folders_from_wanted_list( $sync, %folders_kept ) ;
+        remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ;
         myprint( qq{Host1 ended deleting empty folders\n} ) ;
         return ;
 }
 
 sub remove_deleted_folders_from_wanted_list {
-        my ( $sync, %folders_kept ) = @ARG ;
-        
-        my @h1_folders_wanted_init = @{ $sync->{h1_folders_wanted} } ;
+        my ( $mysync, %folders_kept ) = @ARG ;
+
+        my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ;
         my @h1_folders_wanted_last ;
         foreach my $folder ( @h1_folders_wanted_init ) {
                 if ( $folders_kept{ $folder } ) {
                         push @h1_folders_wanted_last, $folder ;
                 }
         }
-        @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
+        @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
         return ;
 }
 
 sub examine_folder_and_count {
         my ( $imap, $folder, $Side ) = @_ ;
         $Side ||= 'HostX' ;
-        
+
         if ( ! examine_folder( $imap, $folder, $Side ) ) {
                 return ;
         }
@@ -8168,6 +9906,8 @@ sub examine_folder_and_count {
 
 
 sub tests_delete1emptyfolders {
+	note( 'Entering tests_delete1emptyfolders()' ) ;
+
 
         is( undef, delete1emptyfolders(  ), q{delete1emptyfolders: undef} ) ;
         my $syncT ;
@@ -8175,7 +9915,7 @@ sub tests_delete1emptyfolders {
         my $imapT ;
         $syncT->{imap1} = $imapT ;
         is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
-        
+
         require Test::MockObject ;
         $imapT = Test::MockObject->new(  ) ;
         $syncT->{imap1} = $imapT ;
@@ -8186,7 +9926,7 @@ sub tests_delete1emptyfolders {
         # Now connected tests
         $imapT->set_false( 'IsUnconnected' ) ;
         $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
-        
+
         $syncT->{delete1emptyfolders} = 0 ;
         tests_delete1emptyfolders_unit(
                 $syncT,
@@ -8207,7 +9947,7 @@ sub tests_delete1emptyfolders {
 
         # No parents but examine false for all => skip all
         $imapT->set_false( 'is_parent', 'examine' ) ;
-        
+
         tests_delete1emptyfolders_unit(
                 $syncT,
                 [ qw{ INBOX DELME1 DELME2 } ],
@@ -8267,26 +10007,28 @@ sub tests_delete1emptyfolders {
                 q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
         ) ;
 
-
-
-
+	note( 'Leaving  tests_delete1emptyfolders()' ) ;
         return ;
 }
 
 sub tests_delete1emptyfolders_unit {
+	note( 'Entering tests_delete1emptyfolders_unit()' ) ;
+
         my $syncT  = shift ;
         my $folders1wanted_init_ref = shift ;
         my $folders1wanted_after_ref = shift ;
         my $comment = shift || q{delete1emptyfolders:} ;
-        
+
         my @folders1wanted_init  = @{ $folders1wanted_init_ref } ;
         my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
 
         @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
-        
+
         is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
         delete1emptyfolders( $syncT ) ;
         is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
+
+	note( 'Leaving  tests_delete1emptyfolders_unit()' ) ;
         return ;
 }
 
@@ -8300,6 +10042,8 @@ sub extract_header {
 }
 
 sub tests_extract_header {
+	note( 'Entering tests_extract_header()' ) ;
+
 
 
 my $h = <<'EOM';
@@ -8321,7 +10065,8 @@ EOM
 
 
 
-	return ;
+	note( 'Leaving  tests_extract_header()' ) ;
+        return ;
 }
 
 sub decompose_header{
@@ -8361,6 +10106,8 @@ sub decompose_header{
 
 
 sub tests_decompose_header{
+	note( 'Entering tests_decompose_header()' ) ;
+
 
         my $header_dec ;
 
@@ -8373,7 +10120,7 @@ KEY_2: VAL_2
 KEY_3: VAL_3
 KEY_1: VAL_1_other
 KEY_4: VAL_4
-	VAL_4_+
+        VAL_4_+
 KEY_5 BLANC:  VAL_5
 
 KEY_6_BAD_BODY: VAL_6
@@ -8462,12 +10209,32 @@ EOH
         ok( 'VAL_2 VAL_2_+ VAL_2_++'
         eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
 
-	return ;
+	note( 'Leaving  tests_decompose_header()' ) ;
+        return ;
+}
+
+sub tests_epoch {
+	note( 'Entering tests_epoch()' ) ;
+
+        ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
+
+        ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
+
+	note( 'Leaving  tests_epoch()' ) ;
+        return ;
 }
 
 sub epoch {
         # incoming format:
-	# internal date 24-Aug-2010 16:00:00 +0200
+        # internal date 24-Aug-2010 16:00:00 +0200
 
         # outgoing format: epoch
 
@@ -8495,210 +10262,256 @@ sub epoch {
         return( $time ) ;
 }
 
-sub tests_epoch {
-        ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
-        ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
-        ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
-        ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
-        ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
+sub tests_add_header {
+	note( 'Entering tests_add_header()' ) ;
 
-        ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
-        ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
-        ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
-        ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
-        ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
-	return ;
+        ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
+        ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ;
+
+	note( 'Leaving  tests_add_header()' ) ;
+        return ;
 }
 
 sub add_header {
-	my $header_uid = shift || 'mistake' ;
-	my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
+        my $header_uid = shift || 'mistake' ;
+        my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
         return( $header_Message_Id ) ;
 }
 
-sub tests_add_header {
-	ok( 'Message-Id: <mistake@imapsync>' eq add_header(), 'add_header no arg' ) ;
-	ok( 'Message-Id: <123456789@imapsync>' eq add_header(123456789), 'add_header 123456789' ) ;
 
-	return ;
+
+
+sub tests_max_line_length {
+	note( 'Entering tests_max_line_length()' ) ;
+
+        ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
+        ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
+        ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
+        ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
+        ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
+        ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
+        ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
+        ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
+        ok( 3 == max_line_length( "a\nab\n" x 10_000 ), 'max_line_length: 3 == 10_000 a\nab\n' ) ;
+        ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
+
+        ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
+        ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
+        ok( 5 == max_line_length( "a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd" ), 'max_line_length: 5 == a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd' ) ;
+
+	note( 'Leaving  tests_max_line_length()' ) ;
+        return ;
 }
 
-sub tests_Banner{
-
-	my $imap = Mail::IMAPClient->new(  ) ;
-        ok( 'lalala' eq $imap->Banner('lalala'), 'Banner set lalala' ) ;
-        ok( 'lalala' eq $imap->Banner(), 'Banner returns lalala' ) ;
-	return ;
-}
-
-
-
-
 sub max_line_length {
-	my $string = shift ;
+        my $string = shift ;
         my $max = 0 ;
 
         while ( $string =~ m/([^\n]*\n?)/msxg ) {
-        	$max = max( $max, length $1 ) ;
+                $max = max( $max, length $1 ) ;
         }
-	return( $max ) ;
+        return( $max ) ;
 }
 
-sub tests_max_line_length {
-	ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
-	ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
-	ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
-	ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
-	ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
-	ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
-	ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
-	ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
-	ok( 3 == max_line_length( "a\nab\n" x 10000 ), 'max_line_length: 3 == 10000 a\nab\n' ) ;
-	ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
 
-	ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
-	ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
-	ok( 5 == max_line_length( "a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd" ), 'max_line_length: 5 == a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd' ) ;
+sub tests_setlogfile {
+	note( 'Entering tests_setlogfile()' ) ;
+
+	my $mysync = {} ;
+        $mysync->{logdir}  = 'vallogdir' ;
+        $mysync->{logfile} = 'vallogfile.txt' ;
+        is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ),
+                'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
+
+	SKIP: {
+	skip( 'Too hard to have a well known timezone on Windows', 6 ) if ( 'MSWin32' eq $OSNAME ) ;
+
+        local $ENV{TZ} = 'GMT' ;
+
+	$mysync = {
+                timestart => 2,
+	} ;
+
+        is( 'LOG_imapsync/1970_01_01_00_00_02_000__.txt', setlogfile( $mysync ),
+                'setlogfile: default is like LOG_imapsync/1970_01_01_00_00_02_000__.txt' ) ;
+
+        $mysync = {
+                timestart => 2,
+                user1     => 'user1',
+                user2     => 'user2',
+        } ;
+
+        is( 'LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
+                'setlogfile: default is like LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
+
+        $mysync->{logdir}  = undef ;
+        $mysync->{logfile} = undef ;
+        is( 'LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
+                'setlogfile: logdir undef, LOG_imapsync/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
+
+        $mysync->{logdir} = q{} ;
+        $mysync->{logfile} = undef ;
+        is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
+                'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ;
+
+        $mysync->{logdir} = 'vallogdir' ;
+        $mysync->{logfile} = undef ;
+        is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ),
+                'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ;
+
+        $mysync = {
+                user1     => 'us/er1a*|?:"<>b',
+                user2     => 'u/ser2a*|?:"<>b',
+        } ;
+
+        is( 'LOG_imapsync/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt', setlogfile( $mysync ),
+                'setlogfile: logdir undef, LOG_imapsync/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt' ) ;
+
+
+	} ;
+
+	note( 'Leaving  tests_setlogfile()' ) ;
+        return ;
+}
+
+
+sub tests_move_slash {
+	note( 'Entering tests_move_slash()' ) ;
+
+	is( undef, move_slash(  ), 'move_slash: no parameters => undef' ) ;
+	is( '_', move_slash( '/' ), 'move_slash: / => _' ) ;
+	is( '_abc_def_', move_slash( '/abc/def/' ), 'move_slash: /abc/def/ => _abc_def_' ) ;
+	note( 'Leaving  tests_move_slash()' ) ;
 	return ;
 }
 
+sub move_slash {
+	my $string = shift ;
+
+	if ( ! defined $string ) { return ; }
+
+	$string =~ tr{/}{_} ;
+
+	return(  $string ) ;
+}
+
 sub setlogfile {
         my( $mysync ) = shift ;
+	my $suffix = ( filter_forbidden_characters( move_slash( $mysync->{user1} ) ) || q{} )
+			. '_' .
+			( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} ) ;
+
         $mysync->{logdir}  = defined $mysync->{logdir}  ? $mysync->{logdir}  : 'LOG_imapsync' ;
         $mysync->{logfile} = defined $mysync->{logfile} ? "$mysync->{logdir}/$mysync->{logfile}" :
-                logfile( $mysync->{timestart}, $mysync->{user2}, $mysync->{logdir} ) ;
+                logfile( $mysync->{timestart}, $suffix, $mysync->{logdir} ) ;
         #myprint( "logdir  = $mysync->{logdir}\n"  ) ;
         #myprint( "logfile = $mysync->{logfile}\n"  ) ;
         return( $mysync->{logfile} ) ;
 }
 
-sub tests_setlogfile {
-        my $mysync = {
-                timestart => 2,
-                user2     => 'user2',
+
+sub tests_logfile {
+	note( 'Entering tests_logfile()' ) ;
+
+        SKIP: {
+                # Too hard to have a well known timezone on Windows
+                skip( 'Too hard to have a well known timezone on Windows', 8 ) if ( 'MSWin32' eq $OSNAME ) ;
+
+                local $ENV{TZ} = 'GMT' ;
+                { POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
+                        is( '1970_01_01_00_00_00_000.txt', logfile(  ),           'logfile: no args    => 1970_01_01_00_00_00.txt' ) ;
+                        is( '1970_01_01_00_00_00_000.txt', logfile( 0 ),          'logfile: 0          => 1970_01_01_00_00_00.txt' ) ;
+                        is( '1970_01_01_00_01_01_000.txt', logfile( 61 ),         'logfile: 0          => 1970_01_01_00_01_01.txt' ) ;
+                        is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ),     'logfile: 0          => 1970_01_01_00_01_01.txt' ) ;
+                        is( '2010_08_24_14_00_00_000.txt', logfile( 1_282_658_400 ), 'logfile: 1_282_658_400 => 2010_08_24_14_00_00.txt' ) ;
+                        is( '2010_08_24_14_01_01_000.txt', logfile( 1_282_658_461 ), 'logfile: 1_282_658_461 => 2010_08_24_14_01_01.txt' ) ;
+                        is( '2010_08_24_14_01_01_000_poupinette.txt', logfile( 1_282_658_461, 'poupinette' ), 'logfile: 1_282_658_461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ;
+                        is( '2010_08_24_14_01_01_000_removeblanks.txt', logfile( 1_282_658_461, '   remove blanks  ' ), 'logfile: 1_282_658_461   remove blanks   => 2010_08_24_14_01_01_000_removeblanks' ) ;
+                }
+                POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
         } ;
 
-        ok( 'LOG_imapsync/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
-                'setlogfile: default is like LOG_imapsync/1970_01_01_01_00_02_user2.txt' ) ;
-
-        $mysync->{logdir}  = undef ;
-        $mysync->{logfile} = undef ;
-        ok( 'LOG_imapsync/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
-                'setlogfile: logdir undef, LOG_imapsync/1970_01_01_01_00_02_user2.txt' ) ;
-
-        $mysync->{logdir} = q{} ;
-        $mysync->{logfile} = undef ;
-        ok( '1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
-                'setlogfile: logdir empty, 1970_01_01_01_00_02_user2.txt' ) ;
-
-        $mysync->{logdir} = 'vallogdir' ;
-        $mysync->{logfile} = undef ;
-        ok( 'vallogdir/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
-                'setlogfile: logdir vallogdir, vallogdir/1970_01_01_01_00_02_user2.txt' ) ;
-
-        $mysync->{logdir}  = 'vallogdir' ;
-        $mysync->{logfile} = 'vallogfile.txt' ;
-        ok( 'vallogdir/vallogfile.txt' eq setlogfile( $mysync ),
-                'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
-
+	note( 'Leaving  tests_logfile()' ) ;
         return ;
 }
 
 
-sub logfile {
-	my ( $time, $suffix, $dir ) = @_ ;
 
-	$time   ||= 0 ;
-	$suffix ||= q{} ;
-	my $sep_suffix = ( $suffix ) ? '_' : q{} ;
+sub logfile  {
+        my ( $time, $suffix, $dir ) = @_ ;
+
+        $time   ||= 0 ;
+        $suffix ||= q{} ;
+	$suffix =~ tr/ //ds ;
+        my $sep_suffix = ( $suffix ) ? '_' : q{} ;
         $dir    ||= q{} ;
-	my $sep_dir = ( $dir ) ? '/' : q{} ;
+        my $sep_dir = ( $dir ) ? '/' : q{} ;
 
-	my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
+        my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
+	# Because of ab tests or web access, more than one sync withing one second is possible
+	# so we add millisecons
+	$date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding
         my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
-	$debug and myprint( "date_str: $date_str\n"  ) ;
-	$debug and myprint( "logfile : $logfile\n"  ) ;
-	return( $logfile ) ;
+        $debug and myprint( "date_str: $date_str\n"  ) ;
+        $debug and myprint( "logfile : $logfile\n"  ) ;
+        return( $logfile ) ;
 }
 
-sub tests_logfile {
-	SKIP: {
-		# Too hard to have a well known timezone on Windows
-		skip( 'Too hard to have a well known timezone on Windows', 6 ) if ( 'MSWin32' eq $OSNAME ) ;
-
-		local $ENV{TZ} = 'GMT' ;
-		{ POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
-			ok( '1970_01_01_00_00_00.txt' eq logfile(  ),           'logfile: no args    => 1970_01_01_00_00_00.txt' ) ;
-			ok( '1970_01_01_00_00_00.txt' eq logfile( 0 ),          'logfile: 0          => 1970_01_01_00_00_00.txt' ) ;
-			ok( '1970_01_01_00_01_01.txt' eq logfile( 61 ),         'logfile: 0          => 1970_01_01_00_01_01.txt' ) ;
-			ok( '2010_08_24_14_00_00.txt' eq logfile( 1282658400 ), 'logfile: 1282658400 => 2010_08_24_14_00_00.txt' ) ;
-			ok( '2010_08_24_14_01_01.txt' eq logfile( 1282658461 ), 'logfile: 1282658461 => 2010_08_24_14_01_01.txt' ) ;
-			ok( '2010_08_24_14_01_01_poupinette.txt' eq logfile( 1282658461, 'poupinette' ), 'logfile: 1282658461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ;
-                }
-		POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
-	} ;
-	return ;
-}
-
-
-
-
-
-
-
-
-
-
-
-
 
 
 sub tests_million_folders_baby_2 {
-	my %long ;
-	@long{ 1 .. 900_000 } = (1) x 900_000 ;
-	#myprint( %long, "\n"  ) ;
-	my $pasglop = 0 ;
-	foreach my $elem (  1 .. 900_000 ) {
-		#$debug and myprint( "$elem "  ) ;
-		if ( not exists  $long{ $elem }  ) {
-			$pasglop++ ;
-		}
-	}
+	note( 'Entering tests_million_folders_baby_2()' ) ;
+
+        my %long ;
+        @long{ 1 .. 900_000 } = (1) x 900_000 ;
+        #myprint( %long, "\n"  ) ;
+        my $pasglop = 0 ;
+        foreach my $elem (  1 .. 900_000 ) {
+                #$debug and myprint( "$elem "  ) ;
+                if ( not exists  $long{ $elem }  ) {
+                        $pasglop++ ;
+                }
+        }
         ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
-	# myprint( "$pasglop\n"  ) ;
+        # myprint( "$pasglop\n"  ) ;
+
+	note( 'Leaving  tests_million_folders_baby_2()' ) ;
         return ;
 }
 
 
 
 sub tests_always_fail {
-	ok( 0 == 1, '0 == 1' ) ;
-	ok( 1 == 1, '1 == 1' ) ;
+	note( 'Entering tests_always_fail()' ) ;
+
+        is( 0, 1, 'always_fail: 0 is 1' ) ;
+
+	note( 'Leaving  tests_always_fail()' ) ;
         return ;
 }
 
 sub logfileprepa {
-	my $logfile = shift ;
+        my $logfile = shift ;
 
-	my $dirname = dirname( $logfile ) ;
-	is_valid_directory( $dirname ) || return( 0 ) ;
-	return( 1 ) ;
+        my $dirname = dirname( $logfile ) ;
+        do_valid_directory( $dirname ) || return( 0 ) ;
+        return( 1 ) ;
 }
 
 sub teelaunch {
         my $mysync = shift ;
-	my $logfile = $mysync->{logfile} ;
-	logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $!" ;
-	my $logfile_handle ;
-	open $logfile_handle, '>', $logfile
-	  or croak( "Can not open $logfile for write: $!" ) ;
-	my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
-	*STDERR = *$tee{IO} ;
-	select $tee ;
+        my $logfile = $mysync->{logfile} ;
+        logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ;
+
+        open my $logfile_handle, '>', $logfile
+          or croak( "Can not open $logfile for write: $OS_ERROR" ) ;
+        my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
+        *STDERR = *$tee{IO} ;
+        select $tee ;
         $tee->autoflush( 1 ) ;
         $mysync->{logfile_handle} = $logfile_handle ;
         $mysync->{tee} = $tee ;
-	return $logfile_handle ;
+        return $logfile_handle ;
 }
 
 sub getpwuid_any_os {
@@ -8706,311 +10519,641 @@ sub getpwuid_any_os {
 
         return( scalar  getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
         return( scalar  getpwuid $uid ) ; # Unix system
+
+
+}
+
+sub simulong {
+	my $max_seconds = shift ;
+	my $division = 5 ;
+	my $last = $division * $max_seconds ;
+	foreach my $i ( 1 .. ( $last ) ) {
+		myprint( "Are you still here $i/$last\n" ) ;
+		#myprint( "Are you still here $i/$last\n" . ( "Ah" x 40 . "\n") x 4000 ) ;
+		sleep( 1 / $division ) ;
+	}
+
+	return ;
 }
 
 
 
+sub printenv {
+        myprint( "Environment variables listing:\n",
+		( map { "$_ => $ENV{$_}\n" } sort keys  %ENV),
+		"Environment variables listing end\n"   ) ;
+	return ;
+}
+
+sub testsexit {
+	my $mysync = shift ;
+        if ( ! ( $mysync->{ tests }  or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) {
+		return ;
+	}
+        my $test_builder = Test::More->builder ;
+        tests( $mysync ) ; 
+        testsdebug( $mysync ) ;
+	testunitsession( $mysync ) ;
+
+	my @summary = $test_builder->summary() ;
+	my @details = $test_builder->details() ;
+	my $nb_tests_run = scalar( @summary ) ;
+	my $nb_tests_expected = $test_builder->expected_tests() ;
+	my $nb_tests_failed = count_0s( @summary ) ;
+	my $tests_failed = report_failures( @details ) ;
+	if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) {
+		#$test_builder->reset(  ) ;
+		myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n",
+                "List of failed tests:\n", $tests_failed ) ;
+		exit $EXIT_TESTS_FAILED ;
+	}
+        exit ;
+	#return ;
+}
+
+sub after_get_options {
+	my $numopt = shift ;
+
+
+        # exit with --help option or no option at all
+        $debug and myprint( "numopt:$numopt\n"  ) ;
+        myprint( usage( $sync ) ) and exit  if ( $help or not $numopt ) ;
+
+        return ;
+}
+
+sub easyany {
+	my $mysync = shift ;
+
+	# Gmail
+	if ( $mysync->{gmail1} and $mysync->{gmail2} ) {
+		$debug and myprint( "gmail1 gmail2\n") ;
+		gmail12( $mysync ) ;
+		return ;
+	}
+	if ( $mysync->{gmail1}  ) {
+		$debug and myprint( "gmail1\n" ) ;
+		gmail1( $mysync ) ;
+	}
+	if ( $mysync->{gmail2} ) {
+		$debug and myprint( "gmail2\n" ) ;
+		gmail2( $mysync ) ;
+	}
+	# Office 365
+	if ( $mysync->{office1} ) {
+		office1( $mysync ) ;
+	}
+
+	if ( $mysync->{office2} ) {
+		office2( $mysync ) ;
+	}
+
+	# Domino
+	if ( $mysync->{domino1} ) {
+		domino1( $mysync ) ;
+	}
+
+	if ( $mysync->{domino2} ) {
+		domino2( $mysync ) ;
+	}
+
+
+	return ;
+}
+
+# From https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt
+sub gmail12  {
+	my $mysync = shift ;
+	# Gmail at host1 and host2
+	$mysync->{host1} ||= 'imap.gmail.com' ;
+	$mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
+	$mysync->{host2} ||= 'imap.gmail.com' ;
+	$mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
+	$mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation
+	$mysync->{maxbytesafter} ||= 1_000_000_000 ;
+	$mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
+	$mysync->{maxsleep} = $MAX_SLEEP ;
+	
+	push @exclude, '\[Gmail\]$' ;
+	return ;
+}
+
+sub gmail1  {
+	my $mysync = shift ;
+	# Gmail at host2
+	$mysync->{host1} ||= 'imap.gmail.com' ;
+	$mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
+	$mysync->{maxbytespersecond} ||= 40_000 ; # should be 20_000 computed from by Gmail documentation
+	$mysync->{maxbytesafter} ||= 2_500_000_000 ;
+	$mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
+	$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
+	$mysync->{maxsleep} = $MAX_SLEEP ;
+	
+	push @useheader, 'X-Gmail-Received', 'Message-Id' ;
+	push @regextrans2, 's,\[Gmail\].,,' ;
+	push @folderlast, '[Gmail]/All Mail' ;
+	return ;
+}
+
+sub gmail2  {
+	my $mysync = shift ;
+	# Gmail at host2
+	$mysync->{host2} ||= 'imap.gmail.com' ;
+	$mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
+	$mysync->{maxbytespersecond} ||= 20_000 ; # should be 10_000 computed from by Gmail documentation
+	$mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000
+	$maxsize ||= 25_000_000 ;
+	$mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ;
+	$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ;
+	$expunge1            = ( defined $expunge1 )  ? $expunge1  : 1 ;
+	$addheader           = ( defined $addheader ) ? $addheader : 1 ;
+	$mysync->{maxsleep} = $MAX_SLEEP ;
+	
+	push @exclude, '\[Gmail\]$' ;
+	push @useheader, 'X-Gmail-Received', 'Message-Id' ;
+	push @regextrans2, 's,\[Gmail\].,,' ;
+	push @regextrans2, 's/[ ]+/_/g' ;
+	push @regextrans2, q{s/['\\^"]/_/g} ; # Verified this
+	push @folderlast, "[Gmail]/All Mail" ;
+	return ;
+}
+
+
+# From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt
+sub office1 {
+	# Office 365 at host1
+	my $mysync = shift ;
+
+	$debug and myprint( "office1 configuration\n" ) ;
+	$mysync->{host1} ||= 'outlook.office365.com' ;
+	$mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ;
+	return ;
+}
+
+sub office2 {
+	# Office 365 at host1
+	my $mysync = shift ;
+	$mysync->{host2} ||= 'outlook.office365.com' ;
+	$mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ;
+	$maxsize ||= 45_000_000 ;
+	$mysync->{maxmessagespersecond} ||= 4 ;
+	push @regexflag, 's/\\Flagged//g' ;
+	$disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
+	push @regexmess, 's,(.{10500}),$1\r\n,g' ;
+	return ;
+}
+
+sub exchange1 {
+	# Exchange 2010/2013 at host1
+
+	# Well nothing to do so far
+	return ;
+}
+
+sub exchange2 {
+	# Exchange 2010/2013 at host2
+	my $mysync = shift ;
+	$maxsize ||= 10_000_000 ;
+	$mysync->{maxmessagespersecond} ||= 4 ;
+	push @regexflag, 's/\\Flagged//g' ;
+	$disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ;
+	push @regexmess, 's,(.{10500}),$1\r\n,g' ;
+	return ;
+}
+
+sub domino1 {
+	# Domino at host1
+	my $mysync = shift ;
+
+	$sep1    = q{\\} ;
+	$prefix1 = q{} ;
+	$messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
+	return ;
+}
+
+sub domino2 {
+	# Domino at host1
+	my $mysync = shift ;
+
+	$sep2    = q{\\} ;
+	$prefix2 = q{} ;
+	$messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ;
+	push @regextrans2, 's,^Inbox\\\\(.*),$1,i' ;
+	return ;
+}
+
+
+sub tests_resolv {
+	note( 'Entering tests_resolv()' ) ;
+	
+	# is( , resolv(  ), 'resolv:  => ' ) ;
+	is( undef, resolv(  ), 'resolv: no args => undef' ) ;
+	is( undef, resolv( '' ), 'resolv: empty string => undef' ) ;
+	is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ;
+	is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ;
+	is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ;
+	is( '5.135.158.182', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 5.135.158.182' ) ;
+	
+	# ip6-localhost ( in /etc/hosts )
+	is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ;
+	is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ;
+	# ks2
+	is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ),  'resolv:  2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ;
+	is( '2001:41d0:8:d8b6::1', resolv( 'ks2ipv6.lamiral.info' ), 'resolv: ks2ipv6.lamiral.info => 2001:41d0:8:d8b6::1' ) ;
+	# ks3
+	is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ),  'resolv:  2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ;
+	is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ;
+	
+	
+	note( 'Leaving  tests_resolv()' ) ;
+        return ;
+}
+
+
+
+sub resolv {
+	my $host = shift @ARG ;
+	
+	if ( ! $host ) { return ; }
+        my $addr ;
+	if ( defined &Socket::getaddrinfo ) {
+		$addr = resolv_with_getaddrinfo( $host ) ;
+		return( $addr ) ;
+	}
+        
+        
+        
+        my $iaddr = inet_aton( $host ) ;
+        if ( ! $iaddr ) { return ; }
+        $addr = inet_ntoa( $iaddr ) ;
+	
+	return $addr ;
+}
+
+sub resolv_with_getaddrinfo {
+	my $host = shift @ARG ;
+	
+	if ( ! $host ) { return ; }
+
+	my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
+	if (  $err ) {
+		myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
+		return ;
+	}
+
+	my @addr ;
+	while( my $ai = shift @res ) {
+		my ( $err, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ;
+		if ( $err ) {
+			myprint( "Cannot getnameinfo of $host: $err\n" ) ;
+			return ;
+		}
+		$debug and myprint "$host => $ipaddr\n" ;
+		push @addr, $ipaddr ;
+
+		my ( $err_r, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
+		$debug and myprint "$host => $ipaddr => $reverse\n" ;
+	}
+	
+        return $addr[0] ;
+}
+
+sub tests_resolvrev {
+	note( 'Entering tests_resolvrev()' ) ;
+	
+	# is( , resolvrev(  ), 'resolvrev:  => ' ) ;
+	is( undef, resolvrev(  ), 'resolvrev: no args => undef' ) ;
+	is( undef, resolvrev( '' ), 'resolvrev: empty string => undef' ) ;
+	is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ;
+	is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ;
+	is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ;
+	is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ;
+	
+	# ip6-localhost ( in /etc/hosts )
+	is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ;
+	is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ;
+	# ks2
+	is( 'ks2ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ),  'resolvrev:  2001:41d0:8:d8b6::1 => ks2ipv6.lamiral.info' ) ;
+	is( 'ks2ipv6.lamiral.info', resolvrev( 'ks2ipv6.lamiral.info' ), 'resolvrev: ks2ipv6.lamiral.info => ks2ipv6.lamiral.info' ) ;
+	# ks3
+	is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ),  'resolvrev:  2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ;
+	is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ;
+	
+	
+	note( 'Leaving  tests_resolvrev()' ) ;
+        return ;
+}
+
+sub resolvrev {
+	my $host = shift @ARG ;
+	
+	if ( ! $host ) { return ; }
+
+	if ( defined &Socket::getaddrinfo ) {
+		my $name = resolvrev_with_getaddrinfo( $host ) ;
+		return( $name ) ;
+	}
+	
+	return ;
+}
+
+sub resolvrev_with_getaddrinfo {
+	my $host = shift @ARG ;
+	
+	if ( ! $host ) { return ; }
+
+	my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ;
+	if (  $err ) {
+		myprint( "Cannot getaddrinfo of $host: $err\n" ) ;
+		return ;
+	}
+
+	my @name ;
+	while( my $ai = shift @res ) {
+		my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ;
+		if ( $err ) {
+			myprint( "Cannot getnameinfo of $host: $err\n" ) ;
+			return ;
+		}
+		$debug and myprint "$host => $reverse\n" ;
+		push @name, $reverse ;
+	}
+	
+    return $name[0] ;
+}
+
+
+
+sub tests_imapsping {
+	note( 'Entering tests_imapsping()' ) ;
+
+	is( undef, imapsping(  ), 'imapsping: no args => undef' ) ;
+	is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ;
+	is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ;
+        is( 1, imapsping( 'ks2ipv6.lamiral.info' ), 'imapsping: ks2ipv6.lamiral.info => 1' ) ;
+	note( 'Leaving  tests_imapsping()' ) ;
+	return ;
+}
+
+sub imapsping {
+	my $host = shift ;
+	return tcpping( $host, $IMAP_SSL_PORT ) ;
+}
+
+sub tests_tcpping {
+	note( 'Entering tests_tcpping()' ) ;
+
+	is( undef, tcpping(  ), 'tcpping: no args => undef' ) ;
+	is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ;
+	is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ;
+	is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ;
+	is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ;
+	is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ;
+	is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ;
+	is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ;
+	is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ;
+
+	# Net::Ping supports ipv6 only after release 1.50
+	# http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes
+	# Anyway I plan to avoid Net-Ping for that too long standing feature
+	# Net-Ping is integrated in Perl itself, who knows ipv6 for a long time
+	is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ;
+	is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ;
+
+	note( 'Leaving  tests_tcpping()' ) ;
+	return ;
+}
+
+sub tcpping {
+	if ( 2 != scalar( @ARG ) ) {
+		return ;
+	}
+	my ( $host, $port ) = @ARG ;
+	if ( ! $host ) { return ; }
+	if ( ! $port ) { return ; }
+
+	my $mytimeout = $TCP_PING_TIMEOUT ;
+        require Net::Ping ;
+	#my $p = Net::Ping->new( 'tcp' ) ;
+	my $p = Net::Ping->new(  ) ;
+	$p->{port_num} = $port ;
+	$p->service_check( 1 ) ;
+	$p->hires( 1 ) ;
+	my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ;
+	if ( ! defined $ping_ok ) { return ; }
+	my $rtt_approx = sprintf( "%.3f", $rtt ) ;	
+	$debug and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ;
+	$p->close(  ) ;
+	if( $ping_ok ) {
+		return 1 ;
+	}else{
+		return 0 ;
+	}
+}
+
+sub tests_sslcheck {
+	note( 'Entering tests_sslcheck()' ) ;
+
+	my $mysync ;
+
+	is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ;
+
+	$mysync = {
+		sslcheck => 1,
+	} ;
+
+	is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ;
+
+	$mysync = {
+		sslcheck => 1,
+		host1 => 'imapsync.lamiral.info',
+		tls1 => 1,
+	} ;
+
+	is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ;
+
+	$mysync = {
+		sslcheck => 1,
+		host1 => 'imapsync.lamiral.info',
+	} ;
+
+
+	is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info => 1' ) ;
+	is( 1, $mysync->{ssl1}, 'sslcheck: imapsync.lamiral.info => ssl1 1' ) ;
+
+	$mysync->{sslcheck} = 0 ;
+	is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ;
+
+	note( 'Leaving  tests_sslcheck()' ) ;
+	return ;
+}
+
+sub sslcheck {
+	my $mysync = shift ;
+
+	if ( ! $mysync->{sslcheck} ) {
+		return ;
+	}
+	my $nb_on = 0 ;
+	$debug and myprint( "sslcheck\n" ) ;
+	if (
+		( ! defined $mysync->{port1} )
+		and
+		( ! defined $mysync->{tls1} )
+		and
+		( ! defined $mysync->{ssl1} )
+		and
+		( defined $mysync->{host1} )
+		and
+		( probe_imapssl( $mysync->{host1} ) )
+	) {
+		$mysync->{ssl1} = 1 ;
+		myprint( "Host1: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl1 --notls1 to turn off SSL and TLS wizardry)\n" ) ;
+		$nb_on++ ;
+	}
+
+	if (
+		( ! defined $mysync->{port2} )
+		and
+		( ! defined $mysync->{tls2} )
+		and
+		( ! defined $mysync->{ssl2} )
+		and
+		( defined $mysync->{host2} )
+		and
+		( probe_imapssl( $mysync->{host2} ) )
+	) {
+		$mysync->{ssl2} = 1 ;
+		myprint( "Host2: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl2 --notls2 to turn off SSL and TLS wizardry)\n" ) ;
+		$nb_on++ ;
+	}
+	return $nb_on ;
+}
+
+
+sub testslive {
+        my $mysync = shift ;
+        $mysync->{host1} = 'test1.lamiral.info' ;
+        $mysync->{user1} = 'test1' ;
+        $mysync->{password1} = 'secret1' ;
+        $mysync->{host2} = 'test2.lamiral.info' ;
+        $mysync->{user2} = 'test2' ;
+        $mysync->{password2} ='secret2' ;
+        return ;
+}
+
+sub testslive6 {
+        my $mysync = shift ;
+        $mysync->{host1} = 'ks2ipv6.lamiral.info' ;
+        $mysync->{user1} = 'test1' ;
+        $mysync->{password1} = 'secret1' ;
+        $mysync->{host2} = 'ks2ipv6.lamiral.info' ;
+        $mysync->{user2} = 'test2' ;
+        $mysync->{password2} ='secret2' ;
+        return ;
+}
+
+
+sub tests_backslash_caret {
+        note( 'Entering tests_backslash_caret()' ) ;
+
+        is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ;
+        is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ;
+        is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ;
+        is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ;
+        is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ;
+        is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ;
+        is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ;
+        is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ;
+        is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ;
+        is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ;
+
+        note( 'Leaving  tests_backslash_caret()' ) ;
+        return ;
+}
+
+sub backslash_caret {
+        my $string = shift ;
+        
+        $string =~ s{\\ $ }{^}gxms ;
+
+        return $string ;
+}
+
 sub usage {
-	my $localhost_info = localhost_info();
-	my $thank = thank_author();
-	my $imapsync_release = q{};
-	$imapsync_release = check_last_release() if (not defined $releasecheck);
-        my $escape_char = ( 'MSWin32' eq $OSNAME ) ? '^' : '\\';
-        myprint( <<"EOF" ) ;
+	my $mysync = shift ;
+        
+        my $usage = q{} ;
+        my $usage_from_pod ;
+        my $usage_footer = usage_footer( $mysync ) ;
 
- usage: $0 [options]
+        # pod2usage writes on a filehandle only and I want a variable
+        open my $fh_pod2usage, ">", \$usage_from_pod or do { 
+                warn $OS_ERROR ;
+                return ;
+        } ;
+        
+        pod2usage( 
+                -exitval   => 'NOEXIT',
+                -noperldoc => 1,
+                -verbose => 99,
+                -sections => [ qw(NAME VERSION USAGE OPTIONS) ],
+                -indent => 1,
+                -loose => 1,
+                -output => $fh_pod2usage,
+        ) ;
+        close $fh_pod2usage ;
+        
+        if ( 'MSWin32' eq $OSNAME ) {
+                $usage_from_pod = backslash_caret( $usage_from_pod ) ;
+        }
+        $usage = join( q{}, $usage_from_pod, $usage_footer ) ;
 
- Several options are mandatory.
- str means string
- int means integer
- reg means regular expression
- cmd means command
+        return( $usage ) ;
+}
 
- --dry               : Makes imapsync doing nothing, just print what would
-                       be done without --dry.
+sub tests_usage {
+        my $usage ;
+        like( $usage = usage( $sync ), qr/Name:/, 'usage2: contains Name:' ) ;
+        myprint( $usage ) ;
+        like( $usage, qr/Version:/, 'usage2: contains Version:' ) ;
+        like( $usage, qr/Usage:/, 'usage2: contains Usage:' ) ;
+        like( $usage, qr/imapsync/, 'usage2: contains imapsync' ) ;
+        return ;
+}
 
- --host1        str  : Source or "from" imap server. Mandatory.
- --port1        int  : Port to connect on host1. Default is 143, 993 if --ssl1
- --user1        str  : User to login on host1. Mandatory.
- --showpasswords     : Shows passwords on output instead of "MASKED".
-                       Useful to restart a complete run by just reading the log.
- --password1    str  : Password for the user1.
- --host2        str  : "destination" imap server. Mandatory.
- --port2        int  : Port to connect on host2. Default is 143, 993 if --ssl2
- --user2        str  : User to login on host2. Mandatory.
- --password2    str  : Password for the user2.
+sub usage_footer {
+	my $mysync = shift ;
+        
+        my $footer = q{} ;
 
- --passfile1    str  : Password file for the user1. It must contain the
-                       password on the first line. This option avoids to show
-                       the password on the command line like --password1 does.
- --passfile2    str  : Password file for the user2. Contains the password.
+        my $localhost_info = localhost_info(  ) ;
+        my $rcs = $mysync->{rcs} ;
+        my $homepage = homepage(  ) ;
+        my $imapsync_release = q{} ;
+        $imapsync_release = check_last_release(  ) if ( not defined $releasecheck ) ;
 
- --ssl1              : Use a SSL connection on host1.
- --ssl2              : Use a SSL connection on host2.
- --tls1              : Use a TLS connection on host1.
- --tls2              : Use a TLS connection on host2.
- --debugssl     int  : SSL debug mode from 0 to 4.
- --sslargs1     str  : Pass any ssl parameter for host1 ssl or tls connection. Example:
-                       --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
-                       See all possibilities in the new() method of IO::Socket::SSL
-                       http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
- --sslargs2     str  : Pass any ssl parameter for host2 ssl or tls connection.
-                       See --sslargs1
-
- --timeout1     int  : Connection timeout in seconds for host1.
-                       Default is 120 and 0 means no timeout at all.
- --timeout2     int  : Connection timeout in seconds for host2.
-                       Default is 120 and 0 means no timeout at all.
-
- --authmech1    str  : Auth mechanism to use with host1:
-                       PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
- --authmech2    str  : Auth mechanism to use with host2. See --authmech1
-
- --authuser1    str  : User to auth with on host1 (admin user).
-                       Avoid using --authmech1 SOMETHING with --authuser1.
- --authuser2    str  : User to auth with on host2 (admin user).
- --proxyauth1        : Use proxyauth on host1. Requires --authuser1.
-                       Required by Sun/iPlanet/Netscape IMAP servers to
-                       be able to use an administrative user.
- --proxyauth2        : Use proxyauth on host2. Requires --authuser2.
-
- --authmd51          : Use MD5 authentification for host1.
- --authmd52          : Use MD5 authentification for host2.
- --domain1      str  : Domain on host1 (NTLM authentication).
- --domain2      str  : Domain on host2 (NTLM authentication).
-
-
- --folder       str  : Sync this folder.
- --folder       str  : and this one, etc.
- --folderrec    str  : Sync this folder recursively.
- --folderrec    str  : and this one, etc.
-
- --folderfirst  str  : Sync this folder first. --folderfirst "Work"
- --folderfirst  str  : then this one, etc.
- --folderlast   str  : Sync this folder last. --folderlast "[Gmail]/All Mail"
- --folderlast   str  : then this one, etc.
-
- --nomixfolders      : Do not merge folders when host1 is case sensitive
-                       while host2 is not (like Exchange). Only the first
-                       similar folder is synced (ex: Sent SENT sent -> Sent).
-
- --skipemptyfolders  : Empty host1 folders are not created on host2.
-
- --include      reg  : Sync folders matching this regular expression
- --include      reg  : or this one, etc.
-                       in case both --include --exclude options are
-                       use, include is done before.
- --exclude      reg  : Skips folders matching this regular expression
-                       Several folders to avoid:
-                        --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
- --exclude      reg  : or this one, etc.
-
- --subfolder2   str  : Move whole host1 folders hierarchy under this
-                       host2 folder  str    .
-                       It does it by adding two --regextrans2 options before
-                       all others. Add --debug to see what's really going on.
-
- --automap           : guesses folders mapping, for folders like
-                       "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
- --f1f2    str1=str2 : Force folder str1 to be synced to str2,
-                       --f1f2 overrides --automap and --regextrans2.
- --regextrans2  reg  : Apply the whole regex to each destination folders.
- --regextrans2  reg  : and this one. etc.
-                       When you play with the --regextrans2 option, first
-                       add also the safe options --dry --justfolders
-                       Then, when happy, remove --dry, remove --justfolders.
-                       Have in mind that --regextrans2 is applied after prefix
-                       and separator inversion. For examples see
-                       http://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
-
- --tmpdir       str  : Where to store temporary files and subdirectories.
-                       Will be created if it doesn't exist.
-                       Default is system specific, Unix is /tmp but
-                       it's often small and deleted at reboot.
-                       --tmpdir /var/tmp should be better.
- --pidfile      str  : The file where imapsync pid is written.
- --pidfilelocking    : Abort if pidfile already exists. Usefull to avoid
-                       concurrent transfers on the same mailbox.
-
- --nolog             : Turn off logging on file
- --logfile      str  : Change the default log filename (can be dirname/filename).
- --logdir       str  : Change the default log directory. Default is LOG_imapsync
-
- --prefix1      str  : Remove prefix to all destination folders
-                       (usually INBOX. or INBOX/ or an empty string "")
-                       you have to use --prefix1 if host1 imap server
-                       does not have NAMESPACE capability, so imapsync
-                       suggests to use it. All other cases are bad.
- --prefix2      str  : Add prefix to all host2 folders. See --prefix1
- --sep1         str  : Host1 separator in case NAMESPACE is not supported.
- --sep2         str  : Host2 separator in case NAMESPACE is not supported.
-
- --skipmess     reg  : Skips messages maching the regex.
-                       Example: 'm/[\\x80-ff]/' # to avoid 8bits messages.
-                       --skipmess is applied before --regexmess
- --skipmess     reg  : or this one, etc.
-
- --pipemess     cmd  : Apply this cmd command to each message content
-                       before the copy.
- --pipemess     cmd  : and this one, etc.
-
- --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
-
- --regexmess    reg  : Apply the whole regex to each message before transfer.
-                       Example: 's/\\000/ /g' # to replace null by space.
- --regexmess    reg  : and this one, etc.
-
- --regexflag    reg  : Apply the whole regex to each flags list.
-                       Example: 's/\"Junk"//g' # to remove "Junk" flag.
- --regexflag    reg  : and this one, etc.
-
- --delete            : Deletes messages on host1 server after a successful
-                       transfer. Option --delete has the following behavior:
-                       it marks messages as deleted with the IMAP flag
-                       \\Deleted, then messages are really deleted with an
-                       EXPUNGE IMAP command.
-
- --delete2           : Delete messages in host2 that are not in
-                       host1 server. Useful for backup or pre-sync.
- --delete2duplicates : Delete messages in host2 that are duplicates.
-                       Works only without --useuid since duplicates are
-                       detected with an header part of each message.
-
- --delete2folders    : Delete folders in host2 that are not in host1 server.
-                       For safety, first try it like this (it is safe):
-                       --delete2folders --dry --justfolders --nofoldersizes
- --delete2foldersonly   reg : Deleted only folders matching regex.
-                              Example: --delete2foldersonly "/^Junk\$|^INBOX.Junk\$/"
- --delete2foldersbutnot reg : Do not delete folders matching regex.
-                              Example: --delete2foldersbutnot "/Tasks\$|Contacts\$|Foo\$/"
- --noexpunge         : Do not expunge messages on host1.
-                       Expunge really deletes messages marked deleted.
-                       Expunge is made at the beginning, on host1 only.
-                       Newly transferred messages are also expunged if
-                       option --delete is given.
-                       No expunge is done on host2 account (unless --expunge2)
- --expunge1          : Expunge messages on host1 after messages transfer.
- --expunge2          : Expunge messages on host2 after messages transfer.
- --uidexpunge2       : uidexpunge messages on the host2 account
-                       that are not on the host1 account, requires --delete2
- --nomixfolders      : Avoid merging folders that are considered different on
-                       host1 but the same on destination host2 because of
-                       case sensitivities and insensitivities.
-
- --syncinternaldates : Sets the internal dates on host2 same as host1.
-                       Turned on by default. Internal date is the date
-                       a message arrived on a host (mtime).
- --idatefromheader   : Sets the internal dates on host2 same as the
-                       "Date:" headers.
-
- --maxsize      int  : Skip messages larger  (or equal) than  int  bytes
- --minsize      int  : Skip messages smaller (or equal) than  int  bytes
- --maxage       int  : Skip messages older than  int  days.
-                       final stats (skipped) don't count older messages
-                       see also --minage
- --minage       int  : Skip messages newer than  int  days.
-                       final stats (skipped) don't count newer messages
-                       You can do (+ are the messages selected):
-                       past|----maxage+++++++++++++++>now
-                       past|+++++++++++++++minage---->now
-                       past|----maxage+++++minage---->now (intersection)
-                       past|++++minage-----maxage++++>now (union)
-
- --search       str  : Selects only messages returned by this IMAP SEARCH
-                       command. Applied on both sides.
- --search1      str  : Same as --search for selecting host1 messages only.
- --search2      str  : Same as --search for selecting host2 messages only.
-                       --search CRIT equals --search1 CRIT --search2 CRIT
-
- --exitwhenover int  : Stop syncing when total bytes transferred reached.
-                       Gmail per day allows
-                       2500000000 = 2.5 GB downloaded from Gmail as host2
-                        500000000 = 500 MB uploaded to Gmail as host1.
-
- --maxlinelength int : skip messages with a line length longer than  int  bytes.
-                       RFC 2822 says it must be no more than 1000 bytes.
-
- --useheader    str  : Use this header to compare messages on both sides.
-                       Ex: Message-ID or Subject or Date.
- --useheader    str    and this one, etc.
-
- --subscribed        : Transfers subscribed folders.
- --subscribe         : Subscribe to the folders transferred on the
-                       host2 that are subscribed on host1. On by default.
- --subscribeall      : Subscribe to the folders transferred on the
-                       host2 even if they are not subscribed on host1.
-
- --nofoldersizes     : Do not calculate the size of each folder in bytes
-                       and message counts. Default is to calculate them.
- --nofoldersizesatend: Do not calculate the size of each folder in bytes
-                       and message counts at the end. Default is on.
- --justfoldersizes   : Exit after having printed the folder sizes.
-
- --syncacls          : Synchronises acls (Access Control Lists).
- --nosyncacls        : Does not synchronize acls. This is the default.
-                       Acls in IMAP are not standardized, be careful.
-
- --usecache          : Use cache to speedup.
- --nousecache        : Do not use cache. Caveat: --useuid --nousecache creates
-                       duplicates on multiple runs.
- --useuid            : Use uid instead of header as a criterium to recognize
-                       messages. Option --usecache is then implied unless
-                       --nousecache is used.
-
- --debug             : Debug mode.
- --debugfolders      : Debug mode for the folders part only.
- --debugcontent      : Debug content of the messages transfered. Huge ouput.
- --debugflags        : Debug mode for flags.
- --debugimap1        : IMAP debug mode for host1. Very verbose.
- --debugimap2        : IMAP debug mode for host2. Very verbose.
- --debugimap         : IMAP debug mode for host1 and host2.
- --debugmemory       : Debug mode showing memory consumption after each copy.
-
- --errorsmax     int : Exit when int number of errors is reached. Default is 50.
-
- --tests             : Run local non-regression tests. Exit code 0 means all ok.
- --testslive         : Run a live test with test1.lamiral.info imap server.
-                       Useful to check the basics. Needs internet connexion.
-
- --version           : Print only software version.
- --noreleasecheck    : Do not check for new imapsync release (a http request).
- --releasecheck      : Check for new imapsync release (a http request).
- --noid              : Do not send/receive ID command to imap servers.
- --justconnect       : Just connect to both servers and print useful
-                       information. Need only --host1 and --host2 options.
- --justlogin         : Just login to both host1 and host2 with users
-                       credentials, then exit.
- --justfolders       : Do only things about folders (ignore messages).
-
- --help              : print this help.
-
- Example: to synchronize imap account "test1" on "test1.lamiral.info"
-                     to  imap account "test2" on "test2.lamiral.info"
-                     with test1 password "secret1"
-                     and  test2 password "secret2"
-
- $0 $escape_char
-    --host1 test1.lamiral.info --user1 test1 --password1 secret1 $escape_char
-    --host2 test2.lamiral.info --user2 test2 --password2 secret2
-
-$localhost_info
+        $footer = 
+        qq{$localhost_info
 $rcs
 $imapsync_release
 
-$thank
-EOF
-	return( 1 ) ;
+$homepage
+} ;
+        return( $footer ) ;
 }
 
 
+
 sub usage_complete {
-	myprint( <<'EOF'  ) ;
+        # Unused, I guess this function could be deleted
+        my $usage = <<'EOF' ;
 --skipheader   reg     : Don't take into account header keyword
                          matching  reg    ex: --skipheader 'X.*'
 
 --skipsize             : Don't take message size into account to compare
                          messages on both sides. On by default.
-			 Use --no-skipsize for using size comparaison.
+                         Use --no-skipsize for using size comparaison.
 --allowsizemismatch    : allow RFC822.SIZE != fetched msg size
                          consider also --skipsize to avoid duplicate messages
                          when running syncs more than one time per mailbox
@@ -9024,48 +11167,283 @@ sub usage_complete {
 --split2      int      : same thing on host2.
 --nofixInboxINBOX      : Don't fix Inbox INBOX mapping.
 EOF
+        return( $usage ) ;
+}
+
+
+sub myGetOptions {
+# Started as a copy of Luke Ross Getopt::Long::CGI
+# https://metacpan.org/release/Getopt-Long-CGI
+# So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it,
+# which was Perl 5.6 or later licenses at the date of the copy.
+
+    my $mycgi = shift @ARG ;
+    my $arguments_ref = shift @ARG ;
+    my %options = @ARG ;
+
+    if ( not under_cgi_context(  ) ) {
+        # Not CGI - pass upstream for normal command line handling
+        return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ;
+    }
+    my $b_ref = $options{'debugbasket=s'} ;
+
+    my $badthings = 0 ;
+    foreach my $key (sort keys %options) {
+        my $val = $options{$key};
+        #push( @{$b_ref}, "opt:[$key] val:[$val]" . ( ('SCALAR' eq ref($val) and defined  $$val  ) ? " [$$val]" : q{} ) . "\n" ) ;
+
+        if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) {
+                push  @{$b_ref}, "Unknown option type: [$key]\n"  ;
+		$badthings++ ;
+            next ; # Unknown item
+        }
+
+        my $name = [split '|', $1, 1 ]->[0];
+
+        if (($3 || q{}) eq '+') {
+            ${ $val } = $mycgi->param($name); # "Incremental" integer
+        } elsif ($2) {
+            my @values = $mycgi->multi_param($name);
+            my $type = $2;
+	    #myprint( "[$type][@values][", $3 || q{}, "][$val][", ref($val), "]\n" ) ;
+            if (($3 || q{}) eq '%' or ref($val) eq 'HASH') {
+                my %values = map { split /=/mxs, $_ } @values;
+
+                if ($type =~ m/i$/mxs) {
+                    foreach my $k (keys %values) {
+                        $values{$k} = int $values{$k} ;
+                    }
+                } elsif ($type =~ m/f$/mxs) {
+                    foreach my $k (keys %values) {
+                        $values{$k} = 0 + $values{$k}
+                    }
+                }
+                if ( 'REF' eq ref $val ) {
+                        #push( @{$b_ref}, "refref($$val): " . ref($$val) . " %values= ", %values, "\n\n" ) ;
+                        %{ ${ $val } } = %values;
+                } else {
+                        #push( @{$b_ref}, "ref($val): " . ref($val) . " %values= ", %values, "\n\n" ) ;
+                        %{ $val } = %values;
+                }
+            } else {
+                if ($type =~ m/i$/mxs) {
+                    @values = map { int $_ } @values;
+                } elsif ($type =~ m/f$/mxs) {
+                    @values = map { 0 + $_ } @values;
+                }
+                if (($3 || q{}) eq '@' or ref($val) eq 'ARRAY') {
+                    @{ $val } = @values ;
+                } else {
+                    ${ $val } = $values[0] ;
+                }
+            }
+        } else {
+            # Checkbox
+	    # Considers only --name
+	    # Should consider also --no-name and --noname
+            ${ $val } = $mycgi->param($name) ? 1 : undef ;
+            #push( @{$b_ref}, "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
+	    #myprint( "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
+	    #myprint( "param($name) ref($val): " . ref($val) . " \n\n" ) ;
+        }
+    }
+    if ( $badthings ) {
+		return ; # undef or ()
+    } else {
+		return( 1 ) ;
+    }
+}
+
+
+sub tests_get_options  {
+	note( 'Entering tests_get_options()' ) ;
+
+	# CAVEAT: still setting global variables, be carefull
+	# with tests, the context increases! $debug stays on for example.
+	# API:
+	# * input arguments: two ways, command line or CGI
+	#    * the program arguments
+	#    * QUERY_STRING env variable
+	# * return
+	#   * undef if bad things happened like
+	#     * options not known
+	#     * --delete 2 input
+	#   * number of arguments or QUERY_STRING length
+	my $mysync ;
+	is( undef, get_options( $mysync, qw( --noexist ) ),                   'get_options: --noexist  => undef' ) ;
+	is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version  => undef' ) ;
+	is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => undef' ) ;
+	is( 1,     get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ;
+	is( 1,     get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ;
+	is( undef, get_options( $mysync, qw( --debug --noexist --version ) ), 'get_options: --debug --noexist --version  => undef' ) ;
+
+	note( 'Leaving  tests_get_options()' ) ;
+	return ;
+}
+
+sub tests_get_options_cgi  {
+	note( 'Entering tests_get_options_cgi()' ) ;
+
+# Temporary, have to think harder about testing CGI context in command line --tests
+	# API:
+	# * input arguments: two ways, command line or CGI
+	#    * the program arguments
+	#    * QUERY_STRING env variable
+	# * return
+	#   * QUERY_STRING length
+
+	# CGI context
+	local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ;
+
+	# Real full test
+	# = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on'
+	my $mysync ;
+	require CGI ;
+	CGI->import( qw( -no_debug ) ) ;
+
+	# Testing boolean
+	$mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ;
+	local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ;
+	is( 22,   get_options_cgi( $mysync ), 'get_options: QUERY_STRING => 22' ) ;
+	is(  1,   $version,        'get_options: $version => 1' ) ;
+	# debugenv is not allowed in cgi context
+	is(  undef,   $mysync->{debugenv},        'get_options: $mysync->{debugenv} => undef' ) ;
+
+	# QUERY_STRING in this test is only for return value of get_options_cgi
+	# Have to think harder, GET/POST context, is this return value a good thing?
+	local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ;
+	$mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ;
+	is( 36,      get_options_cgi( $mysync,  ), 'get_options: QUERY_STRING => 36' ) ;
+	is( 'test1',  $mysync->{user1},    'get_options: $mysync->{user1} => test1' ) ;
+
+	# Testing @
+	$mysync->{cgi} = CGI->new( 'folder=fd1' ) ;
+	get_options_cgi( $mysync ) ;
+	is_deeply( [ 'fd1' ],  [ @folder ],    'get_options: @folder => fd1' ) ;
+	$mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ;
+	get_options_cgi( $mysync ) ;
+	is_deeply( [ 'fd1', 'fd2' ],  [ @folder ],    'get_options: @folder => fd1' ) ;
+
+	# Testing %
+	$mysync->{cgi} = CGI->new( 'f1f2=s1=d1&f1f2=s2=d2&f1f2=s3=d3' ) ;
+	get_options_cgi( $mysync ) ;
+	#$mysync->{f1f2} = { 's1' => 'd1', 's2' => 'd2' } ;
+	is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' },
+	$mysync->{f1f2}, 'get_options: f1f2 => s1=d1 s2=d2 s3=d3' ) ;
+
+	# Testing boolean ! with --noxxx, doesnot work
+	$mysync->{cgi} = CGI->new( 'nodry=on' ) ;
+	is( undef,  $mysync->{dry},    'get_options: --nodry => $mysync->{dry} => 0' ) ;
+
+	note( 'Leaving  tests_get_options_cgi()' ) ;
 	return ;
 }
 
 
 
-sub get_options {
-	# In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
-	my $numopt = scalar  @ARGV  || length $ENV{'QUERY_STRING'} ;
-	my $argv   = join "\x00", @ARGV ;
+sub get_options_cgi {
+        # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
+	my $mysync = shift @ARG ;
+	my $mycgi = $mysync->{cgi} || return ;
+	my @arguments = @ARG ;
+	# final 0 is used to print usage when no option is given
+        my $numopt = length $ENV{'QUERY_STRING'} || 1 ;
+	$mysync->{f1f2} = {} ;
+        my $opt_ret = myGetOptions(
+	$mycgi,
+	\@arguments,
+	'abort'            => \$mysync->{abort},
+        'host1=s'          => \$mysync->{host1},
+        'host2=s'          => \$mysync->{host2},
+        'user1=s'          => \$mysync->{user1},
+        'user2=s'          => \$mysync->{user2},
+        'password1=s'      => \$mysync->{password1},
+        'password2=s'      => \$mysync->{password2},
+        'dry!'             => \$mysync->{dry},
+        'version'          => \$version,
+        'ssl1!'            => \$mysync->{ssl1},
+        'ssl2!'            => \$mysync->{ssl2},
+        'tls1!'            => \$mysync->{tls1},
+        'tls2!'            => \$mysync->{tls2},
+        'justlogin!'       => \$justlogin,
+        'addheader!'       => \$addheader,
+        'automap!'         => \$mysync->{automap},
+        'justautomap!'     => \$mysync->{justautomap},
+	'gmail1'           => \$mysync->{gmail1},
+	'gmail2'           => \$mysync->{gmail2},
+	'office1'          => \$mysync->{office1},
+	'office2'          => \$mysync->{office2},
+	'exchange1'        => \$mysync->{exchange1},
+	'exchange2'        => \$mysync->{exchange2},
+	'domino1'          => \$mysync->{domino1},
+	'domino2'          => \$mysync->{domino2},
+        'f1f2=s%'          => \$mysync->{f1f2},
+        'folder=s'         => \@folder,
+        'testslive!'       => \$mysync->{testslive},
+        'testslive6!'      => \$mysync->{testslive6},
+        ) ;
 
-	if ( $argv =~ m/-delete\x002/x ) {
-		myprint( "May be you mean --delete2 instead of --delete 2\n"  ) ;
-		exit 1 ;
+        $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n"  ) ;
+
+	if ( ! $opt_ret ) {
+		return ;
 	}
-	$sync->{f1f2} = {} ;
-        my $opt_ret = Imapsync::Getopt::Long::GetOptions(
+	return $numopt ;
+}
+
+sub get_options_cmd {
+	my $mysync = shift @ARG ;
+	my @arguments = @ARG ;
+	my $mycgi = $mysync->{cgi} ;
+	# final 0 is used to print usage when no option is given on command line
+        my $numopt = scalar  @arguments || 0 ;
+        my $argv   = join "\x00", @arguments ;
+
+        if ( $argv =~ m/-delete\x002/x ) {
+                output( $mysync, "May be you mean --delete2 instead of --delete 2\n"  ) ;
+                return ;
+        }
+        $mysync->{f1f2} = {} ;
+        my $opt_ret = myGetOptions(
+	$mycgi,
+	\@arguments,
         'debug!'        => \$debug,
         'debuglist!'    => \$debuglist,
         'debugcontent!' => \$debugcontent,
-        'debugsleep=f'  => \$sync->{debugsleep},
+        'debugsleep=f'  => \$mysync->{debugsleep},
         'debugflags!'   => \$debugflags,
         'debugimap!'    => \$debugimap,
         'debugimap1!'   => \$debugimap1,
         'debugimap2!'   => \$debugimap2,
         'debugdev!'     => \$debugdev,
-        'debugmemory!'  => \$sync->{debugmemory},
-        'debugfolders!' => \$sync->{debugfolders},
-        'debugssl=i'    => \$sync->{debugssl},
-	'debugbasket=s' => \@debugbasket,
-	'debugcgi!'     => \$debugcgi,
-        'host1=s'     => \$host1,
-        'host2=s'     => \$host2,
-        'port1=i'     => \$port1,
-        'port2=i'     => \$port2,
-	'inet4'       => \$sync->{inet4},
-	'inet6'       => \$sync->{inet6},
-        'user1=s'     => \$user1,
-        'user2=s'     => \$user2,
+        'debugmemory!'  => \$mysync->{debugmemory},
+        'debugfolders!' => \$mysync->{debugfolders},
+        'debugssl=i'    => \$mysync->{debugssl},
+        'debugbasket=s' => \@debugbasket,
+        'debugcgi!'     => \$debugcgi,
+	'debugenv'      => \$mysync->{debugenv},
+        'simulong=i'    => \$mysync->{simulong},
+	'abort'         => \$mysync->{abort},
+        'host1=s'     => \$mysync->{host1},
+        'host2=s'     => \$mysync->{host2},
+        'port1=i'     => \$mysync->{port1},
+        'port2=i'     => \$mysync->{port2},
+        'inet4|ipv4'    => \$mysync->{inet4},
+        'inet6|ipv6'    => \$mysync->{inet6},
+        'user1=s'     => \$mysync->{user1},
+        'user2=s'     => \$mysync->{user2},
+	'gmail1'      => \$mysync->{gmail1},
+	'gmail2'      => \$mysync->{gmail2},
+	'office1'     => \$mysync->{office1},
+	'office2'     => \$mysync->{office2},
+	'exchange1'   => \$mysync->{exchange1},
+	'exchange2'   => \$mysync->{exchange2},
+	'domino1'     => \$mysync->{domino1},
+	'domino2'     => \$mysync->{domino2},
         'domain1=s'   => \$domain1,
         'domain2=s'   => \$domain2,
-        'password1=s' => \$password1,
-        'password2=s' => \$password2,
+        'password1=s' => \$mysync->{password1},
+        'password2=s' => \$mysync->{password2},
         'passfile1=s' => \$passfile1,
         'passfile2=s' => \$passfile2,
         'authmd5!'    => \$authmd5,
@@ -9073,34 +11451,34 @@ sub get_options {
         'authmd52!'   => \$authmd52,
         'sep1=s'      => \$sep1,
         'sep2=s'      => \$sep2,
-        'folder=s'    => \@folder,
-        'folderrec=s' => \@folderrec,
-        'include=s'   => \@include,
-        'exclude=s'   => \@exclude,
-        'folderfirst=s' => \@folderfirst,
-        'folderlast=s' => \@folderlast,
-        'prefix1=s'   => \$prefix1,
-        'prefix2=s'   => \$prefix2,
-	'subfolder2=s' => \$subfolder2,
-        'fixslash2!'   => \$fixslash2,
-        'fixInboxINBOX!' => \$fixInboxINBOX,
-        'regextrans2=s' => \@regextrans2,
-        'mixfolders!' => \$mixfolders,
+        'folder=s'          => \@folder,
+        'folderrec=s'       => \@folderrec,
+        'include=s'         => \@include,
+        'exclude=s'         => \@exclude,
+        'folderfirst=s'     => \@folderfirst,
+        'folderlast=s'      => \@folderlast,
+        'prefix1=s'         => \$prefix1,
+        'prefix2=s'         => \$prefix2,
+        'subfolder2=s'      => \$subfolder2,
+        'fixslash2!'        => \$fixslash2,
+        'fixInboxINBOX!'    => \$fixInboxINBOX,
+        'regextrans2=s'     => \@regextrans2,
+        'mixfolders!'       => \$mixfolders,
         'skipemptyfolders!' => \$skipemptyfolders,
-        'regexmess=s' => \@regexmess,
-        'skipmess=s' => \@skipmess,
-        'pipemess=s' => \@pipemess,
-	'pipemesscheck!' => \$pipemesscheck,
+        'regexmess=s'       => \@regexmess,
+        'skipmess=s'        => \@skipmess,
+        'pipemess=s'        => \@pipemess,
+        'pipemesscheck!'    => \$pipemesscheck,
         'disarmreadreceipts!' => \$disarmreadreceipts,
-        'regexflag=s' => \@regexflag,
-        'filterflags!' => \$filterflags,
-        'flagscase!'  => \$flagscase,
+        'regexflag=s'         => \@regexflag,
+        'filterflags!'        => \$filterflags,
+        'flagscase!'          => \$flagscase,
         'syncflagsaftercopy!' => \$syncflagsaftercopy,
-        'delete|delete1!' => \$delete,
-        'delete2!'    => \$delete2,
-        'delete2duplicates!' => \$delete2duplicates,
-        'delete2folders!'    => \$delete2folders,
-        'delete2foldersonly=s' => \$delete2foldersonly,
+        'delete|delete1!'     => \$delete1,
+        'delete2!'            => \$delete2,
+        'delete2duplicates!'  => \$delete2duplicates,
+        'delete2folders!'     => \$delete2folders,
+        'delete2foldersonly=s'   => \$delete2foldersonly,
         'delete2foldersbutnot=s' => \$delete2foldersbutnot,
         'syncinternaldates!' => \$syncinternaldates,
         'idatefromheader!'   => \$idatefromheader,
@@ -9114,11 +11492,10 @@ sub get_options {
         'search2=s'   => \$search2,
         'foldersizes!' => \$foldersizes,
         'foldersizesatend!' => \$foldersizesatend,
-        'dry!'        => \$dry,
-        'expunge!'    => \$expunge,
-        'expunge1!'    => \$expunge1,
-        'expunge2!'    => \$expunge2,
-        'uidexpunge2!' => \$uidexpunge2,
+        'dry!'              => \$mysync->{dry},
+        'expunge1|expunge!' => \$expunge1,
+        'expunge2!'         => \$expunge2,
+        'uidexpunge2!'      => \$uidexpunge2,
         'subscribed!' => \$subscribed,
         'subscribe!'  => \$subscribe,
         'subscribeall|subscribe_all!'  => \$subscribeall,
@@ -9130,8 +11507,8 @@ sub get_options {
         'version'     => \$version,
         'help'        => \$help,
         'timeout=i'   => \$timeout,
-        'timeout1=i'   => \$sync->{h1}->{timeout},
-        'timeout2=i'   => \$sync->{h2}->{timeout},
+        'timeout1=i'   => \$mysync->{h1}->{timeout},
+        'timeout2=i'   => \$mysync->{h2}->{timeout},
         'skipheader=s' => \$skipheader,
         'useheader=s' => \@useheader,
         'wholeheaderifneeded!'   => \$wholeheaderifneeded,
@@ -9140,14 +11517,15 @@ sub get_options {
         'allowsizemismatch!' => \$allowsizemismatch,
         'fastio1!'     => \$fastio1,
         'fastio2!'     => \$fastio2,
-        'ssl1!'        => \$ssl1,
-        'ssl2!'        => \$ssl2,
-        'ssl1_ssl_version=s' => \$sync->{h1}->{sslargs}->{SSL_version},
-        'ssl2_ssl_version=s' => \$sync->{h2}->{sslargs}->{SSL_version},
-        'sslargs1=s%'        => \$sync->{h1}->{sslargs},
-        'sslargs2=s%'        => \$sync->{h2}->{sslargs},
-        'tls1!'        => \$tls1,
-        'tls2!'        => \$tls2,
+	'sslcheck!'    => \$mysync->{sslcheck},
+        'ssl1!'        => \$mysync->{ssl1},
+        'ssl2!'        => \$mysync->{ssl2},
+        'ssl1_ssl_version=s' => \$mysync->{h1}->{sslargs}->{SSL_version},
+        'ssl2_ssl_version=s' => \$mysync->{h2}->{sslargs}->{SSL_version},
+        'sslargs1=s%'        => \$mysync->{h1}->{sslargs},
+        'sslargs2=s%'        => \$mysync->{h2}->{sslargs},
+        'tls1!'        => \$mysync->{tls1},
+        'tls2!'        => \$mysync->{tls2},
         'uid1!'        => \$uid1,
         'uid2!'        => \$uid2,
         'authmech1=s' => \$authmech1,
@@ -9161,13 +11539,15 @@ sub get_options {
         'buffersize=i' => \$buffersize,
         'reconnectretry1=i' => \$reconnectretry1,
         'reconnectretry2=i' => \$reconnectretry2,
-        'tests!'       => \$tests,
-        'testsdebug|tests_debug!' => \$testsdebug,
-        'testslive!'   => \$testslive,
-        'justlogin!'  => \$justlogin,
-        'tmpdir=s'    => \$tmpdir,
-        'pidfile=s'    => \$sync->{pidfile},
-        'pidfilelocking!' => \$sync->{pidfilelocking},
+        'tests!'          => \$mysync->{ tests },
+        'testsdebug|tests_debug!' => \$mysync->{ testsdebug },
+	'testsunit=s@'    => \$mysync->{testsunit},
+        'testslive!'      => \$mysync->{testslive},
+        'testslive6!'     => \$mysync->{testslive6},
+        'justlogin!'      => \$justlogin,
+        'tmpdir=s'        => \$tmpdir,
+        'pidfile=s'       => \$mysync->{pidfile},
+        'pidfilelocking!' => \$mysync->{pidfilelocking},
         'releasecheck!' => \$releasecheck,
         'modulesversion|modules_version!' => \$modulesversion,
         'usecache!'    => \$usecache,
@@ -9179,119 +11559,278 @@ sub get_options {
         'checkselectable!' => \$checkselectable,
         'checkmessageexists!' => \$checkmessageexists,
         'expungeaftereach!' => \$expungeaftereach,
-        'abletosearch!' => \$abletosearch,
-        'showpasswords!' => \$showpasswords,
-        'maxlinelength=i' => \$maxlinelength,
-        'maxlinelengthcmd=s' => \$maxlinelengthcmd,
-        'minmaxlinelength=i' => \$minmaxlinelength,
-        'debugmaxlinelength!' => \$debugmaxlinelength,
+        'abletosearch!'  => \$mysync->{abletosearch},
+        'abletosearch1!' => \$mysync->{abletosearch1},
+        'abletosearch2!' => \$mysync->{abletosearch2},
+        'showpasswords!' => \$mysync->{showpasswords},
+        'maxlinelength=i'        => \$maxlinelength,
+        'maxlinelengthcmd=s'     => \$maxlinelengthcmd,
+        'minmaxlinelength=i'     => \$minmaxlinelength,
+        'debugmaxlinelength!'    => \$debugmaxlinelength,
         'fixcolonbug!'           => \$fixcolonbug,
         'create_folder_old!'     => \$create_folder_old,
-        'maxmessagespersecond=f' => \$maxmessagespersecond,
-        'maxbytespersecond=i'    => \$maxbytespersecond,
+        'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond},
+        'maxbytespersecond=i'    => \$mysync->{maxbytespersecond},
+	'maxbytesafter=i'        => \$mysync->{maxbytesafter},
+	'maxsleep=f'             => \$mysync->{maxsleep},
         'skipcrossduplicates!'   => \$skipcrossduplicates,
         'debugcrossduplicates!'  => \$debugcrossduplicates,
-        'log!'                   => \$sync->{log},
-        'logfile=s'        => \$sync->{logfile},
-        'logdir=s'         => \$sync->{logdir},
-        'errorsmax=i'      => \$sync->{errorsmax},
-        'errorsdump!'      => \$sync->{errorsdump},
+        'log!'                   => \$mysync->{log},
+        'logfile=s'        => \$mysync->{logfile},
+        'logdir=s'         => \$mysync->{logdir},
+        'errorsmax=i'      => \$mysync->{errorsmax},
+        'errorsdump!'      => \$mysync->{errorsdump},
         'fetch_hash_set=s' => \$fetch_hash_set,
-        'automap!'         => \$sync->{automap},
-        'justautomap!'     => \$sync->{justautomap},
-        'id!'              => \$sync->{id},
-        'f1f2=s%'          => \$sync->{f1f2},
-        'justfolderlists!' => \$sync->{justfolderlists},
-        'delete1emptyfolders' => \$sync->{delete1emptyfolders},
+        'automap!'         => \$mysync->{automap},
+        'justautomap!'     => \$mysync->{justautomap},
+        'id!'              => \$mysync->{id},
+        'f1f2=s%'          => \$mysync->{f1f2},
+        'justfolderlists!' => \$mysync->{justfolderlists},
+        'delete1emptyfolders' => \$mysync->{delete1emptyfolders},
         ) ;
 
+        $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n"  ) ;
 
-	$debugcgi and myprint( map { "$_ => $ENV{$_}\n" } sort keys  %ENV   ) ;
-	$debugcgi and myprint( "@debugbasket\n"  ) ;
-        $debug and myprint( "get options: [$opt_ret]\n"  ) ;
-
-        # just the version
-        myprint( imapsync_version(  ), "\n" ) and exit 0 if ( $version ) ;
-        # $tmpdir is used in tests_pipemess()
-	$tmpdir ||= File::Spec->tmpdir(  ) ;
-	if ( $tests or $testsdebug ) {
-		$test_builder = Test::More->builder ;
-		if ( $tests ) { tests(  ) ; }
-		if ( $testsdebug ) { testsdebug(  ) ; }
-		#$test_builder->reset(  ) ;
-		exit ;
+	if ( ! $opt_ret ) {
+		return ;
 	}
+	return $numopt ;
+}
 
-	#$help = 1 if ! $numopt;
-	load_modules(  );
-
-	# exit with --help option or no option at all
-	$debug and myprint( "numopt:$numopt\n"  ) ;
-        usage(  ) and exit  if ( $help or not $numopt ) ;
-
-	# don't go on if options are not all known.
-        exit $EX_USAGE unless ( $opt_ret ) ;
-
-	# init live varaiables
-	testslive(  ) if ( $testslive ) ;
+sub get_options  {
+	my $mysync = shift @ARG ;
+	my @arguments = @ARG ;
+	my $mycgi = $mysync->{cgi} ;
 
+	if ( under_cgi_context(  ) ) {
+		# CGI context
+		return get_options_cgi( $mysync, @arguments )  ;
+	}else{
+		# Command line context ;
+		return get_options_cmd( $mysync, @arguments )  ;
+	}
 	return ;
 }
 
-sub testslive {
-	$host1 = 'test1.lamiral.info' ;
-	$user1 = 'test1' ;
-	$password1 = 'secret1' ;
-	$host2 = 'test2.lamiral.info' ;
-	$user2 = 'test2' ;
-	$password2 ='secret2' ;
+sub testunitsession {
+	my $mysync = shift ;
+	
+	if ( ! $mysync ) { return ; }
+	if ( ! $mysync->{ testsunit } ) { return ; }
+
+	my @functions = @{ $mysync->{ testsunit } } ;
+	
+	if ( ! @functions ) { return ; }
+
+	SKIP: {
+		if ( ! @functions ) { skip 'No test in normal run' ; }
+		testsunit( @functions ) ;
+                done_testing(  ) ;
+	}
+	return ;
+}
+
+sub tests_count_0s {
+	note( 'Entering tests_count_zeros()' ) ;
+	is( 0, count_0s(  ), 'count_0s: no parameters => undef' ) ;
+	is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ;
+	is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ;
+	is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ;
+	is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ;
+	note( 'Leaving  tests_count_zeros()' ) ;
+	return ;
+}
+sub count_0s {
+	my @array = @ARG ;
+	
+	if ( ! @array ) { return 0 ; }
+	my $nb_zeros = 0 ;
+	map { $_ == 0 and $nb_zeros += 1 } @array ;
+	return $nb_zeros ;
+}
+
+sub tests_report_failures {
+        note( 'Entering tests_report_failures()' ) ;
+	is( undef, report_failures(  ), 'report_failures: no parameters => undef' ) ;
+	is( "n° 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => n° 1 - first' ) ;
+	is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ;
+	is( "n° 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => n° 2 - second' ) ;
+	is( "n° 1 - first\nn° 2 - second\n", report_failures( ( {'ok' => 0, name => 'first'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: both failed => n° 1 - first n° 2 - second' ) ;
+	note( 'Leaving  tests_report_failures()' ) ;
+	return ;
+}
+
+sub report_failures {
+	my @details = @ARG ;
+	
+	if ( ! @details ) { return ; }
+	
+	my $counter = 1 ;
+	my $report  = q{} ;
+	foreach my $details ( @details ) {
+		if ( ! $details->{ 'ok' } ) {
+			my $name = $details->{ 'name' } || 'NONAME' ;
+			$report .= "n° $counter - $name\n" ;
+		}
+		$counter += 1 ;
+	}
+	return $report ;
+
+}
+
+sub tests_true {
+	note( 'Entering tests_true()' ) ;
+	is( 1, 1, 'true: 1 is 1' ) ;
+	note( 'Leaving  tests_true()' ) ;
+	return ;
+}
+
+sub tests_testsunit {
+	note( 'Entering tests_testunit()' ) ;
+	is( undef, testsunit(  ), 'testsunit: no parameters => undef' ) ;
+	is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ;
+	is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ;
+	is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ;
+	is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ;
+	note( 'Leaving  tests_testunit()' ) ;
+	return ;
+}
+
+sub testsunit {
+	my @functions = @ARG ;
+	
+	if ( ! @functions ) { #
+                myprint( "testsunit warning: no argument given\n" ) ;
+                return ; 
+        }
+			
+	foreach my $function ( @functions ) {
+		if ( ! $function ) {
+                        myprint( "testsunit warning: argument is empty\n" ) ;
+                        next ; 
+                }
+		if ( ! exists &$function ) {
+                        myprint( "testsunit warning: function $function does not exist\n" ) ;
+                        next ; 
+                }
+		if ( ! defined &$function ) {
+                        myprint( "testsunit warning: function $function is not defined\n" ) ;
+                        next ; 
+                }
+		my $function_ref = \&{ $function } ;
+		&$function_ref() ; 
+	}
 	return ;
 }
 
 sub testsdebug {
-      SKIP: {
-                skip 'No test in normal run' if ( not $testsdebug ) ;
+	my $mysync = shift ;
+	if ( ! $mysync->{ testsdebug } ) { return ; }
+	SKIP: {
+                if ( ! $mysync->{ testsdebug } ) { 
+			skip 'No test in normal run' ;
+		}
+		
+		note( 'Entering testsdebug()' ) ;
+		ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ;
                 #tests_bytes_display_string(  ) ;
                 #tests_ucsecond(  ) ;
                 #tests_mkpath(  ) ;
-                #eval { tests_mkpath(  ) ; } or ok( 0 == 1,  'tests_mkpath fail badly?' ) ;
                 #tests_format_for_imap_arg(  ) ;
                 #tests_is_a_release_number(  ) ;
                 #tests_delete1emptyfolders(  ) ;
                 #tests_memory_consumption(  ) ;
                 #tests_imap2_folder_name() ;
                 #tests_length_ref(  ) ;
-		#tests_is_valid_directory(  ) ;
-                #tests_firstline(  ) ;
                 #tests_diff_or_NA(  ) ;
                 #tests_match_number(  ) ;
                 #tests_all_defined(  ) ;
                 #tests_guess_separator(  ) ;
-                tests_pipemess(  ) ;
                 #tests_message_for_host2(  ) ;
+                #tests_special_from_folders_hash(  ) ;
+                #tests_do_valid_directory(  ) ;
+		#tests_notmatch(  ) ;
+		#tests_match(  ) ;
+		#tests_get_options(  ) ;
+		#tests_rand32(  ) ;
+		#tests_string_to_file(  ) ;
+		#tests_hashsynclocal(  ) ;
+		#tests_output(  ) ;
+		#tests_output_reset_with(  ) ;
+		#tests_output_start(  ) ;
+		#tests_hashsync(  ) ;
+		#tests_check_last_release(  ) ;
+		#tests_cpu_number(  ) ;
+		#tests_load_and_delay(  ) ;
+		#tests_loadavg(  ) ;
+                #tests_backtick(  ) ;
+                #tests_firstline(  ) ;
+                #tests_pipemess(  ) ;
+		#tests_not_long_imapsync_version_public(  ) ;
+		#tests_get_options_cgi(  ) ;
+                #tests_guess_special(  ) ;
+####tests_reconnect_if_needed(  ) ;
+		#tests_reconnect_12_if_needed(  ) ;
+                #tests_sleep_max_bytes(  ) ;
+		#tests_file_to_string(  ) ;
+		#tests_under_cgi_context(  ) ;
+		#tests_umask(  ) ;
+		#tests_umask_str(  ) ;
+		#tests_set_umask(  ) ;
+		#tests_createhashfileifneeded(  ) ;
+                #tests_filter_forbidden_characters(  ) ;
+                #tests_logfile(  ) ;
+                #tests_setlogfile(  ) ;
+		#tests_move_slash(  ) ;
+		#tests_testsunit(  ) ;
+		#tests_always_fail(  ) ;
+		#tests_count_0s(  ) ;
+		#tests_report_failures(  ) ;
+		#tests_max(  ) ;
+		#tests_min(  ) ;
+		#tests_sleep_if_needed(  ) ;
+		#tests_imapsping(  ) ;
+		#tests_tcpping(  ) ;
+		#tests_sslcheck(  ) ;
+		#tests_resolv(  ) ;
+		#tests_resolvrev(  ) ;
+		#tests_connect_socket(  ) ;
+                #tests_probe_imapssl(  ) ;
+                #tests_mailimapclient_connect(  ) ;
+                #tests_guess_prefix(  ) ;
+                #tests_usage(  ) ;
+                #tests_version_from_rcs(  ) ;
+                #tests_mailimapclient_connect_bug(  ) ; # it fails with Mail-IMAPClient <= 3.39
+                tests_backslash_caret(  ) ;
+
+		note( 'Leaving  testsdebug()' ) ;
                 done_testing(  ) ;
-                note('End of imapsync --tests_debug') ;
         }
         return ;
 }
 
-sub tests {
 
-      SKIP: {
-                skip 'No test in normal run' if ( not $tests ) ;
+
+sub tests {
+	my $mysync = shift ;
+	if ( ! $mysync->{ tests } ) { return ; }
+
+	SKIP: {
+                skip 'No test in normal run' if ( ! $mysync->{ tests } ) ;
+		note( 'Entering tests()' ) ;
                 tests_folder_routines(  ) ;
                 tests_compare_lists(  ) ;
-                tests_regexmess();
+                tests_regexmess(  ) ;
                 tests_skipmess(  ) ;
                 tests_flags_regex();
                 tests_ucsecond(  ) ;
                 tests_permanentflags();
                 tests_flags_filter(  ) ;
                 tests_separator_invert(  ) ;
-                tests_imap2_folder_name() ;
-                tests_command_line_nopassword();
+                tests_imap2_folder_name(  ) ;
+                tests_command_line_nopassword(  ) ;
                 tests_good_date(  ) ;
-                tests_max();
+                tests_max(  ) ;
                 tests_remove_not_num();
                 tests_memory_consumption( ) ;
                 tests_is_a_release_number();
@@ -9305,7 +11844,7 @@ sub tests {
                 tests_clean_cache_2(  ) ;
                 tests_touch(  ) ;
                 tests_flagscase(  ) ;
-                eval { tests_mkpath(  ) ; } or ok( 0 == 1,  'tests_mkpath fail badly?' ) ;
+                tests_mkpath(  ) ;
                 tests_extract_header(  ) ;
                 tests_decompose_header(  ) ;
                 tests_epoch(  ) ;
@@ -9316,7 +11855,6 @@ sub tests {
                 tests_cache_folder(  ) ;
                 tests_time_remaining(  ) ;
                 tests_decompose_regex(  ) ;
-                tests_Banner(  ) ;
                 tests_backtick(  ) ;
                 tests_bytes_display_string(  ) ;
                 tests_header_line_normalize(  ) ;
@@ -9340,149 +11878,63 @@ sub tests {
                 tests_quota_extract_storage_limit_in_bytes(  ) ;
                 tests_quota_extract_storage_current_in_bytes(  ) ;
                 tests_guess_special(  ) ;
-		tests_is_valid_directory(  ) ;
+                tests_do_valid_directory(  ) ;
                 tests_delete1emptyfolders(  ) ;
                 tests_message_for_host2(  ) ;
                 tests_length_ref(  ) ;
-                tests_firstline(  ) ;               
+                tests_firstline(  ) ;
                 tests_diff_or_NA(  ) ;
-                #tests_always_fail(  ) ;
                 tests_match_number(  ) ;
                 tests_all_defined(  ) ;
-                done_testing( 693 ) ;
-                note('End of imapsync --tests') ;
+                tests_special_from_folders_hash(  ) ;
+		tests_notmatch(  ) ;
+		tests_match(  ) ;
+		tests_get_options(  ) ;
+		tests_get_options_cgi(  ) ;
+		tests_rand32(  ) ;
+		tests_hashsynclocal(  ) ;
+		tests_hashsync(  ) ;
+		tests_output(  ) ;
+		tests_output_reset_with(  ) ;
+		tests_output_start(  ) ;
+		tests_check_last_release(  ) ;
+		tests_loadavg(  ) ;
+		tests_cpu_number(  ) ;
+		tests_load_and_delay(  ) ;
+		#tests_imapsping(  ) ;
+		#tests_tcpping(  ) ;
+		tests_sslcheck(  ) ;
+		tests_not_long_imapsync_version_public(  ) ;
+		tests_reconnect_if_needed(  ) ;
+		tests_reconnect_12_if_needed(  ) ;
+		tests_sleep_if_needed(  ) ;
+		tests_string_to_file(  ) ;
+		tests_file_to_string(  ) ;
+		tests_under_cgi_context(  ) ;
+		tests_umask(  ) ;
+		tests_umask_str(  ) ;
+		tests_set_umask(  ) ;
+		tests_createhashfileifneeded(  ) ;
+		tests_move_slash(  ) ;
+		tests_testsunit(  ) ;
+		tests_count_0s(  ) ;
+		tests_report_failures(  ) ;
+		tests_min(  ) ;
+		#tests_resolv(  ) ;
+		#tests_resolvrev(  ) ;
+		tests_connect_socket(  ) ;
+                tests_probe_imapssl(  ) ;
+                tests_mailimapclient_connect(  ) ;
+                tests_usage(  ) ;
+                tests_version_from_rcs(  ) ;
+                tests_backslash_caret(  ) ;
+                #tests_mailimapclient_connect_bug(  ) ; # it fails with Mail-IMAPClient <= 3.39
+		#tests_always_fail(  ) ;
+		done_testing( 1012 ) ;
+		note( 'Leaving  tests()' ) ;
         }
         return ;
 }
 
 
 
-# IMAPClient 3.xx ads
-
-package Mail::IMAPClient;
-
-sub Tls {
-	my $self  = shift ;
-	my $value = shift ;
-	if ( defined  $value  ) { $self->{TLS} = $value }
-	return $self->{TLS};
-}
-
-sub Reconnect_counter {
-	my $self  = shift ;
-        my $value = shift ;
-	$self->{Reconnect_counter} = 0 if ( not defined  $self->{Reconnect_counter}  ) ;
-	if ( defined  $value  ) { $self->{Reconnect_counter} = $value }
-	return( $self->{Reconnect_counter} ) ;
-}
-
-
-sub Banner {
-	my $self  = shift ;
-	my $value = shift ;
-	if ( defined $value ) { $self->{ BANNER } = $value }
-	return $self->{ BANNER };
-}
-
-sub capability_update {
-	my $self = shift ;
-
-	delete $self->{CAPABILITY} ;
-	return( $self->capability ) ;
-}
-
-
-package Imapsync::Getopt::Long ;
-# Started as a copy of Luke Ross Getopt::Long::CGI
-# https://metacpan.org/release/Getopt-Long-CGI
-# So this section is under the same license as Getopt-Long-CGI Luke Ross wants it,
-# which was Perl 5.6 or later licenses at the date of the copy.
-
-use strict ;
-use warnings ;
-
-use Getopt::Long(  ) ;
-
-
-sub GetOptions {
-    my %options = @_ ;
-
-    if ( not $ENV{SERVER_SOFTWARE} ) {
-        # Not CGI - pass upstream for normal command line handling
-        return Getopt::Long::GetOptions( %options ) ;
-    }
-    my $b_ref = $options{'debugbasket=s'} ;
-    require CGI ;
-    require CGI::Carp ;
-    CGI::Carp->import( 'fatalsToBrowser' ) ;
-
-    my $cgi = CGI->new(  ) ;
-    $cgi->param( 'debugcgi' ) and myprint( "<h2>Current Values</h2>\n" . $cgi->Dump  ) ;
-
-    foreach my $key (sort keys %options) {
-        my $val = $options{$key};
-	#push( @{$b_ref}, "opt:[$key] val:[$val]" . ( ('SCALAR' eq ref($val) and defined  $$val  ) ? " [$$val]" : q{} ) . "\n" ) ;
-        if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/ ) {
-		push  @{$b_ref}, "Unknown opt: [$key]\n"  ;
-            next ; # Unknown item
-        }
-
-        my $name = [split '|', $1, 1 ]->[0];
-
-        if (($3 || q{}) eq '+') {
-            ${ $val } = $cgi->param($name); # "Incremental" integer
-        } elsif ($2) {
-            my @values = $cgi->param($name);
-            my $type = $2;
-            if (($3 || q{}) eq '%' or ref($val) eq 'HASH') {
-                my %values = map { split /=/, $_, 1 } @values;
-                if ($type =~ m/i$/) {
-                    foreach my $k (keys %values) {
-                        $values{$k} = int $values{$k} ;
-                    }
-                } elsif ($type =~ m/f$/) {
-                    foreach my $k (keys %values) {
-                        $values{$k} = 0 + $values{$k}
-                    }
-                }
-                if ( ref($val) eq 'CODE') {
-                    while(my($k, $v) = each %values) {
-                       $val->($name, $k, $v);
-                    }
-                } elsif ( 'REF' eq ref $val ) {
-			#push( @{$b_ref}, "refref($$val): " . ref($$val) . " %values= ", %values, "\n\n" ) ;
-			%{ ${ $val } } = %values;
-		} else {
-			#push( @{$b_ref}, "ref($val): " . ref($val) . " %values= ", %values, "\n\n" ) ;
-			%{ $val } = %values;
-                }
-            } else {
-                if ($type =~ m/i$/) {
-                    @values = map { int $_ } @values;
-                } elsif ($type =~ m/f$/) {
-                    @values = map { 0 + $_ } @values;
-                }
-                if (($3 || q{}) eq '@' or ref($val) eq 'ARRAY') {
-                    if (ref($val) eq 'CODE') {
-                        $val->($name, \@values)
-                    } else {
-                        @{ $val } = @values ;
-                    }
-                } else {
-                    if (ref($val) eq 'CODE') {
-                        $val->($name, $values[0]);
-                    } else {
-                    	${ $val } = $values[0];
-                    }
-                }
-            }
-        } else {
-            # Checkbox
-            ${ $val } = $cgi->param($name) ? 1 : undef ;
-	    #push( @{$b_ref}, "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ;
-        }
-    }
-    return( 1 ) ;
-}
-
-

From 52f4f850cfb77bff3d34d426f0b5f45dbfe68f1c Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 17 Dec 2017 17:45:12 +0100
Subject: [PATCH 22/40] [Postfix] Fix missing authentication data for
 relayhosts when sender domain is alias domain

---
 data/Dockerfiles/postfix/postfix.sh | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
index 5e60bb18..43dff40d 100755
--- a/data/Dockerfiles/postfix/postfix.sh
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -83,6 +83,9 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
   WHERE id IN (
     SELECT relayhost FROM domain
       WHERE CONCAT('@', domain) = '%s'
+      OR '%s' IN (
+        SELECT CONCAT('@', alias_domain) FROM alias_domain
+      )
   );
 EOF
 

From aa96f227d8fab7c0b264ae88be1fceddb0d0f940 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 17 Dec 2017 17:57:57 +0100
Subject: [PATCH 23/40] [Web] No inline forms in admin

---
 data/web/admin.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/data/web/admin.php b/data/web/admin.php
index e1fe74be..08288386 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -249,7 +249,7 @@ $tfa_data = get_tfa();
         ?>
 
         <legend style="margin-top:40px"><?=$lang['admin']['dkim_add_key'];?></legend>
-        <form class="form-inline" data-id="dkim" role="form" method="post">
+        <form class="form" data-id="dkim" role="form" method="post">
           <div class="form-group">
             <label for="domain">Domain</label>
             <input class="form-control" id="domain" name="domain" placeholder="example.org" required>
@@ -310,7 +310,7 @@ $tfa_data = get_tfa();
         </div>
         <legend><?=$lang['admin']['add_forwarding_host'];?></legend>
         <p class="help-block"><?=$lang['admin']['forwarding_hosts_add_hint'];?></p>
-        <form class="form-inline" data-id="fwdhost" role="form" method="post">
+        <form class="form" data-id="fwdhost" role="form" method="post">
           <div class="form-group">
             <label for="hostname"><?=$lang['admin']['host'];?></label>
             <input class="form-control" id="hostname" name="hostname" placeholder="example.org" required>
@@ -377,7 +377,7 @@ $tfa_data = get_tfa();
         </div>
         <legend><?=$lang['admin']['add_relayhost'];?></legend>
         <p class="help-block"><?=$lang['admin']['add_relayhost_add_hint'];?></p>
-        <form class="form-inline" data-id="rlyhost" role="form" method="post">
+        <form class="form" data-id="rlyhost" role="form" method="post">
           <div class="form-group">
             <label for="hostname"><?=$lang['admin']['host'];?></label>
             <input class="form-control" id="hostname" name="hostname" required>

From 236e4d4a36667045a5f1302d8c4948be5eed0e1c Mon Sep 17 00:00:00 2001
From: Oratorian <ogamenotify@gmail.com>
Date: Mon, 18 Dec 2017 16:41:04 +0100
Subject: [PATCH 24/40] See Issue #826

Fixes dockerapi-mailcow_1 | raise TypeError('port must be an integer')

Containers are now restarting.
---
 data/Dockerfiles/dockerapi/server.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index 1f0223ac..25969681 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -110,7 +110,7 @@ class GracefulKiller:
     self.kill_now = True
 
 def startFlaskAPI():
-  app.run(debug=False, host='0.0.0.0', port='8080', threaded=True)
+  app.run(debug=False, host='0.0.0.0', port=8080, threaded=True)
 
 api.add_resource(containers_get, '/containers/json')
 api.add_resource(container_get, '/containers/<string:container_id>/json')

From c067e6f715311ebf8e25b3615b4d647a58515512 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Wed, 20 Dec 2017 08:59:07 +0100
Subject: [PATCH 25/40] [Compose] Push dockerapi version

---
 docker-compose.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index ad0e9da6..1227d10b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -320,7 +320,7 @@ services:
             - watchdog
 
     dockerapi-mailcow:
-      image: mailcow/dockerapi:1.3
+      image: mailcow/dockerapi:1.4
       restart: always
       build: ./data/Dockerfiles/dockerapi
       oom_score_adj: -10

From 603956ca6a26e19c423b24a2774a09dc02feed1e Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Wed, 20 Dec 2017 21:15:43 +0100
Subject: [PATCH 26/40] [Web] Disable connectors in footable filter for mailbox

---
 data/web/js/mailbox.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index b30ceb80..415b6e28 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -193,6 +193,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -263,6 +264,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -307,6 +309,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -357,6 +360,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -409,6 +413,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -452,6 +457,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -511,6 +517,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {
@@ -564,6 +571,7 @@ jQuery(function($){
       "filtering": {
         "enabled": true,
         "position": "left",
+        "connectors": false,
         "placeholder": lang.filter_table
       },
       "sorting": {

From 8d56534e76bd6fdba77419fb66c2a8a47b6ddc24 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 25 Dec 2017 10:18:46 +0100
Subject: [PATCH 27/40] [Postfix] Don't try to authenticate to relayhosts
 without username, fixes #725

---
 data/Dockerfiles/postfix/postfix.sh | 3 ++-
 docker-compose.yml                  | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
index 43dff40d..c152606d 100755
--- a/data/Dockerfiles/postfix/postfix.sh
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -86,7 +86,8 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
       OR '%s' IN (
         SELECT CONCAT('@', alias_domain) FROM alias_domain
       )
-  );
+  )
+  AND username != '';
 EOF
 
 cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
diff --git a/docker-compose.yml b/docker-compose.yml
index 1227d10b..a58eb142 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -181,7 +181,7 @@ services:
             - dovecot
 
     postfix-mailcow:
-      image: mailcow/postfix:1.9
+      image: mailcow/postfix:1.10
       build: ./data/Dockerfiles/postfix
       volumes:
         - ./data/conf/postfix:/opt/postfix/conf

From eb57fce38f212c5373579dbef7d14f1367732b62 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 25 Dec 2017 10:25:50 +0100
Subject: [PATCH 28/40] [Dovecot] Possibly fixes #722

---
 data/conf/dovecot/dovecot.conf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf
index 6cf1897b..964da804 100644
--- a/data/conf/dovecot/dovecot.conf
+++ b/data/conf/dovecot/dovecot.conf
@@ -163,7 +163,7 @@ namespace {
     prefix = Shared/%%u/
     location = maildir:%%h/:INDEXPVT=~/Shared/%%u
     subscriptions = no
-    list = yes
+    list = children
 }
 protocols = imap sieve lmtp pop3
 service dict {

From a8807b589459373886bb4b30545d77640c990bc7 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 26 Dec 2017 11:23:29 +0100
Subject: [PATCH 29/40] [Helper] Nextcloud: Use latest 12

---
 helper-scripts/nextcloud.sh | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh
index 175f3cd5..781e5133 100755
--- a/helper-scripts/nextcloud.sh
+++ b/helper-scripts/nextcloud.sh
@@ -53,16 +53,8 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
 	fi
 
 	ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
-	NEXTCLOUD_VERSION=$(curl -s https://www.servercow.de/nextcloud/latest.php)
 
-	[[ -z ${NEXTCLOUD_VERSION} ]] && { echo "Error, cannot determine nextcloud version, exiting..."; exit 1; }
-
-	curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2" \
-	  && curl -L# -o nextcloud.tar.bz2.asc "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc" \
-	  && export GNUPGHOME="$(mktemp -d)" \
-	  && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 28806A878AE423A28372792ED75899B9A724937A \
-	  && gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2 \
-	  && rm -r "$GNUPGHOME" nextcloud.tar.bz2.asc \
+	curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-12.tar.bz2" \
 	  && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
 	  && rm nextcloud.tar.bz2 \
 	  && rm -rf ./data/web/nextcloud/updater \

From 65386d4ccfed6eceea534a6b0e4b26793232522a Mon Sep 17 00:00:00 2001
From: Amir Zarrinkafsh <nightah@me.com>
Date: Sat, 30 Dec 2017 13:58:17 +1100
Subject: [PATCH 30/40] Included folder mapping for iOS Mail Trash folder.

---
 data/conf/dovecot/dovecot.conf | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf
index 964da804..19b803df 100644
--- a/data/conf/dovecot/dovecot.conf
+++ b/data/conf/dovecot/dovecot.conf
@@ -55,6 +55,9 @@ namespace inbox {
   mailbox "Deleted Items" {
     special_use = \Trash
   }
+  mailbox "Rubbish" {
+    special_use = \Trash
+  }
   mailbox "Gelöschte Objekte" {
     special_use = \Trash
   }

From 7cb23cf4a30d08b217e0e59d1f28b33ace427941 Mon Sep 17 00:00:00 2001
From: Kristian <feldsam@gmail.com>
Date: Sat, 30 Dec 2017 19:38:56 +0100
Subject: [PATCH 31/40] User - Allow send as all - list all domains inc. alias
 domains

---
 data/web/inc/functions.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 34e24028..16d524a6 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -483,7 +483,7 @@ function user_get_alias_details($username) {
     while ($row = array_shift($run)) {
       $data['aliases_also_send_as'] = $row['send_as'];
     }
-    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
+    $stmt = $pdo->prepare("SELECT IFNULL(CONCAT(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ', ', GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')), '&#10008;') AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` =  TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
     $stmt->execute(array(':username' => $username));
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     while ($row = array_shift($run)) {

From aa5f7a55840d97e9e2b8983fd3616a3532dffbaa Mon Sep 17 00:00:00 2001
From: Kristian <feldsam@gmail.com>
Date: Sun, 31 Dec 2017 15:25:39 +0100
Subject: [PATCH 32/40] Fixed syncjob saving on edit

---
 data/web/inc/functions.mailbox.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 4e728aaa..77512fa0 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -1413,7 +1413,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
               $enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
               $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
-              $exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : '';
+              $exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
               $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
             }
             else {

From 254397af607959611c83144ed5455f37cb63679d Mon Sep 17 00:00:00 2001
From: Kristian <feldsam@gmail.com>
Date: Sun, 31 Dec 2017 15:26:11 +0100
Subject: [PATCH 33/40] Sync job tables enhanced
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Kristián Feldsam <feldsam@gmail.com>

# Conflicts:
#	data/web/js/mailbox.js
---
 data/web/js/mailbox.js    | 7 ++++++-
 data/web/js/user.js       | 8 ++++----
 data/web/lang/lang.en.php | 1 +
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index 415b6e28..39afe717 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -473,6 +473,7 @@ jQuery(function($){
         {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
         {"name":"user2","title":lang.owner},
         {"name":"server_w_port","title":"Server","breakpoints":"xs"},
+        {"name":"exclude","title":lang.excludes,"breakpoints":"all"},
         {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"},
         {"name":"last_run","title":lang.last_run,"breakpoints":"all"},
         {"name":"log","title":"Log"},
@@ -491,7 +492,11 @@ jQuery(function($){
         success: function (data) {
           $.each(data, function (i, item) {
             item.log = '<a href="#syncjobLogModal" data-toggle="modal" data-syncjob-id="' + encodeURI(item.id) + '">Open logs</a>'
-            item.exclude = '<code>' + item.exclude + '</code>'
+            if (!item.exclude > 0) {
+              item.exclude = '-';
+            } else {
+              item.exclude  = '<code>' + item.exclude + '</code>';
+            }
             item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1;
             item.action = '<div class="btn-group">' +
               '<a href="/edit.php?syncjob=' + item.id + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
diff --git a/data/web/js/user.js b/data/web/js/user.js
index 900dced1..3984e855 100644
--- a/data/web/js/user.js
+++ b/data/web/js/user.js
@@ -86,14 +86,14 @@ jQuery(function($){
   function draw_sync_job_table() {
     ft_syncjob_table = FooTable.init('#sync_job_table', {
       "columns": [
-        {"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
+        {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"},
         {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}},
         {"name":"server_w_port","title":"Server"},
         {"name":"enc1","title":lang.encryption,"breakpoints":"xs sm"},
         {"name":"user1","title":lang.username},
-        {"name":"exclude","title":lang.excludes,"breakpoints":"xs sm"},
-        {"name":"mins_interval","title":lang.interval + " (min)"},
-        {"name":"last_run","title":lang.last_run,"breakpoints":"xs sm"},
+        {"name":"exclude","title":lang.excludes,"breakpoints":"all"},
+        {"name":"mins_interval","title":lang.interval + " (min)","breakpoints":"all"},
+        {"name":"last_run","title":lang.last_run,"breakpoints":"all"},
         {"name":"log","title":"Log"},
         {"name":"active","filterable": false,"style":{"maxWidth":"70px","width":"70px"},"title":lang.active},
         {"name":"is_running","filterable": false,"style":{"maxWidth":"120px","width":"100px"},"title":lang.status},
diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php
index b68cf5ee..9c490dd6 100644
--- a/data/web/lang/lang.en.php
+++ b/data/web/lang/lang.en.php
@@ -274,6 +274,7 @@ $lang['mailbox']['deactivate'] = 'Deactivate';
 $lang['mailbox']['owner'] = 'Owner';
 $lang['mailbox']['mins_interval'] = 'Interval (min)';
 $lang['mailbox']['last_run'] = 'Last run';
+$lang['mailbox']['excludes'] = 'Excludes';
 $lang['mailbox']['last_run_reset'] = 'Schedule next';
 $lang['mailbox']['sieve_info'] = 'You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>
 Each filter will be processed in the described order. Neither a failed script nor an issued "keep;" will stop processing of further scripts.<br>

From db032af698fa017cc0cf5cad6f561ce316f51440 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Sun, 31 Dec 2017 17:17:46 +0100
Subject: [PATCH 34/40] [ACME] Fix script

---
 data/Dockerfiles/acme/docker-entrypoint.sh | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh
index ef466d36..8d910e5d 100755
--- a/data/Dockerfiles/acme/docker-entrypoint.sh
+++ b/data/Dockerfiles/acme/docker-entrypoint.sh
@@ -128,13 +128,7 @@ while true; do
   declare -a VALIDATED_CONFIG_DOMAINS
   declare -a ADDITIONAL_VALIDATED_SAN
   IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
-  until [[ ${IPV4} == ${EXTERNAL_IPV4} ]]; do
-    IPV4=$(get_ipv4)
-    if [[ ${IPV4} != ${EXTERNAL_IPV4} ]]; then
-      echo "Waiting for correct source ip..."
-      sleep 30s
-    fi
-  done
+  IPV4=$(get_ipv4)
   # Container ids may have changed
   CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
 

From 87c6770132e4ff3b2ae8fee29ae8a2b4638964dd Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Mon, 1 Jan 2018 09:17:49 +0100
Subject: [PATCH 35/40] [Web] Fix empty passwords for relayhosts

---
 data/web/inc/functions.relayhost.inc.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/data/web/inc/functions.relayhost.inc.php b/data/web/inc/functions.relayhost.inc.php
index 233ca0ad..e6722258 100644
--- a/data/web/inc/functions.relayhost.inc.php
+++ b/data/web/inc/functions.relayhost.inc.php
@@ -56,8 +56,8 @@ function relayhost($_action, $_data = null) {
         $is_now = relayhost('details', $id);
         if (!empty($is_now)) {
           $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname'];
-          $username = (!empty($_data['username'])) ? trim($_data['username']) : $is_now['username'];
-          $password = (!empty($_data['password'])) ? trim($_data['password']) : $is_now['password'];
+          $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
+          $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
           $active   = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
         }
         else {

From d76d103c76f5c4f97e004b740ec8c6c99d44638a Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 2 Jan 2018 10:24:48 +0100
Subject: [PATCH 36/40] [Web] Move DNS diagnostic to modal, some minor fixes

---
 data/web/css/mailbox.css                      | 10 +-
 .../ajax/dns_diagnostics.php}                 | 93 +++++++++++--------
 data/web/inc/functions.inc.php                |  8 +-
 data/web/js/mailbox.js                        | 30 ++++--
 data/web/modals/mailbox.php                   | 12 +++
 5 files changed, 101 insertions(+), 52 deletions(-)
 rename data/web/{diagnostics.php => inc/ajax/dns_diagnostics.php} (81%)

diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css
index 5004cda1..44babe6a 100644
--- a/data/web/css/mailbox.css
+++ b/data/web/css/mailbox.css
@@ -34,4 +34,12 @@ table.footable>tbody>tr.footable-empty>td {
 }
 .inputMissingAttr {
   border-color: #FF4136;
-}
\ No newline at end of file
+}
+.dns-found {
+  max-width: 300px;
+  word-break: break-all;
+}
+.dns-recommended {
+  max-width: 150px;
+  word-break: break-all;
+}
diff --git a/data/web/diagnostics.php b/data/web/inc/ajax/dns_diagnostics.php
similarity index 81%
rename from data/web/diagnostics.php
rename to data/web/inc/ajax/dns_diagnostics.php
index b988747d..bd432726 100644
--- a/data/web/diagnostics.php
+++ b/data/web/inc/ajax/dns_diagnostics.php
@@ -1,14 +1,13 @@
 <?php
-require_once 'inc/prerequisites.inc.php';
-require_once 'inc/spf.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/spf.inc.php';
 
-define('state_good',  "&#10003;");
-define('state_missing',   "&#x2717;");
+define('state_good', '<span class="text-success">OK</span>');
+define('state_missing', "&#x2717;");
 define('state_nomatch', "?");
-define('state_optional', "(optional)");
+define('state_optional', " <sup>2</sup>");
 
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
-require_once("inc/header.inc.php");
 
 $ch = curl_init('http://ip4.mailcow.email');
 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
@@ -80,7 +79,7 @@ foreach ($domains as $domain) {
   $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname);
   $records[] = array($domain, 'TXT', '<a href="http://www.openspf.org/SPF_Record_Syntax" target="_blank">SPF Record Syntax</a>', state_optional);
   $records[] = array('_dmarc.' . $domain, 'TXT', '<a href="http://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>', state_optional);
-  
+
   if (!empty($dkim = dkim('details', $domain))) {
     $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']);
   }
@@ -138,15 +137,11 @@ $data_field = array(
   'TXT' => 'txt',
 );
 ?>
-<div class="container">
-  <h3><?=$lang['diagnostics']['dns_records'];?></h3>
-  <p><?=$lang['diagnostics']['dns_records_24hours'];?></p>
   <div class="table-responsive" id="dnstable">
     <table class="table table-striped">
       <tr> <th><?=$lang['diagnostics']['dns_records_name'];?></th> <th><?=$lang['diagnostics']['dns_records_type'];?></th> <th><?=$lang['diagnostics']['dns_records_data'];?></th ><th><?=$lang['diagnostics']['dns_records_status'];?></th> </tr>
 <?php
-foreach ($records as $record)
-{
+foreach ($records as $record) {
   $record[1] = strtoupper($record[1]);
   $state = state_missing;
   if ($record[1] == 'TLSA') {
@@ -173,8 +168,15 @@ foreach ($records as $record)
       }
       unset($current);
     }
+    elseif ($record[1] == 'TXT') {
+      foreach ($currents as &$current) {
+        if ($current['host'] != $record[0]) {
+          $current['type'] = false;
+        }
+      }
+    }
   }
-  
+
   if ($record[1] == 'CNAME' && count($currents) == 0) {
     // A and AAAA are also valid instead of CNAME
     $a = dns_get_record($record[0], DNS_A);
@@ -182,44 +184,51 @@ foreach ($records as $record)
     if (count($a) > 0 && count($cname) > 0) {
       if ($a[0]['ip'] == $cname[0]['ip']) {
         $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2]));
-      
         $aaaa = dns_get_record($record[0], DNS_AAAA);
         $cname = dns_get_record($record[2], DNS_AAAA);
         if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) {
-          $currents[0]['target'] = $aaaa[0]['ipv6'];
+          $currents[0]['target'] = $aaaa[0]['ipv6'] . ' <sup>1</sup>';
         }
-      } else {
-        $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip']));
+      }
+      else {
+        $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'] . ' <sup>1</sup>'));
       }
     }
   }
-  
+
   foreach ($currents as &$current) {
-    if ($current['type'] != $record[1])
-    {
+    if ($current['type'] != $record[1]) {
+      unset($current);
       continue;
     }
-    
-    elseif ($current['type'] == 'TXT' && strpos($current['txt'], 'v=DMARC1') === 0) {
-      $current['txt'] = str_replace(' ', '', $current['txt']);
-      $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
+    elseif ($current['type'] == 'TXT' &&
+      stripos($current['txt'], 'v=dmarc') === 0 &&
+      stripos($current['host'], '_dmarc') === 0) {
+        $current['txt'] = str_replace(' ', '', $current['txt']);
+        $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
     }
-    else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) {
-      $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
+    elseif ($current['type'] == 'TXT' &&
+      stripos($current['host'], '_dmarc') !== 0 &&
+      stripos($current['txt'], 'v=spf') === 0) {
+        $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
     }
-    else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=DKIM1') === 0) {
-      $current['txt'] = str_replace(' ', '', $current['txt']);
-      if ($current[$data_field[$current['type']]] == $record[2])
-        $state = state_good;
+    elseif ($current['type'] == 'TXT' &&
+      stripos($current['txt'], 'v=dkim') === 0) {
+        $current['txt'] = str_replace(' ', '', $current['txt']);
+        if ($current[$data_field[$current['type']]] == $record[2]) {
+          $state = state_good;
+        }
     }
-    else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) {
-      $state = state_nomatch;
-      if ($current[$data_field[$current['type']]] == $record[2])
-        $state = state_good;
+    elseif ($current['type'] != 'TXT' &&
+      isset($data_field[$current['type']]) && $state != state_good) {
+        $state = state_nomatch;
+        if ($current[$data_field[$current['type']]] == $record[2]) {
+          $state = state_good;
+        }
     }
   }
   unset($current);
-  
+
   if (isset($record[3]) && $record[3] == state_optional && ($state == state_missing || $state == state_nomatch)) {
     $state = state_optional;
   }
@@ -231,15 +240,21 @@ foreach ($records as $record)
     }
     $state = implode('<br />', $state);
   }
-
-  echo sprintf('<tr><td>%s</td><td>%s</td><td style="max-width: 300px; word-break: break-all">%s</td><td style="max-width: 150px; word-break: break-all">%s</td></tr>', $record[0], $record[1], $record[2], $state);
+  echo sprintf('<tr>
+    <td>%s</td>
+    <td>%s</td>
+    <td class="dns-found">%s</td>
+    <td class="dns-recommended">%s</td>
+  </tr>', $record[0], $record[1], $record[2], $state);
 }
 ?>
     </table>
 	</div>
-</div>
+  <p class="help-block">
+  <sup>1</sup> Found A/AAAA record instead of a CNAME. This is supported as long as the A records destination points to the correct resource.<br />
+  <sup>2</sup> This record is optional.
+  </p>
 <?php
-require_once("inc/footer.inc.php");
 } else {
   header('Location: index.php');
   exit();
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 16d524a6..20d82a92 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -75,15 +75,15 @@ function generate_tlsa_digest($hostname, $port, $starttls = null) {
   }
   if (empty($starttls)) {
     $context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'allow_self_signed' => true)));
-    $stream = stream_socket_client('tls://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context);
-    // error_nr can be 0, so checking against error_msg
-    if ($error_msg) {
+    $stream = stream_socket_client('ssl://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context);
+    if (!$stream) {
+      $error_msg = isset($error_msg) ? $error_msg : '-';
       return $error_nr . ': ' . $error_msg;
     }
   }
   else {
     $stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5);
-    if ($error_msg) {
+    if (!$stream) {
       return $error_nr . ': ' . $error_msg;
     }
     $banner = fread($stream, 512 );
diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index 39afe717..b8ee5548 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -55,6 +55,22 @@ $(document).ready(function() {
     });
   });
 
+  // Log modal
+  $('#dnsInfoModal').on('show.bs.modal', function(e) {
+    var domain = $(e.relatedTarget).data('domain');
+    $.ajax({
+      url: '/inc/ajax/dns_diagnostics.php',
+      data: { domain: domain },
+      dataType: 'text',
+      success: function(data){
+        $('.dns-modal-body').html(data);
+      },
+      error: function(xhr, status, error) {
+        $('.dns-modal-body').html(xhr.responseText);
+      }
+    });
+  });
+
   // Sieve data modal
   $('#sieveDataModal').on('show.bs.modal', function(e) {
     var sieveScript = $(e.relatedTarget).data('sieve-script');
@@ -154,7 +170,7 @@ jQuery(function($){
         {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm"},
         {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm"},
         {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
       ],
       "rows": $.ajax({
         dataType: 'json',
@@ -170,17 +186,15 @@ jQuery(function($){
             item.quota = item.quota_used_in_domain + "/" + item.max_quota_for_domain;
             item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox);
             item.chkbox = '<input type="checkbox" data-id="domain" name="multi_select" value="' + item.domain_name + '" />';
+            item.action = '<div class="btn-group">';
             if (role == "admin") {
-              item.action = '<div class="btn-group">' +
-                '<a href="/edit.php?domain=' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
-                '<a href="#" id="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
-                '</div>';
+              item.action += '<a href="/edit.php?domain=' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
+                '<a href="#" id="delete_selected" data-id="single-domain" data-api-url="delete/domain" data-item="' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>';
             }
             else {
-              item.action = '<div class="btn-group">' +
-                '<a href="/edit.php?domain=' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
-                '</div>';
+              item.action += '<a href="/edit.php?domain=' + encodeURI(item.domain_name) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>';
             }
+            item.action += '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURI(item.domain_name) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>';
           });
         }
       }),
diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php
index c60ddb45..aa1c00f5 100644
--- a/data/web/modals/mailbox.php
+++ b/data/web/modals/mailbox.php
@@ -584,3 +584,15 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
     </div>
   </div>
 </div><!-- log modal -->
+<!-- DNS info modal -->
+<div class="modal fade" id="dnsInfoModal" tabindex="-1" role="dialog" aria-labelledby="dnsInfoModalLabel">
+  <div class="modal-dialog modal-lg" role="document">
+    <div class="modal-content">
+      <div class="modal-header"><h4 class="modal-title"><?=$lang['diagnostics']['dns_records'];?></h4></div>
+      <div class="modal-body">
+        <p><?=$lang['diagnostics']['dns_records_24hours'];?></p>
+        <div class="dns-modal-body"></div>
+      </div>
+    </div>
+  </div>
+</div><!-- DNS info modal -->
\ No newline at end of file

From 84748409264d050f479344e876d888af29c815da Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 2 Jan 2018 18:13:43 +0100
Subject: [PATCH 37/40] [Compose] New SOGo and ACME images

---
 docker-compose.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docker-compose.yml b/docker-compose.yml
index a58eb142..7f7b548b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -124,7 +124,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: mailcow/sogo:1.11
+      image: mailcow/sogo:1.12
       build: ./data/Dockerfiles/sogo
       environment:
         - DBNAME=${DBNAME}
@@ -256,7 +256,7 @@ services:
       depends_on:
         - nginx-mailcow
         - mysql-mailcow
-      image: mailcow/acme:1.25
+      image: mailcow/acme:1.26
       build: ./data/Dockerfiles/acme
       dns:
         - 172.22.1.254

From 868abc15bd8f5529320821c1cd68d0242cb46156 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 2 Jan 2018 18:15:33 +0100
Subject: [PATCH 38/40] [Rspamd] Fix worker-controller-password placeholder

---
 data/conf/rspamd/override.d/worker-controller-password.inc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/conf/rspamd/override.d/worker-controller-password.inc b/data/conf/rspamd/override.d/worker-controller-password.inc
index 0d5443fa..dcf2c804 100644
--- a/data/conf/rspamd/override.d/worker-controller-password.inc
+++ b/data/conf/rspamd/override.d/worker-controller-password.inc
@@ -1 +1 @@
-enable_password = "$2$hd67jioinjy4nu6t65tsrb7srexeemp5$tjgnnj3az1gfiqi1tkat7kh8qyoi4gpd5hemkm48k9fmzyf8ypwb";
+# Placeholder

From 102b33d6199f37756b5e19d6d254663470869b94 Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 2 Jan 2018 18:16:07 +0100
Subject: [PATCH 39/40] Ignore worker-controller-password.inc

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 798d9603..69c24f9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ data/conf/nginx/*.conf
 data/conf/nginx/*.custom
 data/conf/nginx/*.bak
 data/conf/dovecot/extra.conf
+data/conf/rspamd/override.d/worker-controller-password.inc

From 34b7ab7104d6a5050233b14a64215e605b5c104a Mon Sep 17 00:00:00 2001
From: "andre.peters" <andre.peters@servercow.de>
Date: Tue, 2 Jan 2018 18:17:27 +0100
Subject: [PATCH 40/40] [Web] Add DNS diagnostics as modal, some minor fixes,
 todo: Detect IPv4/6 by multiple sources and random selection

---
 data/web/inc/ajax/dns_diagnostics.php | 115 ++++++++++++++------------
 data/web/inc/footer.inc.php           |   2 +-
 data/web/inc/init_db.inc.php          |  11 +--
 data/web/js/mailbox.js                |   4 +-
 data/web/lang/lang.de.php             |   2 +
 data/web/lang/lang.en.php             |   3 +
 6 files changed, 79 insertions(+), 58 deletions(-)

diff --git a/data/web/inc/ajax/dns_diagnostics.php b/data/web/inc/ajax/dns_diagnostics.php
index bd432726..0b29d253 100644
--- a/data/web/inc/ajax/dns_diagnostics.php
+++ b/data/web/inc/ajax/dns_diagnostics.php
@@ -9,6 +9,27 @@ define('state_optional', " <sup>2</sup>");
 
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
 
+$domains = mailbox('get', 'domains');
+foreach(mailbox('get', 'domains') as $dn) {
+  $domains = array_merge($domains, mailbox('get', 'alias_domains', $dn));
+}
+
+if (isset($_GET['domain'])) {
+  if (is_valid_domain_name($_GET['domain'])) {
+    if (in_array($_GET['domain'], $domains)) {
+      $domain = $_GET['domain'];
+    }
+    else {
+      echo "No such domain in context";
+      die();
+    }
+  }
+  else {
+    echo "Invalid domain name";
+    die();
+  }
+}
+
 $ch = curl_init('http://ip4.mailcow.email');
 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
 curl_setopt($ch, CURLOPT_VERBOSE, false);
@@ -53,10 +74,6 @@ if (!empty($ip6)) {
   $records[] = array($mailcow_hostname, 'AAAA', $ip6);
   $records[] = array($ptr6, 'PTR', $mailcow_hostname);
 }
-$domains = mailbox('get', 'domains');
-foreach(mailbox('get', 'domains') as $domain) {
-  $domains = array_merge($domains, mailbox('get', 'alias_domains', $domain));
-}
 
 if (!isset($autodiscover_config['sieve'])) {
   $autodiscover_config['sieve'] = array('server' => $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT'))));
@@ -71,50 +88,47 @@ $records[] = array('_' . $autodiscover_config['smtp']['tlsport'] . '._tcp.' . $a
 $records[] = array('_' . $autodiscover_config['imap']['port']    . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port']));
 $records[] = array('_' . $autodiscover_config['pop3']['port']    . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port']));
 $records[] = array('_' . $autodiscover_config['sieve']['port']   . '._tcp.' . $autodiscover_config['sieve']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1));
+$records[] = array($domain, 'MX', $mailcow_hostname);
+$records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname);
+$records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port);
+$records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname);
+$records[] = array($domain, 'TXT', '<a href="http://www.openspf.org/SPF_Record_Syntax" target="_blank">SPF Record Syntax</a>', state_optional);
+$records[] = array('_dmarc.' . $domain, 'TXT', '<a href="http://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>', state_optional);
 
-foreach ($domains as $domain) {
-  $records[] = array($domain, 'MX', $mailcow_hostname);
-  $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname);
-  $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port);
-  $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname);
-  $records[] = array($domain, 'TXT', '<a href="http://www.openspf.org/SPF_Record_Syntax" target="_blank">SPF Record Syntax</a>', state_optional);
-  $records[] = array('_dmarc.' . $domain, 'TXT', '<a href="http://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>', state_optional);
+if (!empty($dkim = dkim('details', $domain))) {
+  $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']);
+}
 
-  if (!empty($dkim = dkim('details', $domain))) {
-    $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']);
+$current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV);
+if (count($current_records) == 0 || $current_records[0]['target'] != '') {
+  if ($autodiscover_config['pop3']['tlsport'] != '110')  {
+    $records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']);
   }
-
-  $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV);
-  if (count($current_records) == 0 || $current_records[0]['target'] != '') {
-    if ($autodiscover_config['pop3']['tlsport'] != '110')  {
-      $records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']);
-    }
-  } else {
-      $records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0');
-  }
-  $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV);
-  if (count($current_records) == 0 || $current_records[0]['target'] != '') {
-    if ($autodiscover_config['pop3']['port'] != '995')  {
-      $records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']);
-    }
-  } else {
-      $records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0');
-  }
-  if ($autodiscover_config['imap']['tlsport'] != '143')  {
-    $records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']);
-  }
-  if ($autodiscover_config['imap']['port'] != '993')  {
-    $records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']);
-  }
-  if ($autodiscover_config['smtp']['tlsport'] != '587')  {
-    $records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']);
-  }
-  if ($autodiscover_config['smtp']['port'] != '465')  {
-    $records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']);
-  }
-  if ($autodiscover_config['sieve']['port'] != '4190')  {
-    $records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']);
+} else {
+    $records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0');
+}
+$current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV);
+if (count($current_records) == 0 || $current_records[0]['target'] != '') {
+  if ($autodiscover_config['pop3']['port'] != '995')  {
+    $records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']);
   }
+} else {
+    $records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0');
+}
+if ($autodiscover_config['imap']['tlsport'] != '143')  {
+  $records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']);
+}
+if ($autodiscover_config['imap']['port'] != '993')  {
+  $records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']);
+}
+if ($autodiscover_config['smtp']['tlsport'] != '587')  {
+  $records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']);
+}
+if ($autodiscover_config['smtp']['port'] != '465')  {
+  $records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']);
+}
+if ($autodiscover_config['sieve']['port'] != '4190')  {
+  $records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']);
 }
 
 $record_types = array(
@@ -205,12 +219,11 @@ foreach ($records as $record) {
       stripos($current['txt'], 'v=dmarc') === 0 &&
       stripos($current['host'], '_dmarc') === 0) {
         $current['txt'] = str_replace(' ', '', $current['txt']);
-        $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
+        $state = $current[$data_field[$current['type']]] . state_optional;
     }
     elseif ($current['type'] == 'TXT' &&
-      stripos($current['host'], '_dmarc') !== 0 &&
       stripos($current['txt'], 'v=spf') === 0) {
-        $state = state_optional . '<br />' . $current[$data_field[$current['type']]];
+        $state = $current[$data_field[$current['type']]] . state_optional;
     }
     elseif ($current['type'] == 'TXT' &&
       stripos($current['txt'], 'v=dkim') === 0) {
@@ -251,12 +264,12 @@ foreach ($records as $record) {
     </table>
 	</div>
   <p class="help-block">
-  <sup>1</sup> Found A/AAAA record instead of a CNAME. This is supported as long as the A records destination points to the correct resource.<br />
-  <sup>2</sup> This record is optional.
+  <sup>1</sup> <?=$lang['diagnostics']['cname_from_a'];?><br />
+  <sup>2</sup> <?=$lang['diagnostics']['optional'];?>
   </p>
 <?php
 } else {
-  header('Location: index.php');
-  exit();
+  echo "Session invalid";
+  die();
 }
 ?>
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
index a082211b..eb419308 100644
--- a/data/web/inc/footer.inc.php
+++ b/data/web/inc/footer.inc.php
@@ -188,7 +188,7 @@ $(document).ready(function() {
       $.ajax({
         method: 'get',
         url: '/inc/ajax/container_ctrl.php',
-        timeout: 3000,
+        timeout: 10000,
         data: {
           'service': container,
           'action': 'restart'
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index a69e1f3e..6c0ef937 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "29112017_1515";
+    $db_version = "02012018_1515";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -540,12 +540,13 @@ function init_db_schema() {
           "c_sn" => "VARCHAR(255)",
           "c_screenname" => "VARCHAR(255)",
           "c_l" => "VARCHAR(255)",
-          "c_mail" => "VARCHAR(255)",
+          "c_mail" => "TEXT",
           "c_o" => "VARCHAR(255)",
           "c_ou" => "VARCHAR(255)",
           "c_telephonenumber" => "VARCHAR(255)",
           "c_categories" => "VARCHAR(255)",
-          "c_component" => "VARCHAR(10) NOT NULL"
+          "c_component" => "VARCHAR(10) NOT NULL",
+          "c_hascertificate" => "INT4 DEFAULT 0"
         ),
         "keys" => array(
           "primary" => array(
@@ -588,8 +589,8 @@ function init_db_schema() {
       "sogo_user_profile" => array(
         "cols" => array(
           "c_uid" => "VARCHAR(255) NOT NULL",
-          "c_defaults" => "TEXT",
-          "c_settings" => "TEXT"
+          "c_defaults" => "LONGTEXT",
+          "c_settings" => "LONGTEXT"
         ),
         "keys" => array(
           "primary" => array(
diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
index b8ee5548..1e08a4ee 100644
--- a/data/web/js/mailbox.js
+++ b/data/web/js/mailbox.js
@@ -58,6 +58,7 @@ $(document).ready(function() {
   // Log modal
   $('#dnsInfoModal').on('show.bs.modal', function(e) {
     var domain = $(e.relatedTarget).data('domain');
+    $('.dns-modal-body').html('<center><span style="font-size:18pt;margin:50px" class="glyphicon glyphicon-refresh glyphicon-spin"></span></center>');
     $.ajax({
       url: '/inc/ajax/dns_diagnostics.php',
       data: { domain: domain },
@@ -443,7 +444,7 @@ jQuery(function($){
         {"sorted": true,"name":"alias_domain","title":lang.alias,"style":{"width":"250px"}},
         {"name":"target_domain","title":lang.target_domain},
         {"name":"active","filterable": false,"style":{"maxWidth":"50px","width":"70px"},"title":lang.active},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
       ],
       "empty": lang.empty,
       "rows": $.ajax({
@@ -458,6 +459,7 @@ jQuery(function($){
             item.action = '<div class="btn-group">' +
               '<a href="/edit.php?aliasdomain=' + encodeURI(item.alias_domain) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
               '<a href="#" id="delete_selected" data-id="single-alias-domain" data-api-url="delete/alias-domain" data-item="' + encodeURI(item.alias_domain) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
+              '<a href="#dnsInfoModal" class="btn btn-xs btn-info" data-toggle="modal" data-domain="' + encodeURI(item.alias_domain) + '"><span class="glyphicon glyphicon-question-sign"></span> DNS</a></div>' +
               '</div>';
             item.chkbox = '<input type="checkbox" data-id="alias-domain" name="multi_select" value="' + item.alias_domain + '" />';
           });
diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php
index 700be0c7..6d80d231 100644
--- a/data/web/lang/lang.de.php
+++ b/data/web/lang/lang.de.php
@@ -550,6 +550,8 @@ $lang['diagnostics']['dns_records_name'] = 'Name';
 $lang['diagnostics']['dns_records_type'] = 'Typ';
 $lang['diagnostics']['dns_records_data'] = 'Korrekte Daten';
 $lang['diagnostics']['dns_records_status'] = 'Aktueller Status';
+$lang['diagnostics']['optional'] = 'Dieser Eintrag ist optional.';
+$lang['diagnostics']['cname_from_a'] = 'Wert abgeleitet von A/AAAA Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.';
 $lang['admin']['relay_from'] = "Absenderadresse";
 $lang['admin']['relay_run'] = "Test durchführen";
 $lang['admin']['customize'] = "Anpassung";
diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php
index 9c490dd6..ac108a5e 100644
--- a/data/web/lang/lang.en.php
+++ b/data/web/lang/lang.en.php
@@ -552,6 +552,9 @@ $lang['diagnostics']['dns_records_name'] = 'Name';
 $lang['diagnostics']['dns_records_type'] = 'Type';
 $lang['diagnostics']['dns_records_data'] = 'Correct Data';
 $lang['diagnostics']['dns_records_status'] = 'Current State';
+$lang['diagnostics']['optional'] = 'This record is optional.';
+$lang['diagnostics']['cname_from_a'] = 'Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.';
+
 $lang['admin']['relay_from'] = '"From:" address';
 $lang['admin']['api_allow_from'] = "Allow API access from these IPs";
 $lang['admin']['api_key'] = "API key";