From adf9daa9b7ec757a0cdc7ed92a5ad53579c2f32c Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Fri, 16 Nov 2018 12:09:55 +0100 Subject: [PATCH 001/439] Modified the update check in update.sh to fetch the newest revision directly from github without having to run git fetch first. --- update.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 5b38d7d0..fe5aafe0 100755 --- a/update.sh +++ b/update.sh @@ -62,8 +62,12 @@ while (($#)); do case "${1}" in --check|-c) echo "Checking remote code for updates..." - git fetch origin #${BRANCH} - if [[ -z $(git log HEAD --pretty=format:"%H" | grep $(git rev-parse origin/${BRANCH})) ]]; then + LATEST_REV=$(git ls-remote --exit-code --refs --quiet https://github.com/mailcow/mailcow-dockerized ${BRANCH} | cut -f1) + if [ $? -ne 0 ]; then + echo "A problem occurred while trying to fetch the latest revision from github." + exit 99 + fi + if [[ -z $(git log HEAD --pretty=format:"%H" | grep "${LATEST_REV}") ]]; then echo "Updated code is available." exit 0 else From 52cd40aa356c8c030f26915be9e65deee051ddf3 Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Wed, 2 Jan 2019 20:17:37 +0100 Subject: [PATCH 002/439] Fix typo in generate_config.sh --- generate_config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_config.sh b/generate_config.sh index ed941db6..64133bd3 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -175,7 +175,7 @@ IPV6_NETWORK=fd4d:6169:6c63:6f77::/64 #SNAT6_TO_SOURCE= -# Create or override API key for web uI +# Create or override API key for web ui # You _must_ define API_ALLOW_FROM, which is a comma separated list of IPs # API_KEY allowed chars: a-z, A-Z, 0-9, - From 7cdd90ddea38ad4c46bf9b1f2ca3137b9051d955 Mon Sep 17 00:00:00 2001 From: Thomas Dietrich Date: Thu, 3 Jan 2019 00:58:25 +0100 Subject: [PATCH 003/439] Update generate_config.sh --- generate_config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_config.sh b/generate_config.sh index 64133bd3..f15714b0 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -180,7 +180,7 @@ IPV6_NETWORK=fd4d:6169:6c63:6f77::/64 # API_KEY allowed chars: a-z, A-Z, 0-9, - #API_KEY= -#API_ALLOW_FROM=127.0.0.1,1.2.3.4 +#API_ALLOW_FROM=172.22.1.1,127.0.0.1 EOF From 1355e993dd8ddc3831304423fd7c8d2f63d5c4cb Mon Sep 17 00:00:00 2001 From: Joshua Hesketh Date: Fri, 15 Feb 2019 17:04:53 +1100 Subject: [PATCH 004/439] Fix building solr I kept hitting an error when building solr `/bin/sh: /docker-entrypoint.sh: Text file busy`, this is caused where the script is attempted to be ran before the previous `chmod` command has properly sync'd to disk. Adding in a sync fixes this trouble for me. --- data/Dockerfiles/solr/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/data/Dockerfiles/solr/Dockerfile b/data/Dockerfiles/solr/Dockerfile index 67cd3384..8f2a873e 100644 --- a/data/Dockerfiles/solr/Dockerfile +++ b/data/Dockerfiles/solr/Dockerfile @@ -4,6 +4,7 @@ COPY docker-entrypoint.sh / RUN apk --no-cache add su-exec curl tzdata \ && chmod +x /docker-entrypoint.sh \ + && sync \ && /docker-entrypoint.sh --bootstrap ENTRYPOINT ["/docker-entrypoint.sh"] From cac67db20314e6a023ef4b8a059d82765bb3023e Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Sat, 23 Feb 2019 17:59:18 +0100 Subject: [PATCH 005/439] add config ALLOW_ADMIN_EMAIL_LOGIN and implement password-less SOGo login admins --- .gitignore | 1 + data/Dockerfiles/dovecot/docker-entrypoint.sh | 11 + data/conf/dovecot/dovecot.conf | 1 + data/conf/nginx/site.conf | 11 + .../templates/sogo.auth_request.template.sh | 7 + data/conf/sogo/sogo.conf | 2 + data/web/js/site/mailbox.js | 1983 +++++++++-------- data/web/mailbox.php | 5 + data/web/sogo-auth.php | 62 + docker-compose.yml | 7 +- generate_config.sh | 4 + update.sh | 1 + 12 files changed, 1104 insertions(+), 991 deletions(-) create mode 100644 data/conf/nginx/templates/sogo.auth_request.template.sh create mode 100644 data/web/sogo-auth.php diff --git a/.gitignore b/.gitignore index 624e1c06..5c6cd161 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ data/conf/nginx/*.custom data/conf/nginx/*.bak data/conf/dovecot/acl_anyone data/conf/dovecot/mail_plugins* +data/conf/dovecot/sogo-sso.conf data/conf/dovecot/extra.conf data/conf/rspamd/custom/* data/conf/portainer/ diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 0589579d..179ffb65 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -118,6 +118,17 @@ default_pass_scheme = SSHA256 password_query = SELECT password FROM mailbox WHERE active = '1' AND username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%' EOF +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + cat < /usr/local/etc/dovecot/sogo-sso.conf +passdb { + driver = static + args = password= allow_real_nets=${IPV4_NETWORK}.248/32 +} +EOF +else + rm -f /usr/local/etc/dovecot/sogo-sso.conf +fi + # Create global sieve_after script cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 80422599..f28f9b5c 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -389,4 +389,5 @@ auth_cache_negative_ttl = 0 auth_cache_ttl = 30 s auth_cache_size = 2 M !include_try /usr/local/etc/dovecot/extra.conf +!include_try /usr/local/etc/dovecot/sogo-sso.conf default_client_limit = 10400 diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 8b8959d5..c19310ae 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -164,6 +164,17 @@ server { client_max_body_size 0; } + # auth_request endpoint if ALLOW_ADMIN_EMAIL_LOGIN is set + location /sogo-auth-verify { + internal; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Content-Length ""; + proxy_pass http://127.0.0.1:80/sogo-auth; + proxy_pass_request_body off; + } + location ^~ /SOGo { include /etc/nginx/conf.d/sogo.active; proxy_set_header X-Real-IP $remote_addr; diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh new file mode 100644 index 00000000..3139d5f8 --- /dev/null +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -0,0 +1,7 @@ +if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then + echo ' +auth_request /sogo-auth-verify; +auth_request_set $user $upstream_http_x_username; +proxy_set_header x-webobjects-remote-user $user; +' +fi diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index aa1a86ec..a8befc2b 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -83,4 +83,6 @@ //SOGoUIxDebugEnabled = YES; //WODontZipResponse = YES; WOLogFile = "/dev/sogo_log"; + + SOGoTrustProxyAuthentication = YES; } diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 00a815e6..eb04590a 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -1,990 +1,993 @@ -$(document).ready(function() { - acl_data = JSON.parse(acl); - FooTable.domainFilter = FooTable.Filtering.extend({ - construct: function(instance){ - this._super(instance); - var domain_list = []; - $.ajax({ - dataType: 'json', - url: '/api/v1/get/domain/all', - jsonp: false, - async: true, - error: function () { - domain_list.push('Cannot read domain list'); - }, - success: function (data) { - $.each(data, function (i, item) { - domain_list.push(item.domain_name); - }); - } - }); - this.domains = domain_list; - this.def = 'All Domains'; - this.$domain = null; - }, - $create: function(){ - this._super(); - var self = this, - $form_grp = $('
', {'class': 'form-group'}) - .append($('
'; - item.chkbox = ''; - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "state": { - "enabled": true - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'aliasdomain_table'); - } - } - }); - } - - function draw_sync_job_table() { - ft_syncjob_table = FooTable.init('#sync_job_table', { - "columns": [ - {"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":"user2","title":lang.owner}, - {"name":"server_w_port","title":"Server","breakpoints":"xs","style":{"word-break":"break-all"}}, - {"name":"exclude","title":lang.excludes,"breakpoints":"all"}, - {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, - {"name":"last_run","title":lang.last_run,"breakpoints":"sm"}, - {"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}, - {"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/syncjobs/all/no_log', - jsonp: false, - error: function () { - console.log('Cannot draw sync job table'); - }, - success: function (data) { - $.each(data, function (i, item) { - item.log = 'Open logs' - item.user2 = escapeHtml(item.user2); - if (!item.exclude > 0) { - item.exclude = '-'; - } else { - item.exclude = '' + item.exclude + ''; - } - item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1; - item.action = ''; - item.chkbox = ''; - if (item.is_running == 1) { - item.is_running = '' + lang.running + ''; - } else { - item.is_running = '' + lang.waiting + ''; - } - if (!item.last_run > 0) { - item.last_run = lang.waiting; - } - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "state": { - "enabled": true - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'sync_job_table'); - } - } - }); - } - - function draw_filter_table() { - ft_filter_table = FooTable.init('#filter_table', { - "columns": [ - {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, - {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, - {"name":"active","style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, - {"name":"filter_type","style":{"maxWidth":"80px","width":"80px"},"title":"Type"}, - {"sorted": true,"name":"username","title":lang.owner,"style":{"maxWidth":"550px","width":"350px"}}, - {"name":"script_desc","title":lang.description,"breakpoints":"xs"}, - {"name":"script_data","title":"Script","breakpoints":"all"}, - {"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/filters/all', - jsonp: false, - error: function () { - console.log('Cannot draw filter table'); - }, - success: function (data) { - $.each(data, function (i, item) { - if (item.active_int == 1) { - item.active = '' + lang.active + ''; - } else { - item.active = '' + lang.inactive + ''; - } - item.script_data = '
' + escapeHtml(item.script_data) + '
' - item.filter_type = '
' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '
' - item.action = ''; - item.chkbox = '' - }); - } - }), - "paging": { - "enabled": true, - "limit": 5, - "size": pagination_size - }, - "state": { - "enabled": true - }, - "filtering": { - "enabled": true, - "delay": 100, - "position": "left", - "connectors": false, - "placeholder": lang.filter_table - }, - "sorting": { - "enabled": true - }, - "on": { - "ready.ft.table": function(e, ft){ - table_mailbox_ready(ft, 'filter_table'); - } - } - }); - }; - - draw_domain_table(); - draw_mailbox_table(); - draw_resource_table(); - draw_alias_table(); - draw_aliasdomain_table(); - draw_sync_job_table(); - draw_filter_table(); - draw_bcc_table(); - draw_recipient_map_table(); - draw_tls_policy_table(); - draw_transport_maps_table(); - -}); +$(document).ready(function() { + acl_data = JSON.parse(acl); + FooTable.domainFilter = FooTable.Filtering.extend({ + construct: function(instance){ + this._super(instance); + var domain_list = []; + $.ajax({ + dataType: 'json', + url: '/api/v1/get/domain/all', + jsonp: false, + async: true, + error: function () { + domain_list.push('Cannot read domain list'); + }, + success: function (data) { + $.each(data, function (i, item) { + domain_list.push(item.domain_name); + }); + } + }); + this.domains = domain_list; + this.def = 'All Domains'; + this.$domain = null; + }, + $create: function(){ + this._super(); + var self = this, + $form_grp = $('
', {'class': 'form-group'}) + .append($('
'; + item.chkbox = ''; + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'aliasdomain_table'); + } + } + }); + } + + function draw_sync_job_table() { + ft_syncjob_table = FooTable.init('#sync_job_table', { + "columns": [ + {"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":"user2","title":lang.owner}, + {"name":"server_w_port","title":"Server","breakpoints":"xs","style":{"word-break":"break-all"}}, + {"name":"exclude","title":lang.excludes,"breakpoints":"all"}, + {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, + {"name":"last_run","title":lang.last_run,"breakpoints":"sm"}, + {"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}, + {"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/syncjobs/all/no_log', + jsonp: false, + error: function () { + console.log('Cannot draw sync job table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.log = 'Open logs' + item.user2 = escapeHtml(item.user2); + if (!item.exclude > 0) { + item.exclude = '-'; + } else { + item.exclude = '' + item.exclude + ''; + } + item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1; + item.action = ''; + item.chkbox = ''; + if (item.is_running == 1) { + item.is_running = '' + lang.running + ''; + } else { + item.is_running = '' + lang.waiting + ''; + } + if (!item.last_run > 0) { + item.last_run = lang.waiting; + } + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'sync_job_table'); + } + } + }); + } + + function draw_filter_table() { + ft_filter_table = FooTable.init('#filter_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px","text-align":"center"},"filterable": false,"sortable": false,"type":"html"}, + {"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"active","style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"filter_type","style":{"maxWidth":"80px","width":"80px"},"title":"Type"}, + {"sorted": true,"name":"username","title":lang.owner,"style":{"maxWidth":"550px","width":"350px"}}, + {"name":"script_desc","title":lang.description,"breakpoints":"xs"}, + {"name":"script_data","title":"Script","breakpoints":"all"}, + {"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/filters/all', + jsonp: false, + error: function () { + console.log('Cannot draw filter table'); + }, + success: function (data) { + $.each(data, function (i, item) { + if (item.active_int == 1) { + item.active = '' + lang.active + ''; + } else { + item.active = '' + lang.inactive + ''; + } + item.script_data = '
' + escapeHtml(item.script_data) + '
' + item.filter_type = '
' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '
' + item.action = ''; + item.chkbox = '' + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "state": { + "enabled": true + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'filter_table'); + } + } + }); + }; + + draw_domain_table(); + draw_mailbox_table(); + draw_resource_table(); + draw_alias_table(); + draw_aliasdomain_table(); + draw_sync_job_table(); + draw_filter_table(); + draw_bcc_table(); + draw_recipient_map_table(); + draw_tls_policy_table(); + draw_transport_maps_table(); + +}); diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 96c2e16d..392e9adf 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -348,6 +348,11 @@ $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false'; echo "var role = '". $role . "';\n"; echo "var is_dual = " . $is_dual . ";\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; +$ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( + "/^([yY][eE][sS]|[yY])+$/", + $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"] +)) ? "true" : "false"; +echo "var ALLOW_ADMIN_EMAIL_LOGIN = " . $ALLOW_ADMIN_EMAIL_LOGIN . ";\n"; ?> /etc/nginx/conf.d/listen_plain.active && envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && - envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && + . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo.active && envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && nginx -qt && until ping phpfpm -c1 > /dev/null; do sleep 1; done && @@ -274,6 +278,7 @@ services: - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - TZ=${TZ} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} volumes: - ./data/web:/web:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro diff --git a/generate_config.sh b/generate_config.sh index a882ec08..e6005b72 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -200,6 +200,10 @@ SOLR_HEAP=1024 USE_WATCHDOG=n +# Allow admins to log into SOGo as email user (without any password) + +ALLOW_ADMIN_EMAIL_LOGIN=n + # Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME) # Can by multiple rcpts, NO quotation marks diff --git a/update.sh b/update.sh index 17e818f9..9c18e0cc 100755 --- a/update.sh +++ b/update.sh @@ -130,6 +130,7 @@ CONFIG_ARRAY=( "ACL_ANYONE" "SOLR_HEAP" "SKIP_SOLR" + "ALLOW_ADMIN_EMAIL_LOGIN" ) sed -i '$a\' mailcow.conf From 0c8f217f49d0a332b1da5bf49fe480bdc2af6b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 23 Feb 2019 22:20:09 +0100 Subject: [PATCH 006/439] Update sogo.auth_request.template.sh Don't want to split hairs! Just consistency. :) --- data/conf/nginx/templates/sogo.auth_request.template.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh index 3139d5f8..1a7b3f72 100644 --- a/data/conf/nginx/templates/sogo.auth_request.template.sh +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -1,6 +1,5 @@ -if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then - echo ' -auth_request /sogo-auth-verify; +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + echo 'auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_username; proxy_set_header x-webobjects-remote-user $user; ' From 88fbc6bf1675957ce40642fe62e56f4d2ce20529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 23 Feb 2019 22:26:41 +0100 Subject: [PATCH 007/439] Update sogo-auth.php Consistency again. :) I moved the prerequisites require_once to the top, ok? --- data/web/sogo-auth.php | 85 +++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 15cbeab8..98cafac5 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -1,4 +1,5 @@ Date: Sat, 23 Feb 2019 22:29:14 +0100 Subject: [PATCH 008/439] Update sogo.auth_request.template.sh --- data/conf/nginx/templates/sogo.auth_request.template.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh index 1a7b3f72..ae1a3879 100644 --- a/data/conf/nginx/templates/sogo.auth_request.template.sh +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -1,4 +1,4 @@ -if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then +if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then echo 'auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_username; proxy_set_header x-webobjects-remote-user $user; From 4482aee747934f062b4c3825f7b3c24083489396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 24 Feb 2019 00:15:09 +0100 Subject: [PATCH 009/439] Update sogo-auth.php --- data/web/sogo-auth.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 98cafac5..77c73590 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -1,10 +1,9 @@ Date: Mon, 25 Feb 2019 00:00:32 +0100 Subject: [PATCH 010/439] [Compose] Add ALLOW_ADMIN_EMAIL_LOGIN to sogo-mailcow to trigger bootstrap on change [Compose] Static IPv4 for Dovecot [SOGo] Remove SOGoIMAPServer from sogo.conf [SOGo] Add SOGoIMAPServer to bootstrap process [Nginx] Disallow editAccount for other accounts than 0 (own) --- data/Dockerfiles/sogo/bootstrap-sogo.sh | 5 +++++ data/conf/nginx/templates/sogo.auth_request.template.sh | 4 +++- data/conf/sogo/sogo.conf | 1 - docker-compose.yml | 7 +++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 5072a306..84176ebd 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -85,6 +85,9 @@ done mkdir -p /var/lib/sogo/GNUstep/Defaults/ +# Force-remove lines from sogo.conf +sed -i '/SOGoIMAPServer/d' /etc/sogo/sogo.conf + # Generate plist header with timezone data cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist @@ -93,6 +96,8 @@ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist OCSAclURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl + SOGoIMAPServer + imap://${IPV4_NETWORK}.250:143/?tls=YES OCSCacheFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder OCSEMailAlarmsFolderURL diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh index ae1a3879..d885d9f5 100644 --- a/data/conf/nginx/templates/sogo.auth_request.template.sh +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -2,5 +2,7 @@ if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' echo 'auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_username; proxy_set_header x-webobjects-remote-user $user; -' +if ($args ~* (.*)(account=(?!0))(.*)) { + return 401; +}' fi diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index a8befc2b..b115d75d 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -26,7 +26,6 @@ // (domain3.tld, domain2.tld) // ); - SOGoIMAPServer = "imap://dovecot:143/?tls=YES"; SOGoSieveServer = "sieve://dovecot:4190/?tls=YES"; SOGoSMTPServer = "postfix:588"; WOPort = "0.0.0.0:20000"; diff --git a/docker-compose.yml b/docker-compose.yml index a4a8b9be..c2394909 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -140,7 +140,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.52 + image: mailcow/sogo:1.53 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} @@ -150,6 +150,8 @@ services: - LOG_LINES=${LOG_LINES:-9999} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - ACL_ANYONE=${ACL_ANYONE:-disallow} + - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} volumes: - ./data/conf/sogo/:/etc/sogo/ - ./data/web/inc/init_db.inc.php:/init_db.inc.php @@ -165,7 +167,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.63 + image: mailcow/dovecot:1.64 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE @@ -210,6 +212,7 @@ services: hostname: ${MAILCOW_HOSTNAME} networks: mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.250 aliases: - dovecot From dd6d253ac07e40bc8e4b6e9f5f53ef208c0b18cb Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Tue, 26 Feb 2019 09:02:35 +0100 Subject: [PATCH 011/439] add random masterpass for sogo admin login add required headers for sogo proxy auth with password add SOGoEncryptionKey add SOGoTrustProxyAuthentication only conditionally if feature is enabled --- data/Dockerfiles/dovecot/docker-entrypoint.sh | 26 +++++++++++-------- data/Dockerfiles/sogo/bootstrap-sogo.sh | 11 ++++++++ .../templates/sogo.auth_request.template.sh | 12 +++++---- data/conf/phpfpm/sogo-sso/.gitkeep | 0 data/conf/sogo/sogo.conf | 2 -- data/web/sogo-auth.php | 23 +++++++++++----- docker-compose.yml | 2 ++ 7 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 data/conf/phpfpm/sogo-sso/.gitkeep diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 179ffb65..28b31420 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -118,17 +118,6 @@ default_pass_scheme = SSHA256 password_query = SELECT password FROM mailbox WHERE active = '1' AND username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%' EOF -if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - cat < /usr/local/etc/dovecot/sogo-sso.conf -passdb { - driver = static - args = password= allow_real_nets=${IPV4_NETWORK}.248/32 -} -EOF -else - rm -f /usr/local/etc/dovecot/sogo-sso.conf -fi - # Create global sieve_after script cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve @@ -146,6 +135,21 @@ echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{p echo ${RAND_USER}@mailcow.local::5000:5000:::: > /usr/local/etc/dovecot/dovecot-master.userdb echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + # Create random master Password for SOGo 'login as user' via proxy auth + RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1) + echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass + cat < /usr/local/etc/dovecot/sogo-sso.conf +passdb { + driver = static + args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS} +} +EOF +else + rm -f /usr/local/etc/dovecot/sogo-sso.pass + rm -f /usr/local/etc/dovecot/sogo-sso.conf +fi + # 401 is user dovecot if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 84176ebd..51e1eea3 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -88,6 +88,13 @@ mkdir -p /var/lib/sogo/GNUstep/Defaults/ # Force-remove lines from sogo.conf sed -i '/SOGoIMAPServer/d' /etc/sogo/sogo.conf +if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + TRUST_PROXY="YES" +else + TRUST_PROXY="NO" +fi +RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) + # Generate plist header with timezone data cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist @@ -98,6 +105,10 @@ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl SOGoIMAPServer imap://${IPV4_NETWORK}.250:143/?tls=YES + SOGoTrustProxyAuthentication + ${TRUST_PROXY} + SOGoEncryptionKey + ${RAND_PASS} OCSCacheFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder OCSEMailAlarmsFolderURL diff --git a/data/conf/nginx/templates/sogo.auth_request.template.sh b/data/conf/nginx/templates/sogo.auth_request.template.sh index d885d9f5..f6d2d98e 100644 --- a/data/conf/nginx/templates/sogo.auth_request.template.sh +++ b/data/conf/nginx/templates/sogo.auth_request.template.sh @@ -1,8 +1,10 @@ if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then echo 'auth_request /sogo-auth-verify; -auth_request_set $user $upstream_http_x_username; -proxy_set_header x-webobjects-remote-user $user; -if ($args ~* (.*)(account=(?!0))(.*)) { - return 401; -}' +auth_request_set $user $upstream_http_x_user; +auth_request_set $auth $upstream_http_x_auth; +auth_request_set $auth_type $upstream_http_x_auth_type; +proxy_set_header x-webobjects-remote-user "$user"; +proxy_set_header Authorization "$auth"; +proxy_set_header x-webobjects-auth-type "$auth_type"; +' fi diff --git a/data/conf/phpfpm/sogo-sso/.gitkeep b/data/conf/phpfpm/sogo-sso/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index b115d75d..f9e9e077 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -82,6 +82,4 @@ //SOGoUIxDebugEnabled = YES; //WODontZipResponse = YES; WOLogFile = "/dev/sogo_log"; - - SOGoTrustProxyAuthentication = YES; } diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 77c73590..d9c10557 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -30,7 +30,8 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"] )); -$session_variable = 'sogo-sso-user'; +$session_var_user = 'sogo-sso-user'; +$session_var_pass = 'sogo-sso-pass'; if (!$ALLOW_ADMIN_EMAIL_LOGIN) { header("Location: /"); @@ -42,7 +43,9 @@ elseif (isset($_GET['login'])) { $login = html_entity_decode(rawurldecode($_GET["login"])); if (filter_var($login, FILTER_VALIDATE_EMAIL)) { if (!empty(mailbox('get', 'mailbox_details', $login))) { - $_SESSION[$session_variable] = $login; + $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); + $_SESSION[$session_var_user] = $login; + $_SESSION[$session_var_pass] = $sogo_sso_pass; header("Location: /SOGo/"); exit; } @@ -54,11 +57,17 @@ elseif (isset($_GET['login'])) { else { // this is an nginx auth_request call, we check for an existing sogo-sso-user session variable session_start(); - $username = ""; - if (isset($_SESSION[$session_variable]) && filter_var($_SESSION[$session_variable], FILTER_VALIDATE_EMAIL)) { - $username = $_SESSION[$session_variable]; + if (isset($_SESSION[$session_var_user]) && filter_var($_SESSION[$session_var_user], FILTER_VALIDATE_EMAIL)) { + $username = $_SESSION[$session_var_user]; + $password = $_SESSION[$session_var_pass]; + header("X-User: $username"); + header("X-Auth: Basic ".base64_encode("$username:$password")); + header("X-Auth-Type: Basic"); + } else { + // if username is empty, SOGo will display the normal login form + header("X-User: "); + header("X-Auth: "); + header("X-Auth-Type: "); } - // if username is empty, SOGo will display the normal login form - header("X-Username: $username"); exit; } diff --git a/docker-compose.yml b/docker-compose.yml index c2394909..4d4b9022 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,6 +106,7 @@ services: - mysql-socket-vol-1:/var/run/mysqld/ - ./data/conf/sogo/:/etc/sogo/ - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro + - ./data/conf/phpfpm/sogo-sso/:/etc/sogo-sso/ - ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf - ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini - ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini @@ -175,6 +176,7 @@ services: - ./data/conf/dovecot:/usr/local/etc/dovecot - ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/conf/sogo/:/etc/sogo/ + - ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/ - vmail-vol-1:/var/vmail - vmail-attachments-vol-1:/var/attachments - crypt-vol-1:/mail_crypt/ From 9f3b79b361788061cd9530e920d4c64be782b755 Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Tue, 26 Feb 2019 09:03:21 +0100 Subject: [PATCH 012/439] ignore sogo master pass file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5c6cd161..5fd3c0f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ rebuild-images.sh data/conf/sogo/sieve.creds +data/conf/phpfpm/sogo-sso/sogo-sso.pass data/conf/dovecot/dovecot-master.passwd data/conf/dovecot/dovecot-master.userdb mailcow.conf From e2f39df7d84c96885e807c2216b8b062dcc75ebe Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Tue, 26 Feb 2019 20:44:53 +0100 Subject: [PATCH 013/439] remove obsolete code, use openssl instead of `cat /dev/urandom` --- data/Dockerfiles/sogo/bootstrap-sogo.sh | 9 ++---- data/web/sogo-auth.php | 37 ++++++------------------- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 51e1eea3..5290f9dc 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -83,19 +83,16 @@ EOF done -mkdir -p /var/lib/sogo/GNUstep/Defaults/ - -# Force-remove lines from sogo.conf -sed -i '/SOGoIMAPServer/d' /etc/sogo/sogo.conf - if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then TRUST_PROXY="YES" else TRUST_PROXY="NO" fi -RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) +# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl +RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) # Generate plist header with timezone data +mkdir -p /var/lib/sogo/GNUstep/Defaults/ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index d9c10557..b08ca4e7 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -1,30 +1,5 @@ Date: Tue, 26 Feb 2019 21:37:08 +0100 Subject: [PATCH 014/439] [Postfix] Remove sasl requiring policies from port 25 --- data/conf/postfix/master.cf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index 79642f6d..efc311a5 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -2,6 +2,7 @@ smtp inet n - n - 1 postscreen smtpd pass - - n - - smtpd -o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname -o smtpd_sasl_auth_enable=no + -o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain smtps inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject From 38911034c3858d3dd55c46673c010fb06f1e4d29 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 26 Feb 2019 22:13:37 +0100 Subject: [PATCH 015/439] Don't break DAV --- data/conf/nginx/site.conf | 17 ++++++++++++++++- docker-compose.yml | 9 +++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index c19310ae..a5dc0222 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -176,7 +176,22 @@ server { } location ^~ /SOGo { - include /etc/nginx/conf.d/sogo.active; + include /etc/nginx/conf.d/sogo_main.active; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header x-webobjects-server-protocol HTTP/1.0; + proxy_set_header x-webobjects-remote-host $remote_addr; + proxy_set_header x-webobjects-server-name $server_name; + proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; + proxy_set_header x-webobjects-server-port $server_port; + client_body_buffer_size 128k; + client_max_body_size 0; + break; + } + + location ^~ /SOGo/dav { + include /etc/nginx/conf.d/sogo_dav.active; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; diff --git a/docker-compose.yml b/docker-compose.yml index 4d4b9022..724c5c6b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -141,7 +141,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.53 + image: mailcow/sogo:1.54 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} @@ -168,7 +168,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.64 + image: mailcow/dovecot:1.65 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE @@ -268,8 +268,9 @@ services: command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && - . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo.active && - envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo.active && + . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_main.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo_main.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo_dav.active && envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && nginx -qt && until ping phpfpm -c1 > /dev/null; do sleep 1; done && From a110378000168848f97947a5d88f7ed9db7373be Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Wed, 27 Feb 2019 23:06:19 +0100 Subject: [PATCH 016/439] always check basic auth against user database for EAS and SOGo if ALLOW_ADMIN_EMAIL_LOGIN is enabled --- data/conf/nginx/site.conf | 41 +++++++++++++-------------------------- data/web/sogo-auth.php | 30 +++++++++++++++++++++++++--- docker-compose.yml | 5 ++--- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index a5dc0222..4c6d1daa 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -142,7 +142,19 @@ server { try_files /autoconfig.php =404; } + # auth_request endpoint if ALLOW_ADMIN_EMAIL_LOGIN is set + location /sogo-auth-verify { + internal; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Content-Length ""; + proxy_pass http://127.0.0.1:80/sogo-auth; + proxy_pass_request_body off; + } + location ^~ /Microsoft-Server-ActiveSync { + include /etc/nginx/conf.d/sogo_proxy_auth.active; include /etc/nginx/conf.d/sogo_eas.active; proxy_connect_timeout 4000; proxy_next_upstream timeout error; @@ -164,34 +176,9 @@ server { client_max_body_size 0; } - # auth_request endpoint if ALLOW_ADMIN_EMAIL_LOGIN is set - location /sogo-auth-verify { - internal; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header Host $http_host; - proxy_set_header Content-Length ""; - proxy_pass http://127.0.0.1:80/sogo-auth; - proxy_pass_request_body off; - } - location ^~ /SOGo { - include /etc/nginx/conf.d/sogo_main.active; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - client_body_buffer_size 128k; - client_max_body_size 0; - break; - } - - location ^~ /SOGo/dav { - include /etc/nginx/conf.d/sogo_dav.active; + include /etc/nginx/conf.d/sogo_proxy_auth.active; + include /etc/nginx/conf.d/sogo.active; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index b08ca4e7..ae882f26 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -8,11 +8,31 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( $session_var_user = 'sogo-sso-user'; $session_var_pass = 'sogo-sso-pass'; +// prevent if feature is disabled if (!$ALLOW_ADMIN_EMAIL_LOGIN) { - header('HTTP/1.0 401 Forbidden'); + header('HTTP/1.0 403 Forbidden'); echo "this feature is disabled"; exit; } +// validate credentials for basic auth requests +elseif (isset($_SERVER['PHP_AUTH_USER'])) { + // load prerequisites only when required + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; + $username = $_SERVER['PHP_AUTH_USER']; + $password = $_SERVER['PHP_AUTH_PW']; + $login_check = check_login($username, $password); + if ($login_check === 'user') { + header("X-User: $username"); + header("X-Auth: Basic ".base64_encode("$username:$password")); + header("X-Auth-Type: Basic"); + exit; + } else { + header('HTTP/1.0 401 Unauthorized'); + echo 'Invalid login'; + exit; + } +} +// check permissions and redirect for direct GET ?login=xy requests elseif (isset($_GET['login'])) { // load prerequisites only when required require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; @@ -32,10 +52,14 @@ elseif (isset($_GET['login'])) { } } } - header('HTTP/1.0 401 Forbidden'); + header('HTTP/1.0 403 Forbidden'); exit; } -else { +// do not check for admin-login / sogo-sso for EAS and DAV requests, SOGo can check auth itself if no authorization header is set +elseif ( + substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28) !== "/Microsoft-Server-ActiveSync" && + substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9) !== "/SOGo/dav" +) { // this is an nginx auth_request call, we check for existing sogo-sso session variables session_start(); if (isset($_SESSION[$session_var_user]) && filter_var($_SESSION[$session_var_user], FILTER_VALIDATE_EMAIL)) { diff --git a/docker-compose.yml b/docker-compose.yml index 724c5c6b..efee7ade 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -268,10 +268,9 @@ services: command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && - . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_main.active && - envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo_main.active && - envsubst < /etc/nginx/conf.d/templates/sogo.template >> /etc/nginx/conf.d/sogo_dav.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && + . /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.active && nginx -qt && until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping sogo -c1 > /dev/null; do sleep 1; done && From fa80d66d6c855e75de14b298103828a0a9a5d469 Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Wed, 27 Feb 2019 23:14:30 +0100 Subject: [PATCH 017/439] match EAS and SOGO/dav case insensitive --- data/web/sogo-auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index ae882f26..37e6f75f 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -57,8 +57,8 @@ elseif (isset($_GET['login'])) { } // do not check for admin-login / sogo-sso for EAS and DAV requests, SOGo can check auth itself if no authorization header is set elseif ( - substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28) !== "/Microsoft-Server-ActiveSync" && - substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9) !== "/SOGo/dav" + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28), "/Microsoft-Server-ActiveSync") == 0 && + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/dav") == 0 ) { // this is an nginx auth_request call, we check for existing sogo-sso session variables session_start(); From 965577c5d820975d1c24cb11ab7b85f9d9f73c99 Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Wed, 27 Feb 2019 23:16:23 +0100 Subject: [PATCH 018/439] fix path check --- data/web/sogo-auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 37e6f75f..0cc4beee 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -57,8 +57,8 @@ elseif (isset($_GET['login'])) { } // do not check for admin-login / sogo-sso for EAS and DAV requests, SOGo can check auth itself if no authorization header is set elseif ( - strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28), "/Microsoft-Server-ActiveSync") == 0 && - strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/dav") == 0 + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28), "/Microsoft-Server-ActiveSync") !== 0 && + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/dav") !== 0 ) { // this is an nginx auth_request call, we check for existing sogo-sso session variables session_start(); From fcbcc117d27d4a607f1a083e5e6c635eeb3c20f5 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 28 Feb 2019 20:22:16 +0100 Subject: [PATCH 019/439] [Netfilter] Detect SOGo 403 [Compose] Update Netfilter --- data/Dockerfiles/netfilter/server.py | 3 ++- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index f43122ea..910679c6 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -31,7 +31,8 @@ RULES[2] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([ RULES[3] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' RULES[4] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' RULES[5] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' -#RULES[6] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' +RULES[6] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' +#RULES[7] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' bans = {} log = {} diff --git a/docker-compose.yml b/docker-compose.yml index efee7ade..46317570 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -333,7 +333,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.22 + image: mailcow/netfilter:1.23 build: ./data/Dockerfiles/netfilter stop_grace_period: 30s depends_on: From 8fd9db88b6c8276018fc3724c234357ccd655f88 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 1 Mar 2019 14:15:25 +0100 Subject: [PATCH 020/439] [Compose] New SOGo image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 46317570..1bb087b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -141,7 +141,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.54 + image: mailcow/sogo:1.55 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} From 752a7c418b3f842d7ea473861094362ea066b104 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 1 Mar 2019 14:17:09 +0100 Subject: [PATCH 021/439] Revert SOGo image version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1bb087b7..46317570 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -141,7 +141,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.55 + image: mailcow/sogo:1.54 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} From 6a7b4387ebd54180decc3e3ea6b2c0c9bf44fc09 Mon Sep 17 00:00:00 2001 From: Marcel Hofer Date: Sat, 2 Mar 2019 12:32:10 +0100 Subject: [PATCH 022/439] allow multiple concurrent admin logins --- data/web/sogo-auth.php | 44 +++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 0cc4beee..08fb1b0b 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -5,7 +5,7 @@ $ALLOW_ADMIN_EMAIL_LOGIN = (preg_match( $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"] )); -$session_var_user = 'sogo-sso-user'; +$session_var_user_allowed = 'sogo-sso-user-allowed'; $session_var_pass = 'sogo-sso-pass'; // prevent if feature is disabled @@ -44,10 +44,10 @@ elseif (isset($_GET['login'])) { // load master password $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); // register username and password in session - $_SESSION[$session_var_user] = $login; + $_SESSION[$session_var_user_allowed][] = $login; $_SESSION[$session_var_pass] = $sogo_sso_pass; // redirect to sogo (sogo will get the correct credentials via nginx auth_request - header("Location: /SOGo/"); + header("Location: /SOGo/so/${login}"); exit; } } @@ -55,24 +55,32 @@ elseif (isset($_GET['login'])) { header('HTTP/1.0 403 Forbidden'); exit; } -// do not check for admin-login / sogo-sso for EAS and DAV requests, SOGo can check auth itself if no authorization header is set +// only check for admin-login on sogo GUI requests elseif ( - strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 28), "/Microsoft-Server-ActiveSync") !== 0 && - strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/dav") !== 0 + strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0 ) { // this is an nginx auth_request call, we check for existing sogo-sso session variables session_start(); - if (isset($_SESSION[$session_var_user]) && filter_var($_SESSION[$session_var_user], FILTER_VALIDATE_EMAIL)) { - $username = $_SESSION[$session_var_user]; - $password = $_SESSION[$session_var_pass]; - header("X-User: $username"); - header("X-Auth: Basic ".base64_encode("$username:$password")); - header("X-Auth-Type: Basic"); - } else { - // if username is empty, SOGo will display the normal login form - header("X-User: "); - header("X-Auth: "); - header("X-Auth-Type: "); + // extract email address from "/SOGo/so/user@domain/xy" + $url_parts = explode("/", $_SERVER['HTTP_X_ORIGINAL_URI']); + $email = $url_parts[3]; + // check if this email is in session allowed list + if ( + !empty($email) && + filter_var($email, FILTER_VALIDATE_EMAIL) && + is_array($_SESSION[$session_var_user_allowed]) && + in_array($email, $_SESSION[$session_var_user_allowed]) + ) { + $username = $email; + $password = $_SESSION[$session_var_pass]; + header("X-User: $username"); + header("X-Auth: Basic ".base64_encode("$username:$password")); + header("X-Auth-Type: Basic"); + exit; } - exit; } + +// if username is empty, SOGo will use the normal login methods / login form +header("X-User: "); +header("X-Auth: "); +header("X-Auth-Type: "); From 1e79ea6c7e2934816bcebff91bbaab36c5f31a2f Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 2 Mar 2019 20:32:01 +0100 Subject: [PATCH 023/439] [Web] Fix transport_check over port 465, fixes #2386 --- data/web/inc/ajax/transport_check.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/web/inc/ajax/transport_check.php b/data/web/inc/ajax/transport_check.php index cbf253a9..e956f4f2 100644 --- a/data/web/inc/ajax/transport_check.php +++ b/data/web/inc/ajax/transport_check.php @@ -58,6 +58,9 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi ) ); $mail->SMTPDebug = 3; + if ($port == 465) { + $mail->SMTPSecure = "ssl"; + } $mail->Debugoutput = function($str, $level) { foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ if (empty($line)) { continue; } From 3873b7a7687bf2a251b68284752a1b0b941d1f74 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 2 Mar 2019 23:48:20 +0100 Subject: [PATCH 024/439] [Update] Add /opt/bin to PATH, fixes #2391 --- update.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 17e818f9..4fc668fb 100755 --- a/update.sh +++ b/update.sh @@ -6,9 +6,12 @@ if [ "$(id -u)" -ne "0" ]; then exit 1 fi -#exit on error and pipefail +# Exit on error and pipefail set -o pipefail +# Add /opt/bin to PATH +PATH=$PATH:/opt/bin + umask 0022 for bin in curl docker-compose docker git awk sha1sum; do From 52890e113f9c1b65035c566b80ff976be22223c5 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 12:07:33 +0100 Subject: [PATCH 025/439] [Web] Update bootstrap to 3.4.1, fixes #2381 --- data/web/css/build/001-bootstrap.min.css | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/web/css/build/001-bootstrap.min.css b/data/web/css/build/001-bootstrap.min.css index 9a1ea64d..82551800 100644 --- a/data/web/css/build/001-bootstrap.min.css +++ b/data/web/css/build/001-bootstrap.min.css @@ -1,10 +1,11 @@ - * bootswatch v3.3.7 +/*! + * bootswatch v3.4.1 * Homepage: http://bootswatch.com - * Copyright 2012-2017 Thomas Park + * Copyright 2012-2019 Thomas Park * Licensed under MIT * Based on Bootstrap *//*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('/fonts/glyphicons-halflings-regular.eot');src:url('/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('/fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('/fonts/glyphicons-halflings-regular.woff') format('woff'),url('/fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#ff851b;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1450px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1450px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:8px;padding-bottom:8px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#28b62c}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ff851b}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ff4136}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:8px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:8px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{color:#158cba;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#ffffff;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#333333;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#158cba}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.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}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:transparent;color:#333333}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:transparent;color:#333333}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:">\00a0";padding:0 5px;color:#999999}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;line-height:1.42857143;text-decoration:none;color:#555555;background-color:#eeeeee;border:1px solid #e2e2e2;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#158cba;border-color:#127ba3;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;background-color:#eeeeee;border-color:#e2e2e2;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:5px;border-top-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:5px;border-top-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:2px;border-top-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:2px;border-top-right-radius:2px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#eeeeee;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{border-radius:5px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#28b62c;border-color:#24a528;color:#ffffff}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#75caeb;border-color:#40b5e3;color:#ffffff}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#ff851b;border-color:#ff7701;color:#ffffff}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#ff4136;border-color:#ff1103;color:#ffffff}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#fafafa;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eeeeee;color:#999999;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#ffffff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1449px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1449px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1450px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1450px){.visible-lg-block{display:block !important}}@media (min-width:1450px){.visible-lg-inline{display:inline !important}}@media (min-width:1450px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1449px){.hidden-md{display:none !important}}@media (min-width:1450px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;line-height:1.5;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;-webkit-box-shadow:none;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{-webkit-box-shadow:none;box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.075);box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;-webkit-box-shadow:none;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.1);box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{-webkit-box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.05);box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;-webkit-box-shadow:inset 0 2px 0 rgba(0,0,0,0.05);box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url("../fonts/glyphicons-halflings-regular.eot");src:url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{box-sizing:border-box}*:before,*:after{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#ff851b}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="search"]{box-sizing:border-box;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:8px;padding-bottom:8px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;background-color:#28b62c;border-color:#ffffff}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;background-color:#ff851b;border-color:#ffffff}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;background-color:#ff4136;border-color:#ffffff}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:8px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:8px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:0.65;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;background-image:none;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;background-image:none;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;background-image:none;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;background-image:none;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;background-image:none;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;background-image:none;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{font-weight:400;color:#158cba;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition-property:height, visibility;transition-duration:0.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#333333;text-decoration:none;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;background-color:#158cba;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:400;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#999999;content:">\00a0"}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;margin-left:-1px;line-height:1.42857143;color:#555555;text-decoration:none;background-color:#eeeeee;border:1px solid #e2e2e2}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;cursor:default;background-color:#158cba;border-color:#127ba3}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee;border-color:#e2e2e2}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:5px;border-bottom-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:5px;border-bottom-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;cursor:not-allowed;background-color:#eeeeee}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:5px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#ffffff;background-color:#28b62c;border-color:#24a528}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{color:#ffffff;background-color:#75caeb;border-color:#40b5e3}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{color:#ffffff;background-color:#ff4136;border-color:#ff1103}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#fafafa;border-radius:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);opacity:0.2}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:0.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);transform:translate(0, -25%);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;background-clip:padding-box;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:0.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:0.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#ffffff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#ffffff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#ffffff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:0.5}.carousel-control.left{background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:0.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #ffffff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#ffffff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file From 083b822cbf1b2f12f8de144161fa8665638f6387 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 12:07:54 +0100 Subject: [PATCH 026/439] [Compose] Update SOGo image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f2776bd7..f61b7a7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,7 +139,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.52 + image: mailcow/sogo:1.53 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} From 69f54b99a1bd026ec24b36fbfbee83c2652f2ffa Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 12:08:26 +0100 Subject: [PATCH 027/439] [Dovecot] ssl_min_protocol is now TLS 1.2 --- 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 d693d39c..481c9ca3 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -28,7 +28,7 @@ mail_attachment_min_size = 128k # Dovecot 2.2 #ssl_protocols = !SSLv3 # Dovecot 2.3 -ssl_min_protocol = TLSv1 +ssl_min_protocol = TLSv1.2 ssl_prefer_server_ciphers = yes ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM From eccf3ff4da5fa80eba14b50e6263bb5d71f0b9fa Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 12:09:10 +0100 Subject: [PATCH 028/439] [Postfix] Mandatory encryption protocol is now min. TLS 1.2 --- data/conf/postfix/main.cf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 83a252d8..beee0d0a 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -94,9 +94,9 @@ smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 -smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 +smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3 -lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3 +lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 lmtp_tls_protocols = !SSLv2, !SSLv2, !SSLv3 smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 smtpd_tls_protocols = !SSLv2, !SSLv3 From 0375703198a77c66253c833fa8fc96bf2b834859 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 12:11:39 +0100 Subject: [PATCH 029/439] [Postfix] Fix mandatory encryption protocols and always require at least TLS 1.2 for LMTP --- data/conf/postfix/main.cf | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index beee0d0a..5bc3daa0 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -94,12 +94,16 @@ smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 + smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3 + lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 -lmtp_tls_protocols = !SSLv2, !SSLv2, !SSLv3 -smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 +lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 + +smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtpd_tls_protocols = !SSLv2, !SSLv3 + smtpd_tls_security_level = may tls_preempt_cipherlist = yes tls_ssl_options = NO_COMPRESSION From 950ab304622d21c01d61dc3eeeab10f3c715e709 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 22:25:56 +0100 Subject: [PATCH 030/439] [Rspamd] Upgrade Rspamd [Compose] New Rspamd image --- data/Dockerfiles/rspamd/Dockerfile | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index a2b5f7f8..4b5cd8cc 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \ gnupg2 \ apt-transport-https \ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ && echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \ @@ -21,7 +21,7 @@ RUN apt-get update && apt-get install -y \ COPY settings.conf /etc/rspamd/settings.conf COPY docker-entrypoint.sh /docker-entrypoint.sh -COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua +#COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index f61b7a7b..d25ce1f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.34 + image: mailcow/rspamd:1.35 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From c4dfed8a96fdfba0bef57c1ab1a8b9bafe178c88 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 3 Mar 2019 22:54:47 +0100 Subject: [PATCH 031/439] [Compose, Rspamd] Downgrade Rspamd --- data/Dockerfiles/rspamd/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 4b5cd8cc..fba23966 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update && apt-get install -y \ COPY settings.conf /etc/rspamd/settings.conf COPY docker-entrypoint.sh /docker-entrypoint.sh -#COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua +COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index d25ce1f7..f61b7a7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.35 + image: mailcow/rspamd:1.34 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From d365813997e7763b264590f8a406bcfe28ee625d Mon Sep 17 00:00:00 2001 From: tha80 <7176001+tha80@users.noreply.github.com> Date: Mon, 4 Mar 2019 13:59:31 +0100 Subject: [PATCH 032/439] Downgraded rspamd deb repository --- data/Dockerfiles/rspamd/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index fba23966..a2b5f7f8 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \ gnupg2 \ apt-transport-https \ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ && echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \ From 5bc8289d32b3cc7ea85541f9a7325a250fb51baf Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 4 Mar 2019 17:56:27 +0100 Subject: [PATCH 033/439] [Watchdog] Minor fixes, print last log lines on error --- data/Dockerfiles/watchdog/watchdog.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index ed9b568d..77e16b0f 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -37,7 +37,7 @@ progress() { log_msg() { if [[ ${2} != "no_redis" ]]; then redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ - tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null + tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null fi echo $(date) $(printf '%s\n' "${1}") } @@ -115,7 +115,7 @@ nginx_checks() { # 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 - cat /dev/null > /tmp/nginx-mailcow + touch /tmp/nginx-mailcow; echo "$(tail -50 /tmp/nginx-mailcow)" > /tmp/nginx-mailcow host_ip=$(get_container_ip nginx-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -140,7 +140,7 @@ unbound_checks() { # 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 - cat /dev/null > /tmp/unbound-mailcow + touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow host_ip=$(get_container_ip unbound-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -172,7 +172,7 @@ mysql_checks() { # 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 - cat /dev/null > /tmp/mysql-mailcow + touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow host_ip=$(get_container_ip mysql-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -198,7 +198,7 @@ sogo_checks() { # 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 - cat /dev/null > /tmp/sogo-mailcow + touch /tmp/sogo-mailcow; echo "$(tail -50 /tmp/sogo-mailcow)" > /tmp/sogo-mailcow host_ip=$(get_container_ip sogo-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -223,7 +223,7 @@ postfix_checks() { # 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 - cat /dev/null > /tmp/postfix-mailcow + touch /tmp/postfix-mailcow; echo "$(tail -50 /tmp/postfix-mailcow)" > /tmp/postfix-mailcow host_ip=$(get_container_ip postfix-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -249,7 +249,7 @@ clamd_checks() { # 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 - cat /dev/null > /tmp/clamd-mailcow + touch /tmp/clamd-mailcow; echo "$(tail -50 /tmp/clamd-mailcow)" > /tmp/clamd-mailcow host_ip=$(get_container_ip clamd-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -274,7 +274,7 @@ dovecot_checks() { # 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 - cat /dev/null > /tmp/dovecot-mailcow + touch /tmp/dovecot-mailcow; echo "$(tail -50 /tmp/dovecot-mailcow)" > /tmp/dovecot-mailcow host_ip=$(get_container_ip dovecot-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -303,7 +303,7 @@ phpfpm_checks() { # 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 - cat /dev/null > /tmp/php-fpm-mailcow + touch /tmp/php-fpm-mailcow; echo "$(tail -50 /tmp/php-fpm-mailcow)" > /tmp/php-fpm-mailcow host_ip=$(get_container_ip php-fpm-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) @@ -388,7 +388,7 @@ rspamd_checks() { # 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 - cat /dev/null > /tmp/rspamd-mailcow + touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow host_ip=$(get_container_ip rspamd-mailcow) err_c_cur=${err_count} SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan -d ' @@ -561,6 +561,9 @@ while true; do CONTAINER_ID= HAS_INITDB= read com_pipe_answer Date: Mon, 4 Mar 2019 17:56:38 +0100 Subject: [PATCH 034/439] [Compose] Update watchdog image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f61b7a7b..7935d424 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -347,7 +347,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.35 + image: mailcow/watchdog:1.36 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog From 4d32eb49ee7dc62cd1d91858f989469145ef387a Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 4 Mar 2019 17:57:44 +0100 Subject: [PATCH 035/439] [Dovecot] Revert to TLS1+ --- 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 481c9ca3..d693d39c 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -28,7 +28,7 @@ mail_attachment_min_size = 128k # Dovecot 2.2 #ssl_protocols = !SSLv3 # Dovecot 2.3 -ssl_min_protocol = TLSv1.2 +ssl_min_protocol = TLSv1 ssl_prefer_server_ciphers = yes ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM From 81f581247b2229607bd4801276cbfd75484a914a Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:06:39 +0100 Subject: [PATCH 036/439] [Compose] Update Rspamd image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7935d424..53f6aec9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.34 + image: mailcow/rspamd:1.35 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From e1ebacca27484f9300accb5647a75eb7dc4f4a48 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:06:51 +0100 Subject: [PATCH 037/439] [Rspamd] Drop rspamd.conf.local file --- data/Dockerfiles/rspamd/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index a2b5f7f8..86336ea5 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \ && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ - && echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \ && apt-get autoremove --purge \ && apt-get clean \ && mkdir -p /run/rspamd \ From 6dc53186733fe13158942f0a39f755da793b76ff Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:08:18 +0100 Subject: [PATCH 038/439] [Rspamd] Delete rspamd.conf.local --- data/conf/rspamd/local.d/rspamd.conf.local | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 data/conf/rspamd/local.d/rspamd.conf.local diff --git a/data/conf/rspamd/local.d/rspamd.conf.local b/data/conf/rspamd/local.d/rspamd.conf.local deleted file mode 100644 index 0662c47d..00000000 --- a/data/conf/rspamd/local.d/rspamd.conf.local +++ /dev/null @@ -1,16 +0,0 @@ -# rspamd.conf.local - -worker "fuzzy" { - # Socket to listen on (UDP and TCP from rspamd 1.3) - bind_socket = "*:11445"; - allow_update = ["127.0.0.1", "::1"]; - # Number of processes to serve this storage (useful for read scaling) - count = 2; - # Backend ("sqlite" or "redis" - default "sqlite") - backend = "redis"; - # Hashes storage time (3 months) - expire = 90d; - # Synchronize updates to the storage each minute - sync = 1min; -} - From 9abbe7eb1d0b0b5998a8daa2d44bd8dba8e85f55 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:09:28 +0100 Subject: [PATCH 039/439] [Postfix] Mandatory protocol for authenticated clients over 587/tcp and 465/tcp is now TLSv1.0+ (reverts previous protocol change for authenticated users only) [Postfix] Force route localhost$ over local: --- data/conf/postfix/local_transport | 1 + data/conf/postfix/main.cf | 2 +- data/conf/postfix/master.cf | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 data/conf/postfix/local_transport diff --git a/data/conf/postfix/local_transport b/data/conf/postfix/local_transport new file mode 100644 index 00000000..5d10028c --- /dev/null +++ b/data/conf/postfix/local_transport @@ -0,0 +1 @@ +/localhost$/ local: diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 5bc3daa0..88d905e7 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -138,5 +138,5 @@ smtp_sasl_mechanism_filter = plain, login smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre mail_name = Postcow -transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf +transport_maps = pcre:/opt/postfix/conf/local_transport, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf smtp_sasl_auth_soft_bounce = no diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index efc311a5..fcc99717 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -6,11 +6,13 @@ smtpd pass - - n - - smtpd smtps inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject + -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 -o tls_preempt_cipherlist=yes submission inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt + -o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 -o tls_preempt_cipherlist=yes 588 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject From ac6e379f09df9fb6f2780ab0fcc05871bad1a21c Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:11:40 +0100 Subject: [PATCH 040/439] [Web] Fix bootstrap pathes --- data/web/css/build/001-bootstrap.min.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/css/build/001-bootstrap.min.css b/data/web/css/build/001-bootstrap.min.css index 82551800..769bf30c 100644 --- a/data/web/css/build/001-bootstrap.min.css +++ b/data/web/css/build/001-bootstrap.min.css @@ -8,4 +8,4 @@ * Bootstrap v3.4.1 (https://getbootstrap.com/) * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url("../fonts/glyphicons-halflings-regular.eot");src:url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{box-sizing:border-box}*:before,*:after{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#ff851b}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="search"]{box-sizing:border-box;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:8px;padding-bottom:8px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;background-color:#28b62c;border-color:#ffffff}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;background-color:#ff851b;border-color:#ffffff}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;background-color:#ff4136;border-color:#ffffff}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:8px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:8px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:0.65;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;background-image:none;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;background-image:none;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;background-image:none;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;background-image:none;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;background-image:none;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;background-image:none;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{font-weight:400;color:#158cba;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition-property:height, visibility;transition-duration:0.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#333333;text-decoration:none;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;background-color:#158cba;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:400;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#999999;content:">\00a0"}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;margin-left:-1px;line-height:1.42857143;color:#555555;text-decoration:none;background-color:#eeeeee;border:1px solid #e2e2e2}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;cursor:default;background-color:#158cba;border-color:#127ba3}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee;border-color:#e2e2e2}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:5px;border-bottom-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:5px;border-bottom-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;cursor:not-allowed;background-color:#eeeeee}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:5px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#ffffff;background-color:#28b62c;border-color:#24a528}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{color:#ffffff;background-color:#75caeb;border-color:#40b5e3}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{color:#ffffff;background-color:#ff4136;border-color:#ff1103}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#fafafa;border-radius:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);opacity:0.2}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:0.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);transform:translate(0, -25%);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;background-clip:padding-box;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:0.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:0.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#ffffff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#ffffff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#ffffff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:0.5}.carousel-control.left{background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:0.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #ffffff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#ffffff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:"Glyphicons Halflings";src:url("/fonts/glyphicons-halflings-regular.eot");src:url("/fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("/fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("/fonts/glyphicons-halflings-regular.woff") format("woff"),url("/fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{box-sizing:border-box}*:before,*:after{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#158cba;text-decoration:none}a:hover,a:focus{color:#158cba;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:5px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:#333333}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#ff851b}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#158cba}a.text-primary:hover,a.text-primary:focus{color:#106a8c}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#158cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#106a8c}.bg-success{background-color:#28b62c}a.bg-success:hover,a.bg-success:focus{background-color:#1f8c22}.bg-info{background-color:#75caeb}a.bg-info:hover,a.bg-info:focus{background-color:#48b9e5}.bg-warning{background-color:#ff851b}a.bg-warning:hover,a.bg-warning:focus{background-color:#e76b00}.bg-danger{background-color:#ff4136}a.bg-danger:hover,a.bg-danger:focus{background-color:#ff1103}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eeeeee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #eeeeee}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #eeeeee}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #eeeeee}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #eeeeee}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#28b62c}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#23a127}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#75caeb}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#5fc1e8}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#ff851b}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#ff7701}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ff4136}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ff291c}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #eeeeee}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="search"]{box-sizing:border-box;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:8px;font-size:14px;line-height:1.42857143;color:#555555}.form-control{display:block;width:100%;height:38px;padding:7px 12px;font-size:14px;line-height:1.42857143;color:#555555;background-color:#ffffff;background-image:none;border:1px solid #e7e7e7;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:28px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:52px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:8px;padding-bottom:8px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-sm{height:28px;line-height:28px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.form-group-sm select.form-control{height:28px;line-height:28px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:28px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-lg{height:52px;line-height:52px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.form-group-lg select.form-control{height:52px;line-height:52px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:52px;min-height:38px;padding:14px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:52px;height:52px;line-height:52px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:28px;height:28px;line-height:28px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;background-color:#28b62c;border-color:#ffffff}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;background-color:#ff851b;border-color:#ffffff}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;background-color:#ff4136;border-color:#ffffff}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:8px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:28px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:8px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:5px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:7px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#555555;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:0.65;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.btn-default:focus,.btn-default.focus{color:#555555;background-color:#d5d5d5;border-color:#a2a2a2}.btn-default:hover{color:#555555;background-color:#d5d5d5;border-color:#c3c3c3}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#555555;background-color:#d5d5d5;background-image:none;border-color:#c3c3c3}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#555555;background-color:#c3c3c3;border-color:#a2a2a2}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#eeeeee;border-color:#e2e2e2}.btn-default .badge{color:#eeeeee;background-color:#555555}.btn-primary{color:#ffffff;background-color:#158cba;border-color:#127ba3}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#106a8c;border-color:#052531}.btn-primary:hover{color:#ffffff;background-color:#106a8c;border-color:#0c516c}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#106a8c;background-image:none;border-color:#0c516c}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#0c516c;border-color:#052531}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#158cba;border-color:#127ba3}.btn-primary .badge{color:#158cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#28b62c;border-color:#23a127}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1f8c22;border-color:#0c390e}.btn-success:hover{color:#ffffff;background-color:#1f8c22;border-color:#186f1b}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1f8c22;background-image:none;border-color:#186f1b}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#186f1b;border-color:#0c390e}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#28b62c;border-color:#23a127}.btn-success .badge{color:#28b62c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#75caeb;border-color:#5fc1e8}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#48b9e5;border-color:#1984ae}.btn-info:hover{color:#ffffff;background-color:#48b9e5;border-color:#29ade0}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#48b9e5;background-image:none;border-color:#29ade0}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#29ade0;border-color:#1984ae}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#75caeb;border-color:#5fc1e8}.btn-info .badge{color:#75caeb;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#e76b00;border-color:#813c00}.btn-warning:hover{color:#ffffff;background-color:#e76b00;border-color:#c35b00}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#e76b00;background-image:none;border-color:#c35b00}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#c35b00;border-color:#813c00}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#ff851b;border-color:#ff7701}.btn-warning .badge{color:#ff851b;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ff4136;border-color:#ff291c}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#ff1103;border-color:#9c0900}.btn-danger:hover{color:#ffffff;background-color:#ff1103;border-color:#de0c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#ff1103;background-image:none;border-color:#de0c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#de0c00;border-color:#9c0900}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ff4136;border-color:#ff291c}.btn-danger .badge{color:#ff4136;background-color:#ffffff}.btn-link{font-weight:400;color:#158cba;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#158cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}.btn-sm,.btn-group-sm>.btn{padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:2px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition-property:height, visibility;transition-duration:0.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid #e7e7e7;border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#eeeeee}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#333333;text-decoration:none;background-color:transparent}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;background-color:#158cba;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#eeeeee}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:52px;padding:13px 16px;font-size:18px;line-height:1.3333333;border-radius:5px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:52px;line-height:52px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:28px;padding:4px 10px;font-size:12px;line-height:1.5;border-radius:2px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:28px;line-height:28px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:7px 12px;font-size:14px;font-weight:400;line-height:1;color:#555555;text-align:center;background-color:#eeeeee;border:1px solid #e7e7e7;border-radius:4px}.input-group-addon.input-sm{padding:4px 10px;font-size:12px;border-radius:2px}.input-group-addon.input-lg{padding:13px 16px;font-size:18px;border-radius:5px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#ffffff}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#ffffff;border-color:#158cba}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e7e7e7}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #e7e7e7}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555555;cursor:default;background-color:#ffffff;border:1px solid #e7e7e7;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#158cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e7e7e7}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e7e7e7;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:11px;margin-bottom:11px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#333333}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-default .navbar-text{color:#555555}.navbar-default .navbar-nav>li>a{color:#999999}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#eeeeee}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ffffff}.navbar-default .navbar-toggle .icon-bar{background-color:#999999}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#999999}.navbar-default .navbar-link:hover{color:#333333}.navbar-default .btn-link{color:#999999}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#eeeeee}.navbar-inverse{background-color:#ffffff;border-color:#e6e6e6}.navbar-inverse .navbar-brand{color:#999999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-text{color:#999999}.navbar-inverse .navbar-nav>li>a{color:#999999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#eeeeee;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#333333;background-color:transparent}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e6e6e6}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#333333;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#eeeeee;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#eeeeee}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#eeeeee}.navbar-inverse .navbar-toggle .icon-bar{background-color:#999999}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#ededed}.navbar-inverse .navbar-link{color:#999999}.navbar-inverse .navbar-link:hover{color:#333333}.navbar-inverse .btn-link{color:#999999}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#333333}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#eeeeee}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#fafafa;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#999999;content:">\00a0"}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:7px 12px;margin-left:-1px;line-height:1.42857143;color:#555555;text-decoration:none;background-color:#eeeeee;border:1px solid #e2e2e2}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#555555;background-color:#eeeeee;border-color:#e2e2e2}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;cursor:default;background-color:#158cba;border-color:#127ba3}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee;border-color:#e2e2e2}.pagination-lg>li>a,.pagination-lg>li>span{padding:13px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:5px;border-bottom-left-radius:5px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:5px;border-bottom-right-radius:5px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#eeeeee;border:1px solid #e2e2e2;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;cursor:not-allowed;background-color:#eeeeee}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#158cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#106a8c}.label-success{background-color:#28b62c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1f8c22}.label-info{background-color:#75caeb}.label-info[href]:hover,.label-info[href]:focus{background-color:#48b9e5}.label-warning{background-color:#ff851b}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#e76b00}.label-danger{background-color:#ff4136}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#ff1103}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:normal;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#158cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#158cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:5px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#ffffff;border:1px solid #eeeeee;border-radius:4px;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#158cba}.thumbnail .caption{padding:9px;color:#555555}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#ffffff;background-color:#28b62c;border-color:#24a528}.alert-success hr{border-top-color:#209023}.alert-success .alert-link{color:#e6e6e6}.alert-info{color:#ffffff;background-color:#75caeb;border-color:#40b5e3}.alert-info hr{border-top-color:#29ade0}.alert-info .alert-link{color:#e6e6e6}.alert-warning{color:#ffffff;background-color:#ff851b;border-color:#ff7701}.alert-warning hr{border-top-color:#e76b00}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{color:#ffffff;background-color:#ff4136;border-color:#ff1103}.alert-danger hr{border-top-color:#e90d00}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#fafafa;border-radius:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#158cba;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#28b62c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#75caeb}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#ff851b}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ff4136}.progress-striped .progress-bar-danger{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #eeeeee}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#999999;cursor:not-allowed;background-color:#eeeeee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#158cba;border-color:#158cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a6dff5}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#ffffff;background-color:#28b62c}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#23a127}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#75caeb}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#5fc1e8}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#ff851b}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#ff7701}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ff4136}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ff291c}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid transparent;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #eeeeee}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid transparent}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid transparent}.panel-default{border-color:transparent}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:transparent}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-primary{border-color:transparent}.panel-primary>.panel-heading{color:#ffffff;background-color:#158cba;border-color:transparent}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-primary>.panel-heading .badge{color:#158cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-success{border-color:transparent}.panel-success>.panel-heading{color:#ffffff;background-color:#28b62c;border-color:transparent}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-success>.panel-heading .badge{color:#28b62c;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-info{border-color:transparent}.panel-info>.panel-heading{color:#ffffff;background-color:#75caeb;border-color:transparent}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-info>.panel-heading .badge{color:#75caeb;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-warning{border-color:transparent}.panel-warning>.panel-heading{color:#ffffff;background-color:#ff851b;border-color:transparent}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-warning>.panel-heading .badge{color:#ff851b;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.panel-danger{border-color:transparent}.panel-danger>.panel-heading{color:#ffffff;background-color:#ff4136;border-color:transparent}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:transparent}.panel-danger>.panel-heading .badge{color:#ff4136;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:transparent}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:5px}.well-sm{padding:9px;border-radius:2px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);opacity:0.2}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:0.5}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);transform:translate(0, -25%);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;background-clip:padding-box;border:1px solid #eeeeee;border:1px solid rgba(0,0,0,0.05);border-radius:5px;box-shadow:0 3px 9px rgba(0,0,0,0.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:0.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:0.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#ffffff;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:5px;box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#ffffff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#ffffff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#ffffff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:4px 4px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:0.5}.carousel-control.left{background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:0.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #ffffff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#ffffff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border-width:0 1px 4px 1px}.btn{padding:9px 12px 7px;border-width:0 1px 4px 1px;font-size:12px;font-weight:bold;text-transform:uppercase}.btn:hover{margin-top:1px;border-bottom-width:3px}.btn:active{margin-top:2px;border-bottom-width:2px;box-shadow:none}.btn-lg,.btn-group-lg>.btn{padding:15px 16px 13px;line-height:15px}.btn-sm,.btn-group-sm>.btn{padding:6px 10px 4px}.btn-xs,.btn-group-xs>.btn{padding:3px 5px 1px}.btn-default:hover,.btn-default:focus,.btn-group.open .dropdown-toggle.btn-default{background-color:#eeeeee;border-color:#e2e2e2}.btn-primary:hover,.btn-primary:focus,.btn-group.open .dropdown-toggle.btn-primary{background-color:#158cba;border-color:#127ba3}.btn-success:hover,.btn-success:focus,.btn-group.open .dropdown-toggle.btn-success{background-color:#28b62c;border-color:#23a127}.btn-info:hover,.btn-info:focus,.btn-group.open .dropdown-toggle.btn-info{background-color:#75caeb;border-color:#5fc1e8}.btn-warning:hover,.btn-warning:focus,.btn-group.open .dropdown-toggle.btn-warning{background-color:#ff851b;border-color:#ff7701}.btn-danger:hover,.btn-danger:focus,.btn-group.open .dropdown-toggle.btn-danger{background-color:#ff4136;border-color:#ff291c}.btn-group.open .dropdown-toggle{box-shadow:none}.navbar-btn:hover{margin-top:8px}.navbar-btn:active{margin-top:9px}.navbar-btn.btn-sm:hover{margin-top:11px}.navbar-btn.btn-sm:active{margin-top:12px}.navbar-btn.btn-xs:hover{margin-top:15px}.navbar-btn.btn-xs:active{margin-top:16px}.btn-group-vertical .btn+.btn:hover{border-top-width:1px}.btn-group-vertical .btn+.btn:active{border-top-width:2px}.text-primary,.text-primary:hover{color:#158cba}.text-success,.text-success:hover{color:#28b62c}.text-danger,.text-danger:hover{color:#ff4136}.text-warning,.text-warning:hover{color:#ff851b}.text-info,.text-info:hover{color:#75caeb}table a:not(.btn),.table a:not(.btn){text-decoration:underline}table .dropdown-menu a,.table .dropdown-menu a{text-decoration:none}table .success,.table .success,table .warning,.table .warning,table .danger,.table .danger,table .info,.table .info{color:#fff}table .success a:not(.btn),.table .success a:not(.btn),table .warning a:not(.btn),.table .warning a:not(.btn),table .danger a:not(.btn),.table .danger a:not(.btn),table .info a:not(.btn),.table .info a:not(.btn){color:#fff}table:not(.table-bordered)>thead>tr>th,.table:not(.table-bordered)>thead>tr>th,table:not(.table-bordered)>tbody>tr>th,.table:not(.table-bordered)>tbody>tr>th,table:not(.table-bordered)>tfoot>tr>th,.table:not(.table-bordered)>tfoot>tr>th,table:not(.table-bordered)>thead>tr>td,.table:not(.table-bordered)>thead>tr>td,table:not(.table-bordered)>tbody>tr>td,.table:not(.table-bordered)>tbody>tr>td,table:not(.table-bordered)>tfoot>tr>td,.table:not(.table-bordered)>tfoot>tr>td{border-color:transparent}.form-control{box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}label{font-weight:normal}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#ff851b}.has-warning .form-control,.has-warning .form-control:focus{border:1px solid #ff851b;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-warning .input-group-addon{border:1px solid #ff851b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ff4136}.has-error .form-control,.has-error .form-control:focus{border:1px solid #ff4136;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-error .input-group-addon{border:1px solid #ff4136}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#28b62c}.has-success .form-control,.has-success .form-control:focus{border:1px solid #28b62c;box-shadow:inset 0 2px 0 rgba(0,0,0,0.075)}.has-success .input-group-addon{border:1px solid #28b62c}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{margin-top:6px;border-color:#e7e7e7;color:#333333;transition:all .2s ease-in-out}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus,.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus,.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{padding-bottom:16px;margin-top:0}.nav-tabs .open>a,.nav-tabs .open>a:hover,.nav-tabs .open>a:focus{border-color:#e7e7e7}.nav-tabs>li.disabled>a:hover,.nav-tabs>li.disabled>a:focus{padding-top:10px;padding-bottom:10px;margin-top:6px}.nav-tabs.nav-justified>li{vertical-align:bottom}.dropdown-menu{margin-top:0;border-width:0 1px 4px 1px;border-top-width:1px;box-shadow:none}.breadcrumb{border-color:#ededed;border-style:solid;border-width:0 1px 4px 1px}.pagination>li>a,.pager>li>a,.pagination>li>span,.pager>li>span{position:relative;top:0;border-width:0 1px 4px 1px;color:#555555;font-size:12px;font-weight:bold;text-transform:uppercase}.pagination>li>a:hover,.pager>li>a:hover,.pagination>li>span:hover,.pager>li>span:hover{top:1px;border-bottom-width:3px}.pagination>li>a:active,.pager>li>a:active,.pagination>li>span:active,.pager>li>span:active{top:2px;border-bottom-width:2px}.pagination>.disabled>a:hover,.pager>.disabled>a:hover,.pagination>.disabled>span:hover,.pager>.disabled>span:hover{top:0;border-width:0 1px 4px 1px}.pagination>.disabled>a:active,.pager>.disabled>a:active,.pagination>.disabled>span:active,.pager>.disabled>span:active{top:0;border-width:0 1px 4px 1px}.pager>li>a,.pager>li>span,.pager>.disabled>a,.pager>.disabled>span,.pager>li>a:hover,.pager>li>span:hover,.pager>.disabled>a:hover,.pager>.disabled>span:hover,.pager>li>a:active,.pager>li>span:active,.pager>.disabled>a:active,.pager>.disabled>span:active{border-left-width:2px;border-right-width:2px}.close{color:#fff;text-decoration:none;opacity:0.4}.close:hover,.close:focus{color:#fff;opacity:1}.alert{border-width:0 1px 4px 1px}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{font-weight:normal}.progress{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.1)}.progress-bar{box-shadow:inset 0 -4px 0 rgba(0,0,0,0.15)}.well{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:#eeeeee}a.list-group-item-success.active{background-color:#28b62c}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#23a127}a.list-group-item-warning.active{background-color:#ff851b}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#ff7701}a.list-group-item-danger.active{background-color:#ff4136}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ff291c}.jumbotron{border:1px solid #e7e7e7;box-shadow:inset 0 2px 0 rgba(0,0,0,0.05)}.panel{border:1px solid #e7e7e7;border-width:0 1px 4px 1px}.panel-default .close{color:#555555}.modal .close{color:#555555}.popover{color:#555555} \ No newline at end of file From bb065dbc22769669f1ef8550f1ab37140e417437 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 6 Mar 2019 15:14:25 +0100 Subject: [PATCH 041/439] [Rspamd] Add fuzzy worker with worker-fuzzy.inc --- data/conf/rspamd/override.d/worker-fuzzy.inc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 data/conf/rspamd/override.d/worker-fuzzy.inc diff --git a/data/conf/rspamd/override.d/worker-fuzzy.inc b/data/conf/rspamd/override.d/worker-fuzzy.inc new file mode 100644 index 00000000..09b39c93 --- /dev/null +++ b/data/conf/rspamd/override.d/worker-fuzzy.inc @@ -0,0 +1,12 @@ +# Socket to listen on (UDP and TCP from rspamd 1.3) +bind_socket = "*:11445"; +allow_update = ["127.0.0.1", "::1"]; +# Number of processes to serve this storage (useful for read scaling) +count = 2; +# Backend ("sqlite" or "redis" - default "sqlite") +backend = "redis"; +# Hashes storage time (3 months) +expire = 90d; +# Synchronize updates to the storage each minute +sync = 1min; + From df07c9df012df17ab047ab327a9a1a6cedf46af6 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 00:04:34 +0100 Subject: [PATCH 042/439] [Compose] Update Rspamd and watchdog image --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 53f6aec9..2fb625ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.35 + image: mailcow/rspamd:1.36 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: @@ -347,7 +347,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.36 + image: mailcow/watchdog:1.37 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog From c792bbcbabdc2dcca9eed178a5c3651969e8570c Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 00:05:55 +0100 Subject: [PATCH 043/439] [Rspamd] make upstream an object --- data/conf/rspamd/override.d/worker-proxy.inc | 2 +- data/web/lang/lang.de.php | 10 +++++----- data/web/lang/lang.en.php | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/data/conf/rspamd/override.d/worker-proxy.inc b/data/conf/rspamd/override.d/worker-proxy.inc index 0df926a7..92527f2b 100644 --- a/data/conf/rspamd/override.d/worker-proxy.inc +++ b/data/conf/rspamd/override.d/worker-proxy.inc @@ -1,6 +1,6 @@ bind_socket = "rspamd:9900"; milter = true; -upstream { +upstream "local" { name = "localhost"; default = true; hosts = "rspamd:11333" diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index 77aab3c6..b5fc4642 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -607,11 +607,11 @@ $lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den $lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.'; $lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.
Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.'; -$lang['admin']['transports_hint'] = 'Transport Maps überwiegen senderabhängige Transport Maps und ignorieren die individuellen Einstellungen eines Benutzers bezüglich Verschlüsselungsrichtlinie, da der Absender bei Ermittlung der Transportregel nicht berücksichtigt wird.
- Der Transport erfolgt immer via "smtp:".
- Ein Eintrag in der TLS Policy Map kann eine Verschlüsselung erzwingen.
- Die Authentifizierung wird anhand des Host Parameters ermittelt, hierbei würde bei einem beispielhaften Next Hop "[host]:25" immer zuerst "host" abfragt und erst im Anschluss "[host]:25".
- Dieses Verhalten schließt die gleichzeitige Verwendung von Einträgen der Art "host" sowie "[host]:25" aus.'; +$lang['admin']['transports_hint'] = '→ Transport Maps überwiegen senderabhängige Transport Maps. +→ Transport Maps ignorieren Mailbox-Einstellungen für ausgehende Verschlüsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.
+→ Der Transport erfolgt immer via "smtp:".
+→ Adressen, die mit "/localhost$/" übereinstimmen, werden immer via "local:" transportiert, daher sind sie von einer Zieldefinition "*" ausgeschlossen.
+→ Die Authentifizierung wird anhand des "Next hop" Parameters ermittelt. Hierbei würde bei einem beispielhaften Wert "[host]:25" immer zuerst "host" abfragt und erst im Anschluss "[host]:25". Dieses Verhalten schließt die gleichzeitige Verwendung von Einträgen der Art "host" sowie "[host]:25" aus.'; $lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten unverschlüsselt gespeichert werden.
Angelegte Transporte dieser Art sind senderabhängig und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.
Diese Einstellungen entsprechen demach nicht dem "relayhost" Parameter in Postfix.'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index a8a276e0..0329a92d 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -631,9 +631,11 @@ $lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally $lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).'; $lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.
The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.'; -$lang['admin']['transports_hint'] = 'A transport map entry overrules a sender-dependent transport map.
-Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries. The transport service is always "smtp:".
-To determine credentials for an exemplary next hop "[host]:25", Postfix always queries for "nexthop" before searching for "[nexthop]:25". This behavior makes it impossible to use "nexthop" and "[nexthop]:25" at the same time.'; +$lang['admin']['transports_hint'] = '→ A transport map entry overrules a sender-dependent transport map.
+→ Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries.
+→ The transport service for defined transports is always "smtp:".
+→ Adresses matching "/localhost$/" will always be transported via "local:", therefore a "*" destination will not apply to those addresses.
+→ To determine credentials for an exemplary next hop "[host]:25", Postfix always queries for "host" before searching for "[host]:25". This behavior makes it impossible to use "host" and "[host]:25" at the same time.'; $lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['host'] = 'Host'; From e04e15ed23ef9e5314a0aad9c46555a501b128dd Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 00:07:11 +0100 Subject: [PATCH 044/439] [Rspamd] Mime from and rcpt can now be checked by from_mime and rcpt_mime --- data/conf/rspamd/dynmaps/settings.php | 43 +++++++-------------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 4d78456e..3d42bcf3 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -107,8 +107,8 @@ function ucl_rcpts($object, $type) { settings { watchdog { priority = 10; - rcpt = "/null@localhost/i"; - from = "/watchdog@localhost/i"; + rcpt_mime = "/null@localhost/i"; + from_mime = "/watchdog@localhost/i"; apply "default" { actions { reject = 9999.0; @@ -199,12 +199,13 @@ while ($row = array_shift($rows)) { ?> whitelist_ { prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'whitelist_from'"); $stmt->execute(array(':object' => $row['object'])); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($item = array_shift($list_items)) { + foreach ($list_items as $item) { ?> from = "//i"; { + whitelist_mime_ { prepare("SELECT `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'whitelist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - header = { + from_mime = "//i"; - "From" = "/()/i"; - } - priority = 5; @@ -297,13 +287,13 @@ while ($row = array_shift($rows)) { ?> blacklist_ { prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'blacklist_from'"); $stmt->execute(array(':object' => $row['object'])); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($item = array_shift($list_items)) { + foreach ($list_items as $item) { ?> from = "//i"; { prepare("SELECT `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'blacklist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { ?> - header = { + from_mime = "//i"; - "From" = "/()/i"; - } - priority = 5; From d65f7a2bd4523f73cb4837d281ad12652fb1f3a4 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 00:08:45 +0100 Subject: [PATCH 045/439] [Watchdog] Do not hammer API too much when running Ipv6 NAT check [Watchdog] Run IPv6 NAT check more often (300s sleep instead of 3600s) --- data/Dockerfiles/watchdog/watchdog.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index 77e16b0f..a9db9321 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -358,10 +358,11 @@ ipv6nat_checks() { trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} - IPV6NAT_CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\")) | .id") + CONTAINERS=$(curl --silent --insecure https://dockerapi/containers/json) + IPV6NAT_CONTAINER_ID=$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\")) | .id") if [[ ! -z ${IPV6NAT_CONTAINER_ID} ]]; then - LATEST_STARTED="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" - LATEST_IPV6NAT="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" + LATEST_STARTED="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" + LATEST_IPV6NAT="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" DIFFERENCE_START_TIME=$(expr ${LATEST_IPV6NAT} - ${LATEST_STARTED} 2>/dev/null) if [[ "${DIFFERENCE_START_TIME}" -lt 30 ]]; then err_count=$(( ${err_count} + 1 )) @@ -375,12 +376,13 @@ ipv6nat_checks() { sleep 1 else diff_c=0 - sleep 3600 + sleep 300 fi done return 1 } + rspamd_checks() { err_count=0 diff_c=0 @@ -391,12 +393,11 @@ rspamd_checks() { touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow host_ip=$(get_container_ip rspamd-mailcow) err_c_cur=${err_count} - SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan -d ' -To: null@localhost + SCORE=$(echo 'To: null@localhost From: watchdog@localhost Empty -' | jq -rc .required_score) +' | usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .required_score) if [[ ${SCORE} != "9999" ]]; then echo "Rspamd settings check failed" 2>> /tmp/rspamd-mailcow 1>&2 err_count=$(( ${err_count} + 1)) From 8e42ad4f1f263e861c0ec8e39a50443464ae853d Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 00:09:07 +0100 Subject: [PATCH 046/439] [Rspamd] Use stable unstable :) --- data/Dockerfiles/rspamd/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 86336ea5..4c2094cf 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \ gnupg2 \ apt-transport-https \ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ && apt-get autoremove --purge \ @@ -20,7 +20,6 @@ RUN apt-get update && apt-get install -y \ COPY settings.conf /etc/rspamd/settings.conf COPY docker-entrypoint.sh /docker-entrypoint.sh -COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua ENTRYPOINT ["/docker-entrypoint.sh"] From d124fa1d5ba31235d6dd776cc2ceba452be8326a Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 7 Mar 2019 11:44:38 +0100 Subject: [PATCH 047/439] [Rspamd] Check if filterconf table was changed and return Last-Modified accordingly --- data/conf/rspamd/dynmaps/settings.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 3d42bcf3..6f2f17a2 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -6,6 +6,8 @@ then any of these will trigger the rule. If a rule is triggered then no more rul */ header('Content-Type: text/plain'); require_once "vars.inc.php"; +// Getting headers sent by the client. +$headers = apache_request_headers(); ini_set('error_reporting', 0); @@ -25,6 +27,22 @@ catch (PDOException $e) { exit; } +// Check if db changed and return header +$stmt = $pdo->prepare("SELECT UNIX_TIMESTAMP(UPDATE_TIME) AS `db_update_time` FROM information_schema.tables + WHERE `TABLE_NAME` = 'filterconf' + AND TABLE_SCHEMA = :dbname;"); +$stmt->execute(array( + ':dbname' => $database_name +)); +$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; + +if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $db_update_time)) { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); + exit; +} else { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); +} + function parse_email($email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); From 2443e956eb407b0985fb49cac3cb7c8eff21ea68 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 8 Mar 2019 12:43:05 +0100 Subject: [PATCH 048/439] [Rspamd] Remove buggy last-modified check --- data/conf/rspamd/dynmaps/settings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 6f2f17a2..66c576f2 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -28,7 +28,7 @@ catch (PDOException $e) { } // Check if db changed and return header -$stmt = $pdo->prepare("SELECT UNIX_TIMESTAMP(UPDATE_TIME) AS `db_update_time` FROM information_schema.tables +/*$stmt = $pdo->prepare("SELECT UNIX_TIMESTAMP(UPDATE_TIME) AS `db_update_time` FROM information_schema.tables WHERE `TABLE_NAME` = 'filterconf' AND TABLE_SCHEMA = :dbname;"); $stmt->execute(array( @@ -42,6 +42,7 @@ if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Sin } else { header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); } +*/ function parse_email($email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; From 15970ab8dc0624eeb8ccdfdc7e5ed2b148b2d953 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 9 Mar 2019 11:22:39 +0100 Subject: [PATCH 049/439] [Postfix] Fix sasl_passwd query from alias domain, fixes #2410 [Web] Major fix, added a line break! [Compose] Update Postfix image --- data/Dockerfiles/postfix/postfix.sh | 5 ++--- data/web/admin.php | 1 + docker-compose.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 6ec5cc1d..dd6beca4 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -104,13 +104,12 @@ 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 + OR domain IN ( + SELECT target_domain FROM alias_domain WHERE CONCAT('@', alias_domain) = '%s' ) ) AND active = '1' AND username != ''; -EOF cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf user = ${DBUSER} diff --git a/data/web/admin.php b/data/web/admin.php index 6ca89e97..6826fec5 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -746,6 +746,7 @@ $tfa_data = get_tfa();
+
diff --git a/docker-compose.yml b/docker-compose.yml index 2fb625ac..ba6ee945 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.29 + image: mailcow/postfix:1.30 build: ./data/Dockerfiles/postfix volumes: - ./data/conf/postfix:/opt/postfix/conf From d9b7a20298f8d04b93e33d8381c60679f596040d Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 9 Mar 2019 12:27:46 +0100 Subject: [PATCH 050/439] [postfix] Revert change until fixed --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ba6ee945..2fb625ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.30 + image: mailcow/postfix:1.29 build: ./data/Dockerfiles/postfix volumes: - ./data/conf/postfix:/opt/postfix/conf From b6d9fbf74732b7a797682f97f1201817054d1097 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 9 Mar 2019 12:30:36 +0100 Subject: [PATCH 051/439] [Postfix] Fix Postfix map --- data/Dockerfiles/postfix/postfix.sh | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index dd6beca4..d3e9ed0c 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -110,6 +110,7 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts ) AND active = '1' AND username != ''; +EOF cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf user = ${DBUSER} diff --git a/docker-compose.yml b/docker-compose.yml index 2fb625ac..ba6ee945 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.29 + image: mailcow/postfix:1.30 build: ./data/Dockerfiles/postfix volumes: - ./data/conf/postfix:/opt/postfix/conf From 3c769100aa92fa4a9a6419c4de086ee044a24b94 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 9 Mar 2019 12:31:27 +0100 Subject: [PATCH 052/439] [Compose] Update Postfix image - again --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ba6ee945..d968cf0d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,7 +211,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.30 + image: mailcow/postfix:1.31 build: ./data/Dockerfiles/postfix volumes: - ./data/conf/postfix:/opt/postfix/conf From 184f27701c1d834d8d749ece8492efbccc30fa60 Mon Sep 17 00:00:00 2001 From: Faisal Misle Date: Sat, 9 Mar 2019 16:17:11 -0600 Subject: [PATCH 053/439] Update vars.inc.php Updated SOGo access default explanation --- data/web/inc/vars.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 918a7ba9..795fb3f3 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -141,7 +141,7 @@ $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false; // Force password change on next login (only allows login to mailcow UI) $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; -// Force password change on next login (only allows login to mailcow UI) +// Enable SOGo access (set to false to disable access by default) $MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; // Send notification when quarantine is not empty (never, hourly, daily, weekly) From 47d4be888451042ce498eb2a105b3771fc07299c Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 10 Mar 2019 09:35:26 +0100 Subject: [PATCH 054/439] [Dovecot] v2.3.5 (PH 0.5.5) [Dovecot] Change Solr cronjob to fit dovecot-fts --- data/Dockerfiles/dovecot/Dockerfile | 4 ++-- data/Dockerfiles/dovecot/docker-entrypoint.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 1320df1f..bc4fca5c 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.3.4 -ENV PIGEONHOLE_VERSION 0.5.4 +ENV DOVECOT_VERSION 2.3.5 +ENV PIGEONHOLE_VERSION 0.5.5 RUN apt-get update && apt-get -y --no-install-recommends install \ automake \ diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 0589579d..80db4b48 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -174,7 +174,7 @@ echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/d echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules -echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize +echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify # Fix more than 1 hardlink issue From 73f836f83e3a6b81ead6b6458fe0ad9d5ec122fd Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 10 Mar 2019 09:36:19 +0100 Subject: [PATCH 055/439] [Compose Update Rspamd, Dovecot and Solr images --- docker-compose.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d968cf0d..8c1d785f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.36 + image: mailcow/rspamd:1.37 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: @@ -164,7 +164,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.63 + image: mailcow/dovecot:1.64 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE @@ -395,11 +395,12 @@ services: - dockerapi solr-mailcow: - image: mailcow/solr:1.2 + image: mailcow/solr:1.3 build: ./data/Dockerfiles/solr restart: always volumes: - solr-vol-1:/opt/solr/server/solr/dovecot/data + - ./data/conf/solr:/etc/solr dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: From c7c115d63a02a7142838a37831d2defaeee7be59 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 10 Mar 2019 09:36:33 +0100 Subject: [PATCH 056/439] [Solr] Use fixed, recommended schema but add EdgeNGramFilterFactory --- data/Dockerfiles/solr/Dockerfile | 5 +- data/Dockerfiles/solr/docker-entrypoint.sh | 390 ++------------------- data/conf/solr/solr-config-7.7.0.xml | 289 +++++++++++++++ data/conf/solr/solr-schema-7.7.0.xml | 50 +++ data/web/inc/functions.inc.php | 4 +- 5 files changed, 366 insertions(+), 372 deletions(-) create mode 100644 data/conf/solr/solr-config-7.7.0.xml create mode 100644 data/conf/solr/solr-schema-7.7.0.xml diff --git a/data/Dockerfiles/solr/Dockerfile b/data/Dockerfiles/solr/Dockerfile index 67cd3384..25235664 100644 --- a/data/Dockerfiles/solr/Dockerfile +++ b/data/Dockerfiles/solr/Dockerfile @@ -1,9 +1,8 @@ -FROM solr:7-alpine +FROM solr:7.7-alpine USER root COPY docker-entrypoint.sh / RUN apk --no-cache add su-exec curl tzdata \ - && chmod +x /docker-entrypoint.sh \ - && /docker-entrypoint.sh --bootstrap + && chmod +x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/solr/docker-entrypoint.sh b/data/Dockerfiles/solr/docker-entrypoint.sh index 108f8b5a..0634874f 100755 --- a/data/Dockerfiles/solr/docker-entrypoint.sh +++ b/data/Dockerfiles/solr/docker-entrypoint.sh @@ -26,395 +26,51 @@ fi # run the optional initdb . /opt/docker-solr/scripts/run-initdb -function solr_config() { - curl -XPOST http://localhost:8983/solr/dovecot/schema -H 'Content-type:application/json' -d '{ - "add-field-type":{ - "name":"long", - "class":"solr.TrieLongField" - }, - "add-field-type":{ - "name":"dovecot_text", - "class":"solr.TextField", - "autoGeneratePhraseQueries":true, - "positionIncrementGap":100, - "indexAnalyser":{ - "charFilter":{ - "class":"solr.MappingCharFilterFactory", - "mapping":"mapping-FoldToASCII.txt" - }, - "charFilter":{ - "class":"solr.MappingCharFilterFactory", - "mapping":"mapping-ISOLatin1Accent.txt" - }, - "charFilter":{ - "class":"solr.HTMLStripCharFilterFactory" - }, - "tokenizer":{ - "class":"solr.StandardTokenizerFactory" - }, - "filter":{ - "class":"solr.StopFilterFactory", - "words":"stopwords.txt", - "ignoreCase":true - }, - "filter":{ - "class":"solr.WordDelimiterGraphFilterFactory", - "generateWordParts":1, - "generateNumberParts":1, - "splitOnCaseChange":1, - "splitOnNumerics":1, - "catenateWords":1, - "catenateNumbers":1, - "catenateAll":1 - }, - "filter":{ - "class":"solr.FlattenGraphFilterFactory" - }, - "filter":{ - "class":"solr.LowerCaseFilterFactory" - }, - "filter":{ - "class":"solr.KeywordMarkerFilterFactory", - "protected":"protwords.txt" - }, - "filter":{ - "class":"solr.PorterStemFilterFactory" - } - }, - "queryAnalyzer":{ - "tokenizer":{ - "class":"solr.StandardTokenizerFactory" - }, - "filter":{ - "class":"solr.SynonymGraphFilterFactory", - "expand":true, - "ignoreCase":true, - "synonyms":synonyms.txt - }, - "filter":{ - "class":"solr.FlattenGraphFilterFactory" - }, - "filter":{ - "class":"solr.StopFilterFactory", - "words":"stopwords.txt", - "ignoreCase":true - }, - "filter":{ - "class":"solr.WordDelimiterGraphFilterFactory", - "generateWordParts":1, - "generateNumberParts":1, - "splitOnCaseChange":1, - "splitOnNumerics":1, - "catenateWords":1, - "catenateNumbers":1, - "catenateAll":1 - }, - "filter":{ - "class":"solr.LowerCaseFilterFactory" - }, - "filter":{ - "class":"solr.KeywordMarkerFilterFactory", - "protected":"protwords.txt" - }, - "filter":{ - "class":"solr.PorterStemFilterFactory" - } - } - }, - "add-field":{ - "name":"uid", - "type":"long", - "indexed":true, - "stored":true, - "required":true - }, - "add-field":{ - "name":"box", - "type":"string", - "indexed":true, - "stored":true, - "required":true - }, - "add-field":{ - "name":"user", - "type":"string", - "indexed":true, - "stored":true, - "required":true - }, - "add-field":{ - "name":"hdr", - "type":"dovecot_text", - "indexed":true, - "stored":false - - }, - "add-field":{ - "name":"body", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "add-field":{ - "name":"from", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "add-field":{ - "name":"to", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "add-field":{ - "name":"cc", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "add-field":{ - "name":"bcc", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "add-field":{ - "name":"subject", - "type":"dovecot_text", - "indexed":true, - "stored":false - } - }' - - curl -XPOST http://localhost:8983/solr/dovecot/schema -H 'Content-type:application/json' -d '{ - "replace-field-type":{ - "name":"long", - "class":"solr.TrieLongField" - }, - "replace-field-type":{ - "name":"dovecot_text", - "class":"solr.TextField", - "autoGeneratePhraseQueries":true, - "positionIncrementGap":100, - "indexAnalyser":{ - "charFilter":{ - "class":"solr.MappingCharFilterFactory", - "mapping":"mapping-FoldToASCII.txt" - }, - "charFilter":{ - "class":"solr.MappingCharFilterFactory", - "mapping":"mapping-ISOLatin1Accent.txt" - }, - "charFilter":{ - "class":"solr.HTMLStripCharFilterFactory" - }, - "tokenizer":{ - "class":"solr.StandardTokenizerFactory" - }, - "filter":{ - "class":"solr.StopFilterFactory", - "words":"stopwords.txt", - "ignoreCase":true - }, - "filter":{ - "class":"solr.WordDelimiterGraphFilterFactory", - "generateWordParts":1, - "generateNumberParts":1, - "splitOnCaseChange":1, - "splitOnNumerics":1, - "catenateWords":1, - "catenateNumbers":1, - "catenateAll":1 - }, - "filter":{ - "class":"solr.FlattenGraphFilterFactory" - }, - "filter":{ - "class":"solr.LowerCaseFilterFactory" - }, - "filter":{ - "class":"solr.KeywordMarkerFilterFactory", - "protected":"protwords.txt" - }, - "filter":{ - "class":"solr.PorterStemFilterFactory" - } - }, - "queryAnalyzer":{ - "tokenizer":{ - "class":"solr.StandardTokenizerFactory" - }, - "filter":{ - "class":"solr.SynonymGraphFilterFactory", - "expand":true, - "ignoreCase":true, - "synonyms":synonyms.txt - }, - "filter":{ - "class":"solr.FlattenGraphFilterFactory" - }, - "filter":{ - "class":"solr.StopFilterFactory", - "words":"stopwords.txt", - "ignoreCase":true - }, - "filter":{ - "class":"solr.WordDelimiterGraphFilterFactory", - "generateWordParts":1, - "generateNumberParts":1, - "splitOnCaseChange":1, - "splitOnNumerics":1, - "catenateWords":1, - "catenateNumbers":1, - "catenateAll":1 - }, - "filter":{ - "class":"solr.LowerCaseFilterFactory" - }, - "filter":{ - "class":"solr.KeywordMarkerFilterFactory", - "protected":"protwords.txt" - }, - "filter":{ - "class":"solr.PorterStemFilterFactory" - } - } - }, - "replace-field":{ - "name":"uid", - "type":"long", - "indexed":true, - "stored":true, - "required":true - }, - "replace-field":{ - "name":"box", - "type":"string", - "indexed":true, - "stored":true, - "required":true - }, - "replace-field":{ - "name":"user", - "type":"string", - "indexed":true, - "stored":true, - "required":true - }, - "replace-field":{ - "name":"hdr", - "type":"dovecot_text", - "indexed":true, - "stored":false - - }, - "replace-field":{ - "name":"body", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "replace-field":{ - "name":"from", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "replace-field":{ - "name":"to", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "replace-field":{ - "name":"cc", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "replace-field":{ - "name":"bcc", - "type":"dovecot_text", - "indexed":true, - "stored":false - }, - "replace-field":{ - "name":"subject", - "type":"dovecot_text", - "indexed":true, - "stored":false - } - }' - - curl -XPOST http://localhost:8983/solr/dovecot/config -H 'Content-type:application/json' -d '{ - "update-requesthandler":{ - "name":"/select", - "class":"solr.SearchHandler", - "defaults":{ - "wt":"xml" - } - } - }' - - curl -XPOST http://localhost:8983/solr/dovecot/config/updateHandler -d '{ - "set-property": { - "updateHandler.autoSoftCommit.maxDocs":500, - "updateHandler.autoSoftCommit.maxTime":120000, - "updateHandler.autoCommit.maxDocs":200, - "updateHandler.autoCommit.maxTime":1800000, - "updateHandler.autoCommit.openSearcher":false - } - }' -} - # fixing volume permission -[[ -d /opt/solr/server/solr/dovecot/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot/data +[[ -d /opt/solr/server/solr/dovecot-fts/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot-fts/data if [[ "${1}" != "--bootstrap" ]]; then sed -i '/SOLR_HEAP=/c\SOLR_HEAP="'${SOLR_HEAP:-1024}'m"' /opt/solr/bin/solr.in.sh else sed -i '/SOLR_HEAP=/c\SOLR_HEAP="256m"' /opt/solr/bin/solr.in.sh fi -# start a Solr so we can use the Schema API, but only on localhost, -# so that clients don't see Solr until we have configured it. - -echo "Starting local Solr instance to setup configuration" -su-exec solr start-local-solr - # keep a sentinel file so we don't try to create the core a second time # for example when we restart a container. +# todo: check if a core exists without sentinel file -SENTINEL=/opt/docker-solr/core_created +SENTINEL=/opt/docker-solr/fts_core_created if [[ -f ${SENTINEL} ]]; then echo "skipping core creation" else - echo "Creating core \"dovecot\"" - su-exec solr /opt/solr/bin/solr create -c "dovecot" + echo "Starting local Solr instance to setup configuration" + su-exec solr start-local-solr + + echo "Creating core \"dovecot-fts\"" + su-exec solr /opt/solr/bin/solr create -c "dovecot-fts" # See https://github.com/docker-solr/docker-solr/issues/27 echo "Checking core" while ! wget -O - 'http://localhost:8983/solr/admin/cores?action=STATUS' | grep -q instanceDir; do echo "Could not find any cores, waiting..." - sleep 5 + sleep 3 done - echo "Created core \"dovecot\"" + + echo "Created core \"dovecot-fts\"" touch ${SENTINEL} + + echo "Stopping local Solr" + su-exec solr stop-local-solr fi -echo "Starting configuration" -while ! wget -O - 'http://localhost:8983/solr/admin/cores?action=STATUS' | grep -q instanceDir; do - echo "Waiting for Solr..." - sleep 5 -done -solr_config -echo "Stopping local Solr" -su-exec solr stop-local-solr +rm -f /opt/solr/server/solr/dovecot-fts/conf/schema.xml +rm -f /opt/solr/server/solr/dovecot-fts/conf/managed-schema +rm -f /opt/solr/server/solr/dovecot-fts/conf/solrconfig.xml -if [[ "${1}" == "--bootstrap" ]]; then - exit 0 -else - exec su-exec solr solr-foreground -fi +cp /etc/solr/solr-config-7.7.0.xml /opt/solr/server/solr/dovecot-fts/conf/solrconfig.xml +cp /etc/solr/solr-schema-7.7.0.xml /opt/solr/server/solr/dovecot-fts/conf/schema.xml + +chown -R solr:solr /opt/solr/server/solr/dovecot-fts/conf/{schema.xml,solrconfig.xml} + +exec su-exec solr solr-foreground diff --git a/data/conf/solr/solr-config-7.7.0.xml b/data/conf/solr/solr-config-7.7.0.xml new file mode 100644 index 00000000..3661874d --- /dev/null +++ b/data/conf/solr/solr-config-7.7.0.xml @@ -0,0 +1,289 @@ + + + + + + + 7.7.0 + + + + + + + + + + + + + + + + ${solr.data.dir:} + + + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + + + ${solr.autoCommit.maxTime:15000} + false + + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + + + + + + + + + + + + + + + + + + + + true + + + 20 + + + 200 + + + false + + + + + + + + + + + + + + + explicit + 10 + + + + + + _text_ + + + + + + diff --git a/data/conf/solr/solr-schema-7.7.0.xml b/data/conf/solr/solr-schema-7.7.0.xml new file mode 100644 index 00000000..a41cbb47 --- /dev/null +++ b/data/conf/solr/solr-schema-7.7.0.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 34872aef..c869c122 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1477,7 +1477,7 @@ function solr_status() { $endpoint = 'http://solr:8983/solr/admin/cores'; $params = array( 'action' => 'STATUS', - 'core' => 'dovecot', + 'core' => 'dovecot-fts', 'indexInfo' => 'true' ); $url = $endpoint . '?' . http_build_query($params); @@ -1494,7 +1494,7 @@ function solr_status() { else { curl_close($curl); $status = json_decode($response, true); - return (!empty($status['status']['dovecot'])) ? $status['status']['dovecot'] : false; + return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false; } return false; } From 0a1e71f7ecdcaf6a27e054ca3580edcf372173e5 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 10 Mar 2019 09:40:31 +0100 Subject: [PATCH 057/439] [Dovecot] Use dovecot-fts core --- 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 d693d39c..b81604a2 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -308,7 +308,7 @@ plugin { acl = vfile fts = solr fts_autoindex = yes - fts_solr = url=http://solr:8983/solr/dovecot/ + fts_solr = url=http://solr:8983/solr/dovecot-fts/ quota = dict:Userquota::proxy::sqlquota quota_rule2 = Trash:storage=+100%% sieve = /var/vmail/sieve/%u.sieve From 5754c6e2aa5251f09e3d95641daf0d76a4af4002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 10 Mar 2019 09:52:31 +0100 Subject: [PATCH 058/439] Update docker-compose.yml --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3e2b6e1d..d67f603b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -239,6 +239,7 @@ services: ports: - "${SMTP_PORT:-25}:25" - "${SMTPS_PORT:-465}:465" + - "${SUBMISSION_PORT:-587}:587" restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 From 78b2bde9423cbb0cb938ca6590624568007de253 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 10 Mar 2019 10:20:49 +0100 Subject: [PATCH 059/439] [Web] Change core to dovecot-fts --- 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 753e1702..82517492 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3525,7 +3525,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } if (strtolower(getenv('SKIP_SOLR')) == 'n') { $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot/update?commit=true'); + curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true'); curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 1); From 4bbb6d78e3db78eca6dd2b4ce154a1b57e4c14a4 Mon Sep 17 00:00:00 2001 From: Robert Christian Date: Sun, 10 Mar 2019 17:20:46 +0100 Subject: [PATCH 060/439] fix solr query ngram --- data/conf/solr/solr-schema-7.7.0.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/data/conf/solr/solr-schema-7.7.0.xml b/data/conf/solr/solr-schema-7.7.0.xml index a41cbb47..f66d1eda 100644 --- a/data/conf/solr/solr-schema-7.7.0.xml +++ b/data/conf/solr/solr-schema-7.7.0.xml @@ -18,7 +18,6 @@ - From 680ddec6874001d78b77784e1f466e72a9e6f5a9 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 11 Mar 2019 08:56:46 +0100 Subject: [PATCH 061/439] [Helper] Do not delete updater --- helper-scripts/nextcloud.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index d04f52d4..e5dab400 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -76,7 +76,7 @@ elif [[ ${NC_UPDATE} == "y" ]]; then curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && rm nextcloud.tar.bz2 \ - && rm -rf ./data/web/nextcloud/updater \ + #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ && mkdir -p ./data/web/nextcloud/custom_apps \ && chmod +x ./data/web/nextcloud/occ @@ -106,7 +106,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && rm nextcloud.tar.bz2 \ - && rm -rf ./data/web/nextcloud/updater \ + #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ && mkdir -p ./data/web/nextcloud/custom_apps \ && chmod +x ./data/web/nextcloud/occ From 40a826a3471db61349eb64f58dd35bdddd0ae19e Mon Sep 17 00:00:00 2001 From: Aaron Larisch Date: Mon, 11 Mar 2019 15:29:30 +0100 Subject: [PATCH 062/439] Fix rejected mails not being quarantized properly if they are tagged --- data/conf/rspamd/meta_exporter/pipe.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 3e29d207..692a0c2e 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -84,6 +84,9 @@ $rcpt_final_mailboxes = array(); // Loop through all rcpts foreach (json_decode($rcpts, true) as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + // Break rcpt into local part and domain part $parsed_rcpt = parse_email($rcpt); From 1c3daedc39051097eb46b64d76d375c879777bf9 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 01:28:04 +0100 Subject: [PATCH 063/439] [Rspamd] Remove headers var from dyn maps --- data/conf/rspamd/dynmaps/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 66c576f2..a2be8ceb 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -7,7 +7,7 @@ then any of these will trigger the rule. If a rule is triggered then no more rul header('Content-Type: text/plain'); require_once "vars.inc.php"; // Getting headers sent by the client. -$headers = apache_request_headers(); +//$headers = apache_request_headers(); ini_set('error_reporting', 0); From 884c7fade1f9991692d3b77b032928c9ec243b2b Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 02:49:31 +0100 Subject: [PATCH 064/439] [Helper] Remove custom_apps from NC --- helper-scripts/nextcloud.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index e5dab400..2ddac47a 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -78,7 +78,6 @@ elif [[ ${NC_UPDATE} == "y" ]]; then && rm nextcloud.tar.bz2 \ #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ - && mkdir -p ./data/web/nextcloud/custom_apps \ && chmod +x ./data/web/nextcloud/occ docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade" @@ -108,10 +107,9 @@ elif [[ ${NC_INSTALL} == "y" ]]; then && rm nextcloud.tar.bz2 \ #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ - && mkdir -p ./data/web/nextcloud/custom_apps \ && chmod +x ./data/web/nextcloud/occ - docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps /web/nextcloud/custom_apps" + docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \ --database mysql \ --database-host mysql \ From 8f6c24e60abc32323695aac0fa10afdb9c9323bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 12 Mar 2019 17:21:45 +0100 Subject: [PATCH 065/439] Update update.sh --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index fe5aafe0..2d528e64 100755 --- a/update.sh +++ b/update.sh @@ -66,7 +66,7 @@ while (($#)); do if [ $? -ne 0 ]; then echo "A problem occurred while trying to fetch the latest revision from github." exit 99 - fi + fi if [[ -z $(git log HEAD --pretty=format:"%H" | grep "${LATEST_REV}") ]]; then echo "Updated code is available." exit 0 From 9d5758362405015b22f5a69df60af9883544af56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristia=CC=81n=20Feldsam?= Date: Tue, 12 Mar 2019 21:54:31 +0100 Subject: [PATCH 066/439] Quarantine - Enhanced JS + Show btn fix event binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristián Feldsam --- data/web/js/site/quarantine.js | 86 +++++++++++++++++----------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js index 090b5054..4df1dbd4 100644 --- a/data/web/js/site/quarantine.js +++ b/data/web/js/site/quarantine.js @@ -1,11 +1,13 @@ // 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>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>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;e127&&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;e191&&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($){ acl_data = JSON.parse(acl); // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; 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' + value[0] + ' (' + value[1] + ')' + - ' - ' + lang.check_hash + '

' - ); - }); - } - else { - $( "#qid_detail_atts" ).text('-'); - } + $('body').on('click', '.show_qid_info', function (e) { + e.preventDefault(); + var qitem = $(this).data('item'); + var qError = $("#qid_error"); + + $('#qidDetailModal').modal('show'); + qError.hide(); + + $.ajax({ + url: '/inc/ajax/qitem_details.php', + data: { id: qitem }, + dataType: 'json', + success: function(data){ + if (typeof data.error !== 'undefined') { + qError.text(data.error); + qError.show(); } - }); - }) - } + $('[data-id="qitems_single"]').each(function(index) { + $(this).attr("data-item", qitem); + }); + + $('#qid_detail_subj').text(data.subject); + $('#qid_detail_text').text(data.text_plain); + $('#qid_detail_text_from_html').text(data.text_html); + + if (typeof data.attachments !== 'undefined') { + qAtts = $("#qid_detail_atts"); + qAtts.text(''); + $.each(data.attachments, function(index, value) { + qAtts.append( + '

' + value[0] + ' (' + value[1] + ')' + + ' - ' + lang.check_hash + '

' + ); + }); + } + else { + qAtts.text('-'); + } + } + }); + }); + // Initial table drawings draw_quarantine_table(); }); From fc63661fbd9261b766f995b3e04904af18184ee3 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:15:26 +0100 Subject: [PATCH 067/439] [Solr] Change default configset before bootstrapping [Solr] Bootstrap cannot be omitted and must occur before mounting the data directory --- data/Dockerfiles/solr/Dockerfile | 6 +++- data/Dockerfiles/solr/docker-entrypoint.sh | 33 +++++-------------- .../solr/solr-config-7.7.0.xml | 0 .../solr/solr-schema-7.7.0.xml | 3 +- 4 files changed, 16 insertions(+), 26 deletions(-) mode change 100755 => 100644 data/Dockerfiles/solr/docker-entrypoint.sh rename data/{conf => Dockerfiles}/solr/solr-config-7.7.0.xml (100%) rename data/{conf => Dockerfiles}/solr/solr-schema-7.7.0.xml (95%) diff --git a/data/Dockerfiles/solr/Dockerfile b/data/Dockerfiles/solr/Dockerfile index 25235664..1c74fde8 100644 --- a/data/Dockerfiles/solr/Dockerfile +++ b/data/Dockerfiles/solr/Dockerfile @@ -1,8 +1,12 @@ FROM solr:7.7-alpine USER root COPY docker-entrypoint.sh / +COPY solr-config-7.7.0.xml / +COPY solr-schema-7.7.0.xml / + RUN apk --no-cache add su-exec curl tzdata \ - && chmod +x /docker-entrypoint.sh + && chmod +x /docker-entrypoint.sh \ + && bash /docker-entrypoint.sh --bootstrap ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/solr/docker-entrypoint.sh b/data/Dockerfiles/solr/docker-entrypoint.sh old mode 100755 new mode 100644 index 0634874f..5a33620d --- a/data/Dockerfiles/solr/docker-entrypoint.sh +++ b/data/Dockerfiles/solr/docker-entrypoint.sh @@ -18,16 +18,10 @@ fi set -e -# allow easier debugging with `docker run -e VERBOSE=yes` -if [[ "$VERBOSE" = "yes" ]]; then - set -x -fi - # run the optional initdb . /opt/docker-solr/scripts/run-initdb # fixing volume permission - [[ -d /opt/solr/server/solr/dovecot-fts/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot-fts/data if [[ "${1}" != "--bootstrap" ]]; then sed -i '/SOLR_HEAP=/c\SOLR_HEAP="'${SOLR_HEAP:-1024}'m"' /opt/solr/bin/solr.in.sh @@ -35,15 +29,13 @@ else sed -i '/SOLR_HEAP=/c\SOLR_HEAP="256m"' /opt/solr/bin/solr.in.sh fi -# keep a sentinel file so we don't try to create the core a second time -# for example when we restart a container. -# todo: check if a core exists without sentinel file +if [[ "${1}" == "--bootstrap" ]]; then + echo "Creating initial configuration" + echo "Modifying default config set" + cp /solr-config-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/solrconfig.xml + cp /solr-schema-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/schema.xml + rm /opt/solr/server/solr/configsets/_default/conf/managed-schema -SENTINEL=/opt/docker-solr/fts_core_created - -if [[ -f ${SENTINEL} ]]; then - echo "skipping core creation" -else echo "Starting local Solr instance to setup configuration" su-exec solr start-local-solr @@ -58,19 +50,12 @@ else done echo "Created core \"dovecot-fts\"" - touch ${SENTINEL} echo "Stopping local Solr" su-exec solr stop-local-solr + + exit 0 fi -rm -f /opt/solr/server/solr/dovecot-fts/conf/schema.xml -rm -f /opt/solr/server/solr/dovecot-fts/conf/managed-schema -rm -f /opt/solr/server/solr/dovecot-fts/conf/solrconfig.xml - -cp /etc/solr/solr-config-7.7.0.xml /opt/solr/server/solr/dovecot-fts/conf/solrconfig.xml -cp /etc/solr/solr-schema-7.7.0.xml /opt/solr/server/solr/dovecot-fts/conf/schema.xml - -chown -R solr:solr /opt/solr/server/solr/dovecot-fts/conf/{schema.xml,solrconfig.xml} - exec su-exec solr solr-foreground + diff --git a/data/conf/solr/solr-config-7.7.0.xml b/data/Dockerfiles/solr/solr-config-7.7.0.xml similarity index 100% rename from data/conf/solr/solr-config-7.7.0.xml rename to data/Dockerfiles/solr/solr-config-7.7.0.xml diff --git a/data/conf/solr/solr-schema-7.7.0.xml b/data/Dockerfiles/solr/solr-schema-7.7.0.xml similarity index 95% rename from data/conf/solr/solr-schema-7.7.0.xml rename to data/Dockerfiles/solr/solr-schema-7.7.0.xml index f66d1eda..31176bd7 100644 --- a/data/conf/solr/solr-schema-7.7.0.xml +++ b/data/Dockerfiles/solr/solr-schema-7.7.0.xml @@ -1,6 +1,6 @@ - + @@ -18,6 +18,7 @@
+ From 837ee3b3b08a6046c7511e022015ac841f809798 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:20:10 +0100 Subject: [PATCH 068/439] [Solr] Keep EdgeNGramFilterFactory out of query [Compose] Update PHP-FPM, Solr and ACME images --- data/Dockerfiles/solr/solr-schema-7.7.0.xml | 1 - docker-compose.yml | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/Dockerfiles/solr/solr-schema-7.7.0.xml b/data/Dockerfiles/solr/solr-schema-7.7.0.xml index 31176bd7..2c2e6343 100644 --- a/data/Dockerfiles/solr/solr-schema-7.7.0.xml +++ b/data/Dockerfiles/solr/solr-schema-7.7.0.xml @@ -18,7 +18,6 @@ - diff --git a/docker-compose.yml b/docker-compose.yml index 8c1d785f..e15c0cca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,7 +94,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.34 + image: mailcow/phpfpm:1.35 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: @@ -296,7 +296,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.48 + image: mailcow/acme:1.49 build: ./data/Dockerfiles/acme dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -395,12 +395,11 @@ services: - dockerapi solr-mailcow: - image: mailcow/solr:1.3 + image: mailcow/solr:1.4 build: ./data/Dockerfiles/solr restart: always volumes: - - solr-vol-1:/opt/solr/server/solr/dovecot/data - - ./data/conf/solr:/etc/solr + - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: From 5b8a983be2a302fda6a7b8f9b27fe0e8919e9353 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:23:38 +0100 Subject: [PATCH 069/439] [Update, Config] Set mode 600 for mailcow.conf --- generate_config.sh | 3 +++ update.sh | 1 + 2 files changed, 4 insertions(+) diff --git a/generate_config.sh b/generate_config.sh index a882ec08..8cde0ffd 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -16,6 +16,7 @@ if [ -f mailcow.conf ]; then case $response in [yY][eE][sS]|[yY]) mv mailcow.conf mailcow.conf_backup + chmod 600 mailcow.conf_backup ;; *) exit 1 @@ -237,5 +238,7 @@ EOF mkdir -p data/assets/ssl +chmod 600 mailcow.conf + # copy but don't overwrite existing certificate cp -n data/assets/ssl-example/*.pem data/assets/ssl/ diff --git a/update.sh b/update.sh index 4fc668fb..05e54928 100755 --- a/update.sh +++ b/update.sh @@ -101,6 +101,7 @@ while (($#)); do done [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;} +chmod 600 mailcow.conf source mailcow.conf DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 2 ]; then From c77368ee705db354bfd1b291c3b0f12e88d8033f Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:24:03 +0100 Subject: [PATCH 070/439] [ACME] Set mode 600 for key files --- data/Dockerfiles/acme/docker-entrypoint.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index bb9a5a53..c8501168 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -42,7 +42,6 @@ mkdir -p ${ACME_BASE}/acme [[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem [[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem - reload_configurations(){ # Reading container IDs # Wrapping as array to ensure trimmed content when calling $NGINX etc. @@ -156,6 +155,7 @@ else exec env TRIGGER_RESTART=1 $(readlink -f "$0") fi fi +chmod 600 ${ACME_BASE}/key.pem log_f "Waiting for database... " no_nl while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do @@ -196,6 +196,9 @@ while true; do log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" fi + chmod 600 ${ACME_BASE}/acme/key.pem + chmod 600 ${ACME_BASE}/acme/account.pem + # Skipping IP check when we like to live dangerously if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then SKIP_IP_CHECK=y From ffed14c277a2edac9623a8defd4a036cf5ce542a Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:24:22 +0100 Subject: [PATCH 071/439] [PHP-FPM] Fix SQL upgrade script --- data/Dockerfiles/phpfpm/docker-entrypoint.sh | 35 +++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 76c4035e..bf055f3a 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -25,23 +25,26 @@ CONTAINER_ID= # Todo: Better check if upgrade failed # This can happen due to a broken sogo_view [ -s /mysql_upgrade_loop ] && SQL_LOOP_C=$(cat /mysql_upgrade_loop) -CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | .id") -if [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ [^a-zA-Z0-9] ]]; then - SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type) - if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then - if [ -z ${SQL_LOOP_C} ]; then - echo 1 > /mysql_upgrade_loop - echo "MySQL applied an upgrade, restarting PHP-FPM..." - exit 1 - else - rm /mysql_upgrade_loop - echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually." - while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do - echo "Waiting for SQL to return..." - sleep 2 - done - fi +until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do + CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | .id" 2> /dev/null) +done +echo "MySQL @ ${CONTAINER_ID}" +SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type) +if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then + if [ -z ${SQL_LOOP_C} ]; then + echo 1 > /mysql_upgrade_loop + echo "MySQL applied an upgrade, restarting PHP-FPM..." + exit 1 + else + rm /mysql_upgrade_loop + echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually." + while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do + echo "Waiting for SQL to return..." + sleep 2 + done fi +else + echo "MySQL is up-to-date" fi # Trigger db init From 9482da211fb97ee21b86fb6a08ba8eaa366515f3 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 12 Mar 2019 23:39:28 +0100 Subject: [PATCH 072/439] [Rspamd] Update to 1.9 stable repository [Compose] Update Rspamd image --- data/Dockerfiles/rspamd/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 4c2094cf..87d92139 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y \ gnupg2 \ apt-transport-https \ && apt-key adv --fetch-keys https://rspamd.com/apt/gpg.key \ - && echo "deb https://rspamd.com/apt/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update && apt-get install -y rspamd \ && rm -rf /var/lib/apt/lists/* \ && apt-get autoremove --purge \ diff --git a/docker-compose.yml b/docker-compose.yml index e15c0cca..49c784eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.37 + image: mailcow/rspamd:1.38 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From d8dbcfac928825bcaad4325b898c0c32d1bf05c2 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 14 Mar 2019 01:46:15 +0100 Subject: [PATCH 073/439] [Web] Continue when a check in add_alias fails [Web] Fix "null" output in mailbox table when comments are missing [Update] Remove obsolete check/replace command --- data/web/inc/functions.mailbox.inc.php | 10 +++++----- data/web/js/site/mailbox.js | 14 ++++++++++++-- update.sh | 3 +-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 82517492..d479f125 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -561,7 +561,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); @@ -573,7 +573,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('domain_not_found', htmlspecialchars($domain)) ); - return false; + continue; } $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :address"); @@ -585,7 +585,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => array('is_spam_alias', htmlspecialchars($address)) ); - return false; + continue; } if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { $_SESSION['return'][] = array( @@ -593,7 +593,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'alias_invalid' ); - return false; + continue; } if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'][] = array( @@ -601,7 +601,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'msg' => 'access_denied' ); - return false; + continue; } $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `active`) VALUES (:address, :public_comment, :private_comment, :goto, :domain, :active)"); diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 00a815e6..674d6703 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -731,8 +731,18 @@ jQuery(function($){ '
'; item.chkbox = ''; item.goto = escapeHtml(item.goto.replace(/,/g, " ")); - item.public_comment = escapeHtml(item.public_comment); - item.private_comment = escapeHtml(item.private_comment); + if (item.public_comment !== null) { + item.public_comment = escapeHtml(item.public_comment); + } + else { + item.public_comment = '-'; + } + if (item.private_comment !== null) { + item.private_comment = escapeHtml(item.private_comment); + } + else { + item.private_comment = '-'; + } if (item.is_catch_all == 1) { item.address = '
Catch-All
' + escapeHtml(item.address); } diff --git a/update.sh b/update.sh index fd5d4f37..be6ef9d7 100755 --- a/update.sh +++ b/update.sh @@ -359,9 +359,8 @@ if grep -q 'SYSCTL_IPV6_DISABLED=1' mailcow.conf; then read -p "Press any key to continue..." < /dev/tty fi -echo -e "Fixing project name... " +# Checking for old project name bug sed -i 's#COMPOSEPROJECT_NAME#COMPOSE_PROJECT_NAME#g' mailcow.conf -sed -i '/COMPOSE_PROJECT_NAME=/s/-//g' mailcow.conf echo -e "Fixing PHP-FPM worker ports for Nginx sites..." sed -i 's#phpfpm:9000#phpfpm:9002#g' data/conf/nginx/*.conf From a614d646154aa783b99230e35807838fc503b38d Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 14 Mar 2019 08:59:24 +0100 Subject: [PATCH 074/439] [SOGo] Adjust sync parameters, revert if you run into problems! --- data/conf/sogo/sogo.conf | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index aa1a86ec..b0ebf698 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -42,15 +42,19 @@ SOGoMaximumPingInterval = 3540; - SOGoInternalSyncInterval = 45; + SOGoInternalSyncInterval = 60; SOGoMaximumSyncInterval = 3540; + // Pre-March-14-2019 // 100 seems to break some Android clients //SOGoMaximumSyncWindowSize = 99; // This should do the trick for Outlook 2016 - SOGoMaximumSyncResponseSize = 512; + //SOGoMaximumSyncResponseSize = 512; + // Post-March-14-2019 + SOGoMaximumSyncResponseSize = 2048; + SOGoMaximumSyncWindowSize = 32; - WOWatchDogRequestTimeout = 20; + WOWatchDogRequestTimeout = 60; WOListenQueueSize = 300; WONoDetach = YES; From d8e356f5901624a37c36d0a17adc9a430dca4c87 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 18 Mar 2019 01:36:32 +0100 Subject: [PATCH 075/439] [SOGo] Revert to previous settings --- data/conf/sogo/sogo.conf | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index b0ebf698..aa1a86ec 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -42,19 +42,15 @@ SOGoMaximumPingInterval = 3540; - SOGoInternalSyncInterval = 60; + SOGoInternalSyncInterval = 45; SOGoMaximumSyncInterval = 3540; - // Pre-March-14-2019 // 100 seems to break some Android clients //SOGoMaximumSyncWindowSize = 99; // This should do the trick for Outlook 2016 - //SOGoMaximumSyncResponseSize = 512; - // Post-March-14-2019 - SOGoMaximumSyncResponseSize = 2048; - SOGoMaximumSyncWindowSize = 32; + SOGoMaximumSyncResponseSize = 512; - WOWatchDogRequestTimeout = 60; + WOWatchDogRequestTimeout = 20; WOListenQueueSize = 300; WONoDetach = YES; From 1fcecd0350a894083f8e40405df1379c843fdd1b Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 18 Mar 2019 10:16:33 +0100 Subject: [PATCH 076/439] [Web] Fix js when adding resource [Web] Reload view and memcached when changing a resource --- data/web/inc/functions.mailbox.inc.php | 2 +- data/web/js/site/mailbox.js | 20 +++++++++++++++++++- data/web/modals/mailbox.php | 21 --------------------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d479f125..b9129f83 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3714,7 +3714,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } break; } - if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox'))) { + if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) { update_sogo_static_view(); } } diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 674d6703..f8aacac7 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -169,7 +169,25 @@ $(document).ready(function() { // $("#active-script").closest('td').css('background-color','#b0f0a0'); // $("#inactive-script").closest('td').css('background-color','#b0f0a0'); // }); - + $('#addResourceModal').on('shown.bs.modal', function() { + $("#multiple_bookings").val($("#multiple_bookings_select").val()); + if ($("#multiple_bookings").val() == "custom") { + $("#multiple_bookings_custom_div").show(); + $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + } + }) + $("#multiple_bookings_select").change(function() { + $("#multiple_bookings").val($("#multiple_bookings_select").val()); + if ($("#multiple_bookings").val() == "custom") { + $("#multiple_bookings_custom_div").show(); + } + else { + $("#multiple_bookings_custom_div").hide(); + } + }); + $("#multiple_bookings_custom").bind ("change keypress keyup blur", function () { + $("#multiple_bookings").val($("#multiple_bookings_custom").val()); + }); }); diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index c12df381..11abbc58 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -785,24 +785,3 @@ if (!isset($_SESSION['mailcow_cc_role'])) { - From 22798a85e503a15e6e8203379450eedc42d91a64 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 18 Mar 2019 14:09:32 +0100 Subject: [PATCH 077/439] [Config] Add MAILDIR_SUB, "Maildir" for new setups by default [Update] Add MAILDIR_SUB= for updated mailcows [Dovecot] Read MAILDIR_SUB for mail_home --- data/Dockerfiles/dovecot/docker-entrypoint.sh | 2 +- docker-compose.yml | 3 ++- generate_config.sh | 3 +++ update.sh | 6 ++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 80db4b48..e3626938 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -106,7 +106,7 @@ chmod 644 /usr/local/etc/dovecot/mail_plugins /usr/local/etc/dovecot/mail_plugin cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf driver = mysql connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" -user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/:VOLATILEDIR=/var/volatile/%u') AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' +user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u') AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' iterate_query = SELECT username FROM mailbox WHERE active='1'; EOF diff --git a/docker-compose.yml b/docker-compose.yml index 49c784eb..302adf12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -164,7 +164,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.64 + image: mailcow/dovecot:1.65 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE @@ -188,6 +188,7 @@ services: - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440} - ACL_ANYONE=${ACL_ANYONE:-disallow} - SKIP_SOLR=${SKIP_SOLR:-y} + - MAILDIR_SUB=${MAILDIR_SUB:-} ports: - "${DOVEADM_PORT:-127.0.0.1:19991}:12345" - "${IMAP_PORT:-143}:143" diff --git a/generate_config.sh b/generate_config.sh index 8cde0ffd..7495f057 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -234,6 +234,9 @@ IPV6_NETWORK=fd4d:6169:6c63:6f77::/64 #API_KEY= #API_ALLOW_FROM=127.0.0.1,1.2.3.4 +# mail_home is ~/Maildir +MAILDIR_SUB=Maildir + EOF mkdir -p data/assets/ssl diff --git a/update.sh b/update.sh index be6ef9d7..d879f593 100755 --- a/update.sh +++ b/update.sh @@ -243,6 +243,12 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# Disable Solr or if you do not want to store a readable index of your mails in solr-vol-1.' >> mailcow.conf echo "SKIP_SOLR=y" >> mailcow.conf fi + elif [[ ${option} == "MAILDIR_SUB" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf + echo "MAILDIR_SUB=" >> mailcow.conf + fi elif ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=n" >> mailcow.conf From 4aae72779ae28e8fbc8e5ccfec962e931948cf43 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 18 Mar 2019 14:15:02 +0100 Subject: [PATCH 078/439] [Dovecot] Remove auth cache --- data/conf/dovecot/dovecot.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index b81604a2..efb869c6 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -384,9 +384,9 @@ service stats { } } imap_max_line_length = 2 M -auth_cache_verify_password_with_worker = yes -auth_cache_negative_ttl = 0 -auth_cache_ttl = 30 s -auth_cache_size = 2 M +#auth_cache_verify_password_with_worker = yes +#auth_cache_negative_ttl = 0 +#auth_cache_ttl = 30 s +#auth_cache_size = 2 M !include_try /usr/local/etc/dovecot/extra.conf default_client_limit = 10400 From ca2ac00422a2cba483151860397abfe09b3517dc Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 18 Mar 2019 19:49:05 +0100 Subject: [PATCH 079/439] [Update] Fix MAILDIR_SUB --- update.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/update.sh b/update.sh index d879f593..02c6e8b3 100755 --- a/update.sh +++ b/update.sh @@ -135,6 +135,7 @@ CONFIG_ARRAY=( "API_KEY" "API_ALLOW_FROM" "MAILDIR_GC_TIME" + "MAILDIR_SUB" "ACL_ANYONE" "SOLR_HEAP" "SKIP_SOLR" @@ -247,6 +248,7 @@ for option in ${CONFIG_ARRAY[@]}; do if ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf + echo "#MAILDIR_SUB=Maildir" >> mailcow.conf echo "MAILDIR_SUB=" >> mailcow.conf fi elif ! grep -q ${option} mailcow.conf; then From 6a13609bf0e1bc75c907bdc46a4099b92de69eb3 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 19 Mar 2019 08:45:08 +0100 Subject: [PATCH 080/439] [Web] Fix slow UI by switching QR provider and only generating qr image on demand --- data/web/inc/ajax/qr_gen.php | 13 +++++++++++++ data/web/inc/footer.inc.php | 9 +++++++++ data/web/inc/prerequisites.inc.php | 3 ++- data/web/modals/footer.php | 2 +- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 data/web/inc/ajax/qr_gen.php diff --git a/data/web/inc/ajax/qr_gen.php b/data/web/inc/ajax/qr_gen.php new file mode 100644 index 00000000..1c543ebe --- /dev/null +++ b/data/web/inc/ajax/qr_gen.php @@ -0,0 +1,13 @@ +getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $totp_secret); +} + +?> diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 365cf7da..b8229e26 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -93,6 +93,15 @@ $(document).ready(function() { } if ($(this).val() == "totp") { $('#TOTPModal').modal('show'); + request_token = $('#tfa-qr-img').data('totp-secret'); + $.ajax({ + url: '/inc/ajax/qr_gen.php', + data: { + token: request_token, + }, + }).done(function (result) { + $("#tfa-qr-img").attr("src", result); + }); $("option:selected").prop("selected", false); } if ($(this).val() == "u2f") { diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 66db8662..7c651803 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -36,7 +36,8 @@ foreach ($css_dir as $css_file) { // U2F API + T/HOTP API $u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']); -$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL); +$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider(); +$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider); // Redis $redis = new Redis(); diff --git a/data/web/modals/footer.php b/data/web/modals/footer.php index b5e49b15..b7ebaf08 100644 --- a/data/web/modals/footer.php +++ b/data/web/modals/footer.php @@ -81,7 +81,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
  1. - +

    :

    From 3c4c760e2992aca4370ec262f1ddc7f3fbbc39fe Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 25 Mar 2019 12:33:58 +0100 Subject: [PATCH 081/439] [Web] Allow logout with broken session [Web] Try to set aria hidden to false when a modal opens --- data/web/inc/footer.inc.php | 4 ++++ data/web/inc/sessions.inc.php | 36 +++++++++++++++++------------------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index b8229e26..f1cca54c 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -26,6 +26,10 @@ $(window).load(function() { $(".overlay").hide(); }); $(document).ready(function() { + $(document).on('shown.bs.modal', function(e) { + modal_id = $(e.relatedTarget).data('target'); + $(modal_id).attr("aria-hidden","false"); + }); // TFA, CSRF, Alerts in footer.inc.php // Other general functions in mailcow.js Date: Mon, 25 Mar 2019 12:34:53 +0100 Subject: [PATCH 082/439] [Web] Update bootstrap slider --- data/web/css/build/004-bootstrap-slider.min.css | 2 +- data/web/js/build/003-bootstrap-slider.min.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/web/css/build/004-bootstrap-slider.min.css b/data/web/css/build/004-bootstrap-slider.min.css index 81c905da..f8e395be 100644 --- a/data/web/css/build/004-bootstrap-slider.min.css +++ b/data/web/css/build/004-bootstrap-slider.min.css @@ -1,5 +1,5 @@ /*! ======================================================= - VERSION 10.6.0 + VERSION 10.6.1 ========================================================= */ /*! ========================================================= * bootstrap-slider.js diff --git a/data/web/js/build/003-bootstrap-slider.min.js b/data/web/js/build/003-bootstrap-slider.min.js index 1ae4e4f2..633988c8 100644 --- a/data/web/js/build/003-bootstrap-slider.min.js +++ b/data/web/js/build/003-bootstrap-slider.min.js @@ -1,5 +1,5 @@ -/*! ======================================================= - VERSION 10.6.0 -========================================================= */ -"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1,tickIndex:null},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=c.hasOwnProperty("min"),g=c.hasOwnProperty("max"),i=0;i0,this.ticksAreValid||(this.options.lock_to_ticks=!1),"auto"===this.options.rtl){var l=window.getComputedStyle(this.element);null!=l?this.options.rtl="rtl"===l.direction:this.options.rtl="rtl"===this.element.style.direction}"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var m,n,o,p,q,r=this.element.style.width,s=!1,t=this.element.parentNode;if(this.sliderElem)s=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var u=document.createElement("div");u.className="slider-track",n=document.createElement("div"),n.className="slider-track-low",m=document.createElement("div"),m.className="slider-selection",o=document.createElement("div"),o.className="slider-track-high",p=document.createElement("div"),p.className="slider-handle min-slider-handle",p.setAttribute("role","slider"),p.setAttribute("aria-valuemin",this.options.min),p.setAttribute("aria-valuemax",this.options.max),q=document.createElement("div"),q.className="slider-handle max-slider-handle",q.setAttribute("role","slider"),q.setAttribute("aria-valuemin",this.options.min),q.setAttribute("aria-valuemax",this.options.max),u.appendChild(n),u.appendChild(m),u.appendChild(o),this.rangeHighlightElements=[];var v=this.options.rangeHighlights;if(Array.isArray(v)&&v.length>0)for(var w=0;w0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",i=0;i0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",i=0;i0&&(g||(this.options.max=Math.max.apply(Math,this.options.ticks)),f||(this.options.min=Math.min.apply(Math,this.options.ticks))),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=n||this.trackLow,this.trackSelection=m||this.trackSelection,this.trackHigh=o||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=p||this.handle1,this.handle2=q||this.handle2,s===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),i=0;ib.max?b.max:a},toValue:function(a){var b=a/100*(this.options.max-this.options.min),c=!0;if(this.options.ticks_positions.length>0){for(var d,e,f,g=0,i=1;i0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=1-this.options.min,c=Math.log(this.options.min+b),d=Math.log(this.options.max+b),e=Math.exp(c+(d-c)*a/100)-b;return Math.round(e)===d?d:(e=this.options.min+Math.round((e-this.options.min)/this.options.step)*this.options.step,h.linear.getValue(e,this.options))},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=1-this.options.min,c=Math.log(this.options.max+b),d=Math.log(this.options.min+b),e=Math.log(a+b);return 100*(e-d)/(c-d)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,lock_to_ticks:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value[0]=this.options.ticks[this._getClosestTickIndex(this._state.value[0])],this._state.value[1]=this.options.ticks[this._getClosestTickIndex(this._state.value[1])]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value=this.options.ticks[this._getClosestTickIndex(this._state.value)]),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this._setTickIndex(),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];this._setDataVal(f),b===!0&&this._trigger("slide",f);var g=!1;return g=Array.isArray(f)?d[0]!==f[0]||d[1]!==f[1]:d!==f,g&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),f===b&&this.$element.removeData(f),this.$element.removeData(c))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(d){var g=this.getValue();return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),d&&d.useCurrentValue===!0&&this.setValue(g),a&&(f===b?(a.data(this.element,b,this),a.data(this.element,c,this)):a.data(this.element,c,this)),this},relayout:function(){return this._resize(),this},_removeTooltipListener:function(a){this.handle1.removeEventListener(a,this.showTooltip,!1),this.handle2.removeEventListener(a,this.showTooltip,!1)},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b0&&a.options.ticks_positions[c]||a._toPercentage(a.options.ticks[c])):f=a._toPercentage(e),d.value[0]=e,d.percentage[0]=f,a._setToolTipOnMouseOver(d),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a,b;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),b=this.options.formatter(this._state.value[0]),isNaN(b)?this.handle1.setAttribute("aria-valuetext",b):this.handle1.removeAttribute("aria-valuetext"),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),b=this.options.formatter(this._state.value[1]),isNaN(b)?this.handle2.setAttribute("aria-valuetext",b):this.handle2.removeAttribute("aria-valuetext"),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var c=0;c0){var h,i="vertical"===this.options.orientation?"height":"width";h="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var j=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var k=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[h]=-j/2+"px"),k=this.tickLabelContainer.offsetHeight;else for(l=0;lk&&(k=this.tickLabelContainer.childNodes[l].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=k+"px")}for(var l=0;l=a[0]&&m<=a[1]&&this._addClass(this.ticks[l],"in-selection"):"after"===this.options.selection&&m>=a[0]?this._addClass(this.ticks[l],"in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.ticks[l],"in-selection"),this.tickLabels[l]&&(this.tickLabels[l].style[i]=j+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[l]?(this.tickLabels[l].style.position="absolute",this.tickLabels[l].style[this.stylePos]=m+"%",this.tickLabels[l].style[h]=-j/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[l].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[l].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[h]=this.sliderElem.offsetWidth/2*-1+"px"),this._removeClass(this.tickLabels[l],"label-in-selection label-is-selection"),this.options.range?m>=a[0]&&m<=a[1]&&(this._addClass(this.tickLabels[l],"label-in-selection"),(m===a[0]||a[1])&&this._addClass(this.tickLabels[l],"label-is-selection")):("after"===this.options.selection&&m>=a[0]?this._addClass(this.tickLabels[l],"label-in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.tickLabels[l],"label-in-selection"),m===a[0]&&this._addClass(this.tickLabels[l],"label-is-selection")))}}var n;if(this.options.range){n=this.options.formatter(this._state.value),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var o=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,o);var p=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,p),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else n=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var q=this.tooltip_min.getBoundingClientRect(),r=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?q.right>r.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):q.right>r.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;a.preventDefault&&a.preventDefault(),this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="horizontal"===this.options.orientation,e="vertical"===this.options.orientation,f=this.options.rtl,g=this.options.reversed;d?f?g||(c=-c):g&&(c=-c):e&&(g||(c=-c))}var h;if(this.ticksAreValid&&this.options.lock_to_ticks){var i=void 0;i=this.options.ticks.indexOf(this._state.value[a]),-1===i&&(i=0,window.console.warn("(lock_to_ticks) _keydown: index should not be -1")),i+=c,i=Math.max(0,Math.min(this.options.ticks.length-1,i)),h=this.options.ticks[i]}else h=this._state.value[a]+c*this.options.step;var j=this._toPercentage(h);if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(j);var k=this._state.keyCtrl?this._state.value[0]:h,l=this._state.keyCtrl?h:this._state.value[1];h=[Math.max(this.options.min,Math.min(this.options.max,k)),Math.max(this.options.min,Math.min(this.options.max,l))]}else h=Math.max(this.options.min,Math.min(this.options.max,h));return this._trigger("slideStart",h),this.setValue(h,!0,!0),this._trigger("slideStop",h),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b;var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._toPercentage(this._state.value[1])a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var c=this._calculateValue(!0);return this.setValue(c,!1,!0),this._trigger("slideStop",c),this._state.dragged=null,!1},_setValues:function(a,b){var c=0===a?0:100;this._state.percentage[a]!==c&&(b.data[a]=this._toValue(this._state.percentage[a]),b.data[a]=this._applyPrecision(b.data[a]))},_calculateValue:function(a){var b={};return this.options.range?(b.data=[this.options.min,this.options.max],this._setValues(0,b),this._setValues(1,b),a&&(b.data[0]=this._snapToClosestTick(b.data[0]),b.data[1]=this._snapToClosestTick(b.data[1]))):(b.data=this._toValue(this._state.percentage[0]),b.data=parseFloat(b.data),b.data=this._applyPrecision(b.data),a&&(b.data=this._snapToClosestTick(b.data))),b.data},_snapToClosestTick:function(a){for(var b=[a,1/0],c=0;ce&&(b=e,c=d)}return c},_setTickIndex:function(){this.ticksAreValid&&(this._state.tickIndex=[this.options.ticks.indexOf(this._state.value[0]),this.options.ticks.indexOf(this._state.value[1])])}},a&&a.fn&&(a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),f=c):(a.bridget(b,d),f=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[f]()}))}(a),d}); \ No newline at end of file +/*! ======================================================= + VERSION 10.6.1 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1,tickIndex:null},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=c.hasOwnProperty("min"),g=c.hasOwnProperty("max"),i=0;i0,this.ticksAreValid||(this.options.lock_to_ticks=!1),"auto"===this.options.rtl){var l=window.getComputedStyle(this.element);null!=l?this.options.rtl="rtl"===l.direction:this.options.rtl="rtl"===this.element.style.direction}"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var m,n,o,p,q,r=this.element.style.width,s=!1,t=this.element.parentNode;if(this.sliderElem)s=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var u=document.createElement("div");u.className="slider-track",n=document.createElement("div"),n.className="slider-track-low",m=document.createElement("div"),m.className="slider-selection",o=document.createElement("div"),o.className="slider-track-high",p=document.createElement("div"),p.className="slider-handle min-slider-handle",p.setAttribute("role","slider"),p.setAttribute("aria-valuemin",this.options.min),p.setAttribute("aria-valuemax",this.options.max),q=document.createElement("div"),q.className="slider-handle max-slider-handle",q.setAttribute("role","slider"),q.setAttribute("aria-valuemin",this.options.min),q.setAttribute("aria-valuemax",this.options.max),u.appendChild(n),u.appendChild(m),u.appendChild(o),this.rangeHighlightElements=[];var v=this.options.rangeHighlights;if(Array.isArray(v)&&v.length>0)for(var w=0;w0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",i=0;i0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",i=0;i0&&(g||(this.options.max=Math.max.apply(Math,this.options.ticks)),f||(this.options.min=Math.min.apply(Math,this.options.ticks))),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=n||this.trackLow,this.trackSelection=m||this.trackSelection,this.trackHigh=o||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=p||this.handle1,this.handle2=q||this.handle2,s===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),i=0;ib.max?b.max:a},toValue:function(a){var b=a/100*(this.options.max-this.options.min),c=!0;if(this.options.ticks_positions.length>0){for(var d,e,f,g=0,i=1;i0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=1-this.options.min,c=Math.log(this.options.min+b),d=Math.log(this.options.max+b),e=Math.exp(c+(d-c)*a/100)-b;return Math.round(e)===d?d:(e=this.options.min+Math.round((e-this.options.min)/this.options.step)*this.options.step,h.linear.getValue(e,this.options))},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=1-this.options.min,c=Math.log(this.options.max+b),d=Math.log(this.options.min+b),e=Math.log(a+b);return 100*(e-d)/(c-d)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,lock_to_ticks:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value[0]=this.options.ticks[this._getClosestTickIndex(this._state.value[0])],this._state.value[1]=this.options.ticks[this._getClosestTickIndex(this._state.value[1])]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this.ticksAreValid&&this.options.lock_to_ticks&&(this._state.value=this.options.ticks[this._getClosestTickIndex(this._state.value)]),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this._setTickIndex(),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];this._setDataVal(f),b===!0&&this._trigger("slide",f);var g=!1;return g=Array.isArray(f)?d[0]!==f[0]||d[1]!==f[1]:d!==f,g&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),f===b&&this.$element.removeData(f),this.$element.removeData(c))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(d){var g=this.getValue();return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),d&&d.useCurrentValue===!0&&this.setValue(g),a&&(f===b?(a.data(this.element,b,this),a.data(this.element,c,this)):a.data(this.element,c,this)),this},relayout:function(){return this._resize(),this},_removeTooltipListener:function(a,b){this.handle1.removeEventListener(a,b,!1),this.handle2.removeEventListener(a,b,!1)},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b0&&a.options.ticks_positions[c]||a._toPercentage(a.options.ticks[c])):f=a._toPercentage(e),d.value[0]=e,d.percentage[0]=f,a._setToolTipOnMouseOver(d),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a,b;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),b=this.options.formatter(this._state.value[0]),isNaN(b)?this.handle1.setAttribute("aria-valuetext",b):this.handle1.removeAttribute("aria-valuetext"),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),b=this.options.formatter(this._state.value[1]),isNaN(b)?this.handle2.setAttribute("aria-valuetext",b):this.handle2.removeAttribute("aria-valuetext"),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var c=0;c0){var h,i="vertical"===this.options.orientation?"height":"width";h="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var j=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var k=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[h]=-j/2+"px"),k=this.tickLabelContainer.offsetHeight;else for(l=0;lk&&(k=this.tickLabelContainer.childNodes[l].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=k+"px")}for(var l=0;l=a[0]&&m<=a[1]&&this._addClass(this.ticks[l],"in-selection"):"after"===this.options.selection&&m>=a[0]?this._addClass(this.ticks[l],"in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.ticks[l],"in-selection"),this.tickLabels[l]&&(this.tickLabels[l].style[i]=j+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[l]?(this.tickLabels[l].style.position="absolute",this.tickLabels[l].style[this.stylePos]=m+"%",this.tickLabels[l].style[h]=-j/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[l].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[l].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[h]=this.sliderElem.offsetWidth/2*-1+"px"),this._removeClass(this.tickLabels[l],"label-in-selection label-is-selection"),this.options.range?m>=a[0]&&m<=a[1]&&(this._addClass(this.tickLabels[l],"label-in-selection"),(m===a[0]||a[1])&&this._addClass(this.tickLabels[l],"label-is-selection")):("after"===this.options.selection&&m>=a[0]?this._addClass(this.tickLabels[l],"label-in-selection"):"before"===this.options.selection&&m<=a[0]&&this._addClass(this.tickLabels[l],"label-in-selection"),m===a[0]&&this._addClass(this.tickLabels[l],"label-is-selection")))}}var n;if(this.options.range){n=this.options.formatter(this._state.value),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var o=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,o);var p=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,p),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else n=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,n),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var q=this.tooltip_min.getBoundingClientRect(),r=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?q.right>r.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):q.right>r.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;a.preventDefault&&a.preventDefault(),this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){this._mousedown(a)},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="horizontal"===this.options.orientation,e="vertical"===this.options.orientation,f=this.options.rtl,g=this.options.reversed;d?f?g||(c=-c):g&&(c=-c):e&&(g||(c=-c))}var h;if(this.ticksAreValid&&this.options.lock_to_ticks){var i=void 0;i=this.options.ticks.indexOf(this._state.value[a]),-1===i&&(i=0,window.console.warn("(lock_to_ticks) _keydown: index should not be -1")),i+=c,i=Math.max(0,Math.min(this.options.ticks.length-1,i)),h=this.options.ticks[i]}else h=this._state.value[a]+c*this.options.step;var j=this._toPercentage(h);if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(j);var k=this._state.keyCtrl?this._state.value[0]:h,l=this._state.keyCtrl?h:this._state.value[1];h=[Math.max(this.options.min,Math.min(this.options.max,k)),Math.max(this.options.min,Math.min(this.options.max,l))]}else h=Math.max(this.options.min,Math.min(this.options.max,h));return this._trigger("slideStart",h),this.setValue(h,!0,!0),this._trigger("slideStop",h),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1; +var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b;var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){void 0!==a.changedTouches&&a.preventDefault&&a.preventDefault()},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._toPercentage(this._state.value[1])a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var c=this._calculateValue(!0);return this.setValue(c,!1,!0),this._trigger("slideStop",c),this._state.dragged=null,!1},_setValues:function(a,b){var c=0===a?0:100;this._state.percentage[a]!==c&&(b.data[a]=this._toValue(this._state.percentage[a]),b.data[a]=this._applyPrecision(b.data[a]))},_calculateValue:function(a){var b={};return this.options.range?(b.data=[this.options.min,this.options.max],this._setValues(0,b),this._setValues(1,b),a&&(b.data[0]=this._snapToClosestTick(b.data[0]),b.data[1]=this._snapToClosestTick(b.data[1]))):(b.data=this._toValue(this._state.percentage[0]),b.data=parseFloat(b.data),b.data=this._applyPrecision(b.data),a&&(b.data=this._snapToClosestTick(b.data))),b.data},_snapToClosestTick:function(a){for(var b=[a,1/0],c=0;ce&&(b=e,c=d)}return c},_setTickIndex:function(){this.ticksAreValid&&(this._state.tickIndex=[this.options.ticks.indexOf(this._state.value[0]),this.options.ticks.indexOf(this._state.value[1])])}},a&&a.fn&&(a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),f=c):(a.bridget(b,d),f=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[f]()}))}(a),d}); \ No newline at end of file From eb2b26699c2375d202dcce319a962d926749e9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Wed, 27 Mar 2019 16:37:15 +0100 Subject: [PATCH 083/439] [Dovcot] Cleanup random user maildirs --- data/Dockerfiles/dovecot/docker-entrypoint.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 231c7e54..7034fc08 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -127,6 +127,10 @@ if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/v if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi if [[ $(stat -c %U /var/attachments) != "vmail" ]] ; then chown -R vmail:vmail /var/attachments ; fi +# Cleanup random user maildirs +rm -rf /var/vmail/mailcow.local/* + + # Create random master for SOGo sieve features RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) From 220cda444900322d5058d8ca4174d895026ce0fa Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 27 Mar 2019 16:49:26 +0100 Subject: [PATCH 084/439] [Compose] Update Dovecot image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f5265f89..75b46082 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -168,7 +168,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.65 + image: mailcow/dovecot:1.66 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From fdb36504442ab4e926bb84d8b74fc078a189a740 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 27 Mar 2019 23:14:17 +0100 Subject: [PATCH 085/439] [Compose] Update watchdog and acme --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 302adf12..62a42c52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -297,7 +297,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.49 + image: mailcow/acme:1.50 build: ./data/Dockerfiles/acme dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -348,7 +348,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.37 + image: mailcow/watchdog:1.38 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog From 8b0f7fa81b1df7f64b2c2991a296766fbd5f1caf Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 27 Mar 2019 23:14:46 +0100 Subject: [PATCH 086/439] [ACME] Write redis key on non-empty exit code --- data/Dockerfiles/acme/docker-entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index c8501168..cd135d9e 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -479,6 +479,7 @@ while true; do ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) log_f "${ACME_RESPONSE_B64}" redis_only b64 log_f "Retrying in 30 minutes..." + redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)" sleep 30m exec $(readlink -f "$0") ;; From e7d17ad1acc9254052524eca5cc672e095f7c288 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 27 Mar 2019 23:15:04 +0100 Subject: [PATCH 087/439] [Watchdog] Check for ACME failures --- data/Dockerfiles/watchdog/watchdog.sh | 49 ++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index a9db9321..cd9920a1 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -5,6 +5,8 @@ trap "kill 0" EXIT # Prepare BACKGROUND_TASKS=() +echo "Waiting for containers to settle..." +sleep 10 if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..." @@ -350,6 +352,38 @@ ratelimit_checks() { return 1 } +acme_checks() { + err_count=0 + diff_c=0 + THRESHOLD=1 + ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME) + if [[ -z "${ACME_LOG_STATUS}" ]]; then + redis-cli -h redis SET ACME_FAIL_TIME 0 + ACME_LOG_STATUS=0 + fi + # 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 + err_c_cur=${err_count} + ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS} + ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME) + if [[ ${ACME_LOG_STATUS_PREV} != ${ACME_LOG_STATUS} ]]; then + err_count=$(( ${err_count} + 1 )) + fi + [ ${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 "ACME" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} + if [[ $? == 10 ]]; then + diff_c=0 + sleep 1 + else + diff_c=0 + sleep $(( ( RANDOM % 30 ) + 10 )) + fi + done + return 1 +} + ipv6nat_checks() { err_count=0 diff_c=0 @@ -518,6 +552,16 @@ done ) & BACKGROUND_TASKS+=($!) +( +while true; do + if ! acme_checks; then + log_msg "ACME client hit error limit" + echo acme-tiny > /tmp/com_pipe + fi +done +) & +BACKGROUND_TASKS+=($!) + ( while true; do if ! ipv6nat_checks; then @@ -567,7 +611,10 @@ while true; do fi if [[ ${com_pipe_answer} == "ratelimit" ]]; then log_msg "At least one ratelimit was applied" - [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "No further information available." + [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please see mailcow UI logs for further information." + elif [[ ${com_pipe_answer} == "acme-tiny" ]]; then + log_msg "acme-tiny client returned non-zero exit code" + [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please check acme-mailcow for ruther information." elif [[ ${com_pipe_answer} =~ .+-mailcow ]] || [[ ${com_pipe_answer} == "ipv6nat-mailcow" ]]; then kill -STOP ${BACKGROUND_TASKS[*]} sleep 3 From 49492dff615116c2084a81ade836b5f66dbb5e5e Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 28 Mar 2019 22:05:12 +0100 Subject: [PATCH 088/439] [Web, Dovecot] Allow empty/unlimited quota --- data/web/edit.php | 3 ++- data/web/inc/functions.inc.php | 8 +++++++ data/web/inc/functions.mailbox.inc.php | 29 +++++++------------------- data/web/js/site/mailbox.js | 3 ++- data/web/modals/mailbox.php | 4 ++-- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/data/web/edit.php b/data/web/edit.php index c44464df..9aa79269 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -521,7 +521,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    max. MiB
    - + + 0 = ∞
    diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index c869c122..ebce5819 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1,4 +1,12 @@ '), '<'); - $quota_m = filter_var($_data['quota'], FILTER_SANITIZE_NUMBER_FLOAT); + $quota_m = intval($_data['quota']); if (empty($name)) { $name = $local_part; } @@ -844,14 +844,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!is_numeric($quota_m) || $quota_m == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'quota_not_0_not_numeric' - ); - return false; - } if (!empty($password) && !empty($password2)) { if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { $_SESSION['return'][] = array( @@ -1993,9 +1985,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); (int)$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); + (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; $domain = $is_now['domain']; - $quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576); $quota_b = $quota_m * 1048576; $password = (!empty($_data['password'])) ? $_data['password'] : null; $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; @@ -2021,14 +2013,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - if (!is_numeric($quota_m) || $quota_m == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('quota_not_0_not_numeric', htmlspecialchars($quota_m)) - ); - continue; - } if ($quota_m > $DomainData['maxquota']) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -3016,15 +3000,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mailboxdata['quota'] = $row['quota']; $mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['quota_used'] = intval($row['bytes']); - $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); + $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['messages'] = $row['messages']; $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; - if ($mailboxdata['percent_in_use'] >= 90) { - $mailboxdata['percent_class'] = "danger"; + if ($mailboxdata['percent_in_use'] === '- ') { + $mailboxdata['percent_class'] = "info"; } elseif ($mailboxdata['percent_in_use'] >= 75) { $mailboxdata['percent_class'] = "warning"; } + elseif ($mailboxdata['percent_in_use'] >= 90) { + $mailboxdata['percent_class'] = "danger"; + } else { $mailboxdata['percent_class'] = "success"; } diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index f8aacac7..ba49892c 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -312,7 +312,8 @@ jQuery(function($){ {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ res = value.split("/"); - return humanFileSize(res[0]) + " / " + humanFileSize(res[1]); + var of_q = (res[1] == 0 ? "∞" : humanFileSize(res[1])); + return humanFileSize(res[0]) + " / " + of_q; }, "sortValue": function(value){ res = value.split("/"); diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 11abbc58..aeb88cca 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -43,8 +43,8 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
    max. - MiB
    - - min. 1 + + 0 = ∞
    From 67d69572655ed84a38c9da0c5baa28de1f3fe677 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 28 Mar 2019 23:04:24 +0100 Subject: [PATCH 089/439] [Web] Show unlimited quota in user view --- data/web/user.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/user.php b/data/web/user.php index b19fbb00..296c41fe 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -42,7 +42,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma foreach ($tfa_data['additional'] as $key_info): ?> -
    🔑 []
    +
    🔑 []
    @@ -199,7 +199,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' %
    -

    / ,

    +

    /


    From 9378a34adb607d49e4314dd52164ca32984f0b17 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 29 Mar 2019 07:46:28 +0100 Subject: [PATCH 090/439] [SOGo] Remove unnamed volume and rsync web content to named volume --- data/Dockerfiles/sogo/Dockerfile | 3 +-- data/Dockerfiles/sogo/bootstrap-sogo.sh | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 085f1bc2..970ec252 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ gettext \ gnupg \ mysql-client \ + rsync \ supervisor \ syslog-ng \ syslog-ng-core \ @@ -52,6 +53,4 @@ RUN chmod +x /bootstrap-sogo.sh \ CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf -VOLUME /usr/lib/GNUstep/SOGo/ - RUN rm -rf /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 5072a306..f9d1f514 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -183,4 +183,8 @@ fi # Copy logo, if any [[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg +# Rsync web content +echo "Syncing web content with named volume" +rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/ + exec gosu sogo /usr/sbin/sogod From b42d0df8e247b7875e87e35dc8d2f03622fa7809 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 29 Mar 2019 07:46:52 +0100 Subject: [PATCH 091/439] [ACME] Allow to skip http verification --- data/Dockerfiles/acme/docker-entrypoint.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index cd135d9e..e79ef977 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -5,6 +5,16 @@ exec 5>&1 # Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 source /srv/expand6.sh +# Skipping IP check when we like to live dangerously +if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_IP_CHECK=y +fi + +# Skipping HTTP check when we like to live dangerously +if [[ "${SKIP_HTTP_VERIFICATION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_HTTP_VERIFICATION=y +fi + log_f() { if [[ ${2} == "no_nl" ]]; then echo -n "$(date) - ${1}" @@ -120,7 +130,10 @@ verify_challenge_path(){ # verify_challenge_path URL 4|6 RAND_FILE=${RANDOM}${RANDOM}${RANDOM} touch /var/www/acme/${RAND_FILE} - if [[ "$(curl -${2} http://${1}/.well-known/acme-challenge/${RAND_FILE} --write-out %{http_code} --silent --output /dev/null)" =~ ^(2|3) ]]; then + if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then + echo '(skipping check, returning 0)' + return 0 + elif [[ "$(curl -${2} http://${1}/.well-known/acme-challenge/${RAND_FILE} --write-out %{http_code} --silent --output /dev/null)" =~ ^(2|3) ]]; then rm /var/www/acme/${RAND_FILE} return 0 else @@ -199,11 +212,6 @@ while true; do chmod 600 ${ACME_BASE}/acme/key.pem chmod 600 ${ACME_BASE}/acme/account.pem - # Skipping IP check when we like to live dangerously - if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - SKIP_IP_CHECK=y - fi - # Cleaning up and init validation arrays unset SQL_DOMAIN_ARR unset VALIDATED_CONFIG_DOMAINS From fdedb6fdce46ee6cd9cc23c0e3f570bcb12eb2cf Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 29 Mar 2019 07:48:12 +0100 Subject: [PATCH 092/439] [Compose] New ACME, watchdog and SOGo images [Compose] Add SKIP_HTTP_VERIFICATION defaulting to n [Compose] Add named volume sogo-web-vol-1 for static web content --- docker-compose.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 62a42c52..af9a2706 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,7 +139,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.53 + image: mailcow/sogo:1.54 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} @@ -154,6 +154,7 @@ services: - ./data/web/inc/init_db.inc.php:/init_db.inc.php - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js - mysql-socket-vol-1:/var/run/mysqld/ + - sogo-web-vol-1:/sogo_web restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -281,8 +282,7 @@ services: - ./data/assets/ssl/:/etc/ssl/mail/:ro - ./data/conf/nginx/:/etc/nginx/conf.d/:rw - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro - volumes_from: - - sogo-mailcow + - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/ 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}" @@ -297,7 +297,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.50 + image: mailcow/acme:1.51 build: ./data/Dockerfiles/acme dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -310,6 +310,7 @@ services: - DBPASS=${DBPASS} - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} - SKIP_IP_CHECK=${SKIP_IP_CHECK:-n} + - SKIP_HTTP_VERIFICATION=${SKIP_HTTP_VERIFICATION:-n} - LE_STAGING=${LE_STAGING:-n} - TZ=${TZ} volumes: @@ -348,7 +349,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.38 + image: mailcow/watchdog:1.39 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog @@ -460,3 +461,4 @@ volumes: solr-vol-1: postfix-vol-1: crypt-vol-1: + sogo-web-vol-1: From c612f7ee4c4e0c7c3d1e030ee853cc1c2287ccde Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 29 Mar 2019 07:48:31 +0100 Subject: [PATCH 093/439] [Config] Add SKIP_HTTP_VERIFICATION --- generate_config.sh | 4 ++++ update.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/generate_config.sh b/generate_config.sh index 7495f057..69613174 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -186,6 +186,10 @@ SKIP_LETS_ENCRYPT=n SKIP_IP_CHECK=n +# Skip HTTP verification in ACME container - y/n + +SKIP_HTTP_VERIFICATION=n + # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n SKIP_CLAMD=${SKIP_CLAMD} diff --git a/update.sh b/update.sh index 02c6e8b3..cbacb78c 100755 --- a/update.sh +++ b/update.sh @@ -139,6 +139,7 @@ CONFIG_ARRAY=( "ACL_ANYONE" "SOLR_HEAP" "SKIP_SOLR" + "SKIP_HTTP_VERIFICATION" ) sed -i '$a\' mailcow.conf From 14e57cf80f37d434e219f685f8c095104385c28b Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 30 Mar 2019 19:14:24 +0100 Subject: [PATCH 094/439] [Web] Add ACL for unlimited quota (default 0) --- data/web/inc/functions.mailbox.inc.php | 17 +++++++++++++++++ data/web/inc/init_db.inc.php | 3 ++- data/web/lang/lang.de.php | 2 ++ data/web/lang/lang.en.php | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 6ecb3a94..70dd0e1f 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -757,6 +757,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $password2 = $_data['password2']; $name = ltrim(rtrim($_data['name'], '>'), '<'); $quota_m = intval($_data['quota']); + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['quarantine_notification'] != "1") && $quota_m === 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } if (empty($name)) { $name = $local_part; } @@ -2000,6 +2008,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + // if already 0 == ok + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } $stmt = $pdo->prepare("SELECT `quota`, `maxquota` FROM `domain` WHERE `domain` = :domain"); diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index a66d2ab3..3c553bfd 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 = "27012019_1217"; + $db_version = "30032019_1905"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -464,6 +464,7 @@ function init_db_schema() { "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", + "unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'", "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", ), "keys" => array( diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index b5fc4642..4d0352d0 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -19,6 +19,7 @@ $lang['footer']['cancel'] = 'Abbrechen'; $lang['footer']['hibp_nok'] = 'Übereinstimmung gefunden! Dieses Passwort ist potentiell gefährlich!'; $lang['footer']['hibp_ok'] = 'Keine Übereinstimmung gefunden.'; +$lang['danger']['unlimited_quota_acl'] = "Unendliche Quota untersagt durch ACL"; $lang['danger']['mysql_error'] = "MySQL Fehler: %s"; $lang['danger']['redis_error'] = "Redis Fehler: %s"; $lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode"; @@ -405,6 +406,7 @@ $lang['acl']['bcc_maps'] = 'BCC Maps'; $lang['acl']['filters'] = 'Filter'; $lang['acl']['ratelimit'] = 'Rate limit'; $lang['acl']['recipient_maps'] = 'Empfängerumschreibungen'; +$lang['acl']['unlimited_quota'] = 'Unendliche Quota für Mailboxen'; $lang['acl']['prohibited'] = 'Untersagt durch Richtlinie'; $lang['mailbox']['quarantine_notification'] = 'Quarantäne-Benachrichtigung'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 0329a92d..3efe810e 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -20,6 +20,7 @@ $lang['footer']['cancel'] = 'Cancel'; $lang['footer']['hibp_nok'] = 'Matched! This is a potentially dangerous password!'; $lang['footer']['hibp_ok'] = 'No match found.'; +$lang['danger']['unlimited_quota_acl'] = "Unlimited quota prohibited by ACL"; $lang['danger']['mysql_error'] = "MySQL error: %s"; $lang['danger']['redis_error'] = "Redis error: %s"; $lang['danger']['unknown_tfa_method'] = "Unknown TFA method"; @@ -418,6 +419,7 @@ $lang['acl']['bcc_maps'] = 'BCC maps'; $lang['acl']['filters'] = 'Filters'; $lang['acl']['ratelimit'] = 'Rate limit'; $lang['acl']['recipient_maps'] = 'Recipient maps'; +$lang['acl']['unlimited_quota'] = 'Unlimited quota for mailboxes'; $lang['acl']['prohibited'] = 'Prohibited by ACL'; $lang['mailbox']['quarantine_notification'] = 'Quarantine notifications'; From 40dda01d917578381a4c014e16eb1b5c43b823c9 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 30 Mar 2019 19:14:56 +0100 Subject: [PATCH 095/439] [Compose] IMPORTANT: Added name for mailcow Docker bridge --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index af9a2706..fa23fe55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -442,6 +442,8 @@ services: networks: mailcow-network: driver: bridge + driver_opts: + com.docker.network.bridge.name: br-mailcow enable_ipv6: true ipam: driver: default From f28b58a5bcec8e485e34ac6f45f264a1e568cdf2 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 31 Mar 2019 15:58:45 +0200 Subject: [PATCH 096/439] [Compose] Update PHP iamge --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1a9a1ba5..c4111f2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,7 +94,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.35 + image: mailcow/phpfpm:1.36 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: From a86f9e0120b3f3b9d50045a2a76cb15840636baf Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 31 Mar 2019 19:07:39 +0200 Subject: [PATCH 097/439] [Compose] New Dovecot image [Dovecot] Update Dovecot to v2.3.5.1 --- data/Dockerfiles/dovecot/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index bc4fca5c..c5517499 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,7 +3,7 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.3.5 +ENV DOVECOT_VERSION 2.3.5.1 ENV PIGEONHOLE_VERSION 0.5.5 RUN apt-get update && apt-get -y --no-install-recommends install \ diff --git a/docker-compose.yml b/docker-compose.yml index c4111f2b..fbd90b00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -169,7 +169,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.66 + image: mailcow/dovecot:1.67 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 79bde4f702adef452d405fa9c69e3b91830f764d Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 31 Mar 2019 19:44:24 +0200 Subject: [PATCH 098/439] [Web] Fix UTF-8 symbol --- data/web/user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/user.php b/data/web/user.php index 296c41fe..7fe37310 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -42,7 +42,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma foreach ($tfa_data['additional'] as $key_info): ?>
    -
    🔑 []
    +
    🔑 []
    From ae21646c5ad9d10b5f20b0e626bc743f765bd0ed Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 31 Mar 2019 22:04:25 +0200 Subject: [PATCH 099/439] [Helper] Fix nc script, fixes #2484 --- helper-scripts/nextcloud.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 2ddac47a..32fd8675 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -78,9 +78,9 @@ elif [[ ${NC_UPDATE} == "y" ]]; then && rm nextcloud.tar.bz2 \ #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ - && chmod +x ./data/web/nextcloud/occ - docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" - docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade" + && chmod +x ./data/web/nextcloud/occ \ + docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" + docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade" fi elif [[ ${NC_INSTALL} == "y" ]]; then From a654f7766d01233b62370c2cb87678fde9b9de62 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 31 Mar 2019 22:05:35 +0200 Subject: [PATCH 100/439] [Helper] Fix nc script, fixes #2484 - again --- helper-scripts/nextcloud.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 32fd8675..3ad9c9b4 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -76,10 +76,9 @@ elif [[ ${NC_UPDATE} == "y" ]]; then curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && rm nextcloud.tar.bz2 \ - #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ && chmod +x ./data/web/nextcloud/occ \ - docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" + docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" \ docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade" fi @@ -105,7 +104,6 @@ elif [[ ${NC_INSTALL} == "y" ]]; then curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && rm nextcloud.tar.bz2 \ - #&& rm -rf ./data/web/nextcloud/updater \ && mkdir -p ./data/web/nextcloud/data \ && chmod +x ./data/web/nextcloud/occ From 82f7cab2599c24eb742881ae19fb9e5e3bfe3ec6 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 1 Apr 2019 22:17:44 +0200 Subject: [PATCH 101/439] [Web] Fix totp qr code, fixes #2490 --- data/web/inc/ajax/qr_gen.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/ajax/qr_gen.php b/data/web/inc/ajax/qr_gen.php index 1c543ebe..a39c3f64 100644 --- a/data/web/inc/ajax/qr_gen.php +++ b/data/web/inc/ajax/qr_gen.php @@ -7,7 +7,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { } if (isset($_GET['token']) && ctype_alnum($_GET['token'])) { - echo $tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $totp_secret); + echo $tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']); } ?> From bb12ce9edc0cd5a51a957e9dafa2c7925e16b83d Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 1 Apr 2019 22:46:13 +0200 Subject: [PATCH 102/439] [Nginx] Fix site when ALLOW_ADMIN_EMAIL_LOGIN=y and reverse proxy is used, fixes #2489 --- data/conf/nginx/site.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 4c6d1daa..978263be 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -34,6 +34,7 @@ server { client_max_body_size 0; + listen 127.0.0.1:65580; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/conf.d/server_name.active; @@ -149,7 +150,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header Content-Length ""; - proxy_pass http://127.0.0.1:80/sogo-auth; + proxy_pass http://127.0.0.1:65580/sogo-auth; proxy_pass_request_body off; } From fae34b8a897c3f7c8656bae46a5b47cd71dd3778 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 1 Apr 2019 22:52:45 +0200 Subject: [PATCH 103/439] I'm an idiot --- data/conf/nginx/site.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 978263be..ccb94fa0 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -34,7 +34,7 @@ server { client_max_body_size 0; - listen 127.0.0.1:65580; + listen 127.0.0.1:65510; include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/conf.d/server_name.active; @@ -150,7 +150,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header Content-Length ""; - proxy_pass http://127.0.0.1:65580/sogo-auth; + proxy_pass http://127.0.0.1:65510/sogo-auth; proxy_pass_request_body off; } From 5ddd37956f40b38d2ed8389bb21d1427ab31fec6 Mon Sep 17 00:00:00 2001 From: Carlos Ferreira Date: Thu, 4 Apr 2019 21:03:50 +0200 Subject: [PATCH 104/439] Fix HTML bug with French language --- data/web/lang/lang.fr.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/lang/lang.fr.php b/data/web/lang/lang.fr.php index 6d40a02e..48b51ae4 100644 --- a/data/web/lang/lang.fr.php +++ b/data/web/lang/lang.fr.php @@ -132,7 +132,7 @@ $lang['user']['spamfilter_red'] = "Rouge: Ce message est un pourriel et sera rej $lang['user']['spamfilter_hint'] = "La première valeur décrit le \"score bas de pourriel\", la seconde représente le \"score haut de pourriel\"."; $lang['user']['spamfilter_table_domain_policy'] = "N/D (Politique du domaine)"; -$lang['user']['tls_policy_warning'] = "Attention : Si vous décidez d'imposer le chiffrement des échanges de courriel, vous pouvez perdre des messages.
    Les messages qui ne respecte pas la politique seront rejetés avec un message d'erreur définitif par le système de courriel.
    Cette option s'applique à votre adresse de courriel principale (identifiant de connexion), tous les alias de domaine ainsi que les alias d'adresse qui n'ont que cette unique boîte comme destinataire."; +$lang['user']['tls_policy_warning'] = "Attention : Si vous décidez d'imposer le chiffrement des échanges de courriel, vous pouvez perdre des messages.
    Les messages qui ne respectent pas la politique seront rejetés avec un message d'erreur définitif par le système de courriel.
    Cette option s'applique à votre adresse de courriel principale (identifiant de connexion), tous les alias de domaine ainsi que les alias d'adresse qui n'ont que cette unique boîte comme destinataire."; $lang['user']['tls_policy'] = "Politique de chiffrement"; $lang['user']['tls_enforce_in'] = "Imposer le TLS entrant"; $lang['user']['tls_enforce_out'] = "Imposer le TLS sortant"; From a9c1b480c5fff5cb6b1d04934d2233e32bafa7c8 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 5 Apr 2019 12:09:18 +0200 Subject: [PATCH 105/439] [Solr] Make entrypoint executable --- data/Dockerfiles/solr/docker-entrypoint.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 data/Dockerfiles/solr/docker-entrypoint.sh diff --git a/data/Dockerfiles/solr/docker-entrypoint.sh b/data/Dockerfiles/solr/docker-entrypoint.sh old mode 100644 new mode 100755 From 794c2080ec8c9e16a4583ae0ad1a8ccd4ff82842 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 5 Apr 2019 12:09:46 +0200 Subject: [PATCH 106/439] [ClamAV] Increase watchdog clamd-mailcow thresholds --- data/Dockerfiles/watchdog/watchdog.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index cd9920a1..f56d3c4c 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -247,7 +247,7 @@ postfix_checks() { clamd_checks() { err_count=0 diff_c=0 - THRESHOLD=5 + THRESHOLD=15 # 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 @@ -263,7 +263,7 @@ clamd_checks() { sleep 1 else diff_c=0 - sleep $(( ( RANDOM % 30 ) + 10 )) + sleep $(( ( RANDOM % 30 ) + 30 )) fi done return 1 From 5284f0a52ff3f57ce359ab92114e6f07d06e1622 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 5 Apr 2019 12:10:02 +0200 Subject: [PATCH 107/439] [Compose] Update watchdog and ClamAV images --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fbd90b00..cbef6a40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -359,7 +359,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.39 + image: mailcow/watchdog:1.40 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog @@ -407,7 +407,7 @@ services: - dockerapi solr-mailcow: - image: mailcow/solr:1.4 + image: mailcow/solr:1.5 build: ./data/Dockerfiles/solr restart: always volumes: From e94c9e1ca60a92b3df08d94d340f3b73e5aba975 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 5 Apr 2019 13:08:16 +0200 Subject: [PATCH 108/439] [Web] Fix class for full mailbox --- data/web/inc/functions.mailbox.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 70dd0e1f..88b8e91e 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3023,12 +3023,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if ($mailboxdata['percent_in_use'] === '- ') { $mailboxdata['percent_class'] = "info"; } - elseif ($mailboxdata['percent_in_use'] >= 75) { - $mailboxdata['percent_class'] = "warning"; - } elseif ($mailboxdata['percent_in_use'] >= 90) { $mailboxdata['percent_class'] = "danger"; } + elseif ($mailboxdata['percent_in_use'] >= 75) { + $mailboxdata['percent_class'] = "warning"; + } else { $mailboxdata['percent_class'] = "success"; } From c8047b9555dbf2066f37f9c2ecd966b471cd69eb Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 14 Apr 2019 13:01:40 +0200 Subject: [PATCH 109/439] [Web] Change session timeout handling [Rspamd] Add missing spamassassin.conf --- data/conf/rspamd/local.d/spamassassin.conf | 1 + data/web/inc/sessions.inc.php | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 data/conf/rspamd/local.d/spamassassin.conf diff --git a/data/conf/rspamd/local.d/spamassassin.conf b/data/conf/rspamd/local.d/spamassassin.conf new file mode 100644 index 00000000..663e987c --- /dev/null +++ b/data/conf/rspamd/local.d/spamassassin.conf @@ -0,0 +1 @@ +ruleset = "/etc/rspamd/custom/sa-rules-heinlein"; diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php index ddc997d1..a94d438c 100644 --- a/data/web/inc/sessions.inc.php +++ b/data/web/inc/sessions.inc.php @@ -21,7 +21,7 @@ elseif (isset($_SERVER['HTTPS'])) { else { $IS_HTTPS = false; } -// session_set_cookie_params($SESSION_LIFETIME, '/', '', $IS_HTTPS, true); + if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } @@ -35,6 +35,13 @@ if (!isset($_SESSION['SESS_REMOTE_UA'])) { $_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT']; } +// Keep session active +if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) { + session_unset(); + session_destroy(); +} +$_SESSION['LAST_ACTIVITY'] = time(); + // API if (!empty($_SERVER['HTTP_X_API_KEY'])) { $stmt = $pdo->prepare("SELECT `allow_from` FROM `api` WHERE `api_key` = :api_key AND `active` = '1';"); @@ -72,8 +79,6 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) { die(); } } -// Update session cookie -// setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME); // Handle logouts if (isset($_POST["logout"])) { From 1188f45d27a88dd764be6aae5024c31f3a0a2314 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 14 Apr 2019 13:25:20 +0200 Subject: [PATCH 110/439] [Compose] Update Rspamd image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index cbef6a40..2a60c029 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.38 + image: mailcow/rspamd:1.39 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From cd88165282da09f2d3dd393201b6a2489ed5f542 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 14 Apr 2019 13:26:53 +0200 Subject: [PATCH 111/439] [Nextcloud] Always install under subdomain, minor changes to site config and install script --- data/assets/nextcloud/nextcloud.conf | 6 +-- data/assets/nextcloud/occ | 2 +- data/assets/nextcloud/site.nextcloud.custom | 44 --------------------- helper-scripts/nextcloud.sh | 42 ++++++++------------ 4 files changed, 21 insertions(+), 73 deletions(-) delete mode 100644 data/assets/nextcloud/site.nextcloud.custom diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf index cf90a32b..243cc406 100644 --- a/data/assets/nextcloud/nextcloud.conf +++ b/data/assets/nextcloud/nextcloud.conf @@ -75,7 +75,7 @@ server { deny all; } - location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) { + location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|oc[ms]-provider/.+)\.php(?:$|/) { fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; @@ -90,12 +90,12 @@ server { fastcgi_read_timeout 1200; } - location ~ ^/(?:updater|ocs-provider)(?:$|/) { + location ~ ^/(?:updater|oc[ms]-provider)(?:$|/) { try_files $uri/ =404; index index.php; } - location ~ \.(?:css|js|woff|svg|gif)$ { + location ~ \.(?:css|js|woff2?|svg|gif)$ { try_files $uri /index.php$uri$is_args$args; add_header Cache-Control "public, max-age=15778463"; add_header X-Content-Type-Options nosniff; diff --git a/data/assets/nextcloud/occ b/data/assets/nextcloud/occ index 2ae08001..5113ac01 100755 --- a/data/assets/nextcloud/occ +++ b/data/assets/nextcloud/occ @@ -1,2 +1,2 @@ #!/bin/bash -docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ ${@} +docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) php /web/nextcloud/occ ${@} diff --git a/data/assets/nextcloud/site.nextcloud.custom b/data/assets/nextcloud/site.nextcloud.custom deleted file mode 100644 index 6ac29902..00000000 --- a/data/assets/nextcloud/site.nextcloud.custom +++ /dev/null @@ -1,44 +0,0 @@ - location ^~ /nextcloud { - location /nextcloud { - rewrite ^ /nextcloud/index.php$uri; - } - location ~ ^/nextcloud/(?:build|tests|config|lib|3rdparty|templates|data)/ { - deny all; - } - location ~ ^/nextcloud/(?:\.|autotest|occ|issue|indie|db_|console) { - deny all; - } - location ~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) { - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param HTTPS on; - fastcgi_param modHeadersAvailable true; - fastcgi_param front_controller_active true; - fastcgi_pass phpfpm:9002; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - client_max_body_size 0; - fastcgi_read_timeout 1200; - } - location ~ ^/nextcloud/(?:updater|ocs-provider)(?:$|/) { - try_files $uri/ =404; - index index.php; - } - location ~ \.(?:css|js|woff|svg|gif)$ { - try_files $uri /nextcloud/index.php$uri$is_args$args; - add_header Cache-Control "public, max-age=15778463"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options "SAMEORIGIN"; - access_log off; - } - location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ { - try_files $uri /nextcloud/index.php$uri$is_args$args; - access_log off; - } - } diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 3ad9c9b4..131b7639 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -83,20 +83,13 @@ elif [[ ${NC_UPDATE} == "y" ]]; then fi elif [[ ${NC_INSTALL} == "y" ]]; then - NC_TYPE= - while [[ ! ${NC_TYPE} =~ ^subfolder$|^subdomain$ ]]; do - read -p "Configure as subdomain or subfolder? [subdomain/subfolder] " NC_TYPE + NC_SUBD= + while [[ -z ${NC_SUBD} ]]; do + read -p "Subdomain to run Nextcloud from [format: nextcloud.domain.tld]: " NC_SUBD done - - if [[ ${NC_TYPE} == "subdomain" ]]; then - NC_SUBD= - while [[ -z ${NC_SUBD} ]]; do - read -p "Which subdomain? [format: nextcloud.domain.tld] " NC_SUBD - done - if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then - read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL - [[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; } - fi + if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then + read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL + [[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; } fi ADMIN_NC_PASS=$( Date: Sun, 14 Apr 2019 20:37:38 +0200 Subject: [PATCH 112/439] [Rspamd] Improve spoofing detection --- data/conf/rspamd/local.d/composites.conf | 4 ++++ data/conf/rspamd/local.d/multimap.conf | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index d775c4f6..46f06743 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -16,3 +16,7 @@ SOGO_CONTACT_EXCLUDE_FWD_HOST { SOGO_CONTACT_SPOOFED { expression = "(R_SPF_PERMFAIL | R_SPF_SOFTFAIL | R_SPF_FAIL) & ~SOGO_CONTACT"; } +SPOOFED_UNAUTH { + expression = "UNAUTH_MAILCOW_DOMAIN & !MAILCOW_WHITE & !R_SPF_ALLOW"; + score = 5.0; +} diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index 7752b813..6ce56f7a 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -83,3 +83,11 @@ GLOBAL_RCPT_BL { prefilter = true; action = "reject"; } + +UNAUTH_MAILCOW_DOMAIN { + type = "header"; + header = "from"; + filter = "email:domain"; + nflags = ["authenticated"]; + map = "redis://DOMAIN_MAP"; +} From ef5cf8130878e1706cdab93611c0f15bc7ac16bd Mon Sep 17 00:00:00 2001 From: sriccio Date: Wed, 17 Apr 2019 10:36:39 +0200 Subject: [PATCH 113/439] [rspamd] Allow to easily use custom rspamd lua plugins Since rspamd 1.9.2 we'll be able to load custom modules from plugins.d directory. This allow to add and configure plugins easily from the data/conf/rspamd/plugins.d Also loading config for custom plugins need rspamd.conf.local or optionally rspamd.conf.override. I added support for this in the docker-compose.yml Idea came while i was writing a custom plugin for Cyren antispam gateway, which can be found here: https://github.com/sriccio/rspamd-plugins --- data/conf/rspamd/plugins.d/README.md | 1 + data/conf/rspamd/rspamd.conf.local | 1 + data/conf/rspamd/rspamd.conf.override | 2 ++ docker-compose.yml | 3 +++ 4 files changed, 7 insertions(+) create mode 100644 data/conf/rspamd/plugins.d/README.md create mode 100644 data/conf/rspamd/rspamd.conf.local create mode 100644 data/conf/rspamd/rspamd.conf.override diff --git a/data/conf/rspamd/plugins.d/README.md b/data/conf/rspamd/plugins.d/README.md new file mode 100644 index 00000000..1516cf2d --- /dev/null +++ b/data/conf/rspamd/plugins.d/README.md @@ -0,0 +1 @@ +This is where you should copy any rspamd custom module diff --git a/data/conf/rspamd/rspamd.conf.local b/data/conf/rspamd/rspamd.conf.local new file mode 100644 index 00000000..9f2f8f1d --- /dev/null +++ b/data/conf/rspamd/rspamd.conf.local @@ -0,0 +1 @@ +# rspamd.conf.local diff --git a/data/conf/rspamd/rspamd.conf.override b/data/conf/rspamd/rspamd.conf.override new file mode 100644 index 00000000..d033e8e2 --- /dev/null +++ b/data/conf/rspamd/rspamd.conf.override @@ -0,0 +1,2 @@ +# rspamd.conf.override + diff --git a/docker-compose.yml b/docker-compose.yml index cbef6a40..c9443b88 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -82,7 +82,10 @@ services: - ./data/conf/rspamd/custom/:/etc/rspamd/custom - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d + - ./data/conf/rspamd/plugins.d/:/etc/rspamd/plugins.d - ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro + - ./data/conf/rspamd/rspamd.conf.local:/etc/rspamd/rspamd.conf.local + - ./data/conf/rspamd/rspamd.conf.override:/etc/rspamd/rspamd.conf.override - rspamd-vol-1:/var/lib/rspamd restart: always dns: From 5be4885c159a4a6633faa5040edc736275b80bab Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 18 Apr 2019 22:09:26 +0200 Subject: [PATCH 114/439] [Watchdog] Send mail when starting [Compose] Update watchdog and remove oom check for compatibility --- data/Dockerfiles/watchdog/Dockerfile | 1 + data/Dockerfiles/watchdog/watchdog.sh | 6 +++++- docker-compose.yml | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 7ab29b68..96c8976c 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -12,6 +12,7 @@ RUN apk add --update \ coreutils \ jq \ fcgi \ + openssl \ nagios-plugins-mysql \ nagios-plugins-dns \ nagios-plugins-disk \ diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index f56d3c4c..a5d37617 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -58,9 +58,10 @@ function mail_error() { log_msg "Cannot determine MX for ${rcpt}, skipping email notification..." return 1 fi + [[ ${1} == "watchdog-mailcow" ]] && SUBJECT="Watchdog started" || SUBJECT="Watchdog: ${1} hit the error rate limit" [ -f "/tmp/${1}" ] && ATTACH="--attach /tmp/${1}@text/plain" || ATTACH= ./smtp-cli --missing-modules-ok \ - --subject="Watchdog: ${1} hit the error rate limit" \ + --subject="${SUBJECT}" \ --body-plain="${BODY}" \ --to=${rcpt} \ --from="watchdog@${MAILCOW_HOSTNAME}" \ @@ -447,6 +448,9 @@ Empty return 1 } +# Notify about start +[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "watchdog-mailcow" "Watchdog started monitoring mailcow." + # Create watchdog agents ( while true; do diff --git a/docker-compose.yml b/docker-compose.yml index 2a60c029..804ba749 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -359,14 +359,14 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.40 + image: mailcow/watchdog:1.41 # Debug #command: /watchdog.sh build: ./data/Dockerfiles/watchdog - oom_kill_disable: true volumes: - rspamd-vol-1:/var/lib/rspamd - mysql-socket-vol-1:/var/run/mysqld/ + - ./data/assets/ssl:/etc/ssl/mail/:ro restart: always environment: - LOG_LINES=${LOG_LINES:-9999} From be2877c875ea936d4b9aa9eee0621378d754fe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristia=CC=81n=20Feldsam?= Date: Sat, 20 Apr 2019 21:50:24 +0200 Subject: [PATCH 115/439] JSON API Consume json in request body. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Draft docs https://feldhostmailhosting.docs.apiary.io Signed-off-by: Kristián Feldsam --- data/web/json_api.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/data/web/json_api.php b/data/web/json_api.php index 53ecf520..8e6407b7 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -64,6 +64,42 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u $object = (isset($query[2])) ? $query[2] : null; $extra = (isset($query[3])) ? $query[3] : null; + // accept json in request body + if($_SERVER['HTTP_CONTENT_TYPE'] === 'application/json') { + $request = file_get_contents('php://input'); + $requestDecoded = json_decode($request, true); + + // check for valid json + if($action != 'get' && $requestDecoded === null) { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Request body doesn\'t contain valid json!' + )); + exit; + } + + // add + if($action == 'add') { + $_POST['attr'] = $request; + } + + // edit + if($action == 'edit') { + $_POST['attr'] = json_encode($requestDecoded['attr']); + $_POST['items'] = json_encode($requestDecoded['items']); + } + + // delete + if($action == 'delete') { + $_POST['items'] = $request; + } + + unset($_SESSION['return']); + unset($_SESSION['success']); + unset($_SESSION['danger']); + unset($_SESSION['error']); + } + $request_incomplete = json_encode(array( 'type' => 'error', 'msg' => 'Cannot find attributes in post data' From 9b303dcc0eb90896668db5159d565172dd869754 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 24 Apr 2019 14:46:45 +0200 Subject: [PATCH 116/439] [Dovecot] Set default_vsz_limit = 1024 M [Web] Form cache for user passwd change modal disabled --- data/conf/dovecot/dovecot.conf | 1 + data/web/modals/user.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index cef0a19a..1a940537 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -391,3 +391,4 @@ imap_max_line_length = 2 M !include_try /usr/local/etc/dovecot/extra.conf !include_try /usr/local/etc/dovecot/sogo-sso.conf default_client_limit = 10400 +default_vsz_limit = 1024 M diff --git a/data/web/modals/user.php b/data/web/modals/user.php index c45283d2..b89b8ac0 100644 --- a/data/web/modals/user.php +++ b/data/web/modals/user.php @@ -178,7 +178,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
  2. - @@ -401,7 +400,6 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    -
    diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh deleted file mode 100755 index 8505077f..00000000 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/compile_mailparse.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -git clone https://github.com/php/pecl-mail-mailparse.git -cd pecl-mail-mailparse -phpize -./configure -sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c -make -sudo mv modules/mailparse.so /home/travis/.phpenv/versions/7.3.2/lib/php/extensions/no-debug-zts-20180731/ -echo 'extension=mailparse.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file From 17918b3e214d19fc4cc31da74511b3fe6b7038d1 Mon Sep 17 00:00:00 2001 From: Howaner Date: Wed, 1 May 2019 00:56:12 +0200 Subject: [PATCH 127/439] Added domain alias handling to quarantine mails and added recipients row to quarantine mail display If a mail is sent to a domain alias domain and rejected, mailcow does not currently store the mail in quarantine. This commit adds domain alias handling to the reject code and should fix this behavior. Also added displaying of recipient addresses into the quarantine mail dialog to be able to see what mail address was "leaked". --- data/conf/rspamd/meta_exporter/pipe.php | 22 ++++++++++++++++++++-- data/web/css/site/quarantine.css | 10 +++++++++- data/web/inc/ajax/qitem_details.php | 20 +++++++++++++++++++- data/web/js/site/quarantine.js | 10 +++++++++- data/web/lang/lang.ca.php | 1 + data/web/lang/lang.cs.php | 1 + data/web/lang/lang.de.php | 1 + data/web/lang/lang.en.php | 1 + data/web/lang/lang.lv.php | 1 + data/web/lang/lang.nl.php | 1 + data/web/modals/quarantine.php | 4 ++++ 11 files changed, 67 insertions(+), 5 deletions(-) diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 692a0c2e..31f6037f 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -131,6 +131,14 @@ foreach (json_decode($rcpts, true) as $rcpt) { )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } $gotos_array = explode(',', $gotos); $loop_c = 0; @@ -159,8 +167,18 @@ foreach (json_decode($rcpts, true) as $rcpt) { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - error_log("QUARANTINE: quarantine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch); - $goto_branch_array = explode(',', $goto_branch); + if ($goto_branch) { + error_log("QUARANTINE: quarantine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("QUARANTINE: quarantine pipe: goto domain " . $parsed_gto['domain'] . " is a domain alias branch for " . $goto_branch); + $goto_branch_array = array($parsed_gto['local'] . '@' . $goto_branch); + } + } } } // goto item was processed, unset diff --git a/data/web/css/site/quarantine.css b/data/web/css/site/quarantine.css index 5d0fa1ef..3a3718d1 100644 --- a/data/web/css/site/quarantine.css +++ b/data/web/css/site/quarantine.css @@ -48,4 +48,12 @@ table.footable>tbody>tr.footable-empty>td { background-color: #d4d4d4; border-radius: 50%; display: inline-block; -} \ No newline at end of file +} + +span.mail-address-item { + background-color: #f5f5f5; + border-radius: 4px; + border: 1px solid #ccc; + padding: 2px 7px; + margin-right: 7px; +} diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php index 71d32cc9..3c82ee6a 100644 --- a/data/web/inc/ajax/qitem_details.php +++ b/data/web/inc/ajax/qitem_details.php @@ -3,8 +3,9 @@ session_start(); header("Content-Type: application/json"); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; if (!isset($_SESSION['mailcow_cc_role'])) { - exit(); + exit(); } + function rrmdir($src) { $dir = opendir($src); while(false !== ( $file = readdir($dir)) ) { @@ -21,6 +22,13 @@ function rrmdir($src) { closedir($dir); rmdir($src); } +function addAddresses(&$list, $mail, $headerName) { + $addresses = $mail->getAddresses($headerName); + foreach ($addresses as $address) { + $list[] = array('address' => $address['address'], 'type' => $headerName); + } +} + if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { $tmpdir = '/tmp/' . $_GET['id'] . '/'; $mailc = quarantine('details', $_GET['id']); @@ -36,6 +44,16 @@ if (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { $html2text = new Html2Text\Html2Text(); // Load msg to parser $mail_parser->setText($mailc['msg']); + + // Get mail recipients + { + $recipientsList = array(); + addAddresses($recipientsList, $mail_parser, 'to'); + addAddresses($recipientsList, $mail_parser, 'cc'); + addAddresses($recipientsList, $mail_parser, 'bcc'); + $data['recipients'] = $recipientsList; + } + // Get text/plain content $data['text_plain'] = $mail_parser->getMessageBody('text'); // Get html content and convert to text diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js index 4df1dbd4..c4d19cf9 100644 --- a/data/web/js/site/quarantine.js +++ b/data/web/js/site/quarantine.js @@ -87,8 +87,16 @@ jQuery(function($){ $('#qid_detail_text').text(data.text_plain); $('#qid_detail_text_from_html').text(data.text_html); + $('#qid_detail_recipients').html(''); + if (typeof data.recipients !== 'undefined') { + $.each(data.recipients, function(index, value) { + var displayStr = value.address + (value.type != 'to' ? (' (' + value.type.toUpperCase() + ')') : ''); + $('#qid_detail_recipients').append('' + displayStr + ''); + }); + } + + var qAtts = $("#qid_detail_atts"); if (typeof data.attachments !== 'undefined') { - qAtts = $("#qid_detail_atts"); qAtts.text(''); $.each(data.attachments, function(index, value) { qAtts.append( diff --git a/data/web/lang/lang.ca.php b/data/web/lang/lang.ca.php index 91072ecf..63722661 100644 --- a/data/web/lang/lang.ca.php +++ b/data/web/lang/lang.ca.php @@ -498,6 +498,7 @@ $lang['quarantine']['show_item'] = "Mostrar"; $lang['quarantine']['check_hash'] = "Comprovar el hash del fitxer a VT"; $lang['quarantine']['qitem'] = "Element en quarantena"; $lang['quarantine']['subj'] = "Assumpte"; +$lang['quarantine']['recipients'] = "Recipients"; $lang['quarantine']['text_plain_content'] = "Contingut (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Contingut (a partir del HTML)"; $lang['quarantine']['atts'] = "Adjunts"; diff --git a/data/web/lang/lang.cs.php b/data/web/lang/lang.cs.php index ca2a4684..efcac064 100644 --- a/data/web/lang/lang.cs.php +++ b/data/web/lang/lang.cs.php @@ -712,6 +712,7 @@ $lang['quarantine']['show_item'] = "Zobrazit položku"; $lang['quarantine']['check_hash'] = "Hledat hash @ VT souboru"; $lang['quarantine']['qitem'] = "Položka v karanténě"; $lang['quarantine']['subj'] = "Předmět"; +$lang['quarantine']['recipients'] = "Příjemci"; $lang['quarantine']['text_plain_content'] = "Obsah (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Obsah (konvertované html)"; $lang['quarantine']['atts'] = "Přílohy"; diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index aeb38c9b..63b33614 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -732,6 +732,7 @@ $lang['quarantine']['show_item'] = "Details"; $lang['quarantine']['check_hash'] = "Checksumme auf VirusTotal suchen"; $lang['quarantine']['qitem'] = "Quarantäneeintrag"; $lang['quarantine']['subj'] = "Betreff"; +$lang['quarantine']['recipients'] = "Empfänger"; $lang['quarantine']['text_plain_content'] = "Inhalt (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Inhalt (html, konvertiert)"; $lang['quarantine']['atts'] = "Anhänge"; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index be92b389..359990a2 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -754,6 +754,7 @@ $lang['quarantine']['show_item'] = "Show item"; $lang['quarantine']['check_hash'] = "Search file hash @ VT"; $lang['quarantine']['qitem'] = "Quarantine item"; $lang['quarantine']['subj'] = "Subject"; +$lang['quarantine']['recipients'] = "Recipients"; $lang['quarantine']['text_plain_content'] = "Content (text/plain)"; $lang['quarantine']['text_from_html_content'] = "Content (converted html)"; $lang['quarantine']['atts'] = "Attachments"; diff --git a/data/web/lang/lang.lv.php b/data/web/lang/lang.lv.php index 2fe3290e..50e9884f 100644 --- a/data/web/lang/lang.lv.php +++ b/data/web/lang/lang.lv.php @@ -494,6 +494,7 @@ $lang['quarantine']['show_item'] = "Parādīt vienumus"; $lang['quarantine']['check_hash'] = "Meklēt faila hašu @ VT"; $lang['quarantine']['qitem'] = "Karantīnas vienumi"; $lang['quarantine']['subj'] = "Priekšmets"; +$lang['quarantine']['recipients'] = "Adresāts"; $lang['quarantine']['text_plain_content'] = "Saturs (teksts/vienkāršs)"; $lang['quarantine']['text_from_html_content'] = "Saturs (konvertēts html)"; $lang['quarantine']['atts'] = "Pielikumi"; diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php index 0c73ee99..1d5bb54b 100644 --- a/data/web/lang/lang.nl.php +++ b/data/web/lang/lang.nl.php @@ -731,6 +731,7 @@ $lang['quarantine']['show_item'] = "Laat item zien"; $lang['quarantine']['check_hash'] = "Zoek bestandshash op in VT"; $lang['quarantine']['qitem'] = "Quarantaine-item"; $lang['quarantine']['subj'] = "Onderwerp"; +$lang['quarantine']['recipients'] = "Ontvangers"; $lang['quarantine']['text_plain_content'] = "Inhoud (tekst)"; $lang['quarantine']['text_from_html_content'] = "Inhoud (geconverteerde html)"; $lang['quarantine']['atts'] = "Bijlagen"; diff --git a/data/web/modals/quarantine.php b/data/web/modals/quarantine.php index e1929927..0d091163 100644 --- a/data/web/modals/quarantine.php +++ b/data/web/modals/quarantine.php @@ -17,6 +17,10 @@ if (!isset($_SESSION['mailcow_cc_role'])) {

    +
    + +

    +
    
    
    From 5bb7b5b368efd32bb03c8ff8da8c85a09fb8b29c Mon Sep 17 00:00:00 2001
    From: Howaner 
    Date: Wed, 1 May 2019 01:03:16 +0200
    Subject: [PATCH 128/439] Fixed XSS
    
    ---
     data/web/js/site/quarantine.js | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js
    index c4d19cf9..2c6f58c1 100644
    --- a/data/web/js/site/quarantine.js
    +++ b/data/web/js/site/quarantine.js
    @@ -90,8 +90,9 @@ jQuery(function($){
             $('#qid_detail_recipients').html('');
             if (typeof data.recipients !== 'undefined') {
               $.each(data.recipients, function(index, value) {
    -            var displayStr = value.address + (value.type != 'to' ? (' (' + value.type.toUpperCase() + ')') : '');
    -            $('#qid_detail_recipients').append('' + displayStr + '');
    +            var elem = $('');
    +            elem.text(value.address + (value.type != 'to' ? (' (' + value.type.toUpperCase() + ')') : ''));
    +            $('#qid_detail_recipients').append(elem);
               });
             }
     
    
    From 9dc7d05fa4dfe147b0690785d4fd9bbe92ae52b4 Mon Sep 17 00:00:00 2001
    From: Geitenijs <40541903+Geitenijs@users.noreply.github.com>
    Date: Wed, 1 May 2019 17:54:27 +0200
    Subject: [PATCH 129/439] Update lang.nl.php
    
    ---
     data/web/lang/lang.nl.php | 9 ++++++++-
     1 file changed, 8 insertions(+), 1 deletion(-)
    
    diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php
    index 1d5bb54b..89243b77 100644
    --- a/data/web/lang/lang.nl.php
    +++ b/data/web/lang/lang.nl.php
    @@ -23,6 +23,8 @@ $lang['footer']['cancel'] = 'Annuleren';
     $lang['footer']['hibp_nok'] = 'Dit is een potentieel onveilig wachtwoord!';
     $lang['footer']['hibp_ok'] = 'Dit wachtwoord is relatief veilig';
     
    +$lang['danger']['transport_dest_exists'] = "Transportbestemming bestaat reeds";
    +$lang['danger']['unlimited_quota_acl'] = "Onbeperkt quotum geweigerd door toegangscontrole";
     $lang['danger']['mysql_error'] = "MySQL-fout: %s";
     $lang['danger']['redis_error'] = "Redis-fout: %s";
     $lang['danger']['unknown_tfa_method'] = "Onbekende tweefactorauthenticatiemethode";
    @@ -410,6 +412,7 @@ $lang['acl']['bcc_maps'] = 'BCC-kaarten';
     $lang['acl']['filters'] = 'Filters';
     $lang['acl']['ratelimit'] = 'Ratelimit';
     $lang['acl']['recipient_maps'] = 'Ontvanger-kaarten';
    +$lang['acl']['unlimited_quota'] = 'Onbeperkt quotum voor postvakken';
     $lang['acl']['prohibited'] = 'Toegang geweigerd';
     
     $lang['mailbox']['quarantine_notification'] = 'Quarantaine-notificaties';
    @@ -618,7 +621,11 @@ $lang['admin']['forwarding_hosts'] = 'Doorstuurhosts';
     $lang['admin']['forwarding_hosts_hint'] = 'Inkomende berichten worden onvoorwaardelijk geaccepteerd vanaf iedere host hieronder vermeld. Deze hosts worden hierdoor niet gecontroleerd op DNSBLs, en zullen de greylisting omzeilen. Spam wordt daarentegen zoals gebruikelijk in de spamfolder geplaatst. Dit wordt vaak gebruikt om mailservers te specificeren die mails doorsturen naar deze Mailcow-server.';
     $lang['admin']['forwarding_hosts_add_hint'] = 'Het is mogelijk om IPv4- of IPv6-adressen, netwerken in CIDR-notatie, hostnames (worden omgezet naar IP-adressen) of domeinnamen (worden tevens omgezet naar IP-adressen of, bij gebrek daaraan, MX-records) op te geven.';
     $lang['admin']['relayhosts_hint'] = 'Stel afzender-afhankelijke transportkaarten in om deze te kunnen gebruiken bij de configuratie van een domein.
    De transportservice is altijd "smtp:". Er wordt rekening gehouden met het uitgaande versleutelingsbeleid van individuele gebruikers.'; -$lang['admin']['transports_hint'] = 'Een transportkaart wordt boven een afzender-afhankelijke transportkaart verkozen.
    Het uitgaande versleutelingsbeleid van individuele gebruikers wordt genegeerd en kan enkel worden gehandhaafd doormiddel van globaal versleutelingsbeleid. De transportservice is altijd "smtp:".
    Om de inloggegevens van een (voorbeeld) nexthop "[host]:25" te bepalen, zoekt Postfix altijd naar "nexthop" voodat er wordt gekeken naar "[nexthop]:25". Dit maakt het onmogelijk om "nexthop" en "[nexthop]:25" tegelijkertijd te gebruiken.'; +$lang['admin']['transports_hint'] = '→ Een transportkaart wordt boven een afzender-afhankelijke transportkaart verkozen.
    + → Het uitgaande versleutelingsbeleid van individuele gebruikers wordt genegeerd en kan enkel worden gehandhaafd doormiddel van globaal versleutelingsbeleid.
    + → De transportservice is altijd "smtp:".
    + → Adressen overeenkomend met "/localhost$/" zullen altijd via "local:" getransporteerd worden, hierdoor zullen "*"-bestemmingen niet van toepassing zijn op deze adressen.
    + → Om de inloggegevens van een (voorbeeld) nexthop "[host]:25" te bepalen, zoekt Postfix altijd naar "nexthop" voodat er wordt gekeken naar "[nexthop]:25". Dit maakt het onmogelijk om "nexthop" en "[nexthop]:25" tegelijkertijd te gebruiken.'; $lang['admin']['add_relayhost_hint'] = 'Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!'; $lang['admin']['add_transports_hint'] = 'Wees ervan bewust dat de authenticatiedata onversleuteld wordt opgeslagen!'; $lang['admin']['host'] = 'Host'; From b55ac86d6b3b328f5758b94d06242461cbbbf555 Mon Sep 17 00:00:00 2001 From: Sven Gottwald <2502366+svengo@users.noreply.github.com> Date: Wed, 1 May 2019 20:28:11 +0200 Subject: [PATCH 130/439] Update Dovecot to v2.3.6 and Pigeonhole to v0.5.6 --- data/Dockerfiles/dovecot/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 9d95b4e0..31f408da 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.3.5.2 -ENV PIGEONHOLE_VERSION 0.5.5 +ENV DOVECOT_VERSION 2.3.6 +ENV PIGEONHOLE_VERSION 0.5.6 RUN apt-get update && apt-get -y --no-install-recommends install \ automake \ From 28c8c53a6e5d8fcdcf148bf9bee87d8c866a364b Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 1 May 2019 22:50:38 +0200 Subject: [PATCH 131/439] [Rspamd] meta_exporter: return false if not matched [Compose] Update Dovecot image --- data/conf/rspamd/local.d/metadata_exporter.conf | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/rspamd/local.d/metadata_exporter.conf b/data/conf/rspamd/local.d/metadata_exporter.conf index afe5c7e1..eaf9a5b2 100644 --- a/data/conf/rspamd/local.d/metadata_exporter.conf +++ b/data/conf/rspamd/local.d/metadata_exporter.conf @@ -20,7 +20,7 @@ return function(task) if ratelimited then return true end - return + return false end EOD; } diff --git a/docker-compose.yml b/docker-compose.yml index 5341f4eb..51dc9e0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -169,7 +169,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.68 + image: mailcow/dovecot:1.69 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 64382c7ece81e81a8717c40df72bb4d1ff77d9ef Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 1 May 2019 23:17:10 +0200 Subject: [PATCH 132/439] [Config] Clarification about mailcow_hostname --- generate_config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_config.sh b/generate_config.sh index d241a9ab..6427643a 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -26,7 +26,7 @@ fi echo "Press enter to confirm the detected value '[value]' where applicable or enter a custom value." while [ -z "${MAILCOW_HOSTNAME}" ]; do - read -p "Hostname (FQDN): " -e MAILCOW_HOSTNAME + read -p "Mail server hostname (FQDN) - this is not your mail domain, but your mail servers hostname: " -e MAILCOW_HOSTNAME DOTS=${MAILCOW_HOSTNAME//[^.]}; if [ ${#DOTS} -lt 2 ] && [ ! -z ${MAILCOW_HOSTNAME} ]; then echo "${MAILCOW_HOSTNAME} is not a FQDN" From 472a99ff00da3e935564a4fb98ed3fc187c5421d Mon Sep 17 00:00:00 2001 From: emericklaw Date: Fri, 3 May 2019 13:31:10 +0100 Subject: [PATCH 133/439] Handle mobileconfig display names with special characters If the account display name contained special characters like & the mobileconfig would fail to import on the iOS device. --- data/web/mobileconfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index ade4f606..abdf2cb8 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -22,7 +22,7 @@ try { $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); $stmt->execute(array(':username' => $email)); $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - $displayname = empty($MailboxData['name']) ? $email : $MailboxData['name']; + $displayname = htmlspecialchars(empty($MailboxData['name']) ? $email : $MailboxData['name']); } catch(PDOException $e) { $displayname = $email; From 12d46cf072875a7703681765db3c092f951edd3e Mon Sep 17 00:00:00 2001 From: emericklaw Date: Fri, 3 May 2019 17:54:33 +0100 Subject: [PATCH 134/439] Updated to not convert quotes I missed using ENT_NOQUOTES since XML only needs & and <> to be replaced in tags, spotted by @mkuron --- data/web/mobileconfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index abdf2cb8..38b249c6 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -22,7 +22,7 @@ try { $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); $stmt->execute(array(':username' => $email)); $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - $displayname = htmlspecialchars(empty($MailboxData['name']) ? $email : $MailboxData['name']); + $displayname = htmlspecialchars(empty($MailboxData['name']) ? $email : $MailboxData['name'], ENT_NOQUOTES); } catch(PDOException $e) { $displayname = $email; From 59882b443ad6cd39f9483fa0665210794df89179 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sat, 4 May 2019 11:45:51 +0200 Subject: [PATCH 135/439] Update imapsync_cron.pl Fix executing imapsync command containing quoted strings in parameters. --- data/Dockerfiles/dovecot/imapsync_cron.pl | 63 ++++++++++------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 4fad97ab..f9e53bf3 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -18,14 +18,6 @@ if ($imapsync_running eq 1) exit; } -sub qqw($) { - my @values = split('(?=--)', $_[0]); - foreach my $val (@values) { - $val=trim($val); - } - return @values -} - $run_dir="/tmp"; $dsn = 'DBI:mysql:database=__DBNAME__;mysql_socket=/var/run/mysqld/mysqld.sock'; $lock_file = $run_dir . "/imapsync_busy"; @@ -114,36 +106,35 @@ while ($row = $sth->fetchrow_arrayref()) { print $passfile1 "$password1\n"; print $passfile2 trim($master_pass) . "\n"; - my @custom_params_a = qqw($custom_params); - my $custom_params_ref = \@custom_params_a; - - my $generated_cmds = [ "/usr/local/bin/imapsync", - "--tmpdir", "/tmp", - "--nofoldersizes", - ($timeout1 gt "0" ? () : ('--timeout1', $timeout1)), - ($timeout2 gt "0" ? () : ('--timeout2', $timeout2)), - ($exclude eq "" ? () : ("--exclude", $exclude)), - ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), - ($maxage eq "0" ? () : ('--maxage', $maxage)), - ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), - ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), - ($subscribeall ne "1" ? () : ('--subscribeall')), - ($delete1 ne "1" ? () : ('--delete')), - ($delete2 ne "1" ? () : ('--delete2')), - ($automap ne "1" ? () : ('--automap')), - ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')), - (!defined($enc1) ? () : ($enc1)), - "--host1", $host1, - "--user1", $user1, - "--passfile1", $passfile1->filename, - "--port1", $port1, - "--host2", "localhost", - "--user2", $user2 . '*' . trim($master_user), - "--passfile2", $passfile2->filename, - '--no-modulesversion']; + my $command = "/usr/local/bin/imapsync"; + $command .= " --tmpdir /tmp"; + $command .= " --pidfile /tmp/imapsync.pid"; + $command .= " --nofoldersizes"; + ($timeout1 gt "0" ? () : ($command .= " --timeout1 ${timeout1}")); + ($timeout2 gt "0" ? () : ($command .= " --timeout2 ${timeout2}")); + ($exclude eq "" ? () : ($command .= qq` --exclude ${exclude}`)); + ($subfolder2 eq "" ? () : ($command .= qq` --subfolder2 ${subfolder2}`)); + ($maxage eq "0" ? () : ($command .= " --maxage ${maxage}")); + ($maxbytespersecond eq "0" ? () : ($command .= " --maxbytespersecond ${maxbytespersecond}")); + ($delete2duplicates ne "1" ? () : ($command .= " --delete2duplicates")); + ($subscribeall ne "1" ? () : ($command .= " --subscribeall")); + ($delete1 ne "1" ? () : ($command .= " --delete")); + ($delete2 ne "1" ? () : ($command .= " --delete2")); + ($automap ne "1" ? () : ($command .= " --automap")); + ($skipcrossduplicates ne "1" ? () : ($command .= " --skipcrossduplicates")); + (!defined($enc1) ? () : ($command .= " ${enc1}")); + $command .= " --host1 ${host1}"; + $command .= qq` --user1 ${user1}`; + $command .= " --passfile1 $passfile1->filename"; + $command .= " --port1 ${port1}"; + $command .= " --host2 localhost"; + $command .= " --user2 ${user2}" . '*' . trim($master_user); + $command .= " --passfile2 $passfile2->filename"; + $command .= " --no-modulesversion"; + ($custom_params eq "" ? () : ($command .= qq` ${custom_params}`)); try { - run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; + my $stdout = `${command}` $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${id} ); From 95fe217ce1b4c9df36c6a30d6e56477b315b84c9 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sat, 4 May 2019 11:58:19 +0200 Subject: [PATCH 136/439] Update imapsync_cron.pl Fix: Reset is_running status in case of exception occurs in running imapsync. Else it will stuck in "running" status. --- data/Dockerfiles/dovecot/imapsync_cron.pl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index f9e53bf3..01cd0ad8 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -140,12 +140,17 @@ while ($row = $sth->fetchrow_arrayref()) { $update->bind_param( 2, ${id} ); $update->execute(); } catch { - $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', last_run = NOW(), is_running = 0 WHERE id = ?"); + $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync' WHERE id = ?"); + $update->bind_param( 1, ${id} ); + $update->execute(); + } finally { + $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); $lockmgr->unlock($lock_file); }; + } $sth->finish(); From f9cd9927b1143b6ee12f725f87b08c5e5b2f49f4 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sat, 4 May 2019 12:02:41 +0200 Subject: [PATCH 137/439] Update imapsync_cron.pl Moved setting "is_running" status to just before the actual execution of imapsync command. --- data/Dockerfiles/dovecot/imapsync_cron.pl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 01cd0ad8..cff2852b 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -93,10 +93,6 @@ while ($row = $sth->fetchrow_arrayref()) { $timeout1 = @$row[19]; $timeout2 = @$row[20]; - $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); - $is_running->bind_param( 1, ${id} ); - $is_running->execute(); - if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } my $template = $run_dir . '/imapsync.XXXXXXX'; @@ -134,6 +130,9 @@ while ($row = $sth->fetchrow_arrayref()) { ($custom_params eq "" ? () : ($command .= qq` ${custom_params}`)); try { + $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); + $is_running->bind_param( 1, ${id} ); + $is_running->execute(); my $stdout = `${command}` $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${stdout} ); From 99eb61a449ede688d392dd0263c29b434acad006 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sat, 4 May 2019 12:13:51 +0200 Subject: [PATCH 138/439] Update init_db.inc.php imapsync table column "returned_text" changed into type "longtext". "mediumtext" is to small for imapsync output on large mailbox. db version value updated. --- data/web/inc/init_db.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 3c553bfd..27db8c1f 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 = "30032019_1905"; + $db_version = "04052019_1210"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -502,7 +502,7 @@ function init_db_schema() { "timeout2" => "SMALLINT NOT NULL DEFAULT '600'", "subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'", "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'", - "returned_text" => "MEDIUMTEXT", + "returned_text" => "LONGTEXT", "last_run" => "TIMESTAMP NULL DEFAULT NULL", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", From e59417ed781e584296a386cd2eb3eb1d88aee330 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sat, 4 May 2019 13:07:23 +0200 Subject: [PATCH 139/439] Update imapsync_cron.pl Second unlocking lock_file failed because it was already unlocked. --- data/Dockerfiles/dovecot/imapsync_cron.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index cff2852b..c583e810 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -146,7 +146,6 @@ while ($row = $sth->fetchrow_arrayref()) { $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); - $lockmgr->unlock($lock_file); }; From 189ea89a7189a0fc3e20601e52254b563014f8ae Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 4 May 2019 23:08:35 +0200 Subject: [PATCH 140/439] [Dovecot] Revert to previous imapsync cron script --- data/Dockerfiles/dovecot/imapsync_cron.pl | 78 ++++++++++++----------- 1 file changed, 42 insertions(+), 36 deletions(-) mode change 100755 => 100644 data/Dockerfiles/dovecot/imapsync_cron.pl diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl old mode 100755 new mode 100644 index c583e810..c63769f0 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -18,6 +18,14 @@ if ($imapsync_running eq 1) exit; } +sub qqw($) { + my @values = split('(?=--)', $_[0]); + foreach my $val (@values) { + $val=trim($val); + } + return @values +} + $run_dir="/tmp"; $dsn = 'DBI:mysql:database=__DBNAME__;mysql_socket=/var/run/mysqld/mysqld.sock'; $lock_file = $run_dir . "/imapsync_busy"; @@ -93,6 +101,10 @@ while ($row = $sth->fetchrow_arrayref()) { $timeout1 = @$row[19]; $timeout2 = @$row[20]; + $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); + $is_running->bind_param( 1, ${id} ); + $is_running->execute(); + if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } my $template = $run_dir . '/imapsync.XXXXXXX'; @@ -102,53 +114,47 @@ while ($row = $sth->fetchrow_arrayref()) { print $passfile1 "$password1\n"; print $passfile2 trim($master_pass) . "\n"; - my $command = "/usr/local/bin/imapsync"; - $command .= " --tmpdir /tmp"; - $command .= " --pidfile /tmp/imapsync.pid"; - $command .= " --nofoldersizes"; - ($timeout1 gt "0" ? () : ($command .= " --timeout1 ${timeout1}")); - ($timeout2 gt "0" ? () : ($command .= " --timeout2 ${timeout2}")); - ($exclude eq "" ? () : ($command .= qq` --exclude ${exclude}`)); - ($subfolder2 eq "" ? () : ($command .= qq` --subfolder2 ${subfolder2}`)); - ($maxage eq "0" ? () : ($command .= " --maxage ${maxage}")); - ($maxbytespersecond eq "0" ? () : ($command .= " --maxbytespersecond ${maxbytespersecond}")); - ($delete2duplicates ne "1" ? () : ($command .= " --delete2duplicates")); - ($subscribeall ne "1" ? () : ($command .= " --subscribeall")); - ($delete1 ne "1" ? () : ($command .= " --delete")); - ($delete2 ne "1" ? () : ($command .= " --delete2")); - ($automap ne "1" ? () : ($command .= " --automap")); - ($skipcrossduplicates ne "1" ? () : ($command .= " --skipcrossduplicates")); - (!defined($enc1) ? () : ($command .= " ${enc1}")); - $command .= " --host1 ${host1}"; - $command .= qq` --user1 ${user1}`; - $command .= " --passfile1 $passfile1->filename"; - $command .= " --port1 ${port1}"; - $command .= " --host2 localhost"; - $command .= " --user2 ${user2}" . '*' . trim($master_user); - $command .= " --passfile2 $passfile2->filename"; - $command .= " --no-modulesversion"; - ($custom_params eq "" ? () : ($command .= qq` ${custom_params}`)); + my @custom_params_a = qqw($custom_params); + my $custom_params_ref = \@custom_params_a; + + my $generated_cmds = [ "/usr/local/bin/imapsync", + "--tmpdir", "/tmp", + "--nofoldersizes", + ($timeout1 gt "0" ? () : ('--timeout1', $timeout1)), + ($timeout2 gt "0" ? () : ('--timeout2', $timeout2)), + ($exclude eq "" ? () : ("--exclude", $exclude)), + ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), + ($maxage eq "0" ? () : ('--maxage', $maxage)), + ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), + ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), + ($subscribeall ne "1" ? () : ('--subscribeall')), + ($delete1 ne "1" ? () : ('--delete')), + ($delete2 ne "1" ? () : ('--delete2')), + ($automap ne "1" ? () : ('--automap')), + ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')), + (!defined($enc1) ? () : ($enc1)), + "--host1", $host1, + "--user1", $user1, + "--passfile1", $passfile1->filename, + "--port1", $port1, + "--host2", "localhost", + "--user2", $user2 . '*' . trim($master_user), + "--passfile2", $passfile2->filename, + '--no-modulesversion']; try { - $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); - $is_running->bind_param( 1, ${id} ); - $is_running->execute(); - my $stdout = `${command}` + run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${id} ); $update->execute(); } catch { - $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync' WHERE id = ?"); - $update->bind_param( 1, ${id} ); - $update->execute(); - } finally { - $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); + $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); + $lockmgr->unlock($lock_file); }; - } $sth->finish(); From 14627645dab12053850b4ccfff0c50d000ee30fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 4 May 2019 23:32:32 +0200 Subject: [PATCH 141/439] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ec500d9c..3425de00 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # mailcow: dockerized - 🐮 + 🐋 = 💕 +## 💡 Entwickler gesucht! +Wir möchten die Kuh clustern und noch sicherererer machen, daher suchen wir dringend Entwickler, die dabei unterstützen. Bis hin zur Festanstellung alle Möglichkeiten offen. Bitte meldet euch bei info@servercow.de + ## Want to support mailcow? Donate via **PayPal** [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68) or via **Liberapay** [![Liberapay.com](https://mailcow.email/img/lp.png)](https://liberapay.com/mailcow) From d32f3e9d16478ff341f7101ed864efc604b85fac Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 13:07:17 +0200 Subject: [PATCH 142/439] Fix processing imapsync custom parameters --- data/Dockerfiles/dovecot/imapsync_cron.pl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index c63769f0..a6ec37cc 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -19,11 +19,20 @@ if ($imapsync_running eq 1) } sub qqw($) { - my @values = split('(?=--)', $_[0]); + my @params = (); + my @values = split(/(?=--)/, $_[0]); foreach my $val (@values) { + my @tmpparam = split(/ /, $val, 2); + foreach my $tmpval (@tmpparam) { + if ($tmpval ne '') { + push @params, $tmpval; + } + } + } + foreach my $val (@params) { $val=trim($val); } - return @values + return @params; } $run_dir="/tmp"; From fbf356d52211f9af52fe7a486a757911b2642dd1 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 13:09:17 +0200 Subject: [PATCH 143/439] Update imapsync script to 1.937 --- data/Dockerfiles/dovecot/imapsync | 7325 ++++++++++++++++++++--------- 1 file changed, 5144 insertions(+), 2181 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync b/data/Dockerfiles/dovecot/imapsync index cfbd1ee8..a75795b0 100755 --- a/data/Dockerfiles/dovecot/imapsync +++ b/data/Dockerfiles/dovecot/imapsync @@ -1,6 +1,6 @@ -#!/usr/bin/perl +#!/usr/bin/env perl -# $Id: imapsync,v 1.882 2018/05/05 21:10:43 gilles Exp gilles $ +# $Id: imapsync,v 1.937 2019/05/01 22:14:00 gilles Exp gilles $ # structure # pod documentation # use pragmas @@ -10,7 +10,7 @@ # default values # folder loop # subroutines -# sub usage { +# sub usage # pod documentation @@ -19,13 +19,13 @@ =head1 NAME -imapsync - Email IMAP tool for syncing, copying and migrating -email mailboxes between two imap servers, one way, +imapsync - Email IMAP tool for syncing, copying and migrating +email mailboxes between two imap servers, one way, and without duplicates. =head1 VERSION -This documentation refers to Imapsync $Revision: 1.882 $ +This documentation refers to Imapsync $Revision: 1.937 $ =head1 USAGE @@ -47,23 +47,23 @@ one 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.) +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. +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, the identification headers are -"Message-Id:" and "Received:" lines +Same specific headers and the transfer is done only once. +By default, the identification headers are +"Message-Id:" and "Received:" lines but this choice can be changed with the --useheader option. -All flags are preserved, unread messages will stay unread, +All flags are preserved, unread messages will stay unread, read ones will stay read, deleted will stay deleted. -You can stop the transfer at any time and restart it later, +You can stop the transfer at any time and restart it later, imapsync works well with bad connections and interruptions, by design. @@ -75,7 +75,7 @@ In that case, use the --delete1 option. Option --delete1 implies also option --expunge1 so all messages marked deleted on host1 will be really deleted. -You can also decide to remove empty folders once all of their +You can also decide to remove empty folders once all of their messages have been transferred. Add --delete1emptyfolders to obtain this behavior. @@ -98,7 +98,7 @@ Michael R. Elkins) for a 2 ways synchronization. usage: imapsync [options] Mandatory options are the six values, three on each sides, -needed to log in into the IMAP servers, ie, +needed to log in into the IMAP servers, ie, a host, a username, and a password, two times. Conventions used: @@ -108,20 +108,20 @@ Conventions used: reg means regular expression cmd means command - --dry : Makes imapsync doing nothing for real, just print what - would be done without --dry. + --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. + --port1 int : Port to connect on host1. Optional since default port is 143 or 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. + --port2 int : Port to connect on host2. Optional since default port is 143 or 993 if --ssl2 --user2 str : User to login on host2. Mandatory. --password2 str : Password for the user2. @@ -135,6 +135,9 @@ Conventions used: the password on the command line like --password1 does. --passfile2 str : Password file for the user2. Contains the password. +You can also pass the passwords in the environment variables +IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2 + =head2 OPTIONS/encryption --nossl1 : Do not use a SSL connection on host1. @@ -219,10 +222,18 @@ Conventions used: --f1f2 str1=str2 : Force folder str1 to be synced to str2, --f1f2 overrides --automap and --regextrans2. - --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. + --subfolder2 str : Syncs the whole host1 folders hierarchy under the + host2 folder named str. + It does it internally by adding three + --regextrans2 options before all others. + Add --debug to see what's really going on. + + --subfolder1 str : Syncs the host1 folders hierarchy under str + to the root hierarchy of host2. + It's the couterpart of a sync done by --subfolder2 + in the reverse order. Use --subfolder2 str + for a backup under str and --subfolder1 str + for the restore from str. --subscribed : Transfers subscribed folders. --subscribe : Subscribe to the folders transferred on the @@ -252,7 +263,7 @@ Conventions used: --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 + --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. @@ -306,7 +317,7 @@ Conventions used: --resyncflags : Resync flags for already transferred messages. On by default. --noresyncflags : Do not resync flags for already transferred messages. - May be useful when a user has already started to play + May be useful when a user has already started to play with its host2 account. =head2 OPTIONS/deletions @@ -377,7 +388,7 @@ Conventions used: command. Applied on both sides. For a complete of what can be search see https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt - + --search1 str : Same as --search but for selecting host1 messages only. --search2 str : Same as --search but for selecting host2 messages only. --search CRIT equals --search1 CRIT --search2 CRIT @@ -403,7 +414,10 @@ Conventions used: --syncacls : Synchronizes acls (Access Control Lists). --nosyncacls : Does not synchronize acls. This is the default. Acls in IMAP are not standardized, be careful. - + --addheader : When a message has no headers to be identified, + --addheader adds a "Message-Id" header, + like "Message-Id: 12345@imapsync", where 12345 + is the imap UID of the message on the host1 folder. =head2 OPTIONS/debugging @@ -426,35 +440,35 @@ Conventions used: Useful to check the ipv6 connectivity. Needs internet. -=head2 OPTIONS/specific +=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 + + + +=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 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. + --abort : terminates a previous call still running. It uses the pidfile to know what process to abort. --exitwhenover int : Stop syncing when total bytes transferred reached. @@ -498,12 +512,12 @@ 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 detailed behavior is 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 +Imapsync activates ssl or tls encryption by default, if possible. +What detailed behavior is 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 @@ -519,7 +533,28 @@ or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt Imapsync will exit with a 0 status (return code) if everything went good. Otherwise, it exits with a non-zero status. +Here is the list of the exit code values (an integer between 0 and 255), +the names reflects their meaning: +=for comment +egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _' + + + EX_OK => 0 ; #/* successful termination */ + EX_USAGE => 64 ; #/* command line usage error */ + EX_NOINPUT => 66 ; #/* cannot open input */ + EX_UNAVAILABLE => 69 ; #/* service unavailable */ + EX_SOFTWARE => 70 ; #/* internal software error */ + EXIT_CATCH_ALL => 1 ; # Any other error + EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num + EXIT_PID_FILE_ERROR => 8 ; + EXIT_CONNECTION_FAILURE => 10 ; + EXIT_TLS_FAILURE => 12 ; + EXIT_AUTHENTICATION_FAILURE => 16 ; + EXIT_SUBFOLDER1_NO_EXISTS => 21 ; + EXIT_WITH_ERRORS => 111 ; + EXIT_WITH_ERRORS_MAX => 112 ; + EXIT_TESTS_FAILED => 254 ; # Like Test::More API =head1 LICENSE AND COPYRIGHT @@ -546,7 +581,7 @@ 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, +only "often gratis" because imapsync is sold by its author, a good way to maintain and support free open public software over decades. @@ -609,13 +644,13 @@ https://imapsync.lamiral.info/examples/ =head1 INSTALL Imapsync works under any Unix with perl. - + Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten and all Server releases 2000, 2003, 2008 and R2, 2012 and R2) as a standalone binary software called imapsync.exe, - usually launched from a batch file in order to avoid always typing + usually launched from a batch file in order to avoid always typing the options. - + Imapsync works under OS X as a standalone binary software called imapsync_bin_Darwin @@ -652,38 +687,54 @@ Feel free to hack imapsync as the NOLIMIT license permits it. See also https://imapsync.lamiral.info/S/external.shtml for a better up to date list. - imap_tools : https://github.com/andrewnimmo/rick-sanders-imap-tools - offlineimap : https://github.com/nicolas33/offlineimap - Doveadm-Sync : http://wiki2.dovecot.org/Tools/Doveadm/Sync - ( Dovecot sync tool ) - mbsync : http://isync.sourceforge.net/ - mailsync : http://mailsync.sourceforge.net/ - mailutil : http://www.washington.edu/imap/ - part of the UW IMAP tookit. - imaprepl : http://www.bl0rg.net/software/ - http://freecode.com/projects/imap-repl/ - imapcopy : http://www.ardiehl.de/imapcopy/ - migrationtool : http://sourceforge.net/projects/migrationtool/ - imapmigrate : http://sourceforge.net/projects/cyrus-utils/ - wonko_imapsync: http://wonko.com/article/554 - see also file W/tools/wonko_ruby_imapsync - exchange-away : http://exchange-away.sourceforge.net/ - pop2imap : http://www.linux-france.org/prj/pop2imap/ +Last updated and verified on Thu Apr 11, 2019. - -Feedback (good or bad) will often be welcome. + imapsync : https://github.com/imapsync/imapsync + (this is an imapsync copy, sometimes delayed, + with --noreleasecheck by default since release 1.592, 2014/05/22) + imap_tools : https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/ + The imap_tools code is now at + https://github.com/andrewnimmo/rick-sanders-imap-tools + imaputils : https://github.com/mtsatsenko/imaputils (very old imap_tools fork) + Doveadm-Sync : https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool ) + davmail : http://davmail.sourceforge.net/ + offlineimap : http://offlineimap.org/ + mbsync : http://isync.sourceforge.net/ + mailsync : http://mailsync.sourceforge.net/ + mailutil : http://www.washington.edu/imap/ part of the UW IMAP tookit. + imaprepl : https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/ + imapcopy (Pascal): http://www.ardiehl.de/imapcopy/ + imapcopy (Java) : https://code.google.com/archive/p/imapcopy/ + imapsize : http://www.broobles.com/imapsize/ + migrationtool : http://sourceforge.net/projects/migrationtool/ + imapmigrate : http://sourceforge.net/projects/cyrus-utils/ + larch : https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail) + wonko_imapsync : http://wonko.com/article/554 (superseded by larch) + pop2imap : http://www.linux-france.org/prj/pop2imap/ (I wrote that too) + exchange-away : http://exchange-away.sourceforge.net/ + SyncBackPro : http://www.2brightsparks.com/syncback/sbpro.html + ImapSyncClient : https://github.com/ridaamirini/ImapSyncClient + MailStore : https://www.mailstore.com/en/products/mailstore-home/ + mnIMAPSync : https://github.com/manusa/mnIMAPSync + imap-upload : http://imap-upload.sourceforge.net/ + (a tool for uploading a local mbox file to IMAP4 server) =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 often broken 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 +away remote imap server, accessible by an often broken low-bandwidth ISDN link. + +I had to verify every mailbox was well transferred, all folders, all messages, +without wasting bandwidth or creating duplicates upon resyncs. The design was +made with the rsync command in mind. + +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). So many happened since then that I wonder +module tarball source (more precisely in the examples/ directory of the +Mail-IMAPClient tarball). + +So many happened since then that I wonder if it remains any lines of the original copy_folder.pl in imapsync source code. @@ -728,6 +779,8 @@ use Cwd ; use Readonly ; use Sys::MemInfo ; use Regexp::Common ; +use Text::ParseWords; +use File::Tail ; local $OUTPUT_AUTOFLUSH = 1 ; @@ -735,6 +788,10 @@ local $OUTPUT_AUTOFLUSH = 1 ; # Let us do like sysexits.h # /usr/include/sysexits.h +# and https://www.tldp.org/LDP/abs/html/exitcodes.html + +# Should avoid 2 126 127 128..128+64=192 255 +# Should use 0 1 3..125 193..254 Readonly my $EX_OK => 0 ; #/* successful termination */ Readonly my $EX_USAGE => 64 ; #/* command line usage error */ @@ -754,21 +811,35 @@ Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */ #Readonly my $EX_CONFIG => 78 ; #/* configuration error */ # Mine -Readonly my $EXIT_BY_SIGNAL => 6 ; +Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error + +Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num Readonly my $EXIT_PID_FILE_ERROR => 8 ; +Readonly my $EXIT_CONNECTION_FAILURE => 10 ; +Readonly my $EXIT_TLS_FAILURE => 12 ; +Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ; +Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ; + Readonly my $EXIT_WITH_ERRORS => 111 ; Readonly my $EXIT_WITH_ERRORS_MAX => 112 ; -Readonly my $EXIT_UNKNOWN => 126 ; + Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API + + + + + + Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ; 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 Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more. @@ -804,6 +875,8 @@ Readonly my $NUMBER_42 => 42 ; Readonly my $NUMBER_100 => 100 ; Readonly my $NUMBER_200 => 200 ; Readonly my $NUMBER_300 => 300 ; +Readonly my $NUMBER_123456 => 123456 ; +Readonly my $NUMBER_654321 => 654321 ; Readonly my $NUMBER_20_000 => 20_000 ; @@ -828,6 +901,14 @@ Readonly my $UMASK_PARANO => '0077' ; Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ; +Readonly my $GMAIL_MAXSIZE => 35_651_584 ; + + +# if ( 'MSWin32' eq $OSNAME ) +# if ( 'darwin' eq $OSNAME ) +# if ( 'linux' eq $OSNAME ) + + # global variables # Currently working to finish with only $sync @@ -835,49 +916,39 @@ Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is availa my( $sync, - $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags, + $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags, $debuglist, $debugdev, $debugmaxlinelength, $debugcgi, $domain1, $domain2, - $passfile1, $passfile2, @include, @exclude, @folderrec, @folderfirst, @folderlast, $prefix1, $prefix2, - $subfolder2, - @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck, + @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck, $flagscase, $filterflags, $syncflagsaftercopy, - $sep1, $sep2, $syncinternaldates, $idatefromheader, $syncacls, $fastio1, $fastio2, - $maxsize, $minsize, $maxage, $minage, - $exitwhenover, + $minsize, $maxage, $minage, $search, $search1, $search2, $skipheader, @useheader, $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize, - $delete1, $delete2, $delete2duplicates, - $expunge1, $expunge2, $uidexpunge2, - $justfoldersizes, + + $authmd5, $authmd51, $authmd52, $subscribed, $subscribe, $subscribeall, $help, - $justfolders, $justbanner, + $justbanner, $fast, - $total_bytes_skipped, - $total_bytes_error, - $nb_msg_skipped, + $nb_msg_skipped_dry_mode, $h1_nb_msg_duplicate, $h2_nb_msg_duplicate, - $h1_nb_msg_noheader, $h2_nb_msg_noheader, - $h1_total_bytes_duplicate, - $h2_total_bytes_duplicate, - $h1_nb_msg_deleted, + $h2_nb_msg_deleted, $h1_bytes_processed, - $h1_nb_msg_processed, + $h1_nb_msg_start, $h1_bytes_start, $h2_nb_msg_start, $h2_bytes_start, $h1_nb_msg_end, $h1_bytes_end, @@ -897,10 +968,7 @@ my( $delete2folders, $delete2foldersonly, $delete2foldersbutnot, $usecache, $debugcache, $cacheaftercopy, $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess, - %h1, %h2, $checkmessageexists, - $expungeaftereach, - $fixslash2, $messageidnodomain, $fixInboxINBOX, $maxlinelength, $maxlinelengthcmd, @@ -912,18 +980,18 @@ my( $disarmreadreceipts, $mixfolders, $skipemptyfolders, $fetch_hash_set, -); +) ; # main program # global variables initialization -# Currently removing all global variables except $sync +# I'm currently removing all global variables except $sync # passing each of them under $sync->{variable_name} $sync->{timestart} = time ; # Is a float because of use Time::HiRres -$sync->{rcs} = q{$Id: imapsync,v 1.882 2018/05/05 21:10:43 gilles Exp gilles $} ; +$sync->{rcs} = q{$Id: imapsync,v 1.937 2019/05/01 22:14:00 gilles Exp gilles $} ; $sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ; @@ -932,26 +1000,27 @@ my @loadavg = loadavg( ) ; $sync->{cpu_number} = cpu_number( ) ; $sync->{loaddelay} = load_and_delay( $sync->{cpu_number}, @loadavg ) ; -$sync->{loadavg} = join( q{ }, $loadavg[ 0 ] ) +$sync->{loadavg} = join( q{ }, $loadavg[ 0 ] ) . " on $sync->{cpu_number} cores and " . ram_memory_info( ) ; -$sync->{total_bytes_transferred} = 0 ; -$total_bytes_skipped = 0; -$total_bytes_error = 0; -$sync->{nb_msg_transferred} = 0; -$nb_msg_skipped = $nb_msg_skipped_dry_mode = 0; -$h1_nb_msg_deleted = $h2_nb_msg_deleted = 0; +$sync->{ total_bytes_transferred } = 0 ; +$sync->{ total_bytes_skipped } = 0; +$sync->{ nb_msg_transferred } = 0; +$sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0; +$sync->{ h1_nb_msg_deleted } = $h2_nb_msg_deleted = 0; $h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0; -$h1_nb_msg_noheader = $h2_nb_msg_noheader = 0; -$h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 0; +$sync->{ h1_nb_msg_noheader } = 0 ; +$h2_nb_msg_noheader = 0 ; $h1_nb_msg_start = $h1_bytes_start = 0 ; $h2_nb_msg_start = $h2_bytes_start = 0 ; -$h1_nb_msg_processed = $h1_bytes_processed = 0 ; +$sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ; + +$sync->{ h2_nb_msg_crossdup } = 0 ; #$h1_nb_msg_end = $h1_bytes_end = 0 ; #$h2_nb_msg_end = $h2_bytes_end = 0 ; @@ -977,7 +1046,7 @@ my %month_abrev = ( my $cgidir ; -# Just create a CGI object if under cgi context only. +# Just create a CGI object if under cgi context only. # Needed for the get_options() call cgibegin( $sync ) ; @@ -990,8 +1059,7 @@ cgibuildheader( $sync ) ; myprint( output( $sync ) ) ; output_reset_with( $sync ) ; -# Can break here if load is too heavy -cgiload( $sync ) ; +# Old place for cgiload( $sync ) ; # don't go on if options are not all known. if ( ! defined $options_good ) { exit $EX_USAGE ; } @@ -1001,8 +1069,8 @@ if ( ! defined $options_good ) { exit $EX_USAGE ; } # 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 the defined value). -$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ; -#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ; +#$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ; +$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ; # just the version if ( $sync->{ version } ) { @@ -1020,8 +1088,12 @@ after_get_options( $sync, $options_good ) ; # Under CGI environment, fix caveat emptor potential issues cgisetcontext( $sync ) ; +# --gmail --gmail --exchange --office etc. easyany( $sync ) ; +$sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ; +sanitize( $sync ) ; + $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ; # Unit tests @@ -1032,41 +1104,54 @@ testslive( $sync ) if ( $sync->{testslive} ) ; testslive6( $sync ) if ( $sync->{testslive6} ) ; # + $sync->{pidfile} = defined $sync->{pidfile} ? $sync->{pidfile} : $sync->{ tmpdir } . '/imapsync.pid' ; $sync->{pidfilelocking} = defined $sync->{pidfilelocking} ? $sync->{pidfilelocking} : 0 ; # old abort place -@{ $sync->{ sigexit } } = ( defined( $sync->{ sigexit } ) ) ? @{ $sync->{ sigexit } } : ( 'QUIT', 'TERM' ) ; +# Unix signals +@{ $sync->{ sigexit } } = ( defined( $sync->{ sigexit } ) ) ? @{ $sync->{ sigexit } } : ( 'QUIT', 'TERM' ) ; @{ $sync->{ sigreconnect } } = ( defined( $sync->{ sigreconnect } ) ) ? @{ $sync->{ sigreconnect } } : ( 'INT' ) ; +@{ $sync->{ sigprint } } = ( defined( $sync->{ sigprint } ) ) ? @{ $sync->{ sigprint } } : ( 'HUP' ) ; +@{ $sync->{ sigignore } } = ( defined( $sync->{ sigignore } ) ) ? @{ $sync->{ sigignore } } : ( ) ; -sig_install( $sync, \&catch_exit, @{ $sync->{ sigexit } } ) ; +local %SIG = %SIG ; +sig_install( $sync, \&catch_exit, @{ $sync->{ sigexit } } ) ; sig_install( $sync, \&catch_reconnect, @{ $sync->{ sigreconnect } } ) ; -# --sigignore can override sigexit and sigreconnect (for the same signals only) +sig_install( $sync, \&catch_print, @{ $sync->{ sigprint } } ) ; +# --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only) sig_install( $sync, \&catch_ignore, @{ $sync->{ sigignore } } ) ; -sig_install( $sync, \&toggle_sleep, 'USR1' ) ; +sig_install_toggle_sleep( $sync ) ; + $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 ; - +# log and output if ( $sync->{log} ) { setlogfile( $sync ) ; teelaunch( $sync ) ; # now $sync->{tee} is a filehandle to STDOUT and the logfile } # STDERR goes to the same place: LOG and STDOUT (if logging is on) -$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ; - +$sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ; + + + $timestart_int = int( $sync->{timestart} ) ; $timebefore = $sync->{timestart} ; + my $timestart_str = localtime( $sync->{timestart} ) ; + +# The prints in the log starts here + myprint( localhost_info( $sync ), "\n" ) ; myprint( "Transfer started at $timestart_str\n" ) ; -myprint( "PID is $PROCESS_ID\n" ) ; +myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\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" ) ; #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; @@ -1076,7 +1161,6 @@ myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (eui $modulesversion = defined $modulesversion ? $modulesversion : 1 ; - my $warn_release = ( $sync->{releasecheck} ) ? check_last_release( ) : $STR_use_releasecheck ; @@ -1089,7 +1173,7 @@ $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ; $sync->{ checkselectable } = defined $sync->{ checkselectable } ? $sync->{ checkselectable } : 1 ; $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ; $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ; -$expungeaftereach = defined $expungeaftereach ? $expungeaftereach : 1 ; +$sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ; # abletosearch is on by default $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ; @@ -1099,41 +1183,65 @@ $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ; $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ; -$fixslash2 = defined $fixslash2 ? $fixslash2 : 1 ; +$sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ; $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ; $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ; $mixfolders = defined $mixfolders ? $mixfolders : 1 ; $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ; -$delete2duplicates = 1 if ( $delete2 and ( ! defined $delete2duplicates ) ) ; +$sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ; $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ; $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ; $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ; -myprint( banner_imapsync( @ARGV ) ) ; +myprint( banner_imapsync( $sync, @ARGV ) ) ; myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ; + myprint( output( $sync ) ) ; +output_reset_with( $sync ) ; do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ; remove_pidfile_not_running( $sync->{pidfile} ) ; +# if another imapsync is running then tail -f its logfile and exit +# useful in cgi context +if ( $sync->{tail} and tail( $sync ) ) +{ + myprint( "Tail -f finished. Now finishing myself\n" ) ; + exit_clean( $sync, $EX_OK ) ; + exit $EX_OK ; +} if ( ! write_pidfile( $sync ) ) { + myprint( "Exiting with return value $EXIT_PID_FILE_ERROR\n" ) ; exit $EXIT_PID_FILE_ERROR ; } -# simulong is just a loop printing some lines for xx seconds with option "--simulong xx". -if ( $sync->{simulong} ) { simulong( $sync->{simulong} ) ; } -# New place to abort -if ( $sync->{abort} ) { - abort( $sync ) ; +# New place for abort +# abort before simulong in order to be able to abort a simulong sync +if ( $sync->{ abort } ) +{ + abort( $sync ) ; } +# simulong is just a loop printing some lines for xx seconds with option "--simulong xx". +if ( $sync->{ simulong } ) +{ + simulong( $sync->{ simulong } ) ; +} + + +# New place for cgiload 2019_03_03 +# because I want to log it +# Can break here if load is too heavy +cgiload( $sync ) ; + + $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ; if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ; @@ -1141,7 +1249,7 @@ if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ; $modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ; -check_lib_version( ) or +check_lib_version( $sync ) or croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n"; @@ -1167,21 +1275,20 @@ if ( $sync->{resyncflags} ) { } - sslcheck( $sync ) ; - +#print Data::Dumper->Dump( [ \$sync ] ) ; $split1 ||= $SPLIT ; $split2 ||= $SPLIT ; -$sync->{host1} || missing_option( '--host1' ) ; +$sync->{host1} || missing_option( $sync, '--host1' ) ; $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; -$sync->{host2} || missing_option( '--host2' ) ; +$sync->{host2} || missing_option( $sync, '--host2' ) ; $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; $debugimap1 = $debugimap2 = 1 if ( $debugimap ) ; -$debug = 1 if ( $debugimap1 or $debugimap2 ) ; +$sync->{ debug } = 1 if ( $debugimap1 or $debugimap2 ) ; # By default, don't take size to compare $skipsize = (defined $skipsize) ? $skipsize : 1; @@ -1200,8 +1307,6 @@ if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) { } - - my $SSL_VERIFY_POLICY ; my %SSL_VERIFY_STR ; @@ -1210,8 +1315,8 @@ Readonly %SSL_VERIFY_STR => ( IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' , IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' , ) ; -$IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ; +$IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ; if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) { @@ -1229,15 +1334,15 @@ if ( $sync->{ssl2} ) { } - if ( $sync->{justconnect} ) { - justconnect( ) ; + justconnect( $sync ) ; myprint( debugmemory( $sync, " after justconnect() call" ) ) ; exit_clean( $sync, $EX_OK ) ; } -$sync->{user1} || missing_option( '--user1' ) ; -$sync->{user2} || missing_option( '--user2' ) ; + +$sync->{user1} || missing_option( $sync, '--user1' ) ; +$sync->{user2} || missing_option( $sync, '--user2' ) ; $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1; @@ -1246,30 +1351,30 @@ $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1; # 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 ) { +if ( $sync->{ delete1 } ) { + if ( ! defined $sync->{ expunge1 } ) { myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ; - $expunge1 = 1 ; + $sync->{ 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' ) ) { - myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use --expunge2 instead\n" ) ; +if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) { + myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ; exit_clean( $sync, $EX_SOFTWARE ) ; } -if ( ( $delete2 or $delete2duplicates ) and not defined $uidexpunge2 ) { +if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) { if ( Mail::IMAPClient->can( 'uidexpunge' ) ) { myprint( "Info: will act as --uidexpunge2\n" ) ; - $uidexpunge2 = 1 ; - }elsif ( not defined $expunge2 ) { + $sync->{ uidexpunge2 } = 1 ; + }elsif ( not defined $sync->{ expunge2 } ) { myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ; - $expunge2 = 1 ; + $sync->{ expunge2 } = 1 ; } } -if ( $delete1 and $delete2 ) { +if ( $sync->{ delete1 } and $sync->{ delete2 } ) { myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ; exit_clean( $sync, $EX_USAGE ) ; } @@ -1310,11 +1415,11 @@ $authmech1 = uc $authmech1; $authmech2 = uc $authmech2; if (defined $proxyauth1 && !$authuser1) { - missing_option( 'With --proxyauth1, --authuser1' ) ; + missing_option( $sync, 'With --proxyauth1, --authuser1' ) ; } if (defined $proxyauth2 && !$authuser2) { - missing_option( 'With --proxyauth2, --authuser2' ) ; + missing_option( $sync, 'With --proxyauth2, --authuser2' ) ; } $authuser1 ||= $sync->{user1}; @@ -1333,7 +1438,7 @@ myprint( "Host2: imap connection timeout is $sync->{h2}->{timeout} seconds\n" ) $syncacls = defined $syncacls ? $syncacls : 0 ; # No folders sizes if --justfolders, unless really wanted. -if ( $justfolders and not defined $foldersizes ) { $foldersizes = 0 ; } +if ( $sync->{ justfolders } and not defined $foldersizes ) { $foldersizes = 0 ; } $foldersizes = ( defined $foldersizes ) ? $foldersizes : 1 ; $foldersizesatend = ( defined $foldersizesatend ) ? $foldersizesatend : $foldersizes ; @@ -1365,18 +1470,14 @@ get_password1( $sync ) ; get_password2( $sync ) ; - $sync->{dry_message} = q{} ; if( $sync->{dry} ) { $sync->{dry_message} = "\t(not really since --dry mode)" ; } - $search1 ||= $search if ( $search ) ; $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} ; } @@ -1390,17 +1491,17 @@ if ( @pipemess and $pipemesscheck ) { 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" ) ; + exit_clean( $sync, $EX_USAGE, "Error: one of --pipemess command is bad, check it\n" ) ; } myprint( "Ok with each --pipemess @pipemess\n" ) ; } if ( $maxlinelengthcmd ) { - myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with an space string.\n" ) ; + 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" ) ; + exit_clean( $sync, $EX_USAGE, "Error: --maxlinelengthcmd command is bad, check it\n" ) ; } myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ; } @@ -1410,7 +1511,7 @@ if ( @regexmess ) { 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' ) ; + exit_clean( $sync, $EX_USAGE, 'Error: one of --regexmess option is bad, check it' ) ; } myprint( "Ok with each --regexmess\n" ) ; } @@ -1420,7 +1521,7 @@ if ( @skipmess ) { 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' ) ; + exit_clean( $sync, $EX_USAGE, 'Error: one of --skipmess option is bad, check it' ) ; } myprint( "Ok with each --skipmess\n" ) ; } @@ -1430,44 +1531,52 @@ if ( @regexflag ) { 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' ) ; + exit_clean( $sync, $EX_USAGE, 'Error: one of --regexflag option is bad, check it' ) ; } myprint( "Ok with each --regexflag\n" ) ; } -$sync->{imap1} = my $imap1 = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $domain1, $sync->{password1}, +$sync->{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( $sync->{host2}, $sync->{port2}, $sync->{user2}, $domain2, $sync->{password2}, +$sync->{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" ) ; +$sync->{ debug } and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ; +$sync->{ debug } and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ; -if ( ! $imap1->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host1' ) ; } +if ( ! $sync->{imap1}->IsAuthenticated( ) ) { exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, 'Not authenticated on host1' ) ; } myprint( "Host1: state Authenticated\n" ) ; -if ( ! $imap2->IsAuthenticated( ) ) { die_clean( 'Not authenticated on host2' ) ; } +if ( ! $sync->{imap2}->IsAuthenticated( ) ) { exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, 'Not authenticated on host2' ) ; } myprint( "Host2: state Authenticated\n" ) ; -myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $imap1->capability() || [] }), "\n" ) ; -myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $imap2->capability() || [] }), "\n" ) ; +myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ; + +#myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ; +#myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ; + +myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ; + # ID on by default since 1.832 $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ; imap_id_stuff( $sync ) ; -#quota( $imap1, 'h1', $sync ) ; # quota on host1 is useless and pollute host2 output. -quota( $imap2, 'h2', $sync ) ; +#quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output. +quota( $sync, $sync->{imap2}, 'h2' ) ; -if ( $sync->{justlogin} ) { - $imap1->logout( ) ; - $imap2->logout( ) ; +maxsize_setting( $sync ) ; + +if ( $sync->{ justlogin } ) { + $sync->{imap1}->logout( ) ; + $sync->{imap2}->logout( ) ; + myprint( "Exiting because of --justlogin\n" ) ; exit_clean( $sync, $EX_OK ) ; } @@ -1490,14 +1599,14 @@ my $h1_folders_wanted_ct = 0 ; # counter of folders done. # All folders on host1 and host2 -@h1_folders_all = sort $imap1->folders( ) ; -@h2_folders_all = sort $imap2->folders( ) ; +@h1_folders_all = sort $sync->{imap1}->folders( ) ; +@h2_folders_all = sort $sync->{imap2}->folders( ) ; myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ; myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ; -foreach my $f ( @h1_folders_all ) { - $h1_folders_all{ $f } = 1 +foreach my $f ( @h1_folders_all ) { + $h1_folders_all{ $f } = 1 } foreach my $f ( @h2_folders_all ) { $h2_folders_all{ $f } = 1 ; @@ -1508,30 +1617,39 @@ $sync->{h1_folders_all} = \%h1_folders_all ; $sync->{h2_folders_all} = \%h2_folders_all ; $sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ; +private_folders_separators_and_prefixes( ) ; + + # Make a hash of subscribed folders in both servers. -for ( $imap1->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ; -for ( $imap2->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ; +for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ; +for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ; -if ( defined $subfolder2 ) { - unshift @regextrans2, - q(s,^$sync->{h2_prefix}(.*),$sync->{h2_prefix}${subfolder2}${h2_sep}$1,), - q(s,^INBOX$,$sync->{h2_prefix}${subfolder2}${h2_sep}INBOX,) ; +if ( defined $sync->{ subfolder1 } ) { + subfolder1( $sync ) ; +} + + + +if ( defined $sync->{ subfolder2 } ) { + subfolder2( $sync ) ; } if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) { - push @regextrans2, $reg ; + push @{ $sync->{ regextrans2 } }, $reg ; } -if ( ( $sync->{folder} and scalar @{ $sync->{folder} } ) - or $subscribed - or scalar @folderrec ) { + +if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) + or $subscribed + or scalar @folderrec ) +{ # folders given by option --folder - if ( $sync->{folder} and scalar @{ $sync->{folder} } ) { - add_to_requested_folders( @{ $sync->{folder} } ); + if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) { + add_to_requested_folders( @{ $sync->{ folder } } ) ; } # option --subscribed @@ -1540,16 +1658,18 @@ if ( ( $sync->{folder} and scalar @{ $sync->{folder} } ) } # option --folderrec - if (scalar @folderrec) { - foreach my $folderrec (@folderrec) { - add_to_requested_folders($imap1->folders($folderrec)); + if ( scalar @folderrec ) { + foreach my $folderrec ( @folderrec ) { + add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ; } } -} else { +} +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); + add_to_requested_folders( @h1_folders_all ) ; } } @@ -1584,7 +1704,7 @@ if ( $sync->{ checkfoldersexist } ) { my @h1_folders_wanted_exist ; myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ; foreach my $folder ( @h1_folders_wanted ) { - ( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ; + ( $sync->{ 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 ; @@ -1602,16 +1722,16 @@ if ( $sync->{ checkselectable } ) { my @h1_folders_wanted_selectable ; myprint( "Host1: Checking 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" ) ; - # It does an imap command LIST "" $folder and then search for no \Noselect - if ( ! $imap1->selectable( $folder ) ) { + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n" ) ; + # It does an imap command LIST "" $folder and then search for no \Noselect + if ( ! $sync->{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 ; - ( $debug or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ; + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Host1: checking folders took ', timenext( ), " s\n" ) ; }else{ myprint( "Host1: Not checking that wanted folders are selectable. Remove --nocheckselectable to get this check.\n" ) ; } @@ -1619,20 +1739,9 @@ if ( $sync->{ checkselectable } ) { $sync->{h1_folders_wanted} = \@h1_folders_wanted ; -my( $h1_sep, $h2_sep ) ; -# what are the private folders separators for each server ? +# Old place of private_folders_separators_and_prefixes( ) call. +#private_folders_separators_and_prefixes( ) ; -( $debug or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ; -$h1_sep = get_separator( $imap1, $sep1, '--sep1', 'Host1', \@h1_folders_all ) ; -$h2_sep = get_separator( $imap2, $sep2, '--sep2', 'Host2', \@h2_folders_all ) ; - - -$sync->{ h1_prefix } = get_prefix( $imap1, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ; -$sync->{ h2_prefix } = get_prefix( $imap2, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ; - - -myprint( "Host1 separator and prefix: [$h1_sep][$sync->{ h1_prefix }]\n" ) ; -myprint( "Host2 separator and prefix: [$h2_sep][$sync->{ h2_prefix }]\n" ) ; # this hack is because LWP post does not pass well a hash in the $form parameter # but it does pass well an array @@ -1643,7 +1752,7 @@ automap( $sync ) ; foreach my $h1_fold ( @h1_folders_wanted ) { my $h2_fold ; - $h2_fold = imap2_folder_name( $h1_fold ) ; + $h2_fold = imap2_folder_name( $sync, $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 }++ ; @@ -1653,7 +1762,7 @@ foreach my $h1_fold ( @h1_folders_wanted ) { foreach my $h1_fold ( @h1_folders_all ) { my $h2_fold ; - $h2_fold = imap2_folder_name( $h1_fold ) ; + $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $h2_folders_from_1_all{ $h2_fold }++ ; } @@ -1666,32 +1775,32 @@ All foldernames are presented between brackets like [X] where X is the foldernam When a foldername contains non-ASCII characters it is presented in the form [X] = [Y] where X is the imap foldername you have to use in command line options and -Y is the uft8 output just printed for convenience, to recognize it. +Y is the utf8 output just printed for convenience, to recognize it. END_LISTING myprint( - "Host1 folders list (first the raw imap format then the [X] = [Y]):\n", - $imap1->list( ), + "Host1: folders list (first the raw imap format then the [X] = [Y]):\n", + $sync->{imap1}->list( ), "\n", jux_utf8_list( @h1_folders_all ), "\n", - "Host2 folders list (first the raw imap format then the [X] = [Y]):\n", - $imap2->list( ), + "Host2: folders list (first the raw imap format then the [X] = [Y]):\n", + $sync->{imap2}->list( ), "\n", jux_utf8_list( @h2_folders_all ), - "\n", - q{} + "\n", + q{} ) ; if ( $subscribed ) { - myprint( + myprint( 'Host1 subscribed folders list: ', jux_utf8_list( sort keys %h1_subscribed_folder ), "\n", ) ; } - + my @h2_folders_not_in_1; @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ; @@ -1731,15 +1840,20 @@ exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ; debugsleep( $sync ) ; if ( $foldersizes ) { - foldersizes_on_h1h2( ) ; + foldersizes_on_h1h2( $sync ) ; } -exit_clean( $sync, $EX_OK ) if ( $justfoldersizes ) ; + +if ( $sync->{ justfoldersizes } ) +{ + myprint( "Exiting because of --justfoldersizes\n" ) ; + exit_clean( $sync, $EX_OK ) ; +} $sync->{stats} = 1 ; -if ( $sync->{'delete1emptyfolders'} ) { +if ( $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } @@ -1755,116 +1869,128 @@ $sync->{begin_transfer_time} = time ; my %uid_candidate_for_deletion ; my %uid_candidate_no_deletion ; -my %h2_folders_of_md5 = ( ) ; +$sync->{ h2_folders_of_md5 } = { } ; FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - my $h2_fold = imap2_folder_name( $h1_fold ) ; + my $h2_fold = imap2_folder_name( $sync, $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 ) ) ; myprint( debugmemory( $sync, " at folder loop" ) ) ; - + # host1 can not be fetched read only, select is needed because of expunge. - select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ; + select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ; debugsleep( $sync ) ; - 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" ) ; + my $h1_fold_nb_messages = count_from_select( $sync->{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( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ; next FOLDER ; } + # Code added from https://github.com/imapsync/imapsync/issues/95 + # Thanks jh1995 + if ( $skipemptyfolders ) { + my $h1_msgs_all_hash_ref_tmp = { } ; + my @h1_msgs_tmp = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref_tmp, $search1, $h1_fold ) ; + my $h1_fold_nb_messages_tmp = scalar( @h1_msgs_tmp ) ; + if ( 0 == $h1_fold_nb_messages_tmp ) { + myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ; + next FOLDER ; + } + } + if ( ! exists $h2_folders_all{ $h2_fold } ) { - create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ; + create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) or next FOLDER ; } 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' ) - or ( create_folder( $imap2, $h2_fold, $h1_fold ) - and select_folder( $imap2, $h2_fold, 'Host2' ) ) + select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) + or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) + and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) ) or next FOLDER ; - my @select_results = $imap2->Results( ) ; + my @select_results = $sync->{imap2}->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" ) ; + myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ; my $permanentflags2 = permanentflags( @select_results ) ; - myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n" ) ; + myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ; - if ( $expunge1 ){ + if ( $sync->{ expunge1 } ) + { myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap1->expunge( ) } ; + if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ; } if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall ) - and not exists $h2_subscribed_folder{ $h2_fold } ) { + and not exists $h2_subscribed_folder{ $h2_fold } ) + { myprint( "Host2: Subscribing to folder $h2_fold\n" ) ; - if ( ! $sync->{dry} ) { $imap2->subscribe( $h2_fold ) } ; + if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ; } - next FOLDER if ( $justfolders ) ; + next FOLDER if ( $sync->{ justfolders } ) ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + 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, $sync->{abletosearch1}, $h1_fold ); + my @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $search1, $sync->{abletosearch1}, $h1_fold ); - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + 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" ) ; - $debug and myprint( "Host1 selecting messages of folder [$h1_fold] took ", timenext(), " s\n" ) ; + myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ; + ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ; + $sync->{ 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, $sync->{abletosearch2}, $h2_fold ) ; + my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $search2, $sync->{abletosearch2}, $h2_fold ) ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + 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" ) ; - $debug and myprint( "Host2 selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ; + myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ; + ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ; + $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext(), " s\n" ) ; my $cache_base = "$sync->{ 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 = $sync->{imap1}->uidvalidity( ) || q{} ; + my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ; - if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } if ( $usecache ) { - myprint( "cache directory: $cache_dir\n" ) ; + myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\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( '[', + $sync->{ debug } and myprint( '[', map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ; } - my %h1_hash = () ; - my %h2_hash = () ; + my %h1_hash = ( ) ; + my %h2_hash = ( ) ; my ( %h1_msgs, %h2_msgs ) ; - @h1_msgs{ @h1_msgs } = (); - @h2_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 } ; @@ -1892,112 +2018,128 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ; } - $debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ; + $sync->{ 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_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache); + $sync->{ 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 ) + $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ; + + my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; + if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; } + + if ( $sync->{abletosearch1} ) + { + $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $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" ) ; + else + { + my $uidnext = $sync->{imap1}->uidnext( $h1_fold ) || $uidnext_default ; + my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ; + $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref ) + if ( @h1_msgs ) ; + } + + $sync->{ 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: ', $sync->{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( $sync, $imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ; - if ( ! defined $rc ) { + foreach my $m ( @h1_msgs_not_in_cache ) + { + my $rc = parse_header_msg( $sync, $sync->{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) { + myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ; + $sync->{ total_bytes_skipped } += $h1_size ; + $sync->{ nb_msg_skipped } += 1 ; + $sync->{ h1_nb_msg_noheader } +=1 ; + $sync->{ 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; + $sync->{ nb_msg_skipped } += 1; $h1_nb_msg_duplicate += 1; - $h1_nb_msg_processed +=1 ; + $sync->{ 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" ) ; - # Getting headers and metada can be so long that host2 might be disconnected here + myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ; + + $sync->{ 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 ; } - $debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ; + $sync->{ 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" ) ; + $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache); + $sync->{ 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" ) ; + $sync->{ 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 + my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; + if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; } + if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) { - $h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref) ; + $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ; }else{ - my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ; + my $uidnext = $sync->{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 ) + $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref ) if ( @h2_msgs ) ; } - $debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n" ) ; + $sync->{ 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( $sync, $imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ; + my $rc = parse_header_msg( $sync, $sync->{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" ) ; + 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_folders_of_md5 foreach my $md5 ( keys %h2_hash ) { - $h2_folders_of_md5{ $md5 }->{ $h2_fold } ++ ; + $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; + } + # %h1_folders_of_md5 + foreach my $md5 ( keys %h1_hash ) { + $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; } my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ; - $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" ) ; + myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ; - $debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ; + $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( ), " s\n" ) ; + + $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ; # messages in host1 that are not in host2 my @h1_hash_keys_sorted_by_uid @@ -2008,30 +2150,31 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { my @h2_hash_keys_sorted_by_uid = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash; + # Deletions on account2. - if( $delete2duplicates and not exists $h2_folders_from_1_several{ $h2_fold } ) { + if( $sync->{ 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 $sync->{dry_message}\n" ) ; - push @h2_expunge, $h2_msg if $uidexpunge2 ; + myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; if ( ! $sync->{dry} ) { - $imap2->delete_message( $h2_msg ) ; + $sync->{imap2}->delete_message( $h2_msg ) ; $h2_nb_msg_deleted += 1 ; } } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ){ + if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } } - if( $delete2 and not exists $h2_folders_from_1_several{ $h2_fold } ) { + if( $sync->{ 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) { @@ -2042,35 +2185,35 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { 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; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; if ( ! ( $sync->{dry} or $isdel ) ) { - $imap2->delete_message($h2_msg); + $sync->{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; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; if ( ! $sync->{dry} ) { - $imap2->delete_message($h2_msg); + $sync->{imap2}->delete_message($h2_msg); $h2_nb_msg_deleted += 1; } } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ) { + if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{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" ) ; + if( $sync->{ 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' } ; @@ -2078,11 +2221,11 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { 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" ) ; + $sync->{ 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" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } } @@ -2101,14 +2244,14 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { # 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" ) ; + $sync->{ 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" ) ; + $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ; }else{ myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ; - push @h2_expunge, $h2_msg if $uidexpunge2 ; + push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; if ( ! $sync->{dry} ) { - $imap2->delete_message( $h2_msg ) ; + $sync->{imap2}->delete_message( $h2_msg ) ; $h2_nb_msg_deleted += 1 ; } } @@ -2116,133 +2259,177 @@ FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) { } my $cnt = scalar @h2_expunge ; - if( @h2_expunge and not $expunge2 ) { + if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; + $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } - if ( $expunge2 ) { + if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ; - $imap2->expunge( ) if ! $sync->{dry} ; + $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } $h2_folders_from_1_several{ $h2_fold }-- ; } - my $h2_uidnext = $imap2->uidnext( $h2_fold ) ; - $debug and myprint( "Host2 uidnext: $h2_uidnext\n" ) ; + my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ; + $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ; $h2_uidguess = $h2_uidnext ; # 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 ; } + my @h1_msgs_to_delete ; 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" ) ; + #myprint( "h1_nb_msg_processed: $sync->{ 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'}; + #my $labels = labels( $sync->{imap1}, $h1_msg ) ; + #print "LABELS: $labels\n" ; + if ( ( not exists $h2_hash{ $m_id } ) - and ( not ( exists $h2_folders_of_md5{ $m_id } ) - or not $skipcrossduplicates ) ) { + and ( not ( exists $sync->{ 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 ) ; - $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ; - if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) { + if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) { + # not expunged + push @h1_msgs_to_delete, $h1_msg ; + } + + # A bug here with imapsync 1.920, fixed in 1.921 + # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2! + if ( $h2_msg and not $sync->{ dry } ) + { + $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ; + } + + # + if( $sync->{ 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 ( total_bytes_max_reached( $sync ) ) { + # a bug when using --delete1 --noexpungeaftereach + # same thing below on all total_bytes_max_reached! + last FOLDER ; + } next MESS; } - else{ + else + { # already on host2 - if ( exists $h2_hash{ $m_id } ) { + 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" ) ; - if ( $usecache ) { + $sync->{ debug } and myprint( "Host1: found that 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" ) 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" ) ; } - $total_bytes_skipped += $h1_size ; - $nb_msg_skipped += 1 ; - $h1_nb_msg_processed +=1 ; + elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } ) + { + my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ; + ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ; + $sync->{ h2_nb_msg_crossdup } +=1 ; + } + $sync->{ total_bytes_skipped } += $h1_size ; + $sync->{ nb_msg_skipped } += 1 ; + $sync->{ 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'}; if ( $sync->{resyncflags} ) { - sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; + sync_flags_fir( $sync, $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" ) ; + $sync->{ debug } and myprint( + "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ; + + if ( $sync->{ resynclabels } ) + { + resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold ) + } } if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } - - if ( $delete1 ) { - delete_message_on_host1( $h1_msg, $h1_fold ) ; + if ( $sync->{ delete1 } ) { + push @h1_msgs_to_delete, $h1_msg ; } } # 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" ) ; - if ( $sync->{resyncflags} ) { - 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 ; } + delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ; + + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + + # MESS_IN_CACHE: + if ( ! $sync->{ delete1 } ) + { + 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" ) ; + if ( $sync->{resyncflags} ) + { + sync_flags_fir( $sync, $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 ; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; + $sync->{ h1_nb_msg_processed } +=1 ; + } } + if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } + + @h1_msgs_to_delete = ( ) ; #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" ) ; + # MESS_BY_UID: + foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) + { + $sync->{ 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 ) { + if( $sync->{ 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( ) ; + last FOLDER if total_bytes_max_reached( $sync ) ; } - if ( $expunge1 ){ + if ( $sync->{ expunge1 } ){ myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap1->expunge( ) } ; + if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ; } - if ( $expunge2 ){ + if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; - if ( ! $sync->{dry} ) { $imap2->expunge( ) } ; + if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ; } - $debug and myprint( 'Time: ', timenext( ), " s\n" ) ; + $sync->{ debug } and myprint( 'Time: ', timenext( ), " s\n" ) ; } myprint( "++++ End looping on each folder\n" ) ; -if ( $delete1 and $sync->{'delete1emptyfolders'} ) { +if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } -( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ; +( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( ), " s\n" ) ; if ( $foldersizesatend ) { @@ -2252,11 +2439,11 @@ Folders sizes after the synchronization. You can remove this foldersizes listing by using "--nofoldersizesatend" END_SIZE - foldersizesatend( ) ; + foldersizesatend( $sync ) ; } -if ( ! lost_connection( $imap1, "for host1 [$sync->{host1}]" ) ) { $imap1->logout( ) ; } -if ( ! lost_connection( $imap2, "for host2 [$sync->{host2}]" ) ) { $imap2->logout( ) ; } +if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; } +if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; } stats( $sync ) ; myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ; @@ -2270,23 +2457,27 @@ exit_clean( $sync, $EX_OK ) ; # subroutines -sub myprint { +sub myprint +{ #print @ARG ; print { $sync->{ tee } || \*STDOUT } @ARG ; return ; } -sub myprintf { +sub myprintf +{ printf { $sync->{ tee } || \*STDOUT } @ARG ; return ; } -sub mysprintf { +sub mysprintf +{ my( $format, @list ) = @ARG ; return sprintf $format, @list ; } -sub output_start { +sub output_start +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2297,7 +2488,8 @@ sub output_start { } -sub tests_output_start { +sub tests_output_start +{ note( 'Entering tests_output_start()' ) ; my $mysync = { } ; @@ -2313,7 +2505,8 @@ sub tests_output_start { return ; } -sub tests_output { +sub tests_output +{ note( 'Entering tests_output()' ) ; my $mysync = { } ; @@ -2329,7 +2522,8 @@ sub tests_output { return ; } -sub output { +sub output +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2341,7 +2535,8 @@ sub output { -sub tests_output_reset_with { +sub tests_output_reset_with +{ note( 'Entering tests_output_reset_with()' ) ; my $mysync = { } ; @@ -2356,7 +2551,8 @@ sub tests_output_reset_with { return ; } -sub output_reset_with { +sub output_reset_with +{ my $mysync = shift @ARG ; if ( not $mysync ) { return ; } @@ -2368,13 +2564,14 @@ sub output_reset_with { -sub abort { +sub abort +{ my $mysync = shift @ARG ; - if ( ! -r $sync->{pidfile} ) { - myprint( "Can not read pidfile $sync->{pidfile}. Exiting.\n" ) ; + if ( ! -r $mysync->{pidfile} ) { + myprint( "Can not read pidfile $mysync->{pidfile}. Exiting.\n" ) ; exit $EX_OK ; } - my $pidtokill = firstline( $sync->{pidfile} ) ; + my $pidtokill = firstline( $mysync->{pidfile} ) ; if ( ! $pidtokill ) { myprint( "No process to abort. Exiting.\n" ) ; exit $EX_OK ; @@ -2401,14 +2598,19 @@ sub abort { if ( kill 'ZERO', $pidtokill ) { myprint( "Process PID $pidtokill still there. Can not do much. Exiting.\n" ) ; exit $EX_OK ; + }else{ + myprint( "Process PID $pidtokill ended. Exiting.\n" ) ; + exit $EX_OK ; } + # well abort job done anyway - return ; + exit $EX_OK ; } -sub docker_context { +sub docker_context +{ my $mysync = shift ; -e '/.dockerenv' || return ; myprint( "Docker context detected with /.dockerenv\n" ) ; @@ -2419,11 +2621,12 @@ sub docker_context { # In case myprint( "Changing current directory to /var/tmp/\n" ) ; chdir '/var/tmp/' ; - + return ; } -sub cgibegin { +sub cgibegin +{ my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } require CGI ; @@ -2434,8 +2637,10 @@ sub cgibegin { return ; } -sub tests_under_cgi_context { +sub tests_under_cgi_context +{ note( 'Entering tests_under_cgi_context()' ) ; + # $ENV{SERVER_SOFTWARE} = 'under imapsync' ; do { # Not in cgi context @@ -2462,7 +2667,8 @@ sub tests_under_cgi_context { } -sub under_cgi_context { +sub under_cgi_context +{ my $mysync = shift ; # Under cgi context if ( $ENV{SERVER_SOFTWARE} ) { @@ -2472,7 +2678,8 @@ sub under_cgi_context { return ; } -sub cgibuildheader { +sub cgibuildheader +{ my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } @@ -2509,19 +2716,24 @@ sub cgibuildheader { return ; } -sub cgiload { - my $mysync = shift ; - if ( ! under_cgi_context( $mysync ) ) { return ; } - 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 cgiload +{ + # Exit on heavy load in CGI context + my $mysync = shift ; + if ( ! under_cgi_context( $mysync ) ) { return ; } + if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon + 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 { +sub tests_set_umask +{ note( 'Entering tests_set_umask()' ) ; + my $save_umask = umask ; my $mysync = {} ; @@ -2536,7 +2748,8 @@ sub tests_set_umask { return ; } -sub set_umask { +sub set_umask +{ my $mysync = shift ; my $previous_umask = umask_str( ) ; my $new_umask = umask_str( $UMASK_PARANO ) ; @@ -2547,8 +2760,10 @@ sub set_umask { return ; } -sub tests_umask_str { +sub tests_umask_str +{ note( 'Entering tests_umask_str()' ) ; + my $save_umask = umask ; is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ; @@ -2580,7 +2795,8 @@ sub tests_umask_str { return ; } -sub umask_str { +sub umask_str +{ my $value = shift ; if ( defined $value ) { @@ -2591,8 +2807,10 @@ sub umask_str { return( sprintf( '%#04o', $current ) ) ; } -sub tests_umask { +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" ) ; @@ -2613,7 +2831,8 @@ sub tests_umask { return ; } -sub cgisetcontext { +sub cgisetcontext +{ my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } @@ -2621,7 +2840,7 @@ sub cgisetcontext { set_umask( $mysync ) ; # Remove all content in unsafe evaled options - @regextrans2 = ( ) ; + @{ $mysync->{ regextrans2 } } = ( ) ; @regexflag = ( ) ; @regexmess = ( ) ; @skipmess = ( ) ; @@ -2651,15 +2870,26 @@ sub cgisetcontext { chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ; $mysync->{ 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" ) ; - # @{ $mysync->{ sigexit } } = ( 'QUIT' ) ; - # output( $mysync, "Setting the QUIT signal to exit properly\n" ) ; + $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ; + $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ; + $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ; + + $skipemptyfolders = defined $skipemptyfolders ? $skipemptyfolders : 1 ; + + # Out of memory with messages over 1 GB ? + $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ; + + # tail -f behaviour on by default + $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ; + + # not sure it's for good + @useheader = qw( Message-Id ) ; + return ; } -sub cgioutputenvcontext { +sub cgioutputenvcontext +{ my $mysync = shift @ARG ; for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) { @@ -2675,7 +2905,8 @@ sub cgioutputenvcontext { -sub debugsleep { +sub debugsleep +{ my $mysync = shift @ARG ; if ( defined $mysync->{debugsleep} ) { myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ; @@ -2684,48 +2915,426 @@ sub debugsleep { return ; } -sub foldersizes_on_h1h2 { +sub foldersizes_on_h1h2 +{ + my $mysync = shift ; + 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. +You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" +but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy. END_SIZE - ( $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 ) ; + ( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $mysync->{imap1}, $search1, $mysync->{abletosearch1}, @h1_folders_wanted ) ; + ( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $mysync->{imap2}, $search2, $mysync->{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" ; - errors_incr( $sync, $error ) ; + errors_incr( $mysync, $error ) ; $foldersizes = 0 ; $foldersizesatend = 0 ; return ; } - my $h2_bytes_limit = $sync->{h2}->{quota_limit_bytes} || 0 ; + my $h2_bytes_limit = $mysync->{h2}->{quota_limit_bytes} || 0 ; if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) { 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 ) ; + errors_incr( $mysync, $error ) ; } return ; } -sub total_bytes_max_reached { +sub total_bytes_max_reached +{ + my $mysync = shift ; - return( 0 ) if not $exitwhenover ; - if ( $sync->{total_bytes_transferred} >= $exitwhenover ) { - myprint( "Maximum bytes transferred reached, $sync->{total_bytes_transferred} >= $exitwhenover, ending sync\n" ) ; + if ( ! $mysync->{ exitwhenover } ) { + return( 0 ) ; + } + if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } ) { + myprint( "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ) ; return( 1 ) ; } + return ; +} + +sub tests_mock_capability +{ + note( 'Entering tests_mock_capability()' ) ; + + my $myimap ; + ok( $myimap = mock_capability( ), + 'mock_capability: (1) no args => a Test::MockObject' + ) ; + ok( $myimap->isa( 'Test::MockObject' ), + 'mock_capability: (2) no args => a Test::MockObject' + ) ; + + is( undef, $myimap->capability( ), + 'mock_capability: (3) no args => capability undef' + ) ; + + ok( mock_capability( $myimap ), + 'mock_capability: (1) one arg => MockObject' + ) ; + + is( undef, $myimap->capability( ), + 'mock_capability: (2) one arg OO style => capability undef' + ) ; + + ok( mock_capability( $myimap, $NUMBER_123456 ), + 'mock_capability: (1) two args 123456 => capability 123456' + ) ; + + is( $NUMBER_123456, $myimap->capability( ), + 'mock_capability: (2) two args 123456 => capability 123456' + ) ; + + ok( mock_capability( $myimap, 'ABCD' ), + 'mock_capability: (1) two args ABCD => capability ABCD' + ) ; + is( 'ABCD', $myimap->capability( ), + 'mock_capability: (2) two args ABCD => capability ABCD' + ) ; + + ok( mock_capability( $myimap, [ 'ABCD' ] ), + 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]' + ) ; + is_deeply( [ 'ABCD' ], $myimap->capability( ), + 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]' + ) ; + + ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ), + 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]' + ) ; + is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ), + 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]' + ) ; + + ok( mock_capability( $myimap, 'ABC', 'DEF' ), + 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]' + ) ; + is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ], + 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]' + ) ; + + ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ), + 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]' + ) ; + is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ], + 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]' + ) ; + + note( 'Leaving tests_mock_capability()' ) ; + return ; +} + +sub sig_install_toggle_sleep +{ + my $mysync = shift ; + if ( ! 'MSWin32' eq $OSNAME ) { + sig_install( $mysync, \&toggle_sleep, 'USR1' ) + } + return ; +} + + +sub mock_capability +{ + my $myimap = shift ; + my @has_capability_value = @ARG ; + my ( $has_capability_value ) = @has_capability_value ; + + if ( ! $myimap ) + { + require_ok( "Test::MockObject" ) ; + $myimap = Test::MockObject->new( ) ; + } + + $myimap->mock( + 'capability', + sub { return wantarray ? + @has_capability_value + : $has_capability_value ; + } + ) ; + + return $myimap ; +} + + +sub tests_capability_of +{ + note( 'Entering tests_capability_of()' ) ; + + is( undef, capability_of( ), + 'capability_of: no args => undef' ) ; + + my $myimap ; + is( undef, capability_of( $myimap ), + 'capability_of: undef => undef' ) ; + + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + is( undef, capability_of( $myimap, 'CACA' ), + 'capability_of: two args unknown capability => undef' ) ; + + + is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ), + 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ; + + note( 'Leaving tests_capability_of()' ) ; + return ; +} + + +sub capability_of +{ + my $imap = shift || return ; + my $capability_keyword = shift || return ; + + my @capability = $imap->capability ; + + if ( ! @capability ) { return ; } + my $capability_value = search_in_array( $capability_keyword, @capability ) ; + + return $capability_value ; +} + + +sub tests_search_in_array +{ + note( 'Entering tests_search_in_array()' ) ; + + is( undef, search_in_array( 'KA' ), + 'search_in_array: no array => undef ' ) ; + + is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ), + 'search_in_array: KA KA=VA => VA ' ) ; + + is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ), + 'search_in_array: KA KA=VA KB=VB => VA ' ) ; + + is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ), + 'search_in_array: KA=VA KB=VB => VB ' ) ; + + note( 'Leaving tests_search_in_array()' ) ; + return ; +} + +sub search_in_array +{ + my ( $key, @array ) = @ARG ; + + foreach my $item ( @array ) + { + + if ( $item =~ /([^=]+)=(.*)/ ) + { + if ( $1 eq $key ) + { + return $2 ; + } + } + } + + return ; } -sub all_defined { + +sub tests_appendlimit_from_capability +{ + note( 'Entering tests_appendlimit_from_capability()' ) ; + + is( undef, appendlimit_from_capability( ), + 'appendlimit_from_capability: no args => undef' + ) ; + + my $myimap ; + is( undef, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: undef arg => undef' + ) ; + + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + # Normal behavior + is( $NUMBER_123456, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456' + ) ; + + # Not a number + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ; + + is( undef, appendlimit_from_capability( $myimap ), + 'appendlimit_from_capability: not a number => undef' + ) ; + + note( 'Leaving tests_appendlimit_from_capability()' ) ; + return ; +} + + +sub appendlimit_from_capability +{ + my $myimap = shift ; + if ( ! $myimap ) + { + myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ; + return ; + } + + #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ; + my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ; + #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ; + if ( is_an_integer( $appendlimit ) ) + { + return $appendlimit ; + } + return ; +} + + +sub tests_appendlimit +{ + note( 'Entering tests_appendlimit()' ) ; + + is( undef, appendlimit( ), + 'appendlimit: no args => undef' + ) ; + + my $mysync = { } ; + + is( undef, appendlimit( $mysync ), + 'appendlimit: no imap2 => undef' + ) ; + + my $myimap ; + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + + $mysync->{ imap2 } = $myimap ; + + is( 123456, appendlimit( $mysync ), + 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456' + ) ; + + note( 'Leaving tests_appendlimit()' ) ; + return ; +} + +sub appendlimit +{ + my $mysync = shift || return ; + my $myimap = $mysync->{ imap2 } ; + + my $appendlimit = appendlimit_from_capability( $myimap ) ; + if ( defined $appendlimit ) + { + myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY\n" ) ; + return $appendlimit ; + } + return ; + +} + + +sub tests_maxsize_setting +{ + note( 'Entering tests_maxsize_setting()' ) ; + + is( undef, maxsize_setting( ), + 'maxsize_setting: no args => undef' + ) ; + + my $mysync ; + + is( undef, maxsize_setting( $mysync ), + 'maxsize_setting: undef arg => undef' + ) ; + + $mysync = { } ; + $mysync->{ maxsize } = $NUMBER_123456 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: --maxsize 123456 alone => 123456' + ) ; + + + $mysync = { } ; + my $myimap ; + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ; + $mysync->{ imap2 } = $myimap ; + + # APPENDLIMIT alone + is( $NUMBER_654321, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 654321 alone => 654321' + ) ; + + is( $NUMBER_654321, $mysync->{ maxsize }, + 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321' + ) ; + + + + # Case: "APPENDLIMIT >= --maxsize" => maxsize. + $mysync->{ maxsize } = $NUMBER_123456 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456' + ) ; + + # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT. + + $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; + $mysync->{ maxsize } = $NUMBER_654321 ; + + is( $NUMBER_123456, maxsize_setting( $mysync ), + 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 ' + ) ; + + note( 'Leaving tests_maxsize_setting()' ) ; + + return ; +} + +sub maxsize_setting +{ + my $mysync = shift || return ; + + $mysync->{ appendlimit } = appendlimit( $mysync ) ; + + my $maxsize ; + + if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) ) + { + return min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ; + } + elsif ( defined $mysync->{ appendlimit } ) + { + $mysync->{ maxsize } = $mysync->{ appendlimit } ; + return $mysync->{ maxsize } ; + }elsif ( defined $mysync->{ maxsize } ) + { + return $mysync->{ maxsize } ; + }else + { + return ; + } +} + + + + +sub all_defined +{ if ( not @ARG ) { return 0 ; } @@ -2737,7 +3346,8 @@ sub all_defined { return 1 ; } -sub tests_all_defined { +sub tests_all_defined +{ note( 'Entering tests_all_defined()' ) ; is( 0, all_defined( ), 'all_defined: no param => 0' ) ; @@ -2754,7 +3364,8 @@ sub tests_all_defined { } -sub tests_hashsynclocal { +sub tests_hashsynclocal +{ note( 'Entering tests_hashsynclocal()' ) ; my $mysync = { @@ -2792,7 +3403,8 @@ sub tests_hashsynclocal { return ; } -sub hashsynclocal { +sub hashsynclocal +{ my $mysync = shift ; my $hashkey = shift ; # Optional, only there for tests my $hashfile = $mysync->{ hashfile } ; @@ -2810,7 +3422,8 @@ sub hashsynclocal { } -sub tests_hashsync { +sub tests_hashsync +{ note( 'Entering tests_hashsync()' ) ; @@ -2827,7 +3440,8 @@ sub tests_hashsync { return ; } -sub hashsync { +sub hashsync +{ my $mysync = shift ; my $hashkey = shift ; @@ -2845,7 +3459,8 @@ sub hashsync { } -sub tests_createhashfileifneeded { +sub tests_createhashfileifneeded +{ note( 'Entering tests_createhashfileifneeded()' ) ; is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ; @@ -2854,7 +3469,8 @@ sub tests_createhashfileifneeded { return ; } -sub createhashfileifneeded { +sub createhashfileifneeded +{ my $hashfile = shift ; my $hashkey = shift || rand32( ) ; @@ -2887,7 +3503,8 @@ sub createhashfileifneeded { return ; } -sub tests_rand32 { +sub tests_rand32 +{ note( 'Entering tests_rand32()' ) ; my $string = rand32( ) ; @@ -2899,14 +3516,16 @@ sub tests_rand32 { return ; } -sub rand32 { +sub rand32 +{ my @chars = ( "a".."z" ) ; my $string; $string .= $chars[rand @chars] for 1..32 ; return $string ; } -sub imap_id_stuff { +sub imap_id_stuff +{ my $mysync = shift ; if ( not $mysync->{id} ) { return ; } ; @@ -2919,7 +3538,8 @@ sub imap_id_stuff { return ; } -sub imap_id { +sub imap_id +{ my ( $mysync, $imap, $Side ) = @_ ; $Side ||= q{} ; @@ -2943,7 +3563,8 @@ sub imap_id { return( $imap_id_response ) ; } -sub imapsync_id { +sub imapsync_id +{ my $mysync = shift ; my $overhashref = shift ; # See http://tools.ietf.org/html/rfc2971.html @@ -2957,7 +3578,7 @@ sub imapsync_id { vendor => 'Gilles LAMIRAL', 'support-url' => 'https://imapsync.lamiral.info/', # Example of date-time: 19-Sep-2015 08:56:07 - date => date_from_rcs( q{$Date: 2018/05/05 21:10:43 $ } ), + date => date_from_rcs( q{$Date: 2019/05/01 22:14:00 $ } ), } ; my $imapsync_id_github = { @@ -2966,7 +3587,7 @@ sub imapsync_id { os => $OSNAME, vendor => 'github', 'support-url' => 'https://github.com/imapsync/imapsync', - date => date_from_rcs( q{$Date: 2018/05/05 21:10:43 $ } ), + date => date_from_rcs( q{$Date: 2019/05/01 22:14:00 $ } ), } ; $imapsync_id = $imapsync_id_lamiral ; @@ -2977,7 +3598,8 @@ sub imapsync_id { return( $imapsync_id_str ) ; } -sub tests_imapsync_id { +sub tests_imapsync_id +{ note( 'Entering tests_imapsync_id()' ) ; my $mysync ; @@ -2987,17 +3609,18 @@ sub tests_imapsync_id { version => 111, os => 'beurk', date => '22-12-1968', - side => 'host1' + side => 'host1' } ), - 'tests_imapsync_id override' + 'tests_imapsync_id override' ) ; note( 'Leaving tests_imapsync_id()' ) ; return ; } -sub format_for_imap_arg { +sub format_for_imap_arg +{ my $ref = shift ; my $string = q{} ; @@ -3018,7 +3641,8 @@ sub format_for_imap_arg { -sub tests_format_for_imap_arg { +sub tests_format_for_imap_arg +{ note( 'Entering tests_format_for_imap_arg()' ) ; ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ; @@ -3029,8 +3653,9 @@ sub tests_format_for_imap_arg { return ; } -sub quota { - my ( $imap, $side, $mysync ) = @_ ; +sub quota +{ + my ( $mysync, $imap, $side ) = @_ ; my %side = ( h1 => 'Host1', @@ -3050,8 +3675,8 @@ sub quota { #$imap->quota( '""' ) ; 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 ) ; + my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ; + my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ; $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ; $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ; my $quota_percent ; @@ -3068,61 +3693,69 @@ sub quota { return ; } -sub tests_quota_extract_storage_limit_in_bytes { - note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ; +sub tests_quota_extract_storage_limit_in_bytes +{ + note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ; + my $mysync = {} ; 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_104_857_600 * $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( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ; - note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ; + note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ; return ; } -sub quota_extract_storage_limit_in_bytes { +sub quota_extract_storage_limit_in_bytes +{ + my $mysync = shift ; my $imap_output = shift ; my $limit_kb ; $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" ) ; + $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ; return( $KIBI * $limit_kb ) ; } -sub tests_quota_extract_storage_current_in_bytes { +sub tests_quota_extract_storage_current_in_bytes +{ note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ; - + my $mysync = {} ; 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 ') ; + ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $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 { +sub quota_extract_storage_current_in_bytes +{ + my $mysync = shift ; my $imap_output = shift ; my $current_kb ; $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" ) ; + $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ; return( $KIBI * $current_kb ) ; } -sub automap { +sub automap +{ my ( $mysync ) = @_ ; if ( $mysync->{automap} ) { @@ -3132,8 +3765,8 @@ sub automap { return ; } - $mysync->{h1_special} = special_from_folders_hash( $mysync->{imap1}, 'Host1' ) ; - $mysync->{h2_special} = special_from_folders_hash( $mysync->{imap2}, 'Host2' ) ; + $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ; + $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ; build_possible_special( $mysync ) ; build_guess_special( $mysync ) ; @@ -3145,7 +3778,8 @@ sub automap { -sub build_guess_special { +sub build_guess_special +{ my ( $mysync ) = shift ; foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) { @@ -3175,7 +3809,8 @@ sub build_guess_special { return ; } -sub guess_special { +sub guess_special +{ my( $folder, $possible_special_ref, $prefix ) = @_ ; my $folder_no_prefix = $folder ; @@ -3189,7 +3824,8 @@ sub guess_special { return( $guess_special ) ; } -sub tests_guess_special { +sub tests_guess_special +{ note( 'Entering tests_guess_special()' ) ; my $possible_special_ref = build_possible_special( my $mysync ) ; @@ -3202,9 +3838,10 @@ sub tests_guess_special { return ; } -sub build_automap { +sub build_automap +{ my $mysync = shift ; - $debug and myprint( "Entering build_automap\n" ) ; + $mysync->{ 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} ; @@ -3253,7 +3890,8 @@ sub build_automap { # I will not add what there is at: # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548 # because it works well without -sub build_possible_special { +sub build_possible_special +{ my $mysync = shift ; my $possible_special = { } ; # All|Archive|Drafts|Flagged|Junk|Sent|Trash @@ -3272,19 +3910,41 @@ sub build_possible_special { 'Elementy wys&AUI-ane'] ; $possible_special->{'\Trash'} = [ 'Trash', 'TRASH', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-', 'Kosz', 'Deleted Items' ] ; - + 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' ] ) ) ; + $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ; return( $possible_special ) ; } -sub special_from_folders_hash { - my ( $imap, $side ) = @_ ; +sub tests_special_from_folders_hash +{ + note( 'Entering tests_special_from_folders_hash()' ) ; + + my $mysync = {} ; + require_ok( "Test::MockObject" ) ; + my $imapT = Test::MockObject->new( ) ; + + is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ; + is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ; + is_deeply( {}, special_from_folders_hash( $mysync, $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( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ; + + note( 'Leaving tests_special_from_folders_hash()' ) ; + return( ) ; +} + +sub special_from_folders_hash +{ + my ( $mysync, $imap, $side ) = @_ ; my %special = ( ) ; if ( ! defined $imap ) { return ; } @@ -3292,7 +3952,7 @@ sub special_from_folders_hash { if ( ! $imap->can( 'folders_hash' ) ) { my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ; - errors_incr( $sync, $error ) ; + errors_incr( $mysync, $error ) ; return( \%special ) ; # empty hash ref } my $folders_hash = $imap->folders_hash( ) ; @@ -3315,26 +3975,10 @@ sub special_from_folders_hash { 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 { +sub errors_incr +{ my ( $mysync, @error ) = @ARG ; - $sync->{nb_errors}++ ; + $mysync->{nb_errors}++ ; if ( @error ) { errors_log( $mysync, @error ) ; @@ -3342,10 +3986,10 @@ sub errors_incr { } $mysync->{errorsmax} ||= $ERRORS_MAX ; - if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) { - myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ; + if ( $mysync->{nb_errors} >= $mysync->{errorsmax} ) { + myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ; if ( $mysync->{errorsdump} ) { - myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ; + myprint( errorsdump( $mysync->{nb_errors}, errors_log( $mysync ) ) ) ; # again since errorsdump( ) can be very verbose and masquerade previous warning myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). Exiting.\n" ) ; } @@ -3354,7 +3998,23 @@ sub errors_incr { return ; } -sub errors_log { +sub tests_errors_log +{ + note( 'Entering tests_errors_log()' ) ; + is( undef, errors_log( ), 'errors_log: no args => undef' ) ; + my $mysync = {} ; + is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ; + is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ; + # cumulative + is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ; + is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ; + is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ; + note( 'Leaving tests_errors_log()' ) ; + return ; +} + +sub errors_log +{ my ( $mysync, @error ) = @ARG ; if ( ! $mysync->{errors_log} ) { @@ -3372,16 +4032,9 @@ sub errors_log { } } -sub tests_errors_log { - note( 'Entering tests_errors_log()' ) ; - - note( 'Leaving tests_errors_log()' ) ; - return ; -} - - -sub errorsdump { +sub errorsdump +{ my( $nb_errors, @errors_log ) = @ARG ; my $error_num = 0 ; my $errors_list = q{} ; @@ -3396,7 +4049,8 @@ sub errorsdump { } -sub tests_live_result { +sub tests_live_result +{ note( 'Entering tests_live_result()' ) ; my $nb_errors = shift ; @@ -3409,77 +4063,94 @@ sub tests_live_result { return ; } -sub foldersizesatend { +sub foldersizesatend +{ + my $mysync = shift ; timenext( ) ; - return if ( $imap1->IsUnconnected( ) ) ; - return if ( $imap2->IsUnconnected( ) ) ; + return if ( $mysync->{imap1}->IsUnconnected( ) ) ; + return if ( $mysync->{imap2}->IsUnconnected( ) ) ; # Get all folders on host2 again since new were created - @h2_folders_all = sort $imap2->folders(); + @h2_folders_all = sort $mysync->{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, $sync->{abletosearch1}, @h1_folders_wanted ) ; - ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, $sync->{abletosearch2}, @h2_folders_from_1_wanted ) ; + ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $mysync->{imap1}, $search1, $mysync->{abletosearch1}, @h1_folders_wanted ) ; + ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $mysync->{imap2}, $search2, $mysync->{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 ) ; + errors_incr( $mysync, $error ) ; } return ; } -sub size_filtered_flag { +sub size_filtered_flag +{ + my $mysync = shift ; my $h1_size = shift ; - if (defined $maxsize and $h1_size >= $maxsize) { + if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) { return( 1 ) ; } - if (defined $minsize and $h1_size <= $minsize) { + 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 ) = @_ ; +sub sync_flags_fir +{ + my ( $mysync, $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 } ; my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ; - return if size_filtered_flag( $h1_size ) ; + return if size_filtered_flag( $mysync, $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{} ; - sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + sync_flags( $mysync, $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 ) = @_ ; +sub sync_flags_after_copy +{ + # Activated with option --syncflagsaftercopy + my( $mysync, $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 ) ; + if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) { + my $h2_flags = "@h2_flags" ; + ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ; + sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; + }else{ + myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ; + } return ; } -sub sync_flags { - my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ; +# Globals +# $debug +# $debugflags +# $permanentflags2 - ( $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" ) ; + +sub sync_flags +{ + my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ; + + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; $h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ; $h2_flags = flagscase( $h2_flags ) ; - ( $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" ) ; + ( $mysync->{ debug } or $debugflags ) and + myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; # compare flags - set flags if there a difference @@ -3487,17 +4158,18 @@ sub sync_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 ( $mysync->{ debug } or $debugflags ) + and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ; - 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 ) ; + # This sets flags exactly. So flags can be removed with this. + # When you remove a \Seen flag on host1 you want it + # to be removed on host2. Just add flags is not what + # we need most of the time, so no + like in "+FLAGS.SILENT". + + if ( not $mysync->{dry} and $diff and not $mysync->{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]: ", + $mysync->{imap2}->LastError || q{}, "\n" ; + errors_incr( $mysync, $error_msg ) ; } return ; @@ -3505,11 +4177,13 @@ sub sync_flags { -sub _filter { +sub _filter +{ + my $mysync = shift ; my $str = shift or return q{} ; my $sz = $SIZE_MAX_STR ; my $len = length $str ; - if ( not $debug and $len > $sz*2 ) { + if ( not $mysync->{ debug } and $len > $sz*2 ) { my $beg = substr $str, 0, $sz ; my $end = substr $str, -$sz, $sz ; $str = $beg . '...' . $end ; @@ -3520,17 +4194,18 @@ sub _filter { -sub lost_connection { - my( $imap, $error_message ) = @_; +sub lost_connection +{ + my( $mysync, $imap, $error_message ) = @_; if ( $imap->IsUnconnected( ) ) { - $sync->{nb_errors}++ ; + $mysync->{nb_errors}++ ; my $lcomm = $imap->LastIMAPCommand || q{} ; my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ; # if string is long try reduce to a more reasonable size - $lcomm = _filter( $lcomm ) ; - $einfo = _filter( $einfo ) ; - myprint( "Failure: last command: $lcomm\n") if ($debug && $lcomm) ; + $lcomm = _filter( $mysync, $lcomm ) ; + $einfo = _filter( $mysync, $einfo ) ; + myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ; myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ; return( 1 ) ; } @@ -3539,8 +4214,10 @@ sub lost_connection { } } -sub tests_max { +sub tests_max +{ 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') ; @@ -3566,10 +4243,11 @@ sub tests_max { return ; } -sub max { +sub max +{ my @list = @_ ; return( undef ) if ( 0 == scalar @list ) ; - + my( @numbers, @notnumbers ) ; foreach my $item ( @list ) { if ( is_number( $item ) ) { @@ -3578,7 +4256,7 @@ sub max { push @notnumbers, $item ; } } - + my @sorted ; if ( @numbers ) { @sorted = sort { $a <=> $b } @numbers ; @@ -3587,11 +4265,14 @@ sub max { }else{ return ; } - + return( pop @sorted ) ; } -sub tests_is_number { +sub tests_is_number +{ + note( 'Entering tests_is_number()' ) ; + ok( ! is_number( ), 'is_number: no args => undef ' ) ; ok( is_number( 1 ), 'is_number: 1 => 1' ) ; ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ; @@ -3603,23 +4284,27 @@ sub tests_is_number { ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ; ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ; ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ; + + note( 'Leaving tests_is_number()' ) ; return ; } -sub is_number { +sub is_number +{ my $item = shift ; - + if ( ! defined $item ) { return ; } - + if ( $item =~ /\A$RE{num}{real}\Z/ ) { return 1 ; } return ; } -sub tests_min { +sub tests_min +{ note( 'Entering tests_min()' ) ; is( 0, min( 0 ), 'min 0 => 0' ) ; @@ -3633,7 +4318,7 @@ sub tests_min { is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ; is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ; is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ; - + is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ; is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; @@ -3649,7 +4334,8 @@ sub tests_min { } -sub min { +sub min +{ my @list = @_ ; return( undef ) if ( 0 == scalar @list ) ; @@ -3661,7 +4347,7 @@ sub min { push @notnumbers, $item ; } } - + my @sorted ; if ( @numbers ) { @sorted = sort { $a <=> $b } @numbers ; @@ -3675,8 +4361,10 @@ sub min { } -sub check_lib_version { - $debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ; +sub check_lib_version +{ + my $mysync = shift ; + $mysync->{ 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 ; @@ -3689,18 +4377,21 @@ sub check_lib_version { return ; } -sub module_version_str { +sub module_version_str +{ my( $module_name, $module_version ) = @_ ; my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ; return( $str ) ; } -sub modulesversion { +sub modulesversion +{ my @list_version; my %modulesversion = ( 'Authen::NTLM' => sub { $Authen::NTLM::VERSION }, + 'CGI' => sub { $CGI::VERSION }, 'Compress::Zlib' => sub { $Compress::Zlib::VERSION }, 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION }, 'Data::Uniqid' => sub { $Data::Uniqid::VERSION }, @@ -3711,10 +4402,11 @@ sub modulesversion { '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::Socket::INET' => sub { $IO::Socket::INET::VERSION }, + 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION }, + 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION }, + 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION }, 'IO::Tee' => sub { $IO::Tee::VERSION }, 'JSON' => sub { $JSON::VERSION }, 'JSON::WebToken' => sub { $JSON::WebToken::VERSION }, @@ -3742,17 +4434,58 @@ sub modulesversion { push @list_version, module_version_str( $module_name, $v ) ; } - return( @list_version ) ; } +sub tests_command_line_nopassword +{ + note( 'Entering tests_command_line_nopassword()' ) ; + + ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); + my $mysync = {} ; + ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' ); + #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; + ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' ); + ok( '--blabla --password1 MASKED --blibli' + eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); + $mysync->{showpasswords} = 1 ; + ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); + ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' ); + #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; + ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' ); + ok( '--blabla --password1 secret1 --blibli' + eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); + + note( 'Leaving tests_command_line_nopassword()' ) ; + return ; +} + # Construct a command line copy with passwords replaced by MASKED. -sub command_line_nopassword { - my @argv = @_ ; +sub command_line_nopassword +{ + my $mysync = shift @ARG ; + my @argv = @ARG ; my @argv_nopassword ; - return( "@argv" ) if $sync->{showpasswords} ; + if ( $mysync->{ cmdcgi } ) { + @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ; + return( "@argv_nopassword" ) ; + } + + if ( $mysync->{showpasswords} ) + { + return( "@argv" ) ; + } + + @argv_nopassword = mask_password_value( @argv ) ; + return("@argv_nopassword") ; +} + +sub mask_password_value +{ + my @argv = @ARG ; + my @argv_nopassword ; while ( @argv ) { my $arg = shift @argv ; # option name or value if ( $arg =~ m/-password[12]/x ) { @@ -3762,34 +4495,54 @@ sub command_line_nopassword { push @argv_nopassword, $arg ; # same option or value } } - return("@argv_nopassword") ; + 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'); - $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'); +sub tests_get_stdin_masked +{ + note( 'Entering tests_get_stdin_masked()' ) ; - note( 'Leaving tests_command_line_nopassword()' ) ; + is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ; + is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ; + + note( 'Leaving tests_get_stdin_masked()' ) ; return ; } -sub ask_for_password { - my ( $user, $host ) = @ARG ; - myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) " ) ; +####################################################### +# The issue is that prompt() does not prompt the prompt +# when the program is used like +# { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi + +# use IO::Prompter ; +sub get_stdin_masked +{ + my $prompt = shift || 'Say something: ' ; + local @ARGV = () ; + my $input = prompt( + -prompt => $prompt, + -echo => '*', + ) ; + #myprint( "You said: $input\n" ) ; + return $input ; +} + +sub ask_for_password_new +{ + my $prompt = shift ; + my $password = get_stdin_masked( $prompt ) ; + return $password ; +} +######################################################### + + +sub ask_for_password +{ + my $prompt = shift ; + myprint( $prompt ) ; Term::ReadKey::ReadMode( 2 ) ; + ## no critic (InputOutput::ProhibitExplicitStdin) my $password = ; chomp $password ; myprint( "\nGot it\n" ) ; @@ -3799,34 +4552,38 @@ sub ask_for_password { # Have to refactor get_password1() get_password2() # to have only get_password() and two calls -sub get_password1 { +sub get_password1 +{ my $mysync = shift ; $mysync->{password1} - || $passfile1 + || $mysync->{ passfile1 } || 'PREAUTH' eq $authmech1 || 'EXTERNAL' eq $authmech1 || $ENV{IMAPSYNC_PASSWORD1} - || do { - myprint( << 'FIN_PASSFILE' ) ; + || 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 + my $user = $authuser1 || $mysync->{user1} ; + my $host = $mysync->{host1} ; + my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; + $mysync->{password1} = ask_for_password( $prompt ) ; + } ; - $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" ) ; + if ( defined $mysync->{ passfile1 } ) { + if ( ! -e -r $mysync->{ passfile1 } ) { + myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ; exit_clean( $mysync, $EX_NOINPUT ) ; } # passfile1 readable - $mysync->{password1} = firstline ( $passfile1 ) ; + $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ; return ; } if ( $ENV{IMAPSYNC_PASSWORD1} ) { @@ -3836,35 +4593,39 @@ FIN_PASSFILE return ; } -sub get_password2 { +sub get_password2 +{ my $mysync = shift ; $mysync->{password2} - || $passfile2 + || $mysync->{ passfile2 } || 'PREAUTH' eq $authmech2 || 'EXTERNAL' eq $authmech2 || $ENV{IMAPSYNC_PASSWORD2} - || do { - myprint( << 'FIN_PASSFILE' ) ; + || 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} ) ; - } ; + my $user = $authuser2 || $mysync->{user2} ; + my $host = $mysync->{host2} ; + my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; + $mysync->{password2} = ask_for_password( $prompt ) ; + } ; - if ( defined $passfile2 ) { - if ( ! -e -r $passfile2 ) { - myprint( "Failure: file from parameter --passfile2 $passfile2 does not exist or is not readable\n" ) ; + if ( defined $mysync->{ passfile2 } ) { + if ( ! -e -r $mysync->{ passfile2 } ) { + myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ; exit_clean( $mysync, $EX_NOINPUT ) ; } # passfile2 readable - $mysync->{password2} = firstline ( $passfile2 ) ; + $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ; return ; } if ( $ENV{IMAPSYNC_PASSWORD2} ) { @@ -3876,63 +4637,169 @@ FIN_PASSFILE -sub catch_ignore { + +sub remove_tmp_files +{ + my $mysync = shift or return ; + $mysync->{pidfile} or return ; + if ( -e $mysync->{pidfile} ) { + unlink $mysync->{pidfile} ; + } + return ; +} + +sub cleanup_before_exit +{ + my $mysync = shift ; + remove_tmp_files( $mysync ) ; + if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() ) + { + myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ; + $mysync->{imap1}->logout( ) ; + } + if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() ) + { + myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ; + $mysync->{imap2}->logout( ) ; + } + if ( $mysync->{log} ) { + myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ; + } + if ( $mysync->{log} and $mysync->{logfile_handle} ) { + #myprint( "Closing $mysync->{ logfile }\n" ) ; + close $mysync->{logfile_handle} ; + } + return ; +} + + + +sub exit_clean +{ + my $mysync = shift @ARG ; + my $status = shift @ARG ; + my @messages = @ARG ; + if ( @messages ) + { + myprint( @messages ) ; + } + myprint( "Exiting with return value $status\n" ) ; + cleanup_before_exit( $mysync ) ; + + exit $status ; +} + +sub missing_option +{ + my $mysync = shift ; + my $option = shift ; + exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ; + return ; +} + + +sub catch_ignore +{ my $mysync = shift ; my $signame = shift ; - + my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID). Received $sigcounter $signame signals so far. Thanks!\n" ) ; + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; stats( $mysync ) ; return ; } -sub catch_exit { +sub catch_exit +{ my $mysync = shift ; my $signame = shift || q{} ; if ( $signame ) { - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID). Asked to terminate\n" ) ; + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Asked to terminate\n" ) ; + if ( $mysync->{stats} ) { + myprint( "Here are the final stats of this sync not completely finished so far\n" ) ; + stats( $mysync ) ; + myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ", + getppid( ), "). I am asked to terminate immediately.\n" ) ; + myprint( "You should resynchronize those accounts by running a sync again,\n", + "since some messages and entire folders might still be missing on host2.\n" ) ; + } + ## no critic (RequireLocalizedPunctuationVars) + $SIG{ $signame } = 'DEFAULT'; # restore default action + # kill myself with $signame + # https://www.cons.org/cracauer/sigint.html + myprint( "Killing myself with signal $signame\n" ) ; + cleanup_before_exit( $mysync ) ; + kill( $signame, $PROCESS_ID ) ; + } + else + { + exit_clean( $mysync, $EXIT_BY_SIGNAL ) ; } - myprint( "Here are the final stats of this sync not completely finished so far\n" ) ; - stats( $mysync ) ; - myprint( "Ended by a signal $signame (my PID is $PROCESS_ID). I am asked to terminate immediately.\n" ) ; - myprint( "You should resynchronize those accounts by running a sync again,\n", - "since some messages and entire folders might still be missing on host2.\n" ) ; - exit_clean( $mysync, $EXIT_BY_SIGNAL ) ; return ; } -sub catch_reconnect { + +sub catch_print +{ my $mysync = shift ; my $signame = shift ; - myprint( "\nGot a signal $signame (my PID is $PROCESS_ID)\n", - "Hit 2 ctr-c within 2 seconds to exit the program\n", - "Hit only 1 ctr-c to reconnect to both imap servers\n", - ) ; - if ( here_twice( $mysync ) ) { - myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ; - catch_exit( $mysync ) ; - }else{ - myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ; - } - 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" ) ; + my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), + "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; return ; } -sub tests_reconnect_12_if_needed { + +sub catch_reconnect +{ + my $mysync = shift ; + my $signame = shift ; + if ( here_twice( $mysync ) ) { + myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ; + catch_exit( $mysync, $signame ) ; + }else{ + myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n", + "Hit 2 ctr-c within 2 seconds to exit the program\n", + "Hit only 1 ctr-c to reconnect to both imap servers\n", + ) ; + myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ; + + if ( ! defined $mysync->{imap1} ) { return ; } + if ( ! defined $mysync->{imap2} ) { return ; } + + myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ; + $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ; + $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $mysync->{imap1}->reconnect( ) ) + { + myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ; + } + else + { + exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; + } + myprint( "Info: reconnecting to host2 imap server\n" ) ; + $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ; + $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; + if ( $mysync->{imap2}->reconnect( ) ) + { + myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ; + } + else + { + exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; + } + myprint( "Info: reconnected to both imap servers\n" ) ; + } + return ; +} + +sub tests_reconnect_12_if_needed +{ note( 'Entering tests_reconnect_12_if_needed()' ) ; my $mysync ; @@ -3949,7 +4816,8 @@ sub tests_reconnect_12_if_needed { return ; } -sub reconnect_12_if_needed { +sub reconnect_12_if_needed +{ my $mysync = shift ; #return 2 ; if ( ! reconnect_if_needed( $mysync->{imap1} ) ) { @@ -3963,7 +4831,8 @@ sub reconnect_12_if_needed { } -sub tests_reconnect_if_needed { +sub tests_reconnect_if_needed +{ note( 'Entering tests_reconnect_if_needed()' ) ; @@ -3983,7 +4852,8 @@ sub tests_reconnect_if_needed { return ; } -sub reconnect_if_needed { +sub reconnect_if_needed +{ # return undef upon failure. # return 1 upon connection success, with or without reconnection. @@ -4015,7 +4885,8 @@ sub reconnect_if_needed { -sub here_twice { +sub here_twice +{ my $mysync = shift ; my $now = time ; my $previous = $mysync->{lastcatch} || 0 ; @@ -4029,35 +4900,42 @@ sub here_twice { } -sub justconnect { - - $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( ) ; +sub justconnect +{ + my $mysync = shift ; + $mysync->{imap1} = connect_imap( $mysync->{host1}, $mysync->{port1}, $debugimap1, + $mysync->{ssl1}, $mysync->{tls1}, 'Host1', $mysync->{h1}->{timeout}, $mysync->{h1} ) ; + $mysync->{imap2} = connect_imap( $mysync->{host2}, $mysync->{port2}, $debugimap2, + $mysync->{ssl2}, $mysync->{tls2}, 'Host2', $mysync->{h2}->{timeout}, $mysync->{h2} ) ; + $mysync->{imap1}->logout( ) ; + $mysync->{imap2}->logout( ) ; return ; } +sub skip_macosx +{ + return ; + return( 'macosx.polarhome.com' eq hostname() ) ; +} -sub tests_mailimapclient_connect { +sub tests_mailimapclient_connect +{ note( 'Entering tests_mailimapclient_connect()' ) ; + my $imap ; - # ipv4 + # 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() - or 'macosx.polarhome.com' eq hostname() - ) { skip( 'Tests avoided on macosx get stuck', 1 ) ; } - is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ; - } + # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP + # 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( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, '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' ) ; @@ -4070,14 +4948,23 @@ sub tests_mailimapclient_connect { 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 ) ; } + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + ) + { + 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' ) ; } @@ -4088,87 +4975,103 @@ sub tests_mailimapclient_connect { return ; } -sub tests_mailimapclient_connect_bug { + +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 )' ) ; - + ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ; + is( 'ks2ipv6.lamiral.info', $imap->Server( 'ks2ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks2ipv6.lamiral.info)' ) ; + is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug 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( ), $!, ) ; + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + ) + { + skip( 'Tests avoided on CUILLERE can not do ipv6', 1 ) ; + } + like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks2ipv6.lamiral.info' ) + or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ; } - #is( $imap->logout( ), undef, 'mailimapclient_connect ipv6: logout in ssl causes failure' ) ; - is( undef, undef $imap, 'mailimapclient_connect ipv6: free variable' ) ; + #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ; + is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ; note( 'Leaving tests_mailimapclient_connect_bug()' ) ; return ; } -sub mailimapclient_connect { - return ; -} - - - -sub tests_connect_socket { +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, - ) ; + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + ) + { + skip( 'Tests avoided on CUILLERE/macosx.polarhome.com/docker cannot do ipv6', 2 ) ; + } - - ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 143 IO::Socket::INET6' ) ; - #$imap->Debug( 1 ) ; - # myprint( $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, - ) ; - # myprint $socket ; - ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ; - #$imap->Debug( 1 ) ; - # myprint $imap->capability( ) ; - $socket->close( ) ; - if ( $imap ) { - $socket->close( ) ; - } - #$socket->close(SSL_no_shutdown => 1) ; - #$imap->logout( ) ; - #myprint "\n" ; - #$imap->logout( ) ; - } + $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 ) ; + # myprint( $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, + ) ; + # myprint( $socket ) ; + ok( $imap = connect_socket( $socket ), 'connect_socket: ks2ipv6.lamiral.info port 993 IO::Socket::SSL' ) ; + #$imap->Debug( 1 ) ; + # myprint( $imap->capability( ) ) ; + # $socket->close( ) ; + if ( $imap ) { + $socket->close( ) ; + } + #$socket->close(SSL_no_shutdown => 1) ; + #$imap->logout( ) ; + #myprint( "\n" ) ; + #$imap->logout( ) ; + } note( 'Leaving tests_connect_socket()' ) ; return ; } -sub connect_socket { +sub connect_socket +{ my( $socket ) = @ARG ; if ( ! defined $socket ) { return ; } - + my $host = $socket->peerhost( ) ; my $port = $socket->peerport( ) ; #print "socket->peerhost: ", $socket->peerhost( ), "\n" ; @@ -4181,37 +5084,48 @@ sub connect_socket { } -sub tests_probe_imapssl { +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 ) ; } + if ( + 'CUILLERE' eq hostname() + or + skip_macosx() + or + -e '/.dockerenv' + ) + { + skip( 'Tests avoided on CUILLERE/macosx.polarhome.com/docker cannot do ipv6', 2 ) ; + } like( probe_imapssl( 'ks2ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks2ipv6.lamiral.info matches "* OK"' ) ; + like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com 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 { +sub probe_imapssl +{ my $host = shift ; - + if ( ! $host ) { return ; } - - my $socket = IO::Socket::SSL->new( + + 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" ; @@ -4220,7 +5134,8 @@ sub probe_imapssl { } -sub connect_imap { +sub connect_imap +{ my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ; my $imap = Mail::IMAPClient->new( ) ; if ( $ssl ) { set_ssl( $imap, $h ) } @@ -4233,7 +5148,7 @@ sub connect_imap { 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" ) ; + or exit_clean( $sync, $EXIT_CONNECTION_FAILURE, "$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] ; @@ -4243,14 +5158,15 @@ sub connect_imap { if ( $tls ) { set_tls( $imap, $h ) ; $imap->starttls( ) - or die_clean("$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; + or exit_clean( $sync, $EXIT_TLS_FAILURE, "$Side: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; myprint( "$Side: Socket successfuly converted to SSL\n" ) ; } return( $imap ) ; } -sub login_imap { +sub login_imap +{ my @allargs = @_ ; my( @@ -4265,7 +5181,7 @@ sub login_imap { 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" ) ; + or exit_clean( $mysync, $EXIT_CONNECTION_FAILURE, "$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] ; @@ -4282,14 +5198,14 @@ sub login_imap { $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]" ) ; + exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ; } } 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" ) ; + or exit_clean( $mysync, $EXIT_TLS_FAILURE, "$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; myprint( "$Side: Socket successfuly converted to SSL\n" ) ; } @@ -4300,46 +5216,52 @@ sub login_imap { } -sub authenticate_imap { - - my($imap, +sub authenticate_imap +{ + my( $imap, $host, $port, $user, $domain, $password, $mydebugimap, $mytimeout, $fastio, $ssl, $tls, $authmech, $authuser, $reconnectretry, $proxyauth, $uid, $split, $Side, $h, $mysync ) = @_ ; check_capability( $imap, $authmech, $Side ) ; + $imap->User( $user ) ; + $imap->Domain( $domain ) if ( defined $domain ) ; + $imap->Authuser( $authuser ) ; + $imap->Password( $password ) ; + + if ( 'X-MASTERAUTH' eq $authmech ) + { + xmasterauth( $imap ) ; + return ; + } if ( $proxyauth ) { $imap->Authmechanism(q{}) ; - $imap->User($authuser) ; + $imap->User( $authuser ) ; } else { $imap->Authmechanism( $authmech ) unless ( $authmech eq 'LOGIN' or $authmech eq 'PREAUTH' ) ; - $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->Domain($domain) if (defined $domain) ; - $imap->Authuser($authuser) ; - $imap->Password($password) ; - unless ( $authmech eq 'PREAUTH' or $imap->login( ) ) { + unless ( $authmech eq 'PREAUTH' or $authmech eq 'X-MASTERAUTH' 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 ) ; + exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, $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") ; + exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$info [LOGIN]: ", $imap->LastError, "\n") ; } if ( $proxyauth ) { @@ -4347,43 +5269,48 @@ sub authenticate_imap { my $info = "$Side failure: Error doing proxyauth as user [$user] on [$host] using proxy-login as [$authuser]" ; my $einfo = $imap->LastError || @{$imap->History}[$LAST] ; chomp $einfo ; - die_clean( "$info: $einfo\n" ) ; + exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$info: $einfo\n" ) ; } } return ; } -sub check_capability { +sub check_capability +{ my( $imap, $authmech, $Side ) = @_ ; - if ($imap->has_capability( "AUTH=$authmech" ) - or $imap->has_capability( $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 ; + $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 ; - } + 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 ; + } - myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n", - $Side, $imap->Server, $authmech ) ; + 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" ) ; + if ( $authmech eq 'PLAIN' ) + { + myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ; } return ; } -sub set_ssl { +sub set_ssl +{ my ( $imap, $h ) = @_ ; # SSL_version can be # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953) @@ -4412,7 +5339,8 @@ sub set_ssl { return ; } -sub set_tls { +sub set_tls +{ my ( $imap, $h ) = @_ ; my $sslargs_hash = $h->{sslargs} ; @@ -4440,7 +5368,8 @@ sub set_tls { -sub init_imap { +sub init_imap +{ my( $host, $port, $user, $domain, $password, $mydebugimap, $mytimeout, $fastio, @@ -4478,7 +5407,8 @@ sub init_imap { } -sub plainauth { +sub plainauth +{ my $code = shift; my $imap = shift; @@ -4514,7 +5444,8 @@ 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 { +sub xoauth2 +{ require JSON::WebToken ; require LWP::UserAgent ; require HTML::Entities ; @@ -4532,15 +5463,15 @@ sub xoauth2 { 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]: $OS_ERROR " ) ; + $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ; + open( my $FILE, '<', $filename ) or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "error [$filename]: $OS_ERROR " ) ; my $jsonfile = $json->decode( join q{}, <$FILE> ) ; close $FILE ; $iss = $jsonfile->{client_id}; $key = $jsonfile->{private_key}; - $debug and myprint( "Service account: $iss\n"); - $debug and myprint( "Private key:\n$key\n"); + $sync->{ debug } and myprint( "Service account: $iss\n"); + $sync->{ debug } and myprint( "Private key:\n$key\n"); } else { # Get iss (service account address), keyfile name, and keypassword if necessary @@ -4549,12 +5480,12 @@ sub xoauth2 { # Assume key password is google default if not provided $keypass = 'notasecret' if not $keypass; - $debug and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); + $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); # Get private key from p12 file (would be better in perl...) $key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`; - $debug and myprint( "Private key:\n$key\n"); + $sync->{ debug } and myprint( "Private key:\n$key\n"); } # Create jwt of oauth2 request @@ -4578,9 +5509,9 @@ sub xoauth2 { assertion => $jwt } ) ; unless( $response->is_success( ) ) { - die_clean( $response->code, "\n", $response->content, "\n" ) ; + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, $response->code, "\n", $response->content, "\n" ) ; }else{ - $debug and myprint( $response->content ) ; + $sync->{ debug } and myprint( $response->content ) ; } # access_token in response is what we need @@ -4589,7 +5520,7 @@ sub xoauth2 { # format as oauth2 auth data my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ; - $debug and myprint( "XOAUTH2 String: $xoauth2_string\n"); + $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n"); return($xoauth2_string); } @@ -4597,7 +5528,8 @@ sub xoauth2 { # xoauth() thanks to Eduardo Bortoluzzi Junior -sub xoauth { +sub xoauth +{ require URI::Escape ; require Data::Uniqid ; @@ -4612,7 +5544,7 @@ sub xoauth { # For Google Apps, the consumer key is the primary domain # TODO: create a command line argument to define the consumer key my @user_parts = split /@/x, $imap->User ; - $debug and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ; + $sync->{ debug } and myprint( "XOAUTH: consumer key: $user_parts[1]\n" ) ; # All the parameters needed to be signed on the XOAUTH my %hash = (); @@ -4637,7 +5569,7 @@ sub xoauth { } $base .= URI::Escape::uri_escape($baseparms); - $debug and myprint( "XOAUTH: base request to sign: $base\n" ) ; + $sync->{ debug } and myprint( "XOAUTH: base request to sign: $base\n" ) ; # Sign it with the consumer secret, informed on the command line (password) my $digest = hmac_sha1( $base, URI::Escape::uri_escape( $imap->Password ) . q{&} ) ; @@ -4662,31 +5594,91 @@ sub xoauth { $string .= $baseparms; - $debug and myprint( "XOAUTH: authentication string: $string\n" ) ; + $sync->{ debug } and myprint( "XOAUTH: authentication string: $string\n" ) ; # It must be base64 encoded return encode_base64("$string", q{}); } +sub xmasterauth +{ + # This is Kerio auth admin + # This code comes from + # https://github.com/imapsync/imapsync/pull/53/files -sub banner_imapsync { + my $imap = shift ; - my @argv = @_ ; + my $user = $imap->User( ) ; + my $password = $imap->Password( ) ; + my $authmech = 'X-MASTERAUTH' ; + + my @challenge = $imap->tag_and_run( $authmech, "+" ) ; + if ( not defined $challenge[0] ) + { + exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n") ; + return ; # hahaha! + } + $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ; + + $challenge[1] =~ s/^\+ |^\s+|\s+$//g ; + $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) + or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n") ; + + $imap->tag_and_run( 'X-SETUSER ' . $user ) + or exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", "X-SETUSER ", $imap->LastError, "\n") ; + + $imap->State( Mail::IMAPClient::Authenticated ) ; + # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands + # $imap->State( Mail::IMAPClient::Selected ) ; + + return ; +} + + +sub tests_do_valid_directory +{ + note( 'Entering tests_do_valid_directory()' ) ; + + Readonly my $NB_UNIX_tests_do_valid_directory => 2 ; + 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' ) ; + } + Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ; + SKIP: { + skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory_non_root ) if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ; + 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 banner_imapsync +{ + my $mysync = shift @ARG ; + my @argv = @ARG ; my $banner_imapsync = join q{}, q{$RCSfile: imapsync,v $ }, - q{$Revision: 1.882 $ }, - q{$Date: 2018/05/05 21:10:43 $ }, - "\n", - "Command line used:\n", - "$PROGRAM_NAME ", command_line_nopassword( @argv ), "\n" ; + q{$Revision: 1.937 $ }, + q{$Date: 2019/05/01 22:14:00 $ }, + "\n", + "Command line used, run by $EXECUTABLE_NAME:\n", + "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ; return( $banner_imapsync ) ; } -sub do_valid_directory { - my $dir = shift; +sub do_valid_directory +{ + my $dir = shift @ARG ; # all good => return ok. return( 1 ) if ( -d $dir and -r _ and -w _ ) ; @@ -4711,31 +5703,11 @@ sub do_valid_directory { return( 0 ) ; } -sub tests_do_valid_directory { - note( 'Entering tests_do_valid_directory()' ) ; - Readonly my $NB_UNIX_tests_do_valid_directory => 2 ; - 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' ) ; - } - Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ; - SKIP: { - skip( 'Tests only for Unix', $NB_UNIX_tests_do_valid_directory_non_root ) if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ; - 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 tests_match_a_pid_number +{ + note( 'Entering tests_match_a_pid_number()' ) ; - -sub tests_match_a_pid_number { is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ; is( undef, match_a_pid_number( '' ), 'match_a_pid_number: "" => undef' ) ; is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ; @@ -4743,23 +5715,31 @@ sub tests_match_a_pid_number { is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ; is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ; is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ; - is( 1, match_a_pid_number( 65535 ), 'match_a_pid_number: 65535 => 1' ) ; + is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ; is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ; - is( undef, match_a_pid_number( 65536 ), 'match_a_pid_number: 65536 => undef' ) ; - is( undef, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => undef' ) ; + is( undef, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => undef' ) ; + is( undef, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => undef' ) ; + + note( 'Leaving tests_match_a_pid_number()' ) ; return ; } -sub match_a_pid_number { - my $pid = shift ; +sub match_a_pid_number +{ + my $pid = shift @ARG ; if ( ! $pid ) { return ; } if ( ! match( $pid, '^\d+$' ) ) { return ; } if ( 0 > $pid ) { return ; } - if ( 65535 < $pid ) { return ; } + #if ( 65535 < $pid ) { return ; } + if ( 99999 < $pid ) { return ; } return 1 ; } -sub tests_remove_pidfile_not_running { +sub tests_remove_pidfile_not_running +{ + note( 'Entering tests_remove_pidfile_not_running()' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ; is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ; is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ; is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ; @@ -4771,24 +5751,26 @@ sub tests_remove_pidfile_not_running { is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ; is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ; is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ; - + + note( 'Leaving tests_remove_pidfile_not_running()' ) ; return ; } -sub remove_pidfile_not_running { - # - my $pid_filename = shift ; - - if ( ! $pid_filename ) { return } ; - if ( ! -e $pid_filename ) { return } ; - if ( ! -f $pid_filename ) { return } ; - +sub remove_pidfile_not_running +{ + # + my $pid_filename = shift @ARG ; + + if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ; + if ( ! -e $pid_filename ) { myprint( "File $pid_filename does not exist\n" ) ; return } ; + if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ; + my $pid = firstline( $pid_filename ) ; - if ( ! match_a_pid_number( $pid ) ) { return } ; + if ( ! match_a_pid_number( $pid ) ) { myprint( "pid $pid in $pid_filename is not a number\n" ) ; return } ; # can't kill myself => do nothing - if ( ! kill 'ZERO', $PROCESS_ID ) { return } ; - - # can't kill the pid => it is gone or own by another user => remove pidfile + if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ; + + # can't kill ZERO the pid => it is gone or own by another user => remove pidfile if ( ! kill 'ZERO', $pid ) { myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ; if ( unlink $pid_filename ) { @@ -4803,110 +5785,234 @@ sub remove_pidfile_not_running { return ; } -sub tests_write_pidfile { + +sub tests_tail +{ + note( 'Entering tests_tail()' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ; + ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ; + ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ; + + is( undef, tail( ), 'tail: no args => undef' ) ; my $mysync ; - - is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ; - - $mysync->{pidfile} = '/no/no/no.pid' ; - is( 1, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => 1' ) ; - $mysync->{pidfilelocking} = 1 ; - is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ; - - $mysync->{pidfile} = 'W/tmp/tests/test.pid' ; - ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ; - is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ; - - $mysync->{pidfilelocking} = 0 ; - is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid => 1' ) ; - is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ; + + $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ; + is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ; $mysync->{pidfilelocking} = 1 ; - is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ; - + is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ; + + + my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ; + is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ; + is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ; + + my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ; + is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ), + 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ; + + is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ; + + $mysync->{ tail } = 1 ; + is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ; + + # put my own pid, won't do tail + $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ; + is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ; + is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ; + + note( 'Leaving tests_tail()' ) ; return ; } -sub write_pidfile { + + +sub tail +{ + # return undef on failures + # return 1 on success + + my $mysync = shift ; + + # no tail when aborting! + if ( $mysync->{ abort } ) { return ; } + + my $pidfile = $mysync->{pidfile} ; + my $lock = $mysync->{pidfilelocking} ; + my $tail = $mysync->{tail} ; + + if ( ! $pidfile ) { return ; } + if ( ! $lock ) { return ; } + if ( ! $tail ) { return ; } + + my $pidtotail = firstline( $pidfile ) ; + if ( ! $pidtotail ) { return ; } + + + + # It should not happen but who knows... + if ( $pidtotail eq $PROCESS_ID ) { return ; } + + + my $filetotail = secondline( $pidfile ) ; + if ( ! $filetotail ) { return ; } + + if ( ! -r $filetotail ) + { + #myprint( "Error: can not read $filetotail\n" ) ; + return ; + } + + myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ; + my $file = File::Tail->new( + name => $filetotail, + nowait => 1, + interval => 1, + tail => 1, + adjustafter => 2 + ); + + my $moretimes = 200 ; + # print one line at least + my $line = $file->read ; + myprint( $line ) ; + while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) ) + { + myprint( $line ); + sleep( 0.02 ) ; + } + + return 1 ; +} + +sub isrunning +{ + my $pidtocheck = shift ; + my $moretimes_ref = shift ; + + if ( kill 'ZERO', $pidtocheck ) + { + #myprint( "$pidtocheck running\n" ) ; + return 1 ; + } + elsif ( $$moretimes_ref >= 0 ) + { + # continue to consider it running + $$moretimes_ref-- ; + return 1 ; + } + else + { + myprint( "Tailed processus $pidtocheck ended\n" ) ; + return ; + } +} + +sub tests_write_pidfile +{ + note( 'Entering tests_write_pidfile()' ) ; + + my $mysync ; + + is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ; + + # no pidfile => ok + $mysync->{pidfile} = q{} ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ; + + # The pidfile path is bad => failure + $mysync->{pidfile} = '/no/no/no.pid' ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ; + + $mysync->{pidfilelocking} = 1 ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ; + + $mysync->{pidfile} = 'W/tmp/tests/test.pid' ; + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ; + is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ; + + $mysync->{pidfilelocking} = 0 ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ; + is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ; + + $mysync->{pidfilelocking} = 1 ; + is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ; + + + $mysync->{pidfilelocking} = 0 ; + $mysync->{ logfile } = 'rrrr.txt' ; + is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ; + is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ; + is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ; + + + note( 'Leaving tests_write_pidfile()' ) ; + return ; +} + + + +sub write_pidfile +{ # returns undef if something is considered fatal # returns 1 otherwise - - if ( ! @ARG ) { return 1 ; } - - my $mysync = shift ; - - # Do not write the pid file if this process goal is to abort the process designed by the pid file - if ( $mysync->{abort} ) { return 1 ; } - - # - my $pid_filename = $mysync->{pidfile} ; - my $lock = $mysync->{pidfilelocking} ; - myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; + if ( ! @ARG ) { return 1 ; } + + my $mysync = shift @ARG ; + + # Do not write the pid file if this process goal is to abort the process designed by the pid file + if ( $mysync->{abort} ) { return 1 ; } + + # + my $pid_filename = $mysync->{ pidfile } ; + my $lock = $mysync->{ pidfilelocking } ; + + if ( ! $pid_filename ) + { + myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; + return( 1 ) ; + } + + 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" ) ; return ; - + } + if ( -e $pid_filename ) { myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ; } + my $pid_string = "$PROCESS_ID\n" ; + my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ; + + if ( $mysync->{ logfile } ) + { + $pid_string .= "$mysync->{ logfile }\n" ; + $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ; + } + if ( open my $FILE_HANDLE, '>', $pid_filename ) { - myprint( "Writing my PID $PROCESS_ID in $pid_filename\n" ) ; - print $FILE_HANDLE $PROCESS_ID ; + myprint( $pid_message ) ; + print $FILE_HANDLE $pid_string ; close $FILE_HANDLE ; return( 1 ) ; - } else { - myprint( "Could not open $pid_filename for writing. Check permissions or disk space.\n" ) ; - if ( $lock ) { - return ; - }else{ - return( 1 ) ; - } + } + else + { + myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ; + return ; } } - -sub remove_tmp_files { - 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 ; - 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 ; -} - -sub die_clean { - my @messages = @_ ; - remove_tmp_files( $sync ) ; - myprint( @messages ) ; - exit 255 ; -} - -sub missing_option { - my ( $option ) = @_ ; - die_clean( "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ; - return ; -} - - -sub fix_Inbox_INBOX_mapping { +sub fix_Inbox_INBOX_mapping +{ my( $h1_all, $h2_all ) = @_ ; my $regex = q{} ; @@ -4919,7 +6025,8 @@ sub fix_Inbox_INBOX_mapping { return( $regex ) ; } -sub tests_fix_Inbox_INBOX_mapping { +sub tests_fix_Inbox_INBOX_mapping +{ note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ; @@ -4954,7 +6061,8 @@ sub tests_fix_Inbox_INBOX_mapping { } -sub jux_utf8_list { +sub jux_utf8_list +{ my @s_inp = @_ ; my $s_out = q{} ; foreach my $s ( @s_inp ) { @@ -4963,19 +6071,21 @@ sub jux_utf8_list { return( $s_out ) ; } -sub tests_jux_utf8_list { +sub tests_jux_utf8_list +{ 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-' ) ; + ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ; note( 'Leaving tests_jux_utf8_list()' ) ; return( 0 ) ; } -sub jux_utf8 { +sub jux_utf8 +{ # juxtapose utf8 at the right if different my ( $s_utf7 ) = shift ; my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ; @@ -4990,15 +6100,16 @@ sub jux_utf8 { } # editing utf8 can be tricky without an utf8 editor -sub tests_jux_utf8 { +sub tests_jux_utf8 +{ 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( '[&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-] = [Проект]' ) ; + ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ; + ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]' eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ; note( 'Leaving tests_jux_utf8()' ) ; return ; @@ -5007,7 +6118,8 @@ sub tests_jux_utf8 { # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm # and then fixed with # https://rt.cpan.org/Public/Bug/Display.html?id=11172 -sub imap_utf7_decode { +sub imap_utf7_decode +{ my ( $s ) = shift ; # Algorithm @@ -5020,7 +6132,8 @@ sub imap_utf7_decode { return( Unicode::String::utf7( $s )->utf8 ) ; } -sub imap_utf7_encode { +sub imap_utf7_encode +{ my ( $s ) = @_ ; $s = Unicode::String::utf8( $s )->utf7 ; @@ -5034,13 +6147,14 @@ sub imap_utf7_encode { -sub select_folder { - my ( $imap, $folder, $hostside ) = @_ ; +sub select_folder +{ + my ( $mysync, $imap, $folder, $hostside ) = @_ ; if ( ! $imap->select( $folder ) ) { my $error = join q{}, "$hostside folder $folder: Could not select: ", $imap->LastError, "\n" ; - errors_incr( $sync, $error ) ; + errors_incr( $mysync, $error ) ; return( 0 ) ; }else{ # ok select succeeded @@ -5048,13 +6162,14 @@ sub select_folder { } } -sub examine_folder { - my ( $imap, $folder, $hostside ) = @_ ; +sub examine_folder +{ + my ( $mysync, $imap, $folder, $hostside ) = @_ ; if ( ! $imap->examine( $folder ) ) { my $error = join q{}, "$hostside folder $folder: Could not examine: ", $imap->LastError, "\n" ; - errors_incr( $sync, $error ) ; + errors_incr( $mysync, $error ) ; return( 0 ) ; }else{ # ok select succeeded @@ -5063,10 +6178,9 @@ sub examine_folder { } - - -sub count_from_select { - my @lines = @_ ; +sub count_from_select +{ + my @lines = @ARG ; my $count ; foreach my $line ( @lines ) { #myprint( "line = [$line]\n" ) ; @@ -5080,23 +6194,10 @@ sub count_from_select { - - - - - - - - - - - - - - - -sub create_folder_old { - my( $imap, $h2_fold, $h1_fold ) = @_ ; +sub create_folder_old +{ + my $mysync = shift @ARG ; + my( $imap, $h2_fold, $h1_fold ) = @ARG ; myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ; if ( ( 'INBOX' eq uc $h2_fold ) @@ -5104,12 +6205,12 @@ sub create_folder_old { myprint( "Folder [$h2_fold] already exists\n" ) ; return( 1 ) ; } - if ( ! $sync->{dry} ){ + if ( ! $mysync->{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 ) ; + errors_incr( $mysync, $error ) ; # success if folder exists ("already exists" error) return( 1 ) if $imap->exists( $h2_fold ) ; # failure since create failed @@ -5121,58 +6222,60 @@ sub create_folder_old { } }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" ) ; + myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; return( 0 ) ; } } -sub create_folder { - my( $imap2 , $h2_fold , $h1_fold ) = @_ ; +sub create_folder +{ + my $mysync = shift @ARG ; + my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ; my( @parts , $parent ) ; - if ( $imap2->IsUnconnected( ) ) { + if ( $myimap2->IsUnconnected( ) ) { myprint( "Host2: Unconnected state\n" ) ; return( 0 ) ; } if ( $create_folder_old ) { - return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ; + return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ; } myprint( "Creating folder [$h2_fold] on host2\n" ) ; if ( ( 'INBOX' eq uc $h2_fold ) - and ( $imap2->exists( $h2_fold ) ) ) { + and ( $myimap2->exists( $h2_fold ) ) ) { myprint( "Folder [$h2_fold] already exists\n" ) ; return( 1 ) ; } - if ( $mixfolders and $imap2->exists( $h2_fold ) ) { - myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ; + if ( $mixfolders and $myimap2->exists( $h2_fold ) ) { + myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ; return( 1 ) ; } - if ( ( not $mixfolders ) and ( $imap2->exists( $h2_fold ) ) ) { + if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) { myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ; return( 0 ) ; } - @parts = split /\Q$h2_sep\E/x, $h2_fold ; + @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ; pop @parts ; - $parent = join $h2_sep, @parts ; + $parent = join $mysync->{ h2_sep }, @parts ; $parent =~ s/^\s+|\s+$//xg ; - if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) { - create_folder( $imap2 , $parent , $h1_fold ) ; + if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) { + create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ; } - if ( ! $sync->{dry} ) { - if ( ! $imap2->create( $h2_fold ) ) { + if ( ! $mysync->{dry} ) { + if ( ! $myimap2->create( $h2_fold ) ) { my $error = join q{}, "Could not create folder [$h2_fold] from [$h1_fold]: " , - $imap2->LastError( ), "\n" ; - errors_incr( $sync, $error ) ; + $myimap2->LastError( ), "\n" ; + errors_incr( $mysync, $error ) ; # success if folder exists ("already exists" error) - return( 1 ) if $imap2->exists( $h2_fold ) ; + return( 1 ) if $myimap2->exists( $h2_fold ) ; # failure since create failed return( 0 ) ; }else{ @@ -5182,8 +6285,8 @@ sub create_folder { } }else{ # dry mode, no folder so many imap will fail, assuming failure - myprint( "Created folder [$h2_fold] on host2 $sync->{dry_message}\n" ) ; - if ( ! $justfolders ) { + myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; + if ( ! $mysync->{ 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" ) ; } @@ -5193,14 +6296,16 @@ sub create_folder { -sub tests_folder_routines { +sub tests_folder_routines +{ 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' ); + + is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => 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" ); @@ -5208,53 +6313,69 @@ sub tests_folder_routines { 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' ) ; + + is_deeply( [ 'folder_bar' ], [ 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( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'M_55' ] ), 'sort_requested_folders: middle' ) ; + ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' ); + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ; + + @folderfirst = ( 'Z_11' ) ; - ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ; + + ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ; + + is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ; + @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( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 2' ) ; + 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' ) ; + + + ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ; @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' ) ; + undef @folderfirst ; + undef @folderlast ; note( 'Leaving tests_folder_routines()' ) ; return ; } -sub sort_requested_folders { +sub sort_requested_folders +{ my @requested_folders_sorted = () ; - foreach my $folder ( @folderfirst ) { - remove_from_requested_folders( $folder ) ; - } + #myprint "folderfirst: @folderfirst\n" ; + my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ; + #myprint "folderfirst_requested: @folderfirst_requested\n" ; - foreach my $folder ( @folderlast ) { - remove_from_requested_folders( $folder ) ; - } + my @folderlast_requested = remove_from_requested_folders( @folderlast ) ; my @middle = sort keys %requested_folder ; - @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ; + @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ; + #myprint "requested_folders_sorted: @requested_folders_sorted\n" ; + add_to_requested_folders( @requested_folders_sorted ) ; return( @requested_folders_sorted ) ; } -sub is_requested_folder { +sub is_requested_folder +{ my ( $folder ) = @_; - return( defined $requested_folder{ $folder } ) ; + return( defined $requested_folder{ $folder } ) ; } -sub add_to_requested_folders { +sub add_to_requested_folders +{ my @wanted_folders = @_ ; foreach my $folder ( @wanted_folders ) { @@ -5263,16 +6384,65 @@ sub add_to_requested_folders { return( keys %requested_folder ) ; } -sub remove_from_requested_folders { - my @wanted_folders = @_ ; +sub tests_remove_from_requested_folders +{ + note( 'Entering tests_remove_from_requested_folders()' ) ; - foreach my $folder ( @wanted_folders ) { - delete $requested_folder{ $folder } ; - } - return( keys %requested_folder ) ; + is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ; + %requested_folder = ( + 'F1' => 1, + ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ; + is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ; + is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ; + is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ; + + %requested_folder = ( + 'F1' => 1, + 'F2' => 1, + ) ; + is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ; + is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ; + is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ; + is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; + + is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ; + is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ; + is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; + + %requested_folder = ( + 'F1' => 1, + 'F2' => 1, + 'F3' => 1, + ) ; + is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ; + is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ; + + + + note( 'Leaving tests_remove_from_requested_folders()' ) ; + return ; } -sub compare_lists { + +sub remove_from_requested_folders +{ + my @unwanted_folders = @_ ; + + my @removed_folders = () ; + foreach my $folder ( @unwanted_folders ) { + if ( exists $requested_folder{ $folder } ) + { + delete $requested_folder{ $folder } ; + push @removed_folders, $folder ; + } + } + return( @removed_folders ) ; +} + +sub compare_lists +{ my ($list_1_ref, $list_2_ref) = @_; return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref); @@ -5306,7 +6476,8 @@ sub compare_lists { return 0 ; } -sub tests_compare_lists { +sub tests_compare_lists +{ note( 'Entering tests_compare_lists()' ) ; my $empty_list_ref = []; @@ -5359,7 +6530,8 @@ sub tests_compare_lists { } -sub guess_prefix { +sub guess_prefix +{ my @foldernames = @_ ; my $prefix_guessed = q{} ; @@ -5376,7 +6548,8 @@ sub guess_prefix { return( $prefix_guessed ) ; } -sub tests_guess_prefix { +sub tests_guess_prefix +{ note( 'Entering tests_guess_prefix()' ) ; is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ; @@ -5396,14 +6569,15 @@ sub tests_guess_prefix { return ; } -sub get_prefix { +sub get_prefix +{ my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ; my( $prefix_out, $prefix_guessed ) ; - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ; + ( $sync->{ 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" ) ; + ( $sync->{ 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] ; @@ -5433,7 +6607,8 @@ sub get_prefix { } -sub guess_separator { +sub guess_separator +{ my @foldernames = @_ ; #return( undef ) unless ( @foldernames ) ; @@ -5447,12 +6622,13 @@ sub guess_separator { $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" ) ; + $sync->{ 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 { +sub tests_guess_separator +{ note( 'Entering tests_guess_separator()' ) ; ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ; @@ -5468,16 +6644,18 @@ sub tests_guess_separator { return ; } -sub get_separator { +sub get_separator +{ my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ; my( $sep_out, $sep_guessed ) ; - ( $debug or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ; + ( $sync->{ 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' ) ) { + ( $sync->{ 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" ) ; @@ -5501,7 +6679,8 @@ sub get_separator { } } } - else{ + else + { if ( defined $sep_in ) { myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ; $sep_out = $sep_in ; @@ -5516,7 +6695,8 @@ sub get_separator { return ; } -sub help_to_guess_sep { +sub help_to_guess_sep +{ my( $imap, $sep_opt ) = @_ ; my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n" @@ -5526,7 +6706,8 @@ sub help_to_guess_sep { return( $help_to_guess_sep ) ; } -sub help_to_guess_prefix { +sub help_to_guess_prefix +{ my( $imap, $prefix_opt ) = @_ ; my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n" @@ -5537,7 +6718,8 @@ sub help_to_guess_prefix { } -sub folders_list_to_help { +sub folders_list_to_help +{ my( $imap ) = shift ; my @folders = $imap->folders ; @@ -5545,122 +6727,462 @@ sub folders_list_to_help { return( $listing ) ; } +sub private_folders_separators_and_prefixes +{ +# what are the private folders separators and prefixes for each server ? + + ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ; + $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ; + $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ; -sub tests_imap2_folder_name { - note( 'Entering tests_imap2_folder_name()' ) ; + $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ; + $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ; -$sync->{ h1_prefix } = $sync->{ h2_prefix } = q{} ; -$h1_sep = '/'; -$h2_sep = '.'; - -$debug and myprint( <<"EOS" -prefix1: [$sync->{ h1_prefix }] -prefix2: [$sync->{ h2_prefix }] -sep1:[$h1_sep] -sep2:[$h2_sep] -EOS -) ; - -$fixslash2 = 0 ; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam'); -ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam'); -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam'); -ok('s pam.spam/sp am' eq imap2_folder_name('s pam/spam.sp am'), 'imap2_folder_name: s pam/spam.sp am'); - -$sync->{f1f2h}{ 'auto' } = 'moto' ; -ok( 'moto' eq imap2_folder_name( 'auto' ), 'imap2_folder_name: auto' ) ; -$sync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ; -ok( 'moto x 2' eq imap2_folder_name( 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ; - -@regextrans2 = ('s,/,X,g'); -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string [s,/,X,g]'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s,/,X,g]'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]'); -ok('spamXspam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]'); -ok('spam.spamXspam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]'); - -@regextrans2 = ( 's, ,_,g' ) ; -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla [s, ,_,g]'); -ok('bla_bla' eq imap2_folder_name('bla bla'), 'imap2_folder_name: blabla [s, ,_,g]'); - -@regextrans2 = ( q{s,(.*),\U$1,} ) ; -ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ; - -$fixslash2 = 1 ; -@regextrans2 = ( ) ; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); -ok('spam_spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam'); -ok('spam.spam_spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam'); -ok('s pam.spam_spa m' eq imap2_folder_name('s pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m'); - -$h1_sep = '.'; -$h2_sep = '/'; -ok(q{} eq imap2_folder_name(q{}), 'imap2_folder_name: empty string'); -ok('blabla' eq imap2_folder_name('blabla'), 'imap2_folder_name: blabla'); -ok('spam.spam' eq imap2_folder_name('spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); -ok('spam/spam' eq imap2_folder_name('spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam'); -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); + myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ; + myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ; + return ; +} +sub subfolder1 +{ + my $mysync = shift ; + my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ; -$fixslash2 = 0 ; -$sync->{ h1_prefix } = q{ }; + if ( $subfolder1 ) + { + # turns off automap + myprint( "Turning off automapping folders because of --subfolder1\n" ) ; + $mysync->{ automap } = undef ; + myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ; + $mysync->{ subfolder1 } = $subfolder1 ; + add_subfolder1_to_folderrec( $mysync ) || exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS ) ; + } + else + { + $mysync->{ subfolder1 } = undef ; + } +} -ok('spam.spam/spam' eq imap2_folder_name('spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); -ok('spam.spam/spam' eq imap2_folder_name(' spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); +sub subfolder2 +{ + my $mysync = shift ; + my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ; + if ( $subfolder2 ) + { + # turns off automap + myprint( "Turning off automapping folders because of --subfolder2\n" ) ; + $mysync->{ automap } = undef ; + myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ; + $mysync->{ subfolder2 } = $subfolder2 ; + set_regextrans2_for_subfolder2( $mysync ) ; + } + else + { + $mysync->{ subfolder2 } = undef ; + } -$h1_sep = '.' ; -$h2_sep = '/' ; -$sync->{ h1_prefix } = 'INBOX.' ; -$sync->{ h2_prefix } = q{} ; -@regextrans2 = ( q{s,(.*),\U$1,} ) ; -ok( 'BLABLA' eq imap2_folder_name( 'blabla' ), 'imap2_folder_name: blabla' ) ; -ok( 'TEST/TEST/TEST/TEST' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; -@regextrans2 = ( q{s,(.*),\L$1,} ) ; -ok( 'test/test/test/test' eq imap2_folder_name( 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; +} + +sub tests_sanitize_subfolder +{ + note( 'Entering tests_sanitize_subfolder()' ) ; + + is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ; + is( undef, sanitize_subfolder( '' ), 'sanitize_subfolder: empty => undef' ) ; + is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ; + is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ; + is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ; + is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ; + is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ; + is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ; + note( 'Leaving tests_sanitize_subfolder()' ) ; + return ; +} - note( 'Leaving tests_imap2_folder_name()' ) ; - return ; +sub sanitize_subfolder +{ + my $subfolder = shift ; + if ( ! $subfolder ) + { + return ; + } + # Remove edging blanks + $subfolder =~ s,^ +| +$,,g ; + # Keep only abcd...ABCD...0123... and -_./ + $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ; + + # A blank subfolder is not a subfolder + if ( ! $subfolder ) + { + return ; + } + else + { + return $subfolder ; + } } -# Global variables to remove: -# $debug -# $sync -sub imap2_folder_name { - my $mysync = $sync ; # will be soon next line - #my $mysync = shift ; + +sub tests_add_subfolder1_to_folderrec +{ + note( 'Entering tests_add_subfolder1_to_folderrec()' ) ; + + is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ; + is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ; + @folderrec = () ; + my $mysync = {} ; + is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ; + is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ; + $mysync->{ subfolder1 } = 'SUBI' ; + $h1_folders_all{ 'SUBI' } = 1 ; + $mysync->{ h1_prefix } = 'INBOX/' ; + is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ; + is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ; + + @folderrec = () ; + $mysync->{ subfolder1 } = 'SUBO' ; + is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ; + is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ; + $h1_folders_all{ 'INBOX/SUBO' } = 1 ; + is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ; + is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ; + + note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ; + return ; +} + + +sub add_subfolder1_to_folderrec +{ + my $mysync = shift ; + if ( ! $mysync || ! $mysync->{ subfolder1 } ) + { + return ; + } + + my $subfolder1 = $mysync->{ subfolder1 } ; + my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ; + + if ( exists $h1_folders_all{ $subfolder1 } ) + { + myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ; + push @folderrec, $subfolder1 ; + } + elsif ( exists $h1_folders_all{ $subfolder1_extended } ) + { + myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ; + push @folderrec, $subfolder1_extended ; + } + else + { + myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ; + } + return @folderrec ; +} + +sub set_regextrans2_for_subfolder2 +{ + my $mysync = shift ; + + + unshift @{ $mysync->{ regextrans2 } }, + q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,), + q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,), + q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },); + + #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ; + return ; +} + + + +# Looks like no globals here + +sub tests_imap2_folder_name +{ + note( 'Entering tests_imap2_folder_name()' ) ; + + my $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + + $mysync->{ debug } and myprint( <<"EOS" +prefix1: [$mysync->{ h1_prefix }] +prefix2: [$mysync->{ h2_prefix }] +sep1: [$sync->{ h1_sep }] +sep2: [$sync->{ h2_sep }] +EOS +) ; + + $mysync->{ fixslash2 } = 0 ; + is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ; + is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ; + + is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ; + is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ; + is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ; + + $mysync->{f1f2h}{ 'auto' } = 'moto' ; + is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ; + $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ; + is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ; + + @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ; + is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ; + is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ; + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]'); + is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]'); + is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]'); + + @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ; + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]'); + is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]'); + + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; + is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ; + + $mysync->{ fixslash2 } = 1 ; + @{ $mysync->{ regextrans2 } } = ( ) ; + is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); + is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam'); + is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam'); + is('s pam.spam_spa m', imap2_folder_name( $mysync, 's pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m'); + + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); + is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); + is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); + is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam'); + is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); + + + + $mysync->{ fixslash2 } = 0 ; + $mysync->{ h1_prefix } = q{ }; + + is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; + is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; + + $mysync->{ h1_sep } = '.' ; + $mysync->{ h2_sep } = '/' ; + $mysync->{ h1_prefix } = 'INBOX.' ; + $mysync->{ h2_prefix } = q{} ; + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; + is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; + is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; + @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ; + is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; + + # INBOX + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1.} ; + $mysync->{ h2_prefix } = q{Pf2/} ; + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + + # + #$mysync->{ debug } = 1 ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ; + + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can! + + + + # subfolder2 + $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + + + set_regextrans2_for_subfolder2( $mysync ) ; + $mysync->{ subfolder2 } = 'S1.S2' ; + is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ; + is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ; + + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1/} ; + $mysync->{ h2_prefix } = q{Pf2.} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '.'; + #$mysync->{ debug } = 1 ; + + set_regextrans2_for_subfolder2( $mysync ) ; + $mysync->{ subfolder2 } = 'Pf2.S1.S2' ; + is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; + is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; + is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'Pf1/F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; + is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; + + # subfolder1 + # scenario as the reverse of the previous tests, separators point of vue + $mysync = {} ; + $mysync->{ h1_prefix } = q{Pf1.} ; + $mysync->{ h2_prefix } = q{Pf2/} ; + $mysync->{ h1_sep } = '.'; + $mysync->{ h2_sep } = '/'; + #$mysync->{ debug } = 1 ; + + $mysync->{ subfolder1 } = 'S1.S2' ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; + + + $mysync->{ subfolder1 } = 'S1.S2.' ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; + + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; + + + # subfolder1 + # scenario as Gmail + $mysync = {} ; + $mysync->{ h1_prefix } = q{} ; + $mysync->{ h2_prefix } = q{} ; + $mysync->{ h1_sep } = '/'; + $mysync->{ h2_sep } = '/'; + #$mysync->{ debug } = 1 ; + + $mysync->{ subfolder1 } = 'S1/S2' ; + is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; + + $mysync->{ subfolder1 } = 'S1/S2/' ; + is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; + is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; + + + note( 'Leaving tests_imap2_folder_name()' ) ; + return ; +} + + +# Global variables to remove: +# + + +sub imap2_folder_name +{ + my $mysync = shift ; my ( $h1_fold ) = shift ; my ( $h2_fold ) ; if ( $mysync->{f1f2h}{ $h1_fold } ) { $h2_fold = $mysync->{f1f2h}{ $h1_fold } ; - ( $debug or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ; return( $h2_fold ) ; } if ( $mysync->{f1f2auto}{ $h1_fold } ) { $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ; - ( $debug or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ; return( $h2_fold ) ; } + if ( $mysync->{ subfolder1 } ) + { + my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ; + # case where subfolder1 has the sep1 at the end, then remove it + my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ; + # remove the subfolder1 part and the sep1 if present after + $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ; + #myprint( "h1_fold=$h1_fold\n" ) ; + } + + if ( ( '' eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) ) + { + $h1_fold = 'INBOX' ; + } + $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ; - $h2_fold = regextrans2( $h2_fold ) ; + $h2_fold = regextrans2( $mysync, $h2_fold ) ; return( $h2_fold ) ; } -sub tests_prefix_seperator_invertion { - undef $h1_sep; - undef $h2_sep ; - + +sub tests_remove_last_char_if_is +{ + note( 'Entering tests_remove_last_char_if_is()' ) ; + + is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ; + is( '', remove_last_char_if_is( '' ), 'remove_last_char_if_is: empty => empty' ) ; + is( '', remove_last_char_if_is( '', 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ; + is( '', remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ; + is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ; + is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ; + note( 'Leaving tests_remove_last_char_if_is()' ) ; + return ; +} + + + + +sub remove_last_char_if_is +{ + my $string = shift ; + my $char = shift ; + + if ( ! defined $string ) + { + return ; + } + + if ( ! defined $char ) + { + return $string ; + } + + my $last_char = substr $string, -1 ; + if ( $char eq $last_char ) + { + chop $string ; + return $string ; + } + else + { + return $string ; + } +} + +sub tests_prefix_seperator_invertion +{ + note( 'Entering tests_prefix_seperator_invertion()' ) ; + is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ; is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ; is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ; @@ -5668,103 +7190,111 @@ sub tests_prefix_seperator_invertion { is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ; is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ; is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ; - + my $mysync = { h1_prefix => '', h2_prefix => '', h1_sep => '/', h2_sep => '/', } ; - + is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ; is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ; is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ; is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ; is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ; is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ; - + $mysync = { h1_prefix => 'PPP', h2_prefix => 'QQQ', h1_sep => 's', h2_sep => 't', } ; - + is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ; is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ; is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ; is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ; is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ; is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ; - + is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ; is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ; is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ; is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ; is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ; - + + note( 'Leaving tests_prefix_seperator_invertion()' ) ; return ; } -# Global variables to remove: -# $h1_sep -# $h2_sep -# $debug +# Global variables to remove: -sub prefix_seperator_invertion { + +sub prefix_seperator_invertion +{ my $mysync = shift ; my $h1_fold = shift ; my $h2_fold ; if ( not defined $h1_fold ) { return ; } - + my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ; my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ; - my $my_h1_sep = $h1_sep || $mysync->{ h1_sep } || '/' ; - my $my_h2_sep = $h2_sep || $mysync->{ h2_sep } || '/' ; - + my $my_h1_sep = $mysync->{ h1_sep } || '/' ; + my $my_h2_sep = $mysync->{ h2_sep } || '/' ; + # first we remove the prefix $h1_fold =~ s/^\Q$my_h1_prefix\E//x ; - ( $debug or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ; - $h2_fold = separator_invert( $h1_fold, $my_h1_sep, $my_h2_sep ) ; - ( $debug or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ; + $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ; + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ; + # Adding the prefix supplied by namespace or the --prefix2 option - $h2_fold = $my_h2_prefix . $h2_fold - unless( ( $my_h2_prefix eq 'INBOX' . $my_h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ; - ( $debug or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ; + # except for INBOX or Inbox + if ( $h2_fold !~ m/^INBOX$/xi ) + { + $h2_fold = $my_h2_prefix . $h2_fold ; + } + + ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ; return( $h2_fold ) ; } -sub tests_separator_invert { +sub tests_separator_invert +{ note( 'Entering tests_separator_invert()' ) ; - $fixslash2 = 0 ; + my $mysync = {} ; + $mysync->{ 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( 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( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ; + ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ; + ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ; + ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ; + ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ; + ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ; + ok( 'la.l/ala' eq separator_invert( $mysync, '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( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; + $mysync->{ fixslash2 } = 1 ; + ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; note( 'Leaving tests_separator_invert()' ) ; return ; } -# Global variables to remove: -# $fixslash2 -sub separator_invert { - my( $h1_fold, $h1_separator, $h2_separator ) = @_ ; +# Global variables to remove: +# +sub separator_invert +{ + my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ; - return( undef ) if ( not defined $h1_fold or not defined $h1_separator or not defined $h2_separator ) ; + return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ; # The separator we hope we'll never encounter: 00000000 == 0x00 my $o_sep = "\000" ; @@ -5772,27 +7302,29 @@ sub separator_invert { $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 ) ; + $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ; return( $h2_fold ) ; } -sub regextrans2 { - my( $h2_fold ) = @_ ; +sub regextrans2 +{ + my( $mysync, $h2_fold ) = @_ ; # Transforming the folder name by the --regextrans2 option(s) - foreach my $regextrans2 ( @regextrans2 ) { + foreach my $regextrans2 ( @{ $mysync->{ 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" ) ; + ( $mysync->{ debug } or $mysync->{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" ) ; + exit_clean( $mysync, $EX_USAGE, "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ; } } return( $h2_fold ) ; } -sub tests_decompose_regex { +sub tests_decompose_regex +{ note( 'Entering tests_decompose_regex()' ) ; ok( 1, 'decompose_regex 1' ) ; @@ -5803,7 +7335,8 @@ sub tests_decompose_regex { return ; } -sub decompose_regex { +sub decompose_regex +{ my $regex = shift ; my( $left_part, $right_part ) ; @@ -5813,7 +7346,8 @@ sub decompose_regex { } -sub foldersizes { +sub foldersizes +{ my ( $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ; my $total_size = 0 ; @@ -5859,7 +7393,7 @@ sub foldersizes { 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" ; + my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; errors_incr( $sync, $error ) ; return ; } @@ -5867,7 +7401,7 @@ sub foldersizes { 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" ; + my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; errors_incr( $sync, $error ) ; return ; } @@ -5894,7 +7428,8 @@ sub foldersizes { return( $total_nb, $total_size ) ; } -sub timenext { +sub timenext +{ my ( $timenow, $timediff ) ; # $timebefore is global, beurk ! $timenow = time ; @@ -5903,7 +7438,8 @@ sub timenext { return( $timediff ) ; } -sub timesince { +sub timesince +{ my $timeinit = shift || 0 ; my ( $timenow, $timediff ) ; $timenow = time ; @@ -5915,7 +7451,8 @@ sub timesince { -sub tests_flags_regex { +sub tests_flags_regex +{ note( 'Entering tests_flags_regex()' ) ; ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ; @@ -5926,7 +7463,7 @@ sub tests_flags_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' ) ; + @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' ) ; @@ -5964,41 +7501,9 @@ sub tests_flags_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/'} ) ; + ok('REM REM REM REM REM jrdH8u' eq flags_regex('REM REM REM REM REM'), q{Add jrdH8u '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' ) ; - + ok('REM REM REM REM REM ' eq flags_regex('REM REM REM REM REM jrdH8u'), q{Remove jrdH8u s/jrdH8u *//} ) ; @regexflag = ( 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg' @@ -6018,11 +7523,33 @@ sub tests_flags_regex { eq flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), 'Keep only regex: Exchange case (Phil)' ) ; + @regexflag = ( 's/\\\\Flagged//g' ) ; + + is('\Deleted \Answered \Draft ', + flags_regex('\\Deleted \\Answered \\Draft \\Flagged '), + 'flags_regex: remove \Flagged 1' ) ; + is('\\Deleted \\Answered \\Draft', + flags_regex('\\Deleted \\Flagged \\Answered \\Draft'), + 'flags_regex: remove \Flagged 2' ) ; + + # I didn't understand why it gives \F + # https://perldoc.perl.org/perlrebackslash.html + # \F Foldcase till \E. Not in []. + # https://perldoc.perl.org/functions/fc.html + + # \F Not available in old Perl so I comment the test + + # @regexflag = ( 's/\\Flagged/X/g' ) ; + #is('\Deleted FX \Answered \FX \Draft \FX', + #flags_regex( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ), + # 'flags_regex: remove \Flagged 3 mistery...' ) ; + note( 'Leaving tests_flags_regex()' ) ; return ; } -sub flags_regex { +sub flags_regex +{ my ( $h1_flags ) = @_ ; foreach my $regexflag ( @regexflag ) { my $h1_flags_orig = $h1_flags ; @@ -6037,12 +7564,13 @@ sub flags_regex { return( $h1_flags ) ; } -sub acls_sync { +sub acls_sync +{ my($h1_fold, $h2_fold) = @_ ; if ( $syncacls ) { - my $h1_hash = $imap1->getacl($h1_fold) + my $h1_hash = $sync->{imap1}->getacl($h1_fold) or myprint( "Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ; - my $h2_hash = $imap2->getacl($h2_fold) + my $h2_hash = $sync->{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 ) { @@ -6052,7 +7580,7 @@ sub acls_sync { $h1_hash->{$user} eq $h2_hash->{$user}); unless ($sync->{dry}) { myprint( "setting acl $h2_fold $user $acl\n" ) ; - $imap2->setacl($h2_fold, $user, $acl) + $sync->{imap2}->setacl($h2_fold, $user, $acl) or myprint( "Could not set acl: $EVAL_ERROR\n" ) ; } } @@ -6061,7 +7589,8 @@ sub acls_sync { } -sub tests_permanentflags { +sub tests_permanentflags +{ note( 'Entering tests_permanentflags()' ) ; my $string; @@ -6081,12 +7610,13 @@ sub tests_permanentflags { return ; } -sub permanentflags { +sub permanentflags +{ my @lines = @_ ; foreach my $line (@lines) { if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) { - ( $debugflags or $debug ) and myprint( "permanentflags: $line" ) ; + ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ; my $permanentflags = $1 ; if ( $permanentflags =~ m{\\\*}x ) { $permanentflags = q{} ; @@ -6097,7 +7627,8 @@ sub permanentflags { return( q{} ) ; } -sub tests_flags_filter { +sub tests_flags_filter +{ note( 'Entering tests_flags_filter()' ) ; ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' ); @@ -6113,7 +7644,8 @@ sub tests_flags_filter { return ; } -sub flags_filter { +sub flags_filter +{ my( $flags, $allowed_flags ) = @_ ; my @flags = split /\s+/x, $flags ; @@ -6125,7 +7657,8 @@ sub flags_filter { return( $flags_out ) ; } -sub flagscase { +sub flagscase +{ my $flags = shift ; my @flags = split /\s+/x, $flags ; @@ -6137,7 +7670,8 @@ sub flagscase { return( $flags_out ) ; } -sub tests_flagscase { +sub tests_flagscase +{ note( 'Entering tests_flagscase()' ) ; ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ; @@ -6155,7 +7689,8 @@ sub tests_flagscase { -sub ucsecond { +sub ucsecond +{ my $string = shift ; my $output ; @@ -6167,8 +7702,10 @@ sub ucsecond { } -sub tests_ucsecond { +sub tests_ucsecond +{ 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' ) ; @@ -6183,7 +7720,8 @@ sub tests_ucsecond { } -sub select_msgs { +sub select_msgs +{ my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ; my ( @msgs ) ; @@ -6196,7 +7734,8 @@ sub select_msgs { } -sub select_msgs_by_search { +sub select_msgs_by_search +{ my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; my ( @msgs, @msgs_all ) ; @@ -6234,7 +7773,8 @@ sub select_msgs_by_search { } -sub select_msgs_by_fetch { +sub select_msgs_by_fetch +{ my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; my ( @msgs, @msgs_all, %fetch ) ; @@ -6251,7 +7791,7 @@ sub select_msgs_by_fetch { @msgs_all = sort { $a <=> $b } keys %fetch ; $debugdev and myprint( "Done fetch_hash()\n" ) ; - + return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; if ( defined $msgs_all_hash_ref ) { @@ -6286,7 +7826,8 @@ sub select_msgs_by_fetch { return( @msgs ) ; } -sub select_msgs_by_age { +sub select_msgs_by_age +{ my( $imap ) = @_ ; my( @max, @min, @msgs, @inter, @union ) ; @@ -6302,7 +7843,8 @@ sub select_msgs_by_age { return( @msgs ) ; } -sub msgs_from_maxmin { +sub msgs_from_maxmin +{ my( $max_ref, $min_ref ) = @_ ; my( @max, @min, @msgs, @inter, @union ) ; @@ -6325,7 +7867,8 @@ sub msgs_from_maxmin { return( @msgs ) ; } -sub tests_msgs_from_maxmin { +sub tests_msgs_from_maxmin +{ note( 'Entering tests_msgs_from_maxmin()' ) ; my @msgs ; @@ -6346,14 +7889,17 @@ sub tests_msgs_from_maxmin { return ; } -sub tests_info_date_from_uid { - +sub tests_info_date_from_uid +{ + note( 'Entering tests_info_date_from_uid()' ) ; + note( 'Leaving tests_info_date_from_uid()' ) ; return ; } -sub info_date_from_uid { - +sub info_date_from_uid +{ + #my $first_uid = $msgs_all[ 0 ] ; #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ; #my $first_epoch = epoch( $first_idate ) ; @@ -6362,7 +7908,8 @@ sub info_date_from_uid { } -sub lastuid { +sub lastuid +{ my $imap = shift ; my $folder = shift ; my $lastuid_guess = shift ; @@ -6390,26 +7937,28 @@ sub lastuid { return( $lastuid ) ; } -sub size_filtered { +sub size_filtered +{ 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; + if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) { + myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; return( 1 ) ; } - if (defined $minsize and $h1_size <= $minsize) { + 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; + $sync->{ total_bytes_skipped } += $h1_size; + $sync->{ nb_msg_skipped } += 1; return( 1 ) ; } return( 0 ) ; } -sub message_exists { +sub message_exists +{ my( $imap, $msg ) = @_ ; return( 1 ) if not $imap->Uid( ) ; @@ -6420,11 +7969,28 @@ sub message_exists { return( 0 ) ; } -sub copy_message { + +# Globals +# $sync->{ total_bytes_skipped } +# $sync->{ nb_msg_skipped } +# $mysync->{ h1_nb_msg_processed } +sub stats_update_skip_message +{ + my $mysync = shift ; # to be used + my $h1_size = shift ; + + $mysync->{ total_bytes_skipped } += $h1_size ; + $mysync->{ nb_msg_skipped } += 1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; + return ; +} + +sub copy_message +{ # copy 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" ) ; + ( $mysync->{ 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{} ; @@ -6432,33 +7998,28 @@ sub copy_message { if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) { - $h1_nb_msg_processed +=1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; return ; } 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 ; + if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) { + stats_update_skip_message( $mysync, $h1_size ) ; return ; } - myprint( debugmemory( $sync, " at C1" ) ) ; + myprint( debugmemory( $mysync, " at C1" ) ) ; my ( $string, $string_len ) ; ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ; - myprint( debugmemory( $sync, " at C2" ) ) ; + myprint( debugmemory( $mysync, " at C2" ) ) ; # not defined or empty $string 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 ; + stats_update_skip_message( $mysync, $h1_size ) ; return ; } @@ -6466,39 +8027,40 @@ sub copy_message { 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 ; + stats_update_skip_message( $mysync, $h1_size ) ; 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" ) ; + ( $mysync->{ 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" ) ; + ( $mysync->{ 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( $mysync, \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ; - $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 ) ; + sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ; } - myprint( debugmemory( $sync, " at C3" ) ) ; + myprint( debugmemory( $mysync, " at C3" ) ) ; return $new_id ; } -sub linelengthstuff { +sub linelengthstuff +{ 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" ) ; @@ -6531,7 +8093,8 @@ sub linelengthstuff { } -sub message_for_host2 { +sub message_for_host2 +{ # global variable list: # @skipmess @@ -6555,16 +8118,19 @@ sub message_for_host2 { 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 (!defined $h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) { + if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size ) + or ( ! defined $h1_flags) or ( ! defined $h1_idate ) + or ( ! $h1_fir_ref) or ( ! $string_ref ) ) + { return ; } - myprint( debugmemory( $sync, " at M1" ) ) ; + myprint( debugmemory( $mysync, " at M1" ) ) ; - my $imap1 = $mysync->{imap1} ; - my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ; - myprint( debugmemory( $sync, " at M2" ) ) ; + my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ; + + myprint( debugmemory( $mysync, " at M2" ) ) ; my $string_len = length_ref( $string_ref ) ; @@ -6573,10 +8139,9 @@ sub message_for_host2 { # 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" ; + $mysync->{imap1}->LastError || q{}, "\n" ; errors_incr( $mysync, $error ) ; - $total_bytes_error += $h1_size if ( $h1_size ) ; - $h1_nb_msg_processed +=1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; return ; } @@ -6621,7 +8186,7 @@ sub message_for_host2 { if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) { my $header = add_header( $h1_msg ) ; - $debug and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ; + $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ; ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ; } @@ -6633,12 +8198,15 @@ sub message_for_host2 { ${ $string_ref }, "F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ; - myprint( debugmemory( $sync, " at M3" ) ) ; + myprint( debugmemory( $mysync, " at M3" ) ) ; return $string_len ; } -sub tests_message_for_host2 { + + +sub tests_message_for_host2 +{ note( 'Entering tests_message_for_host2()' ) ; @@ -6647,7 +8215,7 @@ sub tests_message_for_host2 { is( undef, message_for_host2( ), q{message_for_host2: no 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 ; + require_ok( "Test::MockObject" ) ; my $imapT = Test::MockObject->new( ) ; $mysync->{imap1} = $imapT ; my $string ; @@ -6711,13 +8279,496 @@ sub tests_message_for_host2 { return ; } -sub length_ref { +sub tests_labels_remove_subfolder1 +{ + note( 'Entering tests_labels_remove_subfolder1()' ) ; + is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ; + is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ; + is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ; + is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ), + 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; + + is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ; + + + is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ; + + is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ; + + is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ; + + is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ), + 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ; + + is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ; + + is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ), + 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ; + + is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ), + 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ; + + + note( 'Leaving tests_labels_remove_subfolder1()' ) ; + return ; +} + + + +sub labels_remove_subfolder1 +{ + my $labels = shift ; + my $subfolder1 = shift ; + + if ( not defined $labels ) { return ; } + if ( not defined $subfolder1 ) { return $labels ; } + + my @labels = quotewords('\s+', 1, $labels ) ; + #myprint( "@labels\n" ) ; + my @labels_subfolder2 ; + + foreach my $label ( @labels ) + { + if ( $label =~ m{zzzzzzzzzz} ) + { + # \Seen \Deleted ... stay the same + push @labels_subfolder2, $label ; + } + else + { + # Remove surrounding quotes if any, to add them again in case of space + $label = join( '', quotewords('\s+', 0, $label ) ) ; + $label =~ s{$subfolder1/?}{} ; + if ( 'INBOX' eq $label ) + { + push @labels_subfolder2, q{"\\\\Inbox"} ; + } + elsif ( $label =~ m{\\} ) + { + push @labels_subfolder2, qq{"\\$label"} ; + } + elsif ( $label =~ m{ } ) + { + push @labels_subfolder2, qq{"$label"} ; + } + else + { + push @labels_subfolder2, $label ; + } + } + } + + my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ; + + return $labels_subfolder2 ; +} + +sub tests_labels_remove_special +{ + note( 'Entering tests_labels_remove_special()' ) ; + + is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ; + is( '', labels_remove_special( '' ), 'labels_remove_special: empty string => empty string' ) ; + is( '', labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ; + is( '', labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ; + is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ; + is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ; + note( 'Leaving tests_labels_remove_special()' ) ; + return ; +} + + + + +sub labels_remove_special +{ + my $labels = shift ; + + if ( not defined $labels ) { return ; } + + my @labels = quotewords('\s+', 1, $labels ) ; + myprint( "labels before remove_non_folded: @labels\n" ) ; + my @labels_remove_special ; + + foreach my $label ( @labels ) + { + if ( $label =~ m{^\"\\\\} ) + { + # not kept + } + else + { + push @labels_remove_special, $label ; + } + } + + my $labels_remove_special = join( ' ', sort @labels_remove_special ) ; + + return $labels_remove_special ; +} + + +sub tests_labels_add_subfolder2 +{ + note( 'Entering tests_labels_add_subfolder2()' ) ; + is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ; + is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ; + is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ; + is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ), + 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; + + is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ; + + + is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ; + + is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ), + 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ; + + is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ), + 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ; + + is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ), + 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ; + + is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ), + 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ; + + # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ... + is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ; + + # but not with INBOX folder + is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ), + 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ; + + # two times => one time + is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ; + + is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ), + 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ; + + note( 'Leaving tests_labels_add_subfolder2()' ) ; + return ; +} + +sub labels_add_subfolder2 +{ + my $labels = shift ; + my $subfolder2 = shift ; + my $h1_folder = shift || q{} ; + + if ( not defined $labels ) { return ; } + if ( not defined $subfolder2 ) { return $labels ; } + + # Isn't it messy? + if ( 'INBOX' eq $h1_folder ) + { + $labels .= ' "\\\\Inbox"' ; + } + + my @labels = uniq( quotewords('\s+', 1, $labels ) ) ; + myprint( "labels before subfolder2: @labels\n" ) ; + my @labels_subfolder2 ; + + + foreach my $label ( @labels ) + { + # Isn't it more messy? + if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) ) + { + if ( $subfolder2 =~ m{ } ) + { + push @labels_subfolder2, qq{"$subfolder2/INBOX"} ; + } + else + { + push @labels_subfolder2, "$subfolder2/INBOX" ; + } + } + if ( $label =~ m{^\"\\\\} ) + { + # \Seen \Deleted ... stay the same + #push @labels_subfolder2, $label ; + # Remove surrounding quotes if any, to add them again + $label = join( '', quotewords('\s+', 0, $label ) ) ; + push @labels_subfolder2, qq{"$subfolder2/\\$label"} ; + + } + else + { + # Remove surrounding quotes if any, to add them again in case of space + $label = join( '', quotewords('\s+', 0, $label ) ) ; + if ( $label =~ m{ } ) + { + push @labels_subfolder2, qq{"$subfolder2/$label"} ; + } + else + { + push @labels_subfolder2, "$subfolder2/$label" ; + } + } + } + + my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ; + + return $labels_subfolder2 ; +} + +sub tests_labels +{ + note( 'Entering tests_labels()' ) ; + + is( undef, labels( ), 'labels: no parameters => undef' ) ; + is( undef, labels( undef ), 'labels: undef => undef' ) ; + require_ok( "Test::MockObject" ) ; + my $myimap = Test::MockObject->new( ) ; + + $myimap->mock( 'fetch_hash', + sub { + return( + { '1' => { + 'X-GM-LABELS' => '\Seen Blabla' + } + } + ) ; + } + ) ; + $myimap->mock( 'Debug' , sub { } ) ; + $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one + + is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ; + is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ; + + note( 'Leaving tests_labels()' ) ; + return ; +} + +sub labels +{ + my ( $myimap, $uid ) = @ARG ; + + if ( not all_defined( $myimap, $uid ) ) { + return ; + } + + my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ; + + my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ; + #$labels = $myimap->Unescape( $labels ) ; + return $labels ; +} + +sub tests_synclabels +{ + note( 'Entering tests_synclabels()' ) ; + + is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ; + is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ; + my $mysync ; + is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ; + + require_ok( "Test::MockObject" ) ; + $mysync = {} ; + + my $myimap1 = Test::MockObject->new( ) ; + $myimap1->mock( 'fetch_hash', + sub { + return( + { '1' => { + 'X-GM-LABELS' => '\Seen Blabla' + } + } + ) ; + } + ) ; + $myimap1->mock( 'Debug', sub { } ) ; + $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one + + my $myimap2 = Test::MockObject->new( ) ; + + $myimap2->mock( 'store', + sub { + return 1 ; + } + ) ; + + + $mysync->{imap1} = $myimap1 ; + $mysync->{imap2} = $myimap2 ; + + is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ; + + is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ; + is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ; + + note( 'Leaving tests_synclabels()' ) ; + return ; +} + + +sub synclabels +{ + my( $mysync, $uid1, $uid2 ) = @ARG ; + + if ( not all_defined( $mysync, $uid1, $uid2 ) ) { + return ; + } + my $myimap1 = $mysync->{ 'imap1' } || return ; + my $myimap2 = $mysync->{ 'imap2' } || return ; + + $mysync->{debuglabels} and $myimap1->Debug( 1 ) ; + my $labels1 = labels( $myimap1, $uid1 ) ; + $mysync->{debuglabels} and $myimap1->Debug( 0 ) ; + $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ; + + + + if ( $mysync->{ subfolder1 } and $labels1 ) + { + $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; + $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ; + } + + if ( $mysync->{ subfolder2 } and $labels1 ) + { + $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ; + $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ; + } + + my $store ; + if ( $labels1 and not $mysync->{ dry } ) + { + $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ; + $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ; + $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ; + } + return $store ; +} + + +sub tests_resynclabels +{ + note( 'Entering tests_resynclabels()' ) ; + + is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ; + is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ; + my $mysync ; + is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ; + + my ( $h1_fir_ref, $h2_fir_ref ) ; + + $mysync->{ debuglabels } = 1 ; + $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; + $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; + + # labels are equal + is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), + 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ; + + # labels are different + $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ; + require_ok( "Test::MockObject" ) ; + my $myimap2 = Test::MockObject->new( ) ; + $myimap2->mock( 'store', + sub { + return 1 ; + } + ) ; + $myimap2->mock( 'Debug', sub { } ) ; + $mysync->{imap2} = $myimap2 ; + + is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), + 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ; + + note( 'Leaving tests_resynclabels()' ) ; + return ; +} + + + +sub resynclabels +{ + my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ; + + if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) { + return ; + } + + my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ; + my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ; + + if ( $mysync->{ subfolder1 } and $labels1 ) + { + $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; + } + + if ( $mysync->{ subfolder2 } and $labels1 ) + { + $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ; + $labels2 = labels_remove_special( $labels2 ) ; + } + $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ; + $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ; + + my $store ; + if ( $labels1 eq $labels2 ) + { + # no sync needed + $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ; + return 1 ; + } + elsif ( not $mysync->{ dry } ) + { + # sync needed + $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ; + $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ; + $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ; + } + + return $store ; +} + +sub tests_uniq +{ + note( 'Entering tests_uniq()' ) ; + + is( 0, uniq( ), 'uniq: undef => 0' ) ; + is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ; + is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ; + is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ; + note( 'Leaving tests_uniq()' ) ; + return ; +} + +sub uniq +{ + my @list = @ARG ; + my %seen = ( ) ; + my @uniq = ( ) ; + foreach my $item ( @list ) { + if ( ! $seen{ $item } ) { + $seen{ $item } = 1 ; + push( @uniq, $item ) ; + } + } + return @uniq ; +} + + +sub length_ref +{ my $string_ref = shift ; my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string return $string_len ; } -sub tests_length_ref { +sub tests_length_ref +{ note( 'Entering tests_length_ref()' ) ; my $notdefined ; @@ -6733,29 +8784,31 @@ sub tests_length_ref { return ; } -sub date_for_host2 { +sub date_for_host2 +{ my( $h1_msg, $h1_idate ) = @_ ; my $h1_date = q{} ; if ( $syncinternaldates ) { $h1_date = $h1_idate ; - $debug and myprint( "internal date from host1: [$h1_date]\n" ) ; + $sync->{ 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" ) ; + $sync->{ 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 = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ; + $sync->{ 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" ) ; + $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ; } return( $h1_date ) ; } -sub flags_for_host2 { +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 ; @@ -6769,7 +8822,8 @@ sub flags_for_host2 { return( $h1_flags ) ; } -sub subject { +sub subject +{ my $string = shift ; my $subject = q{} ; @@ -6782,7 +8836,8 @@ sub subject { return( $subject ) ; } -sub tests_subject { +sub tests_subject +{ note( 'Entering tests_subject()' ) ; ok( q{} eq subject( q{} ), 'subject: null') ; @@ -6833,32 +8888,27 @@ EOF # GlobVar -# $sync # $max_msg_size_in_bytes -# $imap2 -# $imap1 -# $total_bytes_error -# $h1_nb_msg_processed # $h2_uidguess # ... # # -sub append_message_on_host2 { - my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ; - myprint( debugmemory( $sync, " at A1" ) ) ; +sub append_message_on_host2 +{ + my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ; + myprint( debugmemory( $mysync, " at A1" ) ) ; my $new_id ; - if ( ! $sync->{dry} ) { + if ( ! $mysync->{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 ) ; - myprint( debugmemory( $sync, " at A2" ) ) ; + $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ; + myprint( debugmemory( $mysync, " at A2" ) ) ; if ( ! $new_id){ my $subject = subject( ${ $string_ref } ) ; - my $error_imap = $imap2->LastError || q{} ; - 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 ; + my $error_imap = $mysync->{imap2}->LastError || q{} ; + my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size], Flags:[$h1_flags] ) to folder $h2_fold: $error_imap\n" ; + errors_incr( $mysync, $error ) ; + $mysync->{ h1_nb_msg_processed } +=1 ; return ; } else{ @@ -6866,44 +8916,47 @@ sub append_message_on_host2 { # $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 ) ; + $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ; } + if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) } $h2_uidguess += 1 ; - $sync->{total_bytes_transferred} += $h1_size ; - $sync->{nb_msg_transferred} += 1 ; - $h1_nb_msg_processed +=1 ; + $mysync->{total_bytes_transferred} += $h1_size ; + $mysync->{nb_msg_transferred} += 1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; - my $time_spent = timesince( $sync->{begin_transfer_time} ) ; - my $rate = bytes_display_string( $sync->{total_bytes_transferred} / $time_spent ) ; + my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; + my $rate = bytes_display_string( $mysync->{total_bytes_transferred} / $time_spent ) ; my $eta = eta( $time_spent, - $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, + $mysync->{ h1_nb_msg_processed }, $h1_nb_msg_start, $mysync->{nb_msg_transferred} ) ; + my $amount_transferred = bytes_display_string( $mysync->{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, $mysync->{nb_msg_transferred}/$time_spent, $rate, $amount_transferred, $eta ); - sleep_if_needed( $sync ) ; + sleep_if_needed( $mysync ) ; 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" ) ; } - if ( $delete1 ) { - delete_message_on_host1( $h1_msg, $h1_fold ) ; + if ( $mysync->{ delete1 } ) { + delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ; } #myprint( "PRESS ENTER" ) and my $a = <> ; + return( $new_id ) ; } } else{ $nb_msg_skipped_dry_mode += 1 ; - $h1_nb_msg_processed +=1 ; + $mysync->{ h1_nb_msg_processed } +=1 ; } return ; } -sub tests_sleep_if_needed { +sub tests_sleep_if_needed +{ note( 'Entering tests_sleep_if_needed()' ) ; is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ; @@ -6925,7 +8978,7 @@ sub tests_sleep_if_needed { $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' ) ; @@ -6935,7 +8988,8 @@ sub tests_sleep_if_needed { } -sub sleep_if_needed { +sub sleep_if_needed +{ my( $mysync ) = shift ; if ( ! $mysync ) { @@ -6947,11 +9001,11 @@ sub sleep_if_needed { ) { return ; } - + $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ; # Must be positive $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ; - + my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ; @@ -6975,7 +9029,8 @@ sub sleep_if_needed { return 0 ; } -sub sleep_max_messages { +sub sleep_max_messages +{ # 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 ) } ; @@ -6985,7 +9040,8 @@ sub sleep_max_messages { } -sub tests_sleep_max_messages { +sub tests_sleep_max_messages +{ note( 'Entering tests_sleep_max_messages()' ) ; ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ; @@ -7000,7 +9056,8 @@ sub tests_sleep_max_messages { } -sub sleep_max_bytes { +sub sleep_max_bytes +{ # 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 ; @@ -7014,7 +9071,8 @@ sub sleep_max_bytes { } -sub tests_sleep_max_bytes { +sub tests_sleep_max_bytes +{ note( 'Entering tests_sleep_max_bytes()' ) ; ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ; @@ -7031,24 +9089,154 @@ sub tests_sleep_max_bytes { } +sub delete_message_on_host1 +{ + my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ; + if ( ! $mysync->{ delete1 } ) { return ; } + if ( ! @h1_msg ) { return ; } + delete_messages_on_any( + $mysync, + $mysync->{imap1}, + "Host1: $h1_fold", + $expunge, + $split1, + @h1_msg ) ; + return ; +} +sub tests_operators_and_exclam_precedence +{ + note( 'Entering tests_operators_and_exclam_precedence()' ) ; -# 6 GlobVar: $sync $imap1 $h1_nb_msg_deleted $expunge1 -sub delete_message_on_host1 { - my( $h1_msg, $h1_fold ) = @_ ; + is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ; + is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ; + is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ; + is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ; + + # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans" + # and change sub delete_messages_on_any() but got 4 more warnings... So now commented. + + #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ; + #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ; + #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ; + #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ; + + is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ; + is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ; + is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ; + is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ; + + is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ; + + note( 'Leaving tests_operators_and_exclam_precedence()' ) ; + return ; +} + +sub delete_messages_on_any +{ + my( $mysync, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ; 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 ) ; + + my $dry_message = $mysync->{ dry_message } ; + $expunge_message = 'and expunged' if ( $expunge ) ; + # "Host1: msg " + + $imap->Debug( 1 ) ; + + while ( my @messages_part = splice @messages, 0, $split ) + { + foreach my $message ( @messages_part ) + { + myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ; + } + if ( ! $mysync->{dry} && @messages_part ) + { + my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ; + if ( defined $nb_deleted ) + { + $mysync->{ h1_nb_msg_deleted } += $nb_deleted ; + } + else + { + my $error_imap = $imap->LastError || q{} ; + my $error = join( q{}, "$hostX_folder folder, could not delete ", + scalar @messages_part, ' messages: ', $error_imap, "\n" ) ; + errors_incr( $mysync, $error ) ; + } + } } + + if ( $expunge ) { + uidexpunge_or_expunge( $mysync, $imap, @messages ) ; + } + + $imap->Debug( 0 ) ; + return ; } -sub eta { +sub tests_uidexpunge_or_expunge +{ + note( 'Entering tests_uidexpunge_or_expunge()' ) ; + + + is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ; + my $mysync ; + is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ; + $mysync = {} ; + is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ; + my $imap ; + is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ; + + require_ok( "Test::MockObject" ) ; + $imap = Test::MockObject->new( ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ; + + my @messages = ( ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ; + + @messages = ( '2', '1' ) ; + $imap->mock( 'uidexpunge', sub { return ; } ) ; + $imap->mock( 'expunge', sub { return ; } ) ; + is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ; + + $imap->mock( 'expunge', sub { return 1 ; } ) ; + is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ; + + $imap->mock( 'uidexpunge', sub { return 1 ; } ) ; + is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ; + + note( 'Leaving tests_uidexpunge_or_expunge()' ) ; + return ; +} + +sub uidexpunge_or_expunge +{ + my $mysync = shift ; + my $imap = shift ; + my @messages = @ARG ; + + if ( ! $imap ) { return ; } ; + if ( ! @messages ) { return ; } ; + + # Doing uidexpunge + my @uidexpunge_result = $imap->uidexpunge( @messages ) ; + if ( @uidexpunge_result ) { + return 1 ; + } + # Failure so doing expunge + my $expunge_result = $imap->expunge( ) ; + if ( $expunge_result ) { + return 1 ; + } + # bad trip + return ; +} + + +sub eta +{ my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ; return( q{} ) if not $foldersizes ; @@ -7058,7 +9246,8 @@ sub eta { 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 { +sub time_remaining +{ my( $my_time_spent, $h1_nb_processed, $my_h1_nb_msg_start, $nb_transferred ) = @_ ; @@ -7067,7 +9256,8 @@ sub time_remaining { } -sub tests_time_remaining { +sub tests_time_remaining +{ note( 'Entering tests_time_remaining()' ) ; @@ -7080,7 +9270,8 @@ sub tests_time_remaining { } -sub cache_map { +sub cache_map +{ my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_; my ( %map1_2, %map2_1, %done2 ) ; @@ -7121,7 +9312,8 @@ sub cache_map { return( \%map1_2, \%map2_1) ; } -sub tests_cache_map { +sub tests_cache_map +{ note( 'Entering tests_cache_map()' ) ; #$debugcache = 1 ; @@ -7163,14 +9355,16 @@ sub tests_cache_map { } -sub cache_dir_fix { +sub cache_dir_fix +{ my $cache_dir = shift ; $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ; #myprint( "cache_dir_fix: $cache_dir\n" ) ; return( $cache_dir ) ; } -sub tests_cache_dir_fix { +sub tests_cache_dir_fix +{ note( 'Entering tests_cache_dir_fix()' ) ; ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' ); @@ -7187,14 +9381,16 @@ sub tests_cache_dir_fix { return ; } -sub cache_dir_fix_win { +sub cache_dir_fix_win +{ my $cache_dir = shift ; $cache_dir =~ s/(\[|\])/[$1]/xg ; #myprint( "cache_dir_fix_win: $cache_dir\n" ) ; return( $cache_dir ) ; } -sub tests_cache_dir_fix_win { +sub tests_cache_dir_fix_win +{ note( 'Entering tests_cache_dir_fix_win()' ) ; ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' ); @@ -7207,13 +9403,14 @@ sub tests_cache_dir_fix_win { -sub get_cache { +sub get_cache +{ 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" ) ; -d $cache_dir or return( undef ); # exit if cache directory doesn't exist - $debugcache and myprint( "cache_dir : $cache_dir\n" ) ; + $debugcache and myprint( "cache_dir : $cache_dir\n" ) ; if ( 'MSWin32' ne $OSNAME ) { @@ -7239,7 +9436,8 @@ sub get_cache { } -sub tests_get_cache { +sub tests_get_cache +{ note( 'Entering tests_get_cache()' ) ; ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' ); @@ -7330,7 +9528,8 @@ sub tests_get_cache { return ; } -sub match_a_cache_file { +sub match_a_cache_file +{ my $file = shift ; my ( $cache_uid1, $cache_uid2 ) ; @@ -7342,7 +9541,8 @@ sub match_a_cache_file { return( $cache_uid1, $cache_uid2 ) ; } -sub tests_match_a_cache_file { +sub tests_match_a_cache_file +{ note( 'Entering tests_match_a_cache_file()' ) ; my ( $tuid1, $tuid2 ) ; @@ -7378,7 +9578,8 @@ sub tests_match_a_cache_file { return ; } -sub clean_cache { +sub clean_cache +{ 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" ) ; @@ -7404,7 +9605,8 @@ sub clean_cache { return( 1 ) ; } -sub tests_clean_cache { +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' ) ; @@ -7458,7 +9660,8 @@ sub tests_clean_cache { return ; } -sub tests_clean_cache_2 { +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' ) ; @@ -7518,19 +9721,20 @@ sub tests_clean_cache_2 { -sub tests_mkpath { +sub tests_mkpath +{ note( 'Entering tests_mkpath()' ) ; 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" 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...' ) ; @@ -7559,7 +9763,7 @@ sub tests_mkpath { # 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' ) ; } + 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' ) ; @@ -7568,8 +9772,8 @@ sub tests_mkpath { 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' ) ; - - + + } ; note( 'Leaving tests_mkpath()' ) ; @@ -7577,7 +9781,8 @@ sub tests_mkpath { return 1 ; } -sub tests_touch { +sub tests_touch +{ note( 'Entering tests_touch()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ; @@ -7592,7 +9797,8 @@ sub tests_touch { } -sub touch { +sub touch +{ my @files = @_ ; my $failures = 0 ; @@ -7609,7 +9815,8 @@ sub touch { } -sub tests_tmpdir_has_colon_bug { +sub tests_tmpdir_has_colon_bug +{ note( 'Entering tests_tmpdir_has_colon_bug()' ) ; ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ; @@ -7621,7 +9828,8 @@ sub tests_tmpdir_has_colon_bug { return ; } -sub tmpdir_has_colon_bug { +sub tmpdir_has_colon_bug +{ my $path = shift ; my $path_filtered = filter_forbidden_characters( $path ) ; @@ -7632,7 +9840,8 @@ sub tmpdir_has_colon_bug { return( 0 ) ; } -sub tmpdir_fix_colon_bug { +sub tmpdir_fix_colon_bug +{ my $mysync = shift ; my $err = 0 ; if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) { @@ -7679,10 +9888,10 @@ sub tmpdir_fix_colon_bug { } -sub tests_cache_folder { +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{}, '/>pp /path/fol_d1/fold2' ) ; @@ -7697,11 +9906,12 @@ sub tests_cache_folder { return ; } -sub cache_folder { +sub cache_folder +{ my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ; - my $sep_1 = $h1_sep || '/'; - my $sep_2 = $h2_sep || '/'; + my $sep_1 = $sync->{ h1_sep } || '/'; + my $sep_2 = $sync->{ 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 ) ; @@ -7712,24 +9922,10 @@ sub cache_folder { return( $cache_folder ) ; } -sub filter_forbidden_characters { - 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 ; - } - $string =~ s{[\Q*|?:"<>\E\t\r\n\\]}{_}xg ; - #myprint( "[$string]\n" ) ; - return( $string ) ; -} - -sub tests_filter_forbidden_characters { +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' ) ; @@ -7756,14 +9952,23 @@ sub tests_filter_forbidden_characters { return ; } -sub convert_sep_to_slash { - my ( $folder, $sep ) = @_ ; +sub filter_forbidden_characters +{ + my $string = shift ; - $folder =~ s{\Q$sep\E}{/}xg ; - return( $folder ) ; + if ( ! defined $string ) { return ; } + + if ( 'MSWin32' eq $OSNAME ) { + # Move trailing whitespace to _ " a b /c d " -> " a b_/c d_" + $string =~ s{\ (/|$)}{_$1}xg ; + } + $string =~ s{[\Q*|?:"<>\E\t\r\n\\]}{_}xg ; + #myprint( "[$string]\n" ) ; + return( $string ) ; } -sub tests_convert_sep_to_slash { +sub tests_convert_sep_to_slash +{ note( 'Entering tests_convert_sep_to_slash()' ) ; @@ -7779,8 +9984,17 @@ sub tests_convert_sep_to_slash { return ; } +sub convert_sep_to_slash +{ + my ( $folder, $sep ) = @_ ; -sub tests_regexmess { + $folder =~ s{\Q$sep\E}{/}xg ; + return( $folder ) ; +} + + +sub tests_regexmess +{ note( 'Entering tests_regexmess()' ) ; ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ; @@ -7826,7 +10040,7 @@ sub tests_regexmess { eq regexmess("\n" . 'From '), 'From mbox 3 remove'); - #myprint( "[", regexmess("From zzz\n" . 'From '), "]" ) ; + #myprint( "[", regexmess("From zzz\n" . 'From '), "]" ) ; ok( q{} . 'From ' eq regexmess("From zzz\n" . 'From '), 'From mbox 4 remove'); @@ -8266,23 +10480,25 @@ EOM } -sub regexmess { +sub regexmess +{ my ( $string ) = @_ ; foreach my $regexmess ( @regexmess ) { - $debug and myprint( "eval \$string =~ $regexmess\n" ) ; + $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ; my $ret = eval "\$string =~ $regexmess ; 1" ; - #myprint( "eval [$ret]\n" ) ; + #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { - myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ; + myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ; return( undef ) ; } } - $debug and myprint( "$string\n" ) ; + $sync->{ debug } and myprint( "$string\n" ) ; return( $string ) ; } -sub tests_skipmess { +sub tests_skipmess +{ note( 'Entering tests_skipmess()' ) ; ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ; @@ -8503,17 +10719,18 @@ EOM return ; } -sub skipmess { +sub skipmess +{ my ( $string ) = @_ ; my $match ; - #myprint( "$string\n" ) ; + #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" ) ; + $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ; + my $ret = eval "\$match = \$string =~ $skipmess ; 1" ; + #myprint( "eval [$ret]\n" ) ; + $sync->{ debug } and myprint( "match [$match]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { - myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ; + myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ; return( undef ) ; } return( $match ) if ( $match ) ; @@ -8524,7 +10741,8 @@ sub skipmess { -sub tests_bytes_display_string { +sub tests_bytes_display_string +{ note( 'Entering tests_bytes_display_string()' ) ; @@ -8552,13 +10770,14 @@ sub tests_bytes_display_string { 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" ) ; + #myprint( bytes_display_string( 1_180_591_620_717_411_303_424 ), "\n" ) ; note( 'Leaving tests_bytes_display_string()' ) ; return ; } -sub bytes_display_string { +sub bytes_display_string +{ my ( $bytes ) = @_ ; my $readable_value = q{} ; @@ -8594,11 +10813,48 @@ sub bytes_display_string { } # if you have exabytes (EiB) of email to transfer, you have too much email! } - #myprint( "$bytes = $readable_value\n" ) ; + #myprint( "$bytes = $readable_value\n" ) ; return( $readable_value ) ; } -sub stats { + +sub tests_useheader_suggestion +{ + note( 'Entering tests_useheader_suggestion()' ) ; + + is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ; + my $mysync = {} ; + + $mysync->{ h1_nb_msg_noheader } = 0 ; + is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ; + $mysync->{ h1_nb_msg_noheader } = 2 ; + is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ), + 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ; + + note( 'Leaving tests_useheader_suggestion()' ) ; + return ; +} + +sub useheader_suggestion +{ + my $mysync = shift ; + if ( ! defined $mysync->{ h1_nb_msg_noheader } ) + { + return ; + } + elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } ) + { + return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ; + } + else + { + return q{} ; + } + return ; +} + +sub stats +{ my $mysync = shift ; if ( ! $mysync->{stats} ) { @@ -8615,74 +10871,76 @@ sub stats { my $memory_ratio = ($max_msg_size_in_bytes) ? mysprintf('%.1f', $memory_consumption_at_end / $max_msg_size_in_bytes) : 'NA' ; - - 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} " ) ; + # my $useheader_suggestion = useheader_suggestion( $mysync ) ; + 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", + myprint( "Messages skipped : $mysync->{ 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 found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ; + myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ; + myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ; + nb_messages_in_1_not_in_2( $mysync ) ; + nb_messages_in_2_not_in_1( $mysync ) ; + myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ; + myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ; + myprint( "Messages deleted on host1 : $mysync->{ h1_nb_msg_deleted }\n" ) ; + myprint( "Messages deleted on host2 : $h2_nb_msg_deleted\n" ) ; + myprintf( "Total bytes transferred : %s (%s)\n", $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) ) ; - myprintf( "Total bytes duplicate host2 : %s (%s)\n", - $h2_total_bytes_duplicate, - bytes_display_string( $h2_total_bytes_duplicate) ) ; - myprintf( "Total bytes skipped : %s (%s)\n", - $total_bytes_skipped, - bytes_display_string( $total_bytes_skipped ) ) ; - myprintf( "Total bytes error : %s (%s)\n", - $total_bytes_error, - bytes_display_string( $total_bytes_error ) ) ; + myprintf( "Total bytes skipped : %s (%s)\n", + $mysync->{ total_bytes_skipped }, + bytes_display_string( $mysync->{ total_bytes_skipped } ) ) ; $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 at the end : %.1f MiB (started with %.1f MiB)\n", + 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 at the end : %.1f MiB (started with %.1f MiB)\n", $memory_consumption_at_end / $KIBI / $KIBI, $memory_consumption_at_start / $KIBI / $KIBI ) ; - myprintf("Biggest message : %s bytes (%s)\n", + myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ; + + 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 $mysync->{nb_errors} errors\n\n" ) ; - myprint( $warn_release, "\n" ) ; - myprint( homepage( ), "\n" ) ; + comment_on_final_diff_in_1_not_in_2( $mysync ) ; + comment_on_final_diff_in_2_not_in_1( $mysync ) ; + myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ; + + myprint( $warn_release, "\n" ) ; + myprint( homepage( ), "\n" ) ; return ; } -sub diff_or_NA { +sub diff_or_NA +{ my( $n1, $n2 ) = @ARG ; if ( not defined $n1 or not defined $n2 ) { @@ -8697,7 +10955,8 @@ sub diff_or_NA { return( $n1 - $n2 ) ; } -sub match_number { +sub match_number +{ my $n = shift @ARG ; if ( not defined $n ) { @@ -8712,7 +10971,8 @@ sub match_number { } -sub tests_match_number { +sub tests_match_number +{ note( 'Entering tests_match_number()' ) ; @@ -8730,7 +10990,8 @@ sub tests_match_number { -sub tests_diff_or_NA { +sub tests_diff_or_NA +{ note( 'Entering tests_diff_or_NA()' ) ; @@ -8752,12 +11013,14 @@ sub tests_diff_or_NA { return ; } -sub homepage { +sub homepage +{ return( 'Homepage: https://imapsync.lamiral.info/' ) ; } -sub load_modules { +sub load_modules +{ if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} @@ -8774,15 +11037,16 @@ sub load_modules { -sub parse_header_msg { +sub parse_header_msg +{ my ( $mysync, $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" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, 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" ) ; + 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 ; @@ -8790,7 +11054,7 @@ sub parse_header_msg { $head = decompose_header( $whole_header ) ; $headnum = scalar keys %{ $head } ; - $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ; } #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ; @@ -8801,7 +11065,7 @@ sub parse_header_msg { if ( ( ! $headstr ) and ( $mysync->{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" ) ; + myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ; $headstr .= uc $header ; $s_fir->{$m_uid}->{NO_HEADER} = 1; } @@ -8813,7 +11077,7 @@ sub parse_header_msg { 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" ) ; + $mysync->{ debug } and myprint( "$side: uid $m_uid sig $m_md5 size $size idate $idate\n" ) ; my $key ; if ($skipsize) { $key = "$m_md5"; @@ -8832,7 +11096,8 @@ sub parse_header_msg { return( 1 ) ; } -sub header_construct { +sub header_construct +{ my( $head, $side, $m_uid ) = @_ ; @@ -8846,10 +11111,10 @@ sub header_construct { my $H = header_line_normalize( $h, $val ) ; # show stuff in debug mode - $debug and myprint( "$side uid $m_uid header [$H]", "\n" ) ; + $sync->{ 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" ) ; + $sync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ; next ; } $headstr .= "$H" ; @@ -8859,7 +11124,8 @@ sub header_construct { } -sub header_line_normalize { +sub header_line_normalize +{ my( $header_key, $header_val ) = @_ ; # no 8-bit data in headers ! @@ -8888,7 +11154,8 @@ sub header_line_normalize { return( $header_line ) ; } -sub tests_header_line_normalize { +sub tests_header_line_normalize +{ note( 'Entering tests_header_line_normalize()' ) ; @@ -8905,52 +11172,130 @@ sub tests_header_line_normalize { } -sub firstline { +sub tests_firstline +{ + note( 'Entering tests_firstline()' ) ; + + is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ; + + is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ; + is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ; + + is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ; + is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ; + + is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ; + is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ; + + is( "blabla\nTiti\n" , string_to_file( "blabla\nTiti\n", 'W/tmp/tests/firstline4.txt' ), 'firstline: put blabla\nTiti\n in W/tmp/tests/firstline4.txt' ) ; + is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ; + + note( 'Leaving tests_firstline()' ) ; + return ; +} + +sub firstline +{ # extract the first line of a file (without \n) + # return empty string if error or empty string - my( $file ) = @_ ; - my $line = q{} ; + my $file = shift ; + my $line ; - 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{} ; - close $FILE ; - chomp $line ; + $line = nthline( $file, 1 ) ; return $line ; } -sub tests_firstline { - 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()' ) ; +sub tests_secondline +{ + note( 'Entering tests_secondline()' ) ; + + is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ; + + is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/secondline.txt' ), 'secondline: put L1\nL2\nL3\nL4\n in W/tmp/tests/secondline.txt' ) ; + is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ; + + + note( 'Leaving tests_secondline()' ) ; return ; } +sub secondline +{ + # extract the second line of a file (without \n) + # return empty string if error or empty string + + my $file = shift ; + my $line ; + + $line = nthline( $file, 2 ) ; + return $line ; +} + + + + +sub tests_nthline +{ + note( 'Entering tests_nthline()' ) ; + + is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; + + ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ; + + is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/nthline.txt' ), 'nthline: put L1\nL2\nL3\nL4\n in W/tmp/tests/nthline.txt' ) ; + is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ; + + + note( 'Leaving tests_nthline()' ) ; + return ; +} + + +sub nthline +{ + # extract the nth line of a file (without \n) + # return empty string if error or empty string + + my $file = shift ; + my $num = shift ; + + if ( ! all_defined( $file, $num ) ) { return q{} ; } + + my $line ; + + $line = ( file_to_array( $file ) )[$num - 1] ; + if ( ! defined $line ) + { + return q{} ; + } + else + { + chomp $line ; + return $line ; + } + +} + # Should be unit tested and then be used by file_to_string, refactoring file_to_string -sub file_to_array { +sub file_to_array +{ my( $file ) = shift ; my @string ; open my $FILE, '<', $file or do { - myprint( "Error reading file $file : $OS_ERROR" ) ; + myprint( "Error reading file $file : $OS_ERROR\n" ) ; return ; } ; @string = <$FILE> ; @@ -8959,7 +11304,8 @@ sub file_to_array { } -sub tests_file_to_string { +sub tests_file_to_string +{ note( 'Entering tests_file_to_string()' ) ; is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ; @@ -8967,7 +11313,7 @@ sub tests_file_to_string { 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/' ) ; + 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' ) ; @@ -8979,7 +11325,8 @@ sub tests_file_to_string { return ; } -sub file_to_string { +sub file_to_string +{ my $file = shift ; if ( ! $file ) { return ; } if ( ! -e $file ) { return ; } @@ -8997,7 +11344,8 @@ sub file_to_string { } -sub tests_string_to_file { +sub tests_string_to_file +{ note( 'Entering tests_string_to_file()' ) ; is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ; @@ -9017,7 +11365,8 @@ sub tests_string_to_file { return ; } -sub string_to_file { +sub string_to_file +{ my( $string, $file ) = @_ ; if( ! defined $string ) { return ; } if( ! defined $file ) { return ; } @@ -9036,7 +11385,7 @@ sub string_to_file { return $string ; } -q^ +0 and <<'MULTILINE_COMMENT' ; This is a multiline comment. Based on David Carter discussion, to do: * Call parameters stay the same. @@ -9050,9 +11399,11 @@ OK * in case of CHILD_ERROR, return( undef, $error ) * 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. -^ if 0 ; # End of multiline comment. +MULTILINE_COMMENT +# End of multiline comment. -sub pipemess { +sub pipemess +{ my ( $string, @commands ) = @_ ; my $error = q{} ; foreach my $command ( @commands ) { @@ -9085,7 +11436,7 @@ sub pipemess { myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ; } } - #myprint( "[$string]\n" ) ; + #myprint( "[$string]\n" ) ; if ( wantarray ) { return ( $string, $error ) ; }else{ @@ -9095,7 +11446,8 @@ sub pipemess { -sub tests_pipemess { +sub tests_pipemess +{ note( 'Entering tests_pipemess()' ) ; @@ -9148,7 +11500,7 @@ sub tests_pipemess { ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ; is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ; - 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' ) ; + 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' ) ; @@ -9162,12 +11514,12 @@ sub tests_pipemess { ( $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"}xm, '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"}xm, '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' ) ; @@ -9191,9 +11543,10 @@ sub tests_pipemess { -sub tests_is_a_release_number { +sub tests_is_a_release_number +{ note( 'Entering tests_is_a_release_number()' ) ; - + is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ; 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' ) ; @@ -9204,7 +11557,8 @@ sub tests_is_a_release_number { return ; } -sub is_a_release_number { +sub is_a_release_number +{ my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+\.\d+$}xo ) ; @@ -9212,7 +11566,8 @@ sub is_a_release_number { -sub imapsync_version_public { +sub imapsync_version_public +{ my $local_version = imapsync_version( $sync ) ; my $imapsync_basename = imapsync_basename( ) ; @@ -9237,7 +11592,8 @@ sub imapsync_version_public { return( $last_release ) ; } -sub not_long_imapsync_version_public { +sub not_long_imapsync_version_public +{ #myprint( "Entering not_long_imapsync_version_public\n" ) ; my $fake = shift ; @@ -9254,7 +11610,7 @@ sub not_long_imapsync_version_public { POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { croak 'alarm' } ) ) - or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ; + or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ; } my $ret = eval { @@ -9262,12 +11618,12 @@ sub not_long_imapsync_version_public { { $val = imapsync_version_public( ) ; #sleep 4 ; - #myprint( "End of imapsync_version_public\n" ) ; + #myprint( "End of imapsync_version_public\n" ) ; } alarm 0 ; 1 ; } ; - #myprint( "eval [$ret]\n" ) ; + #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { #myprint( "$EVAL_ERROR" ) ; if ($EVAL_ERROR =~ /alarm/) { @@ -9283,7 +11639,8 @@ sub not_long_imapsync_version_public { } } -sub tests_not_long_imapsync_version_public { +sub tests_not_long_imapsync_version_public +{ note( 'Entering tests_not_long_imapsync_version_public()' ) ; @@ -9294,20 +11651,21 @@ sub tests_not_long_imapsync_version_public { return ; } -sub check_last_release { +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" ) ; + $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ; my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ; - + if ( $public_release eq 'unknown' ) { return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ; } - + if ( $public_release eq 'timeout' ) { return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ; } - + if ( ! is_a_release_number( $public_release ) ) { return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ; } @@ -9323,7 +11681,8 @@ sub check_last_release { return( 'really unknown' ) ; # Should never arrive here } -sub tests_check_last_release { +sub tests_check_last_release +{ note( 'Entering tests_check_last_release()' ) ; diag( check_last_release( 1.1 ) ) ; @@ -9342,7 +11701,8 @@ sub tests_check_last_release { return ; } -sub imapsync_version { +sub imapsync_version +{ my $mysync = shift ; my $rcs = $mysync->{rcs} ; my $version ; @@ -9352,7 +11712,8 @@ sub imapsync_version { } -sub tests_version_from_rcs { +sub tests_version_from_rcs +{ note( 'Entering tests_version_from_rcs()' ) ; is( undef, version_from_rcs( ), 'version_from_rcs: no args => UNKNOWN' ) ; @@ -9364,22 +11725,24 @@ sub tests_version_from_rcs { } -sub version_from_rcs { +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 { +sub tests_imapsync_basename +{ note( 'Entering tests_imapsync_basename()' ) ; ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync'); @@ -9389,14 +11752,16 @@ sub tests_imapsync_basename { return ; } -sub imapsync_basename { +sub imapsync_basename +{ return basename( $PROGRAM_NAME ) ; } -sub localhost_info { +sub localhost_info +{ my $mysync = shift ; my( $infos ) = join( q{}, "Here is imapsync ", imapsync_version( $mysync ), @@ -9411,7 +11776,8 @@ sub localhost_info { return( $infos ) ; } -sub tests_cpu_number { +sub tests_cpu_number +{ note( 'Entering tests_cpu_number()' ) ; is( 1, is_an_integer( cpu_number( ) ), "cpu_number: is_an_integer" ) ; @@ -9424,7 +11790,8 @@ sub tests_cpu_number { return ; } -sub cpu_number { +sub cpu_number +{ my $cpu_number_forced = shift ; # Well, here 1 is better than 0 or undef @@ -9434,19 +11801,19 @@ sub cpu_number { 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 env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ; - }elsif ( 'darwin' eq $OSNAME ) { + $sync->{ debug } and myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ; + }elsif ( 'darwin' eq $OSNAME or 'freebsd' eq $OSNAME ) { $cpu_number = backtick( "sysctl -n hw.ncpu" ) ; chomp( $cpu_number ) ; - $debug and myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ; + $sync->{ debug } and myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ; }elsif ( ! -e '/proc/cpuinfo' ) { - $debug and myprint( "Number of processors not found so I might assume there is only 1\n" ) ; + $sync->{ debug } and myprint( "Number of processors not found so I might assume there is only 1\n" ) ; $cpu_number = 1 ; }elsif( @cpuinfo = file_to_array( '/proc/cpuinfo' ) ) { $cpu_number = grep { /^processor/mxs } @cpuinfo ; - $debug and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ; + $sync->{ debug } and myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ; } - + if ( defined $cpu_number_forced ) { $cpu_number = $cpu_number_forced ; } @@ -9454,28 +11821,34 @@ sub cpu_number { } -sub tests_integer_or_1 { +sub tests_integer_or_1 +{ + note( 'Entering tests_integer_or_1()' ) ; is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ; is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ; is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ; is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ; is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ; + + note( 'Leaving tests_integer_or_1()' ) ; return ; } -sub integer_or_1 { +sub integer_or_1 +{ my $number = shift ; - if ( is_an_integer( $number ) ) { - return $number ; + if ( is_an_integer( $number ) ) { + return $number ; } # else return 1 ; } -sub tests_is_an_integer { +sub tests_is_an_integer +{ note( 'Entering tests_is_an_integer()' ) ; - + is( undef, is_an_integer( ), 'is_an_integer: no args => undef ' ) ; ok( is_an_integer( 1 ), 'is_an_integer: 1 => yes ') ; ok( is_an_integer( $NUMBER_42 ), 'is_an_integer: 42 => yes ') ; @@ -9491,7 +11864,8 @@ sub tests_is_an_integer { return ; } -sub is_an_integer { +sub is_an_integer +{ my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+$}xo ) ; @@ -9500,7 +11874,8 @@ sub is_an_integer { -sub tests_loadavg { +sub tests_loadavg +{ note( 'Entering tests_loadavg()' ) ; @@ -9535,10 +11910,14 @@ sub tests_loadavg { } -sub loadavg { +sub loadavg +{ if ( 'linux' eq $OSNAME ) { return ( loadavg_linux( @ARG ) ) ; } + if ( 'freebsd' eq $OSNAME ) { + return ( loadavg_freebsd( @ARG ) ) ; + } if ( 'darwin' eq $OSNAME ) { return ( loadavg_darwin( @ARG ) ) ; } @@ -9549,7 +11928,8 @@ sub loadavg { } -sub loadavg_linux { +sub loadavg_linux +{ my $line = shift ; if ( ! $line ) { @@ -9558,14 +11938,37 @@ sub loadavg_linux { 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 myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ; + $sync->{ debug } and myprint( "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_freebsd +{ + my $file = shift ; + # Example of output of command "sysctl vm.loadavg": + # vm.loadavg: { 0.15 0.08 0.08 } + my $loadavg ; -sub loadavg_darwin { + if ( ! defined $file ) { + eval { + $loadavg = `/sbin/sysctl vm.loadavg` ; + #myprint( "LOADAVG FREEBSD: $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 ; + $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; + return ( $avg_1_min, $avg_5_min, $avg_15_min ) ; +} + +sub loadavg_darwin +{ my $file = shift ; # Example of output of command "sysctl vm.loadavg": # vm.loadavg: { 0.15 0.08 0.08 } @@ -9583,11 +11986,12 @@ sub loadavg_darwin { 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 myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; + $sync->{ debug } and myprint( "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 { +sub loadavg_windows +{ my $file = shift ; # Example of output of command "wmic cpu get loadpercentage": # LoadPercentage @@ -9609,7 +12013,7 @@ sub loadavg_windows { my $num = $1 ; $num /= 100 ; - $debug and myprint( "System load: $num\n" ) ; + $sync->{ debug } and myprint( "System load: $num\n" ) ; return ( $num ) ; } @@ -9618,39 +12022,65 @@ sub loadavg_windows { -sub tests_load_and_delay { +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( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is 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 ' ) ; +# Old behavior, rather strict + # 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' ) ; + # 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 ' ) ; - note( 'Leaving tests_load_and_delay()' ) ; - return ; +# New behavior, tolerate more load + + 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( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 1 ' ) ; + is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 1 ' ) ; + is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 5 ' ) ; + is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 15 ' ) ; + + is( 1, load_and_delay( 1, 4, 0, 0 ), 'load_and_delay: one core, load1m=4 load5m=0 load15m=0 => 1 ' ) ; + is( 1, load_and_delay( 1, 4, 0, 4 ), 'load_and_delay: one core, load1m=4 load5m=0 load15m=4 => 1 ' ) ; + is( 5, load_and_delay( 1, 4, 4, 0 ), 'load_and_delay: one core, load1m=4 load5m=4 load15m=0 => 5 ' ) ; + is( 15, load_and_delay( 1, 4, 4, 4 ), 'load_and_delay: one core, load1m=4 load5m=4 load15m=4 => 15 ' ) ; + + is( 0, load_and_delay( 4, 0, 9, 9 ), 'load_and_delay: four core, load1m=0 load5m=9 load15m=9 => 0 ' ) ; + is( 1, load_and_delay( 4, 9, 0, 0 ), 'load_and_delay: four core, load1m=9 load5m=0 load15m=0 => 1 ' ) ; + is( 1, load_and_delay( 4, 9, 0, 9 ), 'load_and_delay: four core, load1m=9 load5m=0 load15m=9 => 1 ' ) ; + is( 5, load_and_delay( 4, 9, 9, 0 ), 'load_and_delay: four core, load1m=9 load5m=9 load15m=0 => 5 ' ) ; + is( 15, load_and_delay( 4, 9, 9, 9 ), 'load_and_delay: four core, load1m=9 load5m=9 load15m=9 => 15 ' ) ; + + note( 'Leaving tests_load_and_delay()' ) ; + return ; } -sub load_and_delay { +sub load_and_delay +{ # Basically return 0 if load is not heavy, ie <= 1 per processor + # Not enough arguments if ( 4 > scalar @ARG ) { return ; } my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ; @@ -9660,17 +12090,18 @@ sub load_and_delay { # 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 + if ( $avg_1_min <= 2 ) { return 0 ; } + if ( $avg_5_min <= 2 ) { return 1 ; } # Retry in 1 minute + if ( $avg_15_min <= 2 ) { return 5 ; } # Retry in 5 minutes return 15 ; # Retry in 15 minutes } -sub ram_memory_info { +sub ram_memory_info +{ # In GigaBytes so division by 1024 * 1024 * 1024 - # + # return( - sprintf( "%.1f/%.1f free GiB of RAM", + sprintf( "%.1f/%.1f free GiB of RAM", Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ), Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ), ) @@ -9679,20 +12110,22 @@ sub ram_memory_info { -sub tests_memory_stress { +sub tests_memory_stress +{ note( 'Entering tests_memory_stress()' ) ; - + is( undef, memory_stress( ), 'memory_stress: => undef' ) ; - + note( 'Leaving tests_memory_stress()' ) ; return ; } -sub memory_stress { +sub memory_stress +{ my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ; my $i = 1 ; - + myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ; myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; @@ -9700,7 +12133,8 @@ sub memory_stress { } -sub tests_memory_consumption { +sub tests_memory_consumption +{ note( 'Entering tests_memory_consumption()' ) ; like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ; @@ -9717,33 +12151,36 @@ sub tests_memory_consumption { return ; } -sub memory_consumption { +sub memory_consumption +{ # memory consumed by imapsync until now in bytes return( ( memory_consumption_of_pids( ) )[0] ); } -sub debugmemory { +sub debugmemory +{ my $mysync = shift ; if ( ! $mysync->{debugmemory} ) { return q{} ; } - + my $precision = shift ; return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ; } -sub memory_consumption_of_pids { +sub memory_consumption_of_pids +{ my @pid = @_; @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ; - $debug and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ; + $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ; my @val ; - if ( 'MSWin32' eq $OSNAME ) { + if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) { @val = memory_consumption_of_pids_win32( @pid ) ; }else{ # Unix my @ps = qx{ ps -o vsz -p @pid } ; #myprint( "ps: @ps" ) ; - + # Use IPC::Open3 from perlcrit -3 # It stalls on Darwin, don't understand why! #my @ps = backtick( "ps -o vsz -p @pid" ) ; @@ -9752,14 +12189,15 @@ sub memory_consumption_of_pids { shift @ps; # First line is column name "VSZ" chomp @ps; # convert to octets - + @val = map { $_ * $KIBI } @ps ; } - $debug and myprint "@val\n" ; + $sync->{ debug } and myprint( "@val\n" ) ; return( @val ) ; } -sub memory_consumption_of_pids_win32 { +sub memory_consumption_of_pids_win32 +{ # Windows my @PID = @_; my %PID; @@ -9791,7 +12229,8 @@ sub memory_consumption_of_pids_win32 { } -sub tests_backtick { +sub tests_backtick +{ note( 'Entering tests_backtick()' ) ; is( undef, backtick( ), 'backtick: no args' ) ; @@ -9803,11 +12242,11 @@ sub tests_backtick { @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]" ) ; + $sync->{ 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]]" ) ; + $sync->{ 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' ) ; @@ -9821,11 +12260,11 @@ sub tests_backtick { my @output ; @output = backtick( 'echo Hello World!' ) ; ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ; - $debug and myprint( "[@output]" ) ; + $sync->{ 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]" ) ; + $sync->{ debug } and myprint( "[@output]" ) ; # Scalar context ok( "Hello World!\n" eq backtick( 'echo Hello World!' ), 'backtick: echo Hello World! scalar' ) ; @@ -9834,8 +12273,8 @@ sub tests_backtick { # Return error positive value, that's ok is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ; my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ; - $debug and myprint "MEM=$mem\n" ; - + $sync->{ debug } and myprint( "MEM=$mem\n" ) ; + } note( 'Leaving tests_backtick()' ) ; @@ -9843,7 +12282,8 @@ sub tests_backtick { } -sub backtick { +sub backtick +{ my $command = shift ; if ( ! $command ) { return ; } @@ -9856,7 +12296,7 @@ sub backtick { } ; if ( $EVAL_ERROR ) { myprint( $EVAL_ERROR ) ; - return ; + return ; } if ( ! $eval ) { return ; } if ( ! $pid ) { return ; } @@ -9876,7 +12316,91 @@ sub backtick { } -sub remove_not_num { + +sub tests_check_binary_embed_all_dyn_libs +{ + note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ; + + is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ; + + note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ; + + return ; +} + + +sub check_binary_embed_all_dyn_libs +{ + my @search_dyn_lib_locale = search_dyn_lib_locale( ) ; + + if ( @search_dyn_lib_locale ) + { + myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ; + myprint( @search_dyn_lib_locale ) ; + if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) + { + return 0 ; + } + elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} ) + { + return 0 ; + } + else + { + # is always ok for non binary + return 1 ; + } + } + else + { + # Found only embedded dynamic lib + myprint( "Found nothing\n" ) ; + return 1 ; + } +} + +sub search_dyn_lib_locale +{ + if ( 'darwin' eq $OSNAME ) + { + return search_dyn_lib_locale_darwin( ) ; + } + if ( 'linux' eq $OSNAME ) + { + return search_dyn_lib_locale_linux( ) ; + } + if ( 'MSWin32' eq $OSNAME ) + { + return search_dyn_lib_locale_MSWin32( ) ; + } +} + +sub search_dyn_lib_locale_darwin +{ + my $command = qq{ lsof -p $PID | grep ' REG ' | grep .dylib | grep -v '/par-' } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return backtick( $command ) ; +} + +sub search_dyn_lib_locale_linux +{ + my $command = qq{ lsof -p $PID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return backtick( $command ) ; +} + +sub search_dyn_lib_locale_MSWin32 +{ + my $command = qq{ Listdlls.exe $PID|findstr Strawberry } ; + # $command = qq{ Listdlls.exe $PID|findstr Strawberry } ; + myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; + return qx( $command ) ; +} + + + +sub remove_not_num +{ my $string = shift ; $string =~ tr/0-9//cd ; @@ -9884,7 +12408,8 @@ sub remove_not_num { return( $string ) ; } -sub tests_remove_not_num { +sub tests_remove_not_num +{ note( 'Entering tests_remove_not_num()' ) ; ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ; @@ -9896,7 +12421,8 @@ sub tests_remove_not_num { return ; } -sub remove_Ko { +sub remove_Ko +{ my $string = shift; if ($string =~ /^(.*)\sKo$/xo) { return($1); @@ -9905,7 +12431,8 @@ sub remove_Ko { } } -sub remove_qq { +sub remove_qq +{ my $string = shift; if ($string =~ /^"(.*)"$/xo) { return($1); @@ -9914,7 +12441,8 @@ sub remove_qq { } } -sub memory_consumption_ratio { +sub memory_consumption_ratio +{ my ($base) = @_; $base ||= 1; @@ -9923,24 +12451,26 @@ sub memory_consumption_ratio { } -sub date_from_rcs { +sub date_from_rcs +{ 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 ) ; 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( "header: [$1][$2][$3][$4][$5][$6]\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" ) ; + #myprint( "$d\n" ) ; } return( $d ) ; } -sub tests_date_from_rcs { +sub tests_date_from_rcs +{ note( 'Entering tests_date_from_rcs()' ) ; ok('19-Sep-2015 16:11:07 +0000' @@ -9950,7 +12480,8 @@ sub tests_date_from_rcs { return ; } -sub good_date { +sub good_date +{ # two incoming formats: # header Tue, 24 Aug 2010 16:00:00 +0200 # internal 24-Aug-2010 16:00:00 +0200 @@ -9963,7 +12494,7 @@ sub good_date { 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" ) ; + #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 ; @@ -10017,12 +12548,12 @@ sub good_date { 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( "header: [$1][$2][$3][$4][$5][$6]\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" ) ; + #myprint( "$d\n" ) ; last SWITCH ; } @@ -10080,7 +12611,8 @@ sub good_date { } -sub tests_good_date { +sub tests_good_date +{ note( 'Entering tests_good_date()' ) ; ok(q{} eq good_date(), 'good_date no arg'); @@ -10102,15 +12634,15 @@ sub tests_good_date { ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16.00.00 +0200'), 'good_date header period time sep'); - ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1'); + ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24 Aug 1997 5:6:2 +0200'), 'good_date header 1digit time vals'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas'); ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev'); - ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space'); + ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space'); ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders'); 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('"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' ) ; note( 'Leaving tests_good_date()' ) ; @@ -10118,7 +12650,8 @@ sub tests_good_date { } -sub tests_list_keys_in_2_not_in_1 { +sub tests_list_keys_in_2_not_in_1 +{ note( 'Entering tests_list_keys_in_2_not_in_1()' ) ; @@ -10135,62 +12668,197 @@ sub 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; +sub list_keys_in_2_not_in_1 +{ + my $hash_1_ref = shift; + my $hash_2_ref = shift; my @list; - foreach my $folder ( sort keys %{ $folders2_ref } ) { - next if exists $folders1_ref->{$folder}; - push @list, $folder; + foreach my $key ( sort keys %{ $hash_2_ref } ) { + #$debug and print "$folder\n" ; + next if exists $hash_1_ref->{$key} ; + push @list, $key ; } - return(@list); + #$debug and print "@list\n" ; + return( @list ) ; } -sub list_folders_in_2_not_in_1 { +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) ; + 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) ; + @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 tests_nb_messages_in_2_not_in_1 +{ + note( 'Entering tests_stats_across_folders()' ) ; + is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ; + + my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ; + is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ; + + $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ; + $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ; + + is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ; + + $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ; + is( 1, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: one message in_2_not_in_1 => 1' ) ; + + $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ; + is( 2, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: two messages in_2_not_in_1 => 2' ) ; + + note( 'Leaving tests_stats_across_folders()' ) ; + return ; +} + +sub nb_messages_in_2_not_in_1 +{ + my $mysync = shift ; + if ( not defined $mysync ) { return ; } + + $mysync->{ nb_messages_in_2_not_in_1 } = scalar( + list_keys_in_2_not_in_1( + $mysync->{ h1_folders_of_md5 }, + $mysync->{ h2_folders_of_md5 } ) ) ; + + return $mysync->{ nb_messages_in_2_not_in_1 } ; } -sub tests_match { - note( 'Entering tests_match()' ) ; +sub nb_messages_in_1_not_in_2 +{ + my $mysync = shift ; + if ( not defined $mysync ) { return ; } - # undef serie - is( undef, match( ), 'match: no args => undef' ) ; - is( undef, match( 'lalala' ), 'match: one args => undef' ) ; + $mysync->{ nb_messages_in_1_not_in_2 } = scalar( + list_keys_in_2_not_in_1( + $mysync->{ h2_folders_of_md5 }, + $mysync->{ h1_folders_of_md5 } ) ) ; - # 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' ) ; + return $mysync->{ nb_messages_in_1_not_in_2 } ; +} - 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' ) ; +sub comment_on_final_diff_in_1_not_in_2 +{ + my $mysync = shift ; + + if ( not defined $mysync + or $mysync->{ justfolders } + or $mysync->{ useuid } + ) + { + return ; + } + + my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ; + my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; + $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ; + $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ; + + if ( 0 == $nb_identified_h1_messages ) { return ; } + + # Calculate if not yet done + if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } ) + { + nb_messages_in_1_not_in_2( $mysync ) ; + } + + + if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } ) + { + myprint( "The sync looks good, all $nb_identified_h1_messages identified messages in host1 are on host2.\n" ) ; + } + else + { + myprint( "The sync is not finished, there are $mysync->{ nb_messages_in_1_not_in_2 } identified messages in host1 that are not on host2.\n" ) ; + } + + if ( 1 <= $mysync->{ h1_nb_msg_noheader } ) + { + myprint( "There are $mysync->{ h1_nb_msg_noheader } unidentified messages (usually Sent or Draft messages). To sync them add option --addheader\n" ) ; + } + + return ; +} + +sub comment_on_final_diff_in_2_not_in_1 +{ + my $mysync = shift ; + + if ( not defined $mysync + or $mysync->{ justfolders } + or $mysync->{ useuid } + ) + { + return ; + } + + my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; + # Calculate if not yet done + if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } ) + { + nb_messages_in_2_not_in_1( $mysync ) ; + } + + if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } ) + { + myprint( "The sync is strict, all $nb_identified_h2_messages identified messages in host2 are on host1.\n" ) ; + } + else + { + myprint( "The sync is not strict, there are ", + $mysync->{ nb_messages_in_2_not_in_1 }, + " messages in host2 that are not on host1.", + " Use --delete2 to delete them and have a strict sync.\n" ) ; + } + return ; +} + + +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( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ; + is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ; + is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ; + is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ; + is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ; + is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ; + is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ; + is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ; + + is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ; + is( 's/foo/bar/g', match( 's/foo/bar/g', 's/foo/bar/g' ), 'match: s/foo/bar/g =~ s/foo/bar/g => s/foo/bar/g' ) ; + + + 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/ => 0' ) ; + + is( 'LALALA', 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( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ; is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ; note( 'Leaving tests_match()' ) ; @@ -10198,15 +12866,16 @@ sub tests_match { return ; } -sub match { +sub match +{ my( $var, $regex ) = @ARG ; # undef cases if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; } # normal cases - if ( eval { $var =~ $regex } ) { - return 1 ; + if ( eval { $var =~ qr{$regex} } ) { + return $var ; }elsif ( $EVAL_ERROR ) { myprint( "Fatal regex $regex\n" ) ; return ; @@ -10217,7 +12886,8 @@ sub match { } -sub tests_notmatch { +sub tests_notmatch +{ note( 'Entering tests_notmatch()' ) ; # undef serie @@ -10232,7 +12902,7 @@ sub tests_notmatch { # 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' ) ; @@ -10251,7 +12921,8 @@ sub tests_notmatch { return ; } -sub notmatch { +sub notmatch +{ my( $var, $regex ) = @ARG ; # undef cases @@ -10270,30 +12941,32 @@ sub notmatch { } -sub delete_folders_in_2_not_in_1 { +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" ) ; + 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" ) ; + 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} ) ; + $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ; + $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ; if ( $res ) { - myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ; + myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ; }else{ - myprint( "Deleting $folder failed", "\n" ) ; + myprint( "Deleting $folder failed", "\n" ) ; } } return ; } -sub delete_folder { +sub delete_folder +{ my ( $mysync, $imap, $folder, $Side ) = @_ ; if ( ! $mysync ) { return ; } if ( ! $imap ) { return ; } @@ -10306,15 +12979,16 @@ sub delete_folder { $res = $imap->delete( $folder ) ; } if ( $res ) { - myprint( "$Side deleted $folder", $mysync->{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 { +sub delete1emptyfolders +{ my $mysync = shift ; if ( ! $mysync ) { return ; } # abort if no parameter if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off @@ -10327,30 +13001,30 @@ sub delete1emptyfolders { 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" ) ; + myprint( "Host1: folder $folder has subfolders\n" ) ; $folders_kept{ $folder }++ ; next ; } - my $nb_messages_select = examine_folder_and_count( $imap, $folder, 'Host1' ) ; + my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ; if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder } my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ; if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) { - myprint( "Host1 folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; + myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } if ( 0 != $nb_messages_select + $nb_messages_search ) { - myprint( "Host1 folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; + myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE" if ( uc $folder eq 'INBOX' ) { - myprint( "Host1 Not deleting $folder\n" ) ; + myprint( "Host1: Not deleting $folder\n" ) ; $folders_kept{ $folder }++ ; next ; } - myprint( "Host1 deleting empty folder $folder\n" ) ; + 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... @@ -10366,7 +13040,8 @@ sub delete1emptyfolders { return ; } -sub remove_deleted_folders_from_wanted_list { +sub remove_deleted_folders_from_wanted_list +{ my ( $mysync, %folders_kept ) = @ARG ; my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ; @@ -10380,11 +13055,13 @@ sub remove_deleted_folders_from_wanted_list { return ; } -sub examine_folder_and_count { - my ( $imap, $folder, $Side ) = @_ ; + +sub examine_folder_and_count +{ + my ( $mysync, $imap, $folder, $Side ) = @_ ; $Side ||= 'HostX' ; - if ( ! examine_folder( $imap, $folder, $Side ) ) { + if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) { return ; } my $nb_messages_select = count_from_select( $imap->History ) ; @@ -10392,7 +13069,8 @@ sub examine_folder_and_count { } -sub tests_delete1emptyfolders { +sub tests_delete1emptyfolders +{ note( 'Entering tests_delete1emptyfolders()' ) ; @@ -10403,7 +13081,7 @@ sub tests_delete1emptyfolders { $syncT->{imap1} = $imapT ; is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ; - require Test::MockObject ; + require_ok( "Test::MockObject" ) ; $imapT = Test::MockObject->new( ) ; $syncT->{imap1} = $imapT ; @@ -10498,7 +13176,8 @@ sub tests_delete1emptyfolders { return ; } -sub tests_delete1emptyfolders_unit { +sub tests_delete1emptyfolders_unit +{ note( 'Entering tests_delete1emptyfolders_unit()' ) ; my $syncT = shift ; @@ -10519,20 +13198,20 @@ sub tests_delete1emptyfolders_unit { return ; } -sub extract_header { +sub extract_header +{ my $string = shift ; my ( $header ) = split /\n\n/x, $string ; if ( ! $header ) { return( q{} ) ; } - #myprint( "[$header]\n" ) ; + #myprint( "[$header]\n" ) ; return( $header ) ; } -sub tests_extract_header { +sub tests_extract_header +{ note( 'Entering tests_extract_header()' ) ; - - my $h = <<'EOM'; Message-Id: <20100428101817.A66CB162474E@plume.est.belle> Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST) @@ -10566,19 +13245,19 @@ sub decompose_header{ my ($key, $val ) ; my @line = split /\n|\r\n/x, $string ; foreach my $line ( @line ) { - #myprint( "DDD $line\n" ) ; + #myprint( "DDD $line\n" ) ; # End of header last if ( $line =~ m{^$}xo ) ; # Key: value if ( $line =~ m/(^[^:]+):\s(.*)/xo ) { $key = $1 ; $val = $2 ; - $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ; + $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ; push @{ $header->{ $key } }, $val ; # blanc and value => value from previous line continues }elsif( $line =~ m/^(\s+)(.*)/xo ) { $val = $2 ; - $debugdev and myprint( "DDD V [$val]\n" ) ; + $debugdev and myprint( "DDD V [$val]\n" ) ; @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ; # dirty line? }else{ @@ -10700,7 +13379,8 @@ EOH return ; } -sub tests_epoch { +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' ) ; @@ -10714,7 +13394,7 @@ sub tests_epoch { 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' ) ; - + is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; @@ -10722,7 +13402,8 @@ sub tests_epoch { return ; } -sub epoch { +sub epoch +{ # incoming format: # internal date 24-Aug-2010 16:00:00 +0200 @@ -10736,10 +13417,10 @@ sub epoch { my $time ; if ( $d =~ m{(\d{1,2})-([A-Z][a-z]{2})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-))(\d{2})(\d{2})}xo ) { - #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ; + #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ; ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ; - #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ; + #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ; $sign = +1 if ( '+' eq $sign ) ; $sign = $MINUS_ONE if ( '-' eq $sign ) ; @@ -10756,7 +13437,8 @@ sub epoch { return( $time ) ; } -sub tests_add_header { +sub tests_add_header +{ note( 'Entering tests_add_header()' ) ; ok( 'Message-Id: ' eq add_header(), 'add_header no arg' ) ; @@ -10766,7 +13448,8 @@ sub tests_add_header { return ; } -sub add_header { +sub add_header +{ my $header_uid = shift || 'mistake' ; my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ; return( $header_Message_Id ) ; @@ -10775,7 +13458,8 @@ sub add_header { -sub tests_max_line_length { +sub tests_max_line_length +{ note( 'Entering tests_max_line_length()' ) ; ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ; @@ -10797,7 +13481,8 @@ sub tests_max_line_length { return ; } -sub max_line_length { +sub max_line_length +{ my $string = shift ; my $max = 0 ; @@ -10808,7 +13493,8 @@ sub max_line_length { } -sub tests_setlogfile { +sub tests_setlogfile +{ note( 'Entering tests_setlogfile()' ) ; my $mysync = {} ; @@ -10860,7 +13546,7 @@ sub tests_setlogfile { is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ; - + $mysync = { timestart => 2, user1 => 'user1', @@ -10894,37 +13580,39 @@ sub tests_setlogfile { "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ; - + } ; note( 'Leaving tests_setlogfile()' ) ; return ; } -sub setlogfile { +sub setlogfile +{ my( $mysync ) = shift ; - + # When aborting another process the log file name finishes with "_abort.txt" my $abort_suffix = ( $mysync->{abort} ) ? '_abort' : q{} ; # When acting as a proxy the log file name finishes with "_remote.txt" # proxy mode is not done yet my $remote_suffix = ( $mysync->{remote} ) ? '_remote' : q{} ; - + my $suffix = ( filter_forbidden_characters( move_slash( $mysync->{user1} ) ) || q{} ) - . '_' - . ( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} ) + . '_' + . ( filter_forbidden_characters( move_slash( $mysync->{user2} ) ) || q{} ) . $remote_suffix . $abort_suffix ; $mysync->{logdir} = defined $mysync->{logdir} ? $mysync->{logdir} : $DEFAULT_LOGDIR ; - - $mysync->{logfile} = defined $mysync->{logfile} - ? "$mysync->{logdir}/$mysync->{logfile}" + + $mysync->{logfile} = defined $mysync->{logfile} + ? "$mysync->{logdir}/$mysync->{logfile}" : logfile( $mysync->{timestart}, $suffix, $mysync->{logdir} ) ; return( $mysync->{logfile} ) ; } -sub tests_logfile { +sub tests_logfile +{ note( 'Entering tests_logfile()' ) ; SKIP: { @@ -10942,10 +13630,10 @@ sub tests_logfile { 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' ) ; - is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ), + is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ), 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ; - is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ), + is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ), 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ; @@ -10959,7 +13647,8 @@ sub tests_logfile { } -sub logfile { +sub logfile +{ my ( $time, $suffix, $dir ) = @_ ; $time ||= 0 ; @@ -10979,7 +13668,8 @@ sub logfile { -sub tests_move_slash { +sub tests_move_slash +{ note( 'Entering tests_move_slash()' ) ; is( undef, move_slash( ), 'move_slash: no parameters => undef' ) ; @@ -10989,7 +13679,8 @@ sub tests_move_slash { return ; } -sub move_slash { +sub move_slash +{ my $string = shift ; if ( ! defined $string ) { return ; } @@ -11002,21 +13693,22 @@ sub move_slash { -sub tests_million_folders_baby_2 { +sub tests_million_folders_baby_2 +{ note( 'Entering tests_million_folders_baby_2()' ) ; my %long ; @long{ 1 .. 900_000 } = (1) x 900_000 ; - #myprint( %long, "\n" ) ; + #myprint( %long, "\n" ) ; my $pasglop = 0 ; foreach my $elem ( 1 .. 900_000 ) { - #$debug and myprint( "$elem " ) ; + #$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 ; @@ -11024,7 +13716,8 @@ sub tests_million_folders_baby_2 { -sub tests_always_fail { +sub tests_always_fail +{ note( 'Entering tests_always_fail()' ) ; is( 0, 1, 'always_fail: 0 is 1' ) ; @@ -11033,31 +13726,87 @@ sub tests_always_fail { return ; } -sub logfileprepa { - my $logfile = shift ; - my $dirname = dirname( $logfile ) ; - do_valid_directory( $dirname ) || return( 0 ) ; - return( 1 ) ; +sub tests_logfileprepa +{ + note( 'Entering tests_logfileprepa()' ) ; + + is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ; + my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ; + is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ; + + note( 'Leaving tests_logfileprepa()' ) ; + return ; } -sub teelaunch { +sub logfileprepa +{ + my $logfile = shift ; + + if ( ! defined( $logfile ) ) + { + return ; + }else + { + #myprint( "[$logfile]\n" ) ; + my $dirname = dirname( $logfile ) ; + do_valid_directory( $dirname ) || return( 0 ) ; + return( 1 ) ; + } +} + + +sub tests_teelaunch +{ + note( 'Entering tests_teelaunch()' ) ; + + is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ; + my $mysync = {} ; + is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ; + $mysync->{logfile} = '' ; + is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ; + $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ; + isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ; + is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ; + is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ; + is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ; + is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\nHoo\n' ) ; + + note( 'Leaving tests_teelaunch()' ) ; + return ; +} + +sub teelaunch +{ my $mysync = shift ; + + if ( ! defined( $mysync ) ) + { + return ; + } + my $logfile = $mysync->{logfile} ; + + if ( ! $logfile ) + { + return ; + } + logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ; + # This is a log file opened during the whole sync + ## no critic (InputOutput::RequireBriefOpen) 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 $tee ; } -sub getpwuid_any_os { +sub getpwuid_any_os +{ my $uid = shift ; return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system @@ -11066,13 +13815,14 @@ sub getpwuid_any_os { } -sub simulong { +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 ) ; + my $last_count = $division * $max_seconds ; + foreach my $i ( 1 .. ( $last_count ) ) { + myprint( "Are you still here $i/$last_count\n" ) ; + #myprint( "Are you still here $i/$last_count\n" . ( "Ah" x 40 . "\n") x 4000 ) ; sleep( 1 / $division ) ; } @@ -11081,20 +13831,22 @@ sub simulong { -sub printenv { +sub printenv +{ myprint( "Environment variables listing:\n", - ( map { "$_ => $ENV{$_}\n" } sort keys %ENV), - "Environment variables listing end\n" ) ; + ( map { "$_ => $ENV{$_}\n" } sort keys %ENV), + "Environment variables listing end\n" ) ; return ; } -sub testsexit { +sub testsexit +{ my $mysync = shift ; if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) { return ; } my $test_builder = Test::More->builder ; - tests( $mysync ) ; + tests( $mysync ) ; testsdebug( $mysync ) ; testunitsession( $mysync ) ; @@ -11110,18 +13862,32 @@ sub testsexit { "List of failed tests:\n", $tests_failed ) ; exit $EXIT_TESTS_FAILED ; } - exit ; - #return ; + + cleanup_mess_from_tests( ) ; + # Cover is larger with --tests --testslive + if ( ! $mysync->{ testslive } ) + { + exit ; + } + # $eeee ; + return ; } -sub after_get_options { +sub cleanup_mess_from_tests +{ + undef @pipemess ; + return ; +} + +sub after_get_options +{ my $mysync = shift ; my $numopt = shift ; # exit with --help option or no option at all - $debug and myprint( "numopt:$numopt\n" ) ; - + $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ; + if ( $help or not $numopt ) { myprint( usage( $mysync ) ) ; exit ; @@ -11130,21 +13896,82 @@ sub after_get_options { return ; } -sub easyany { +sub tests_remove_edging_blanks +{ + note( 'Entering tests_remove_edging_blanks()' ) ; + + is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ; + is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ; + is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ; + + note( 'Leaving tests_remove_edging_blanks()' ) ; + return ; +} + + + +sub remove_edging_blanks +{ + my $string = shift ; + if ( ! defined $string ) + { + return ; + } + $string =~ s,^ +| +$,,g ; + return $string ; +} + + +sub tests_sanitize +{ + note( 'Entering tests_remove_edging_blanks()' ) ; + + is( undef, sanitize( ), 'sanitize: no args => undef' ) ; + my $mysync = {} ; + + $mysync->{ host1 } = ' example.com ' ; + $mysync->{ user1 } = ' to to ' ; + $mysync->{ password1 } = ' sex is good! ' ; + is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ; + is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ; + is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ; + is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ; + note( 'Leaving tests_remove_edging_blanks()' ) ; + return ; +} + + +sub sanitize +{ + my $mysync = shift ; + if ( ! defined $mysync ) + { + return ; + } + + foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) ) + { + $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ; + } + return ; +} + +sub easyany +{ my $mysync = shift ; # Gmail if ( $mysync->{gmail1} and $mysync->{gmail2} ) { - $debug and myprint( "gmail1 gmail2\n") ; + $mysync->{ debug } and myprint( "gmail1 gmail2\n") ; gmail12( $mysync ) ; return ; } if ( $mysync->{gmail1} ) { - $debug and myprint( "gmail1\n" ) ; + $mysync->{ debug } and myprint( "gmail1\n" ) ; gmail1( $mysync ) ; } if ( $mysync->{gmail2} ) { - $debug and myprint( "gmail2\n" ) ; + $mysync->{ debug } and myprint( "gmail2\n" ) ; gmail2( $mysync ) ; } # Office 365 @@ -11156,6 +13983,16 @@ sub easyany { office2( $mysync ) ; } + # Exchange + if ( $mysync->{exchange1} ) { + exchange1( $mysync ) ; + } + + if ( $mysync->{exchange2} ) { + exchange2( $mysync ) ; + } + + # Domino if ( $mysync->{domino1} ) { domino1( $mysync ) ; @@ -11165,135 +14002,185 @@ sub easyany { domino2( $mysync ) ; } - return ; } # From https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt -sub gmail12 { +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->{maxbytespersecond} ||= 20_000 ; # should be 10_000 when computed from Gmail documentation $mysync->{maxbytesafter} ||= 1_000_000_000 ; - $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; - $mysync->{maxsleep} = $MAX_SLEEP ; - + $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; + $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; + $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ; + $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ; + $mysync->{ reynclabels } = ( defined $mysync->{ reynclabels } ) ? $mysync->{ reynclabels } : 1 ; push @exclude, '\[Gmail\]$' ; + push @folderlast, '[Gmail]/All Mail' ; return ; } -sub gmail1 { + +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 ; + $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; + $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; $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' ; + push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; + push @folderlast, '[Gmail]/All Mail' ; return ; } -sub gmail2 { +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->{ maxsize } ||= 25_000_000 ; $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; - $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; - $expunge1 = ( defined $expunge1 ) ? $expunge1 : 1 ; + #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; + $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ; $mysync->{addheader} = ( defined $mysync->{addheader} ) ? $mysync->{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" ; + $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; + + $mysync->{maxsize} = ( defined $mysync->{maxsize} ) ? $mysync->{maxsize} : $GMAIL_MAXSIZE ; + + if ( ! $mysync->{noexclude} ) { + push @exclude, '\[Gmail\]$' ; + } + push @useheader, 'Message-Id' ; + push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; + + # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced + # by the two more specific following regexes, + # they remove just the beginning and trailing blanks, not all. + push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ; + push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ; + # + push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this + push @folderlast, '[Gmail]/All Mail' ; return ; } # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt -sub office1 { +sub office1 +{ # Office 365 at host1 my $mysync = shift ; - $debug and myprint( "office1 configuration\n" ) ; + output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ; + output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ; $mysync->{host1} ||= 'outlook.office365.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; + if ( ! $mysync->{noexclude} ) { + push @exclude, '^Files$' ; + } 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 office2 +{ + # Office 365 at host2 + my $mysync = shift ; + output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ; + output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ; + output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ; + output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ; + $mysync->{host2} ||= 'outlook.office365.com' ; + $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; + $mysync->{ maxsize } ||= 45_000_000 ; + $mysync->{maxmessagespersecond} ||= 4 ; + #push @regexflag, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10 + $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; + # I dislike double negation but here is one + if ( ! $mysync->{noregexmess} ) + { + push @regexmess, 's,(.{10500}),$1\r\n,g' ; + } + # and another... + if ( ! $mysync->{nof1f2} ) + { + push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ; + } + return ; } -sub exchange1 { - # Exchange 2010/2013 at host1 - - # Well nothing to do so far - return ; +sub exchange1 +{ + # Exchange 2010/2013 at host1 + my $mysync = shift ; + output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ; + # 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 exchange2 +{ + # Exchange 2010/2013 at host2 + my $mysync = shift ; + output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ; + output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ; + output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ; + $mysync->{ maxsize } ||= 10_000_000 ; + $mysync->{maxmessagespersecond} ||= 4 ; + $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; + # I dislike double negation but here are two + if ( ! $mysync->{noregexflag} ) { + push @regexflag, 's/\\\\Flagged//g' ; + } + if ( ! $mysync->{noregexmess} ) { + push @regexmess, 's,(.{10500}),$1\r\n,g' ; + } + return ; } -sub domino1 { +sub domino1 +{ # Domino at host1 my $mysync = shift ; - $sep1 = q{\\} ; + $mysync->{ sep1 } = q{\\} ; $prefix1 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; return ; } -sub domino2 { +sub domino2 +{ # Domino at host1 my $mysync = shift ; - $sep2 = q{\\} ; + $mysync->{ sep2 } = q{\\} ; $prefix2 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; - push @regextrans2, 's,^Inbox\\\\(.*),$1,i' ; + push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ; return ; } -sub tests_resolv { +sub tests_resolv +{ note( 'Entering tests_resolv()' ) ; - + # is( , resolv( ), 'resolv: => ' ) ; is( undef, resolv( ), 'resolv: no args => undef' ) ; is( undef, resolv( '' ), 'resolv: empty string => undef' ) ; @@ -11301,7 +14188,7 @@ sub tests_resolv { 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' ) ; @@ -11311,64 +14198,67 @@ sub tests_resolv { # 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 { +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 { +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" ) ; + my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; + if ( $err_getaddrinfo ) { + myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\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" ) ; + my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ; + if ( $err_getnameinfo ) { + myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ; return ; } - $debug and myprint( "$host => $ipaddr\n" ) ; + $sync->{ 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" ) ; + my $reverse ; + ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ; + $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ; } - + return $addr[0] ; } -sub tests_resolvrev { +sub tests_resolvrev +{ note( 'Entering tests_resolvrev()' ) ; - + # is( , resolvrev( ), 'resolvrev: => ' ) ; is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ; is( undef, resolvrev( '' ), 'resolvrev: empty string => undef' ) ; @@ -11376,7 +14266,7 @@ sub tests_resolvrev { 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' ) ; @@ -11386,28 +14276,30 @@ sub tests_resolvrev { # 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 { +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 { +sub resolvrev_with_getaddrinfo +{ my $host = shift @ARG ; - + if ( ! $host ) { return ; } my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; @@ -11423,16 +14315,17 @@ sub resolvrev_with_getaddrinfo { myprint( "Cannot getnameinfo of $host: $err\n" ) ; return ; } - $debug and myprint( "$host => $reverse\n" ) ; + $sync->{ debug } and myprint( "$host => $reverse\n" ) ; push @name, $reverse ; } - + return $name[0] ; } -sub tests_imapsping { +sub tests_imapsping +{ note( 'Entering tests_imapsping()' ) ; is( undef, imapsping( ), 'imapsping: no args => undef' ) ; @@ -11443,12 +14336,14 @@ sub tests_imapsping { return ; } -sub imapsping { +sub imapsping +{ my $host = shift ; return tcpping( $host, $IMAP_SSL_PORT ) ; } -sub tests_tcpping { +sub tests_tcpping +{ note( 'Entering tests_tcpping()' ) ; is( undef, tcpping( ), 'tcpping: no args => undef' ) ; @@ -11472,7 +14367,8 @@ sub tests_tcpping { return ; } -sub tcpping { +sub tcpping +{ if ( 2 != scalar( @ARG ) ) { return ; } @@ -11489,8 +14385,8 @@ sub tcpping { $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" ) ; + my $rtt_approx = sprintf( "%.3f", $rtt ) ; + $sync->{ 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 ; @@ -11499,7 +14395,8 @@ sub tcpping { } } -sub tests_sslcheck { +sub tests_sslcheck +{ note( 'Entering tests_sslcheck()' ) ; my $mysync ; @@ -11537,7 +14434,7 @@ sub tests_sslcheck { host1 => 'imapsync.lamiral.info', host2 => 'test2.lamiral.info', } ; - + is( 2, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info => 2' ) ; $mysync = { @@ -11546,21 +14443,22 @@ sub tests_sslcheck { host2 => 'test2.lamiral.info', tls1 => 1, } ; - + is( 1, sslcheck( $mysync ), 'sslcheck: imapsync.lamiral.info + test2.lamiral.info + tls1 => 1' ) ; - + note( 'Leaving tests_sslcheck()' ) ; return ; } -sub sslcheck { +sub sslcheck +{ my $mysync = shift ; if ( ! $mysync->{sslcheck} ) { return ; } my $nb_on = 0 ; - $debug and myprint( "sslcheck\n" ) ; + $mysync->{ debug } and myprint( "sslcheck\n" ) ; if ( ( ! defined $mysync->{port1} ) and @@ -11602,30 +14500,33 @@ sub sslcheck { } -sub testslive { +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' ; + $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 { +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' ; + $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 { +sub tests_backslash_caret +{ note( 'Entering tests_backslash_caret()' ) ; is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ; @@ -11643,56 +14544,62 @@ sub tests_backslash_caret { return ; } -sub backslash_caret { +sub backslash_caret +{ my $string = shift ; - + $string =~ s{\\ $ }{^}gxms ; return $string ; } -sub tests_split_around_equal { +sub tests_split_around_equal +{ note( 'Entering tests_split_around_equal()' ) ; + is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ; is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ; - + note( 'Leaving tests_split_around_equal()' ) ; return ; } -sub split_around_equal { +sub split_around_equal +{ if ( ! @ARG ) { return ; } ; return map { split /=/mxs, $_ } @ARG ; - + } -sub tests_sig_install { +sub tests_sig_install +{ note( 'Entering tests_sig_install()' ) ; - my $mysync ; + + my $mysync ; is( undef, sig_install( ), 'sig_install: no args => undef' ) ; is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ; $mysync = { } ; is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ; - + SKIP: { Readonly my $SKIP_15 => 15 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; - + $mysync->{ debugsig } = 1 ; - # Assign USR1 to call sub tototo + # Assign USR1 to call sub tototo # Surely a better value than undef should be returned when doing real signal stuff is( undef, sig_install( $mysync, \&tototo, 'USR1' ), 'sig_install: USR1 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ; is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ; - - # Assign USR2 to call sub tototo + + # Assign USR2 to call sub tototo is( undef, sig_install( $mysync, \&tototo, 'USR2' ), 'sig_install: USR2 tototo' ) ; is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ; @@ -11701,87 +14608,104 @@ sub tests_sig_install { is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ; - + local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ; - - # Assign USR1 + USR2 to call sub tototo + + # Assign USR1 + USR2 to call sub tototo is( undef, sig_install( $mysync, \&tototo, 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ; is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ; - + is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ; } - - note( 'Leaving tests_sig_install()' ) ; + + note( 'Leaving tests_sig_install()' ) ; return ; } - -sub sig_install { +# +sub sig_install +{ my $mysync = shift ; if ( ! $mysync ) { return ; } my $mysub = shift ; if ( ! $mysub ) { return ; } - + my @signals = @ARG ; - - $mysync->{ debugsig } and myprint( "In sig_install with $mysync and $mysub\n" ) ; - + + $mysync->{ debugsig } and myprint( "In sig_install with $mysync and $mysub\n" ) ; + my $subsignal = sub { my $signame = shift ; - $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysync\n" ) ; + $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysync\n" ) ; &$mysub( $mysync, $signame ) ; } ; foreach my $signal ( @signals ) { $mysync->{ debugsig } and myprint( "Installing signal $signal for $subsignal\n") ; output( $mysync, "kill -$signal $PROCESS_ID # special behavior\n" ) ; + ## no critic (RequireLocalizedPunctuationVars) $SIG{ $signal } = $subsignal ; } return ; } -sub tototo { +sub tototo +{ my $mysync = shift ; myprint("In tototo with @ARG\n" ) ; $mysync->{ tototo_calls } += 1 ; return ; } -sub tests_toggle_sleep { +sub mygetppid +{ + if ( 'MSWin32' eq $OSNAME ) { + return( 'unknown under MSWin32 (too complicated)' ) ; + } else { + # Unix + return( getppid( ) ) ; + } +} + + + +sub tests_toggle_sleep +{ note( 'Entering tests_toggle_sleep()' ) ; + is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ; my $mysync ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ; $mysync = { } ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ; - + $mysync->{maxsleep} = 3 ; is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ; - + is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; - + SKIP: { Readonly my $SKIP_9 => 9 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; - + $mysync->{ debugsig } = 1 ; - # Assign USR1 to call sub toggle_sleep + # Assign USR1 to call sub toggle_sleep is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ; - + $mysync->{maxsleep} = 4 ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ; is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ; @@ -11795,28 +14719,30 @@ sub tests_toggle_sleep { is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ; is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ; } - + note( 'Leaving tests_toggle_sleep()' ) ; return ; } -sub toggle_sleep { +sub toggle_sleep +{ my $mysync = shift ; - + myprint("In toggle_sleep with @ARG\n" ) ; if ( !defined( $mysync ) ) { return ; } if ( !defined( $mysync->{maxsleep} ) ) { return ; } - + $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ; myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ; return $mysync->{maxsleep} ; } -sub mypod2usage { +sub mypod2usage +{ my $fh_pod2usage = shift ; - + pod2usage( -exitval => 'NOEXIT', -noperldoc => 1, @@ -11830,21 +14756,22 @@ sub mypod2usage { return ; } -sub usage { +sub usage +{ my $mysync = shift ; - + if ( ! defined $mysync ) { return ; } - + my $usage = q{} ; my $usage_from_pod ; my $usage_footer = usage_footer( $mysync ) ; # pod2usage writes on a filehandle only and I want a variable - open my $fh_pod2usage, ">", \$usage_from_pod + open my $fh_pod2usage, ">", \$usage_from_pod or do { warn $OS_ERROR ; return ; } ; mypod2usage( $fh_pod2usage ) ; close $fh_pod2usage ; - + if ( 'MSWin32' eq $OSNAME ) { $usage_from_pod = backslash_caret( $usage_from_pod ) ; } @@ -11853,7 +14780,10 @@ sub usage { return( $usage ) ; } -sub tests_usage { +sub tests_usage +{ + note( 'Entering tests_usage()' ) ; + my $usage ; like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ; myprint( $usage ) ; @@ -11862,13 +14792,16 @@ sub tests_usage { like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ; is( undef, usage( ), 'usage: no args => undef' ) ; + + note( 'Leaving tests_usage()' ) ; return ; } -sub usage_footer { +sub usage_footer +{ my $mysync = shift ; - + my $footer = q{} ; my $localhost_info = localhost_info( $mysync ) ; @@ -11876,11 +14809,11 @@ sub usage_footer { my $homepage = homepage( ) ; my $imapsync_release = $STR_use_releasecheck ; - + if ( $mysync->{releasecheck} ) { $imapsync_release = check_last_release( ) ; } - + $footer = qq{$localhost_info $rcs $imapsync_release @@ -11891,7 +14824,8 @@ $homepage -sub usage_complete { +sub usage_complete +{ # Unused, I guess this function could be deleted my $usage = <<'EOF' ; --skipheader reg : Don't take into account header keyword @@ -11909,14 +14843,15 @@ sub usage_complete { --reconnectretry2 int : same as --reconnectretry1 but for host2 --split1 int : split the requests in several parts on host1. int is the number of messages handled per request. - default is like --split1 500. + default is like --split1 100. --split2 int : same thing on host2. --nofixInboxINBOX : Don't fix Inbox INBOX mapping. EOF return( $usage ) ; } -sub myGetOptions { +sub myGetOptions +{ # Started as a copy of Luke Ross Getopt::Long::CGI # https://metacpan.org/release/Getopt-Long-CGI @@ -11926,7 +14861,7 @@ sub myGetOptions { my $mysync = shift @ARG ; my $arguments_ref = shift @ARG ; my %options = @ARG ; - + my $mycgi = $mysync->{cgi} ; if ( not under_cgi_context() ) { @@ -11936,7 +14871,7 @@ sub myGetOptions { } # We must be in CGI context now - if ( !defined( $mycgi ) ) { return ; } + if ( ! defined( $mycgi ) ) { return ; } my $badthings = 0 ; foreach my $key ( sort keys %options ) { @@ -11986,20 +14921,38 @@ sub myGetOptions { } if ( ( $3 || q{} ) eq '@' ) { @{ ${$val} } = @values ; + my @option = map +( "--$name", "$_" ), @values ; + push @{ $mysync->{ cmdcgi } }, @option ; } elsif ( ref( $val ) eq 'ARRAY' ) { @{$val} = @values ; } - else { - ${$val} = $values[0] ; + elsif ( my $value = $values[0] ) + { + ${$val} = $value ; + push @{ $mysync->{ cmdcgi } }, "--$name", $value ; + } + else + { + } } } - else { + else + { # Checkbox # Considers only --name # Should consider also --no-name and --noname - ${$val} = $mycgi->param( $name ) ? 1 : undef ; + my $value = $mycgi->param( $name ) ; + if ( $value ) + { + ${$val} = 1 ; + push @{ $mysync->{ cmdcgi } }, "--$name" ; + } + else + { + ${$val} = undef ; + } } } if ( $badthings ) { @@ -12013,7 +14966,8 @@ sub myGetOptions { my @blabla ; # just used to check get_options_cgi() with an array -sub tests_get_options_cgi_context { +sub tests_get_options_cgi_context +{ note( 'Entering tests_get_options_cgi()' ) ; # Temporary, have to think harder about testing CGI context in command line --tests @@ -12051,7 +15005,7 @@ sub tests_get_options_cgi_context { is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ; is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ; #local $ENV{'QUERY_STRING'} = undef ; - + # Testing @ $mysync->{cgi} = CGI->new( 'blabla=fd1' ) ; get_options( $mysync ) ; @@ -12063,15 +15017,15 @@ sub tests_get_options_cgi_context { # Testing s@ as ref $mysync->{cgi} = CGI->new( 'folder=fd1' ) ; get_options( $mysync ) ; - is_deeply( [ 'fd1' ], $mysync->{folder}, 'get_options cgi context: $mysync->{folder} => fd1' ) ; + is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ; $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ; get_options( $mysync ) ; - is_deeply( [ 'fd1', 'fd2' ], $mysync->{folder}, 'get_options cgi context: $mysync->{folder} => fd1, fd2' ) ; + is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ; # Testing % $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ; get_options( $mysync ) ; - + is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' }, $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ; @@ -12083,29 +15037,36 @@ sub tests_get_options_cgi_context { $mysync->{cgi} = CGI->new( 'host1=example.com' ) ; get_options( $mysync ) ; is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ; - + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync->{cgi} = CGI->new( 'simulong=' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ; - + $mysync->{cgi} = CGI->new( 'simulong' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ; - + $mysync->{cgi} = CGI->new( 'simulong=4' ) ; get_options( $mysync ) ; is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ; - is( undef, $mysync->{folder}, 'get_options cgi context: --simulong=4 => $mysync->{folder} => undef' ) ; + is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; - + + $mysync ={} ; + $mysync->{cgi} = CGI->new( 'justfoldersizes=on' ) ; + get_options( $mysync ) ; + is( 1, $mysync->{ justfoldersizes }, 'get_options cgi context: --justfoldersizes=1 => justfoldersizes => 1' ) ; + myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; + note( 'Leaving tests_get_options_cgi_context()' ) ; return ; } -sub get_options_cgi { +sub get_options_cgi +{ # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET). my $mysync = shift @ARG ; $mysync->{cgi} || return ; @@ -12144,19 +15105,28 @@ sub get_options_cgi { 'domino2' => \$mysync->{domino2}, 'f1f2=s@' => \$mysync->{f1f2}, 'f1f2h=s%' => \$mysync->{f1f2h}, - 'folder=s@' => \$mysync->{folder}, + 'folder=s@' => \$mysync->{ folder }, 'blabla=s' => \@blabla, 'testslive!' => \$mysync->{testslive}, 'testslive6!' => \$mysync->{testslive6}, 'releasecheck!' => \$mysync->{releasecheck}, 'simulong=i' => \$mysync->{simulong}, + 'debugsleep=f' => \$mysync->{debugsleep}, + 'subfolder1=s' => \$mysync->{ subfolder1 }, + 'subfolder2=s' => \$mysync->{ subfolder2 }, + 'justfolders!' => \$mysync->{ justfolders }, + 'justfoldersizes!' => \$mysync->{ justfoldersizes }, + 'delete1!' => \$mysync->{ delete1 }, + 'delete2!' => \$mysync->{ delete2 }, + 'delete2duplicates!' => \$mysync->{ delete2duplicates }, + 'tail!' => \$mysync->{tail}, -# blabla and f1f2h=s% could be removed but -# tests_get_options_cgi() should be split before +# blabla and f1f2h=s% could be removed but +# tests_get_options_cgi() should be split before # with a sub tests_myGetOptions() ) ; - $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; + $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; if ( ! $opt_ret ) { return ; @@ -12164,23 +15134,24 @@ sub get_options_cgi { 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 +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" ) ; + output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ; return ; } $mysync->{f1f2h} = {} ; my $opt_ret = myGetOptions( - $mysync, - \@arguments, - 'debug!' => \$debug, + $mysync, + \@arguments, + 'debug!' => \$mysync->{ debug }, 'debuglist!' => \$debuglist, 'debugcontent!' => \$debugcontent, 'debugsleep=f' => \$mysync->{debugsleep}, @@ -12193,10 +15164,11 @@ sub get_options_cmd { 'debugfolders!' => \$mysync->{debugfolders}, 'debugssl=i' => \$mysync->{debugssl}, 'debugcgi!' => \$debugcgi, - 'debugenv' => \$mysync->{debugenv}, - 'debugsig' => \$mysync->{debugsig}, + 'debugenv!' => \$mysync->{debugenv}, + 'debugsig!' => \$mysync->{debugsig}, + 'debuglabels!' => \$mysync->{debuglabels}, 'simulong=i' => \$mysync->{simulong}, - 'abort' => \$mysync->{abort}, + 'abort' => \$mysync->{abort}, 'host1=s' => \$mysync->{host1}, 'host2=s' => \$mysync->{host2}, 'port1=i' => \$mysync->{port1}, @@ -12205,59 +15177,66 @@ sub get_options_cmd { '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}, + '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' => \$mysync->{password1}, 'password2=s' => \$mysync->{password2}, - 'passfile1=s' => \$passfile1, - 'passfile2=s' => \$passfile2, + 'passfile1=s' => \$mysync->{ passfile1 }, + 'passfile2=s' => \$mysync->{ passfile2 }, 'authmd5!' => \$authmd5, 'authmd51!' => \$authmd51, 'authmd52!' => \$authmd52, - 'sep1=s' => \$sep1, - 'sep2=s' => \$sep2, - 'folder=s@' => \$mysync->{folder}, + 'sep1=s' => \$mysync->{ sep1 }, + 'sep2=s' => \$mysync->{ sep2 }, + 'sanitize!' => \$mysync->{ sanitize }, + 'folder=s@' => \$mysync->{ folder }, 'folderrec=s' => \@folderrec, 'include=s' => \@include, 'exclude=s' => \@exclude, + 'noexclude' => \$mysync->{noexclude}, 'folderfirst=s' => \@folderfirst, 'folderlast=s' => \@folderlast, 'prefix1=s' => \$prefix1, 'prefix2=s' => \$prefix2, - 'subfolder2=s' => \$subfolder2, - 'fixslash2!' => \$fixslash2, + 'subfolder1=s' => \$mysync->{ subfolder1 }, + 'subfolder2=s' => \$mysync->{ subfolder2 }, + 'fixslash2!' => \$mysync->{ fixslash2 }, 'fixInboxINBOX!' => \$fixInboxINBOX, - 'regextrans2=s' => \@regextrans2, + 'regextrans2=s@' => \$mysync->{ regextrans2 }, 'mixfolders!' => \$mixfolders, 'skipemptyfolders!' => \$skipemptyfolders, 'regexmess=s' => \@regexmess, + 'noregexmess' => \$mysync->{noregexmess}, 'skipmess=s' => \@skipmess, 'pipemess=s' => \@pipemess, 'pipemesscheck!' => \$pipemesscheck, 'disarmreadreceipts!' => \$disarmreadreceipts, 'regexflag=s' => \@regexflag, + 'noregexflag' => \$mysync->{noregexflag}, 'filterflags!' => \$filterflags, 'flagscase!' => \$flagscase, 'syncflagsaftercopy!' => \$syncflagsaftercopy, - 'resyncflags!' => \$mysync->{resyncflags}, - 'delete|delete1!' => \$delete1, - 'delete2!' => \$delete2, - 'delete2duplicates!' => \$delete2duplicates, + 'resyncflags!' => \$mysync->{ resyncflags }, + 'synclabels!' => \$mysync->{ synclabels }, + 'resynclabels!' => \$mysync->{ resynclabels }, + 'delete|delete1!' => \$mysync->{ delete1 }, + 'delete2!' => \$mysync->{ delete2 }, + 'delete2duplicates!' => \$mysync->{ delete2duplicates }, 'delete2folders!' => \$delete2folders, 'delete2foldersonly=s' => \$delete2foldersonly, 'delete2foldersbutnot=s' => \$delete2foldersbutnot, 'syncinternaldates!' => \$syncinternaldates, 'idatefromheader!' => \$idatefromheader, 'syncacls!' => \$syncacls, - 'maxsize=i' => \$maxsize, + 'maxsize=i' => \$mysync->{ maxsize }, 'minsize=i' => \$minsize, 'maxage=i' => \$maxage, 'minage=i' => \$minage, @@ -12267,15 +15246,15 @@ sub get_options_cmd { 'foldersizes!' => \$foldersizes, 'foldersizesatend!' => \$foldersizesatend, 'dry!' => \$mysync->{dry}, - 'expunge1|expunge!' => \$expunge1, - 'expunge2!' => \$expunge2, - 'uidexpunge2!' => \$uidexpunge2, - 'subscribed!' => \$subscribed, + 'expunge1|expunge!' => \$mysync->{ expunge1 }, + 'expunge2!' => \$mysync->{ expunge2 }, + 'uidexpunge2!' => \$mysync->{ uidexpunge2 }, + 'subscribed' => \$subscribed, 'subscribe!' => \$subscribe, 'subscribeall|subscribe_all!' => \$subscribeall, 'justbanner!' => \$justbanner, - 'justfolders!'=> \$justfolders, - 'justfoldersizes!' => \$justfoldersizes, + 'justfolders!'=> \$mysync->{ justfolders }, + 'justfoldersizes!' => \$mysync->{ justfoldersizes }, 'fast!' => \$fast, 'version' => \$mysync->{version}, 'help' => \$help, @@ -12290,7 +15269,7 @@ sub get_options_cmd { 'allowsizemismatch!' => \$allowsizemismatch, 'fastio1!' => \$fastio1, 'fastio2!' => \$fastio2, - 'sslcheck!' => \$mysync->{sslcheck}, + 'sslcheck!' => \$mysync->{sslcheck}, 'ssl1!' => \$mysync->{ssl1}, 'ssl2!' => \$mysync->{ssl2}, 'ssl1_ssl_version=s' => \$mysync->{h1}->{sslargs}->{SSL_version}, @@ -12314,7 +15293,7 @@ sub get_options_cmd { 'reconnectretry2=i' => \$reconnectretry2, 'tests!' => \$mysync->{ tests }, 'testsdebug|tests_debug!' => \$mysync->{ testsdebug }, - 'testsunit=s@' => \$mysync->{testsunit}, + 'testsunit=s@' => \$mysync->{testsunit}, 'testslive!' => \$mysync->{testslive}, 'testslive6!' => \$mysync->{testslive6}, 'justlogin!' => \$mysync->{justlogin}, @@ -12332,11 +15311,11 @@ sub get_options_cmd { 'debugcache!' => \$debugcache, 'useuid!' => \$useuid, 'addheader!' => \$mysync->{addheader}, - 'exitwhenover=i' => \$exitwhenover, + 'exitwhenover=i' => \$mysync->{ exitwhenover }, 'checkselectable!' => \$mysync->{ checkselectable }, 'checkfoldersexist!' => \$mysync->{ checkfoldersexist }, 'checkmessageexists!' => \$checkmessageexists, - 'expungeaftereach!' => \$expungeaftereach, + 'expungeaftereach!' => \$mysync->{ expungeaftereach }, 'abletosearch!' => \$mysync->{abletosearch}, 'abletosearch1!' => \$mysync->{abletosearch1}, 'abletosearch2!' => \$mysync->{abletosearch2}, @@ -12349,11 +15328,12 @@ sub get_options_cmd { 'create_folder_old!' => \$create_folder_old, 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond}, 'maxbytespersecond=i' => \$mysync->{maxbytespersecond}, - 'maxbytesafter=i' => \$mysync->{maxbytesafter}, - 'maxsleep=f' => \$mysync->{maxsleep}, + 'maxbytesafter=i' => \$mysync->{maxbytesafter}, + 'maxsleep=f' => \$mysync->{maxsleep}, 'skipcrossduplicates!' => \$skipcrossduplicates, 'debugcrossduplicates!' => \$debugcrossduplicates, 'log!' => \$mysync->{log}, + 'tail!' => \$mysync->{tail}, 'logfile=s' => \$mysync->{logfile}, 'logdir=s' => \$mysync->{logdir}, 'errorsmax=i' => \$mysync->{errorsmax}, @@ -12363,27 +15343,33 @@ sub get_options_cmd { 'justautomap!' => \$mysync->{justautomap}, 'id!' => \$mysync->{id}, 'f1f2=s@' => \$mysync->{f1f2}, + 'nof1f2' => \$mysync->{nof1f2}, 'f1f2h=s%' => \$mysync->{f1f2h}, 'justfolderlists!' => \$mysync->{justfolderlists}, 'delete1emptyfolders' => \$mysync->{delete1emptyfolders}, ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; - $debug and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; + $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; my $numopt_after = scalar @arguments ; #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ; if ( $numopt_after ) { - myprint( "Extra arguments found: @arguments\n", "It usually means a quoting issue in the command line\n" ) ; + myprint( + "Extra arguments found: @arguments\n", + "It usually means a quoting issue in the command line ", + "or some misspelling options.\n", + ) ; return ; } - if ( ! $opt_ret ) { - return ; - } - return $numopt ; + if ( ! $opt_ret ) { + return ; + } + return $numopt ; } - -sub tests_get_options { + +sub tests_get_options +{ note( 'Entering tests_get_options()' ) ; # CAVEAT: still setting global variables, be careful @@ -12397,45 +15383,46 @@ sub tests_get_options { # * options not known # * --delete 2 input # * number of arguments or QUERY_STRING length - my $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --noexist ) ), 'get_options: --noexist => undef' ) ; - is( undef, $mysync3->{ noexist }, 'get_options: --noexist => undef' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ; - is( 1, $mysync3->{ version }, 'get_options: --version => 1' ) ; - is( undef, $mysync3->{ noexist }, 'get_options: --noexist => undef' ) ; - $mysync3 = { } ; - is( 1, get_options( $mysync3, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ; - is( 1, $delete2, 'get_options: --delete2 => $delete2 = 1' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, qw( --delete 2 ) ), 'get_options: --delete 2 => undef' ) ; - is( undef, $delete1, 'get_options: --delete 2 => $delete1 still undef ; good!' ) ; - $mysync3 = { } ; - is( undef, get_options( $mysync3, "--delete 2" ), 'get_options: --delete 2 => undef' ) ; + my $mysync = { } ; + is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ; + is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ; + is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ; + is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; + $mysync = { } ; + is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ; + is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ; + is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ; + $mysync = { } ; + is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ; - is( 1, get_options( $mysync3, "--version" ), 'get_options: --version => 1' ) ; - is( 1, get_options( $mysync3, "--help" ), 'get_options: --help => 1' ) ; - - is( undef, get_options( $mysync3, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ; - is( 1, get_options( $mysync3, qw( --version ) ), 'get_options: --version => 1' ) ; - is( undef, get_options( $mysync3, qw( extra ) ), 'get_options: extra => undef' ) ; - is( undef, get_options( $mysync3, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => 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( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ; + is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ; + is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ; + is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ; + + $mysync = { } ; + is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ; + is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ; + #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; - $mysync3 = { } ; - is( 2, get_options( $mysync3, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ; - is( 'HOST_01', $mysync3->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ; - #myprint( Data::Dumper->Dump( [ $mysync3 ] ) ) ; - note( 'Leaving tests_get_options()' ) ; return ; } -sub get_options { +sub get_options +{ my $mysync = shift @ARG ; my @arguments = @ARG ; - #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; + #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; my $ret ; if ( under_cgi_context( ) ) { # CGI context @@ -12444,13 +15431,13 @@ sub get_options { # Command line context ; $ret = get_options_cmd( $mysync, @arguments ) ; } ; - #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; + #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; foreach my $key ( sort keys %{ $mysync } ) { if ( ! defined $mysync->{$key} ) { delete $mysync->{$key} ; next ; } - if ( 'ARRAY' eq ref( $mysync->{$key} ) + if ( 'ARRAY' eq ref( $mysync->{$key} ) and 0 == scalar( @{ $mysync->{$key} } ) ) { delete $mysync->{$key} ; } @@ -12458,14 +15445,15 @@ sub get_options { return $ret ; } -sub testunitsession { +sub testunitsession +{ my $mysync = shift ; - + if ( ! $mysync ) { return ; } if ( ! $mysync->{ testsunit } ) { return ; } my @functions = @{ $mysync->{ testsunit } } ; - + if ( ! @functions ) { return ; } SKIP: { @@ -12476,7 +15464,8 @@ sub testunitsession { return ; } -sub tests_count_0s { +sub tests_count_0s +{ note( 'Entering tests_count_zeros()' ) ; is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ; is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ; @@ -12486,17 +15475,20 @@ sub tests_count_0s { note( 'Leaving tests_count_zeros()' ) ; return ; } -sub count_0s { +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 { +sub tests_report_failures +{ note( 'Entering tests_report_failures()' ) ; + is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ; is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ; is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ; @@ -12506,11 +15498,12 @@ sub tests_report_failures { return ; } -sub report_failures { +sub report_failures +{ my @details = @ARG ; - + if ( ! @details ) { return ; } - + my $counter = 1 ; my $report = q{} ; foreach my $details ( @details ) { @@ -12524,15 +15517,19 @@ sub report_failures { } -sub tests_true { +sub tests_true +{ note( 'Entering tests_true()' ) ; + is( 1, 1, 'true: 1 is 1' ) ; note( 'Leaving tests_true()' ) ; return ; } -sub tests_testsunit { +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' ) ; @@ -12542,139 +15539,63 @@ sub tests_testsunit { return ; } -sub testsunit { +sub testsunit +{ my @functions = @ARG ; - + if ( ! @functions ) { # myprint( "testsunit warning: no argument given\n" ) ; - return ; + return ; } - + foreach my $function ( @functions ) { if ( ! $function ) { myprint( "testsunit warning: argument is empty\n" ) ; - next ; + next ; } if ( ! exists &$function ) { myprint( "testsunit warning: function $function does not exist\n" ) ; - next ; + next ; } if ( ! defined &$function ) { myprint( "testsunit warning: function $function is not defined\n" ) ; - next ; + next ; } my $function_ref = \&{ $function } ; - &$function_ref() ; + &$function_ref() ; } return ; } -sub 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( ) ; - #tests_format_for_imap_arg( ) ; - #tests_is_a_release_number( ) ; - #tests_delete1emptyfolders( ) ; - #tests_memory_consumption( ) ; - #tests_imap2_folder_name() ; - #tests_length_ref( ) ; - #tests_diff_or_NA( ) ; - #tests_match_number( ) ; - #tests_all_defined( ) ; - #tests_guess_separator( ) ; - #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( ) ; - tests_write_pidfile( ) ; - tests_remove_pidfile_not_running( ) ; - tests_match_a_pid_number( ) ; - note( 'Leaving testsdebug()' ) ; +sub testsdebug +{ + # Now a little obsolete since there is + # imapsync ... --testsunit "anyfunction" + 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_check_binary_embed_all_dyn_libs( ) ; + note( 'Leaving testsdebug()' ) ; done_testing( ) ; } return ; } -sub tests_template { - note( 'Entering tests_template()' ) ; - is( undef, undef, 'template: undef is undef' ) ; - is_deeply( {}, {}, 'template: a hash is a hash' ) ; - is_deeply( [], [], 'template: an array is an array' ) ; - note( 'Leaving tests_template()' ) ; - return ; -} +sub tests +{ + my $mysync = shift ; + if ( ! $mysync->{ tests } ) { return ; } - -sub tests { - my $mysync = shift ; - if ( ! $mysync->{ tests } ) { return ; } - - SKIP: { + SKIP: { skip 'No test in normal run' if ( ! $mysync->{ tests } ) ; - note( 'Entering tests()' ) ; + note( 'Entering tests()' ) ; tests_folder_routines( ) ; tests_compare_lists( ) ; tests_regexmess( ) ; @@ -12797,12 +15718,54 @@ sub tests { tests_template( ) ; tests_split_around_equal( ) ; tests_toggle_sleep( ) ; + tests_labels( ) ; + tests_synclabels( ) ; + tests_uidexpunge_or_expunge( ) ; + tests_appendlimit_from_capability( ) ; + tests_maxsize_setting( ) ; + tests_mock_capability( ) ; + tests_appendlimit( ) ; + tests_capability_of( ) ; + tests_search_in_array( ) ; + tests_operators_and_exclam_precedence( ) ; + tests_teelaunch( ) ; + tests_logfileprepa( ) ; + tests_useheader_suggestion( ) ; + tests_nb_messages_in_2_not_in_1( ) ; + tests_labels_add_subfolder2( ) ; + tests_labels_remove_subfolder1( ) ; + tests_resynclabels( ) ; + tests_labels_remove_special( ) ; + tests_uniq( ) ; + tests_remove_from_requested_folders( ) ; + tests_errors_log( ) ; + tests_add_subfolder1_to_folderrec( ) ; + tests_sanitize_subfolder( ) ; + tests_remove_edging_blanks( ) ; + tests_sanitize( ) ; + tests_remove_last_char_if_is( ) ; + tests_check_binary_embed_all_dyn_libs( ) ; + tests_nthline( ) ; + tests_secondline( ) ; + tests_tail( ) ; #tests_always_fail( ) ; - done_testing( 1181 ) ; + done_testing( 1441 ) ; note( 'Leaving tests()' ) ; } return ; } +sub tests_template +{ + note( 'Entering tests_template()' ) ; + + is( undef, undef, 'template: undef is undef' ) ; + is_deeply( {}, {}, 'template: a hash is a hash' ) ; + is_deeply( [], [], 'template: an array is an array' ) ; + note( 'Leaving tests_template()' ) ; + return ; +} + + From b7b532768cd96ac7496bdbae99fbb29c23da1bc0 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 13:12:09 +0200 Subject: [PATCH 144/439] Moved set "is_running" status Moved set "is_running" status just before the actual execution of imapsync --- data/Dockerfiles/dovecot/imapsync_cron.pl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index a6ec37cc..cc4465cc 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -110,10 +110,6 @@ while ($row = $sth->fetchrow_arrayref()) { $timeout1 = @$row[19]; $timeout2 = @$row[20]; - $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); - $is_running->bind_param( 1, ${id} ); - $is_running->execute(); - if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } my $template = $run_dir . '/imapsync.XXXXXXX'; @@ -152,7 +148,12 @@ while ($row = $sth->fetchrow_arrayref()) { '--no-modulesversion']; try { + $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); + $is_running->bind_param( 1, ${id} ); + $is_running->execute(); + run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; + $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${id} ); From 54a039fbd102e8df9ebf8a35925a1ab807973cb7 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 13:15:34 +0200 Subject: [PATCH 145/439] Fix: Processing result of running imapsync command There was a possibility the status is_running never set back to 0. Also the unlock command could be executed twice. --- data/Dockerfiles/dovecot/imapsync_cron.pl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index cc4465cc..d4799421 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -154,17 +154,21 @@ while ($row = $sth->fetchrow_arrayref()) { run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; - $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); + $update = $dbh->prepare("UPDATE imapsync SET returned_text = ? WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${id} ); $update->execute(); } catch { - $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', last_run = NOW(), is_running = 0 WHERE id = ?"); + $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync' WHERE id = ?"); + $update->bind_param( 1, ${id} ); + $update->execute(); + } finally { + $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); - $lockmgr->unlock($lock_file); }; + } $sth->finish(); From d6833d0b29082e48da7073aadd59b3f443739fc7 Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 13:24:12 +0200 Subject: [PATCH 146/439] Add extra perl modules for imapsync --- data/Dockerfiles/dovecot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 31f408da..3211d167 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -93,7 +93,7 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ && make clean \ && cd .. \ && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION \ - && cpanm Data::Uniqid Mail::IMAPClient String::Util \ + && cpanm Data::Uniqid Mail::IMAPClient String::Util File::Tail Dist::CheckConflicts Module::Implementation Package::Stash Package::Stash::XS Test::Fatal Test::Mock::Guard Test::Requires \ && groupadd -g 5000 vmail \ && groupadd -g 401 dovecot \ && groupadd -g 402 dovenull \ From f7e5f8b51f1f74d8ff794beb210311903821db51 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 May 2019 14:01:17 +0200 Subject: [PATCH 147/439] [Web] Minor changes --- data/web/edit.php | 3 +- data/web/lang/lang.de.php | 1 + data/web/lang/lang.en.php | 1 + data/web/modals/mailbox.php | 757 ++++++++++++++++++------------------ 4 files changed, 383 insertions(+), 379 deletions(-) diff --git a/data/web/edit.php b/data/web/edit.php index 060af548..6d387eb7 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -1085,7 +1085,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    - + +
    diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index 63b33614..0cea4988 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -438,6 +438,7 @@ $lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel'; $lang['add']['delete1'] = 'Lösche Nachricht nach Übertragung vom Quell-Server'; $lang['add']['delete2'] = 'Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind'; $lang['add']['custom_params'] = 'Eigene Parameter'; +$lang['add']['custom_params_hint'] = 'Richtig: --param=xy, falsch: --param xy'; $lang['add']['subscribeall'] = 'Alle synchronisierten Ordner abonnieren'; $lang['add']['timeout1'] = 'Timeout für Verbindung zum Remote-Host'; $lang['add']['timeout2'] = 'Timeout für Verbindung zum lokalen Host'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 359990a2..8044b018 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -451,6 +451,7 @@ $lang['add']['delete2duplicates'] = 'Delete duplicates on destination'; $lang['add']['delete1'] = 'Delete from source when completed'; $lang['add']['delete2'] = 'Delete messages on destination that are not on source'; $lang['add']['custom_params'] = 'Custom parameters'; +$lang['add']['custom_params_hint'] = 'Right: --param=xy, wrong: --param xy'; $lang['add']['subscribeall'] = 'Subscribe all folders'; $lang['add']['timeout1'] = 'Timeout for connection to remote host'; $lang['add']['timeout2'] = 'Timeout for connection to local host'; diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index aeb88cca..123a5934 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -1,7 +1,7 @@ @@ -85,53 +85,53 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
    @@ -177,63 +177,63 @@ if (!isset($_SESSION['mailcow_cc_role'])) { + + @@ -247,44 +247,44 @@ if (!isset($_SESSION['mailcow_cc_role'])) { + + @@ -298,37 +298,37 @@ if (!isset($_SESSION['mailcow_cc_role'])) { @@ -360,7 +360,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
    -
    -
    - -
    -
    -
    + +
    + +
    +
    -
    -
    - -
    -
    -
    + +
    + + +
    +
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    -
    -
    - +
    + + @@ -535,7 +536,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { @@ -603,7 +604,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { @@ -674,7 +675,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { @@ -715,7 +716,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) { From 4ebc871966e29b311d2c6a55e0057a84920980ad Mon Sep 17 00:00:00 2001 From: hunter-nl Date: Sun, 5 May 2019 14:35:34 +0200 Subject: [PATCH 148/439] Added noreleasecheck parameter --- data/Dockerfiles/dovecot/imapsync_cron.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index d4799421..8bc3d976 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -145,7 +145,8 @@ while ($row = $sth->fetchrow_arrayref()) { "--host2", "localhost", "--user2", $user2 . '*' . trim($master_user), "--passfile2", $passfile2->filename, - '--no-modulesversion']; + '--no-modulesversion', + '--noreleasecheck']; try { $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); From 05a2301ea6a5711bb71d4787e231aa0cf14c0f57 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 May 2019 20:01:53 +0200 Subject: [PATCH 149/439] [Web] Allow to rename alias to Alias --- data/web/inc/functions.mailbox.inc.php | 40 ++++++++++++++------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 88b8e91e..df4344f8 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1695,25 +1695,27 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - 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( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) - ); - continue; + if (strtolower($is_now['address']) != strtolower($address)) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + 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( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) + ); + continue; + } } $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); From 9529b7a542d31e0db56e26408a38bd1a2db40d49 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 May 2019 20:44:29 +0200 Subject: [PATCH 150/439] [Web] Various UI fixes --- data/web/css/site/mailbox.css | 2 +- data/web/js/site/mailbox.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/data/web/css/site/mailbox.css b/data/web/css/site/mailbox.css index 80ff063b..61b6cfcf 100644 --- a/data/web/css/site/mailbox.css +++ b/data/web/css/site/mailbox.css @@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow: auto !important; + overflow: inherit !important; } @media screen and (max-width: 767px) { .table-responsive { diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 42b948a0..0f966eb3 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -299,6 +299,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'domain_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'domain_table'); } } }); @@ -412,6 +415,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'mailbox_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'mailbox_table'); } } }); @@ -477,6 +483,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'resource_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'resource_table'); } } }); @@ -539,6 +548,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'bcc_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'bcc_table'); } } }); @@ -596,6 +608,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'recipient_map_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'recipient_map_table'); } } }); @@ -659,6 +674,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'tls_policy_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'tls_policy_table'); } } }); @@ -720,6 +738,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'transport_maps_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'transport_maps_table'); } } }); @@ -810,6 +831,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'alias_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'alias_table'); } } }); @@ -864,6 +888,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'aliasdomain_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'aliasdomain_table'); } } }); @@ -939,6 +966,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'sync_job_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'sync_job_table'); } } }); @@ -1002,6 +1032,9 @@ jQuery(function($){ "on": { "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'filter_table'); + }, + "after.ft.filtering": function(e, ft){ + table_mailbox_ready(ft, 'filter_table'); } } }); From acd8860efa753e6cd9bdf6cb1dff268bd48951bc Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 May 2019 20:49:13 +0200 Subject: [PATCH 151/439] [Web] More minor css fixes --- data/web/js/site/mailbox.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 0f966eb3..cd5bc747 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -239,8 +239,8 @@ jQuery(function($){ }, }, {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}}, - {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"maxWidth":"100px","width":"100px"}}, - {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md"}, + {"name":"rl","title":"RL","breakpoints":"xs sm md lg","style":{"maxWidth":"100px","width":"100px"}}, + {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md lg"}, {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} ], @@ -311,8 +311,8 @@ jQuery(function($){ "columns": [ {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, {"sorted": true,"name":"username","style":{"word-break":"break-all","min-width":"120px"},"title":lang.username}, - {"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm"}, - {"name":"domain","title":lang.domain,"breakpoints":"xs sm"}, + {"name":"name","title":lang.fname,"style":{"word-break":"break-all","min-width":"120px"},"breakpoints":"xs sm md lg"}, + {"name":"domain","title":lang.domain,"breakpoints":"xs sm md lg"}, {"name":"quota","style":{"whiteSpace":"nowrap"},"title":lang.domain_quota,"formatter": function(value){ res = value.split("/"); var of_q = (res[1] == 0 ? "∞" : humanFileSize(res[1])); @@ -323,7 +323,7 @@ jQuery(function($){ return Number(res[0]); }, }, - {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"}, + {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"all"}, {"name":"tls_enforce_in","filterable": false,"title":lang.tls_enforce_in,"breakpoints":"all"}, {"name":"tls_enforce_out","filterable": false,"title":lang.tls_enforce_out,"breakpoints":"all"}, {"name":"quarantine_notification","filterable": false,"title":lang.quarantine_notification,"breakpoints":"all"}, @@ -332,7 +332,7 @@ jQuery(function($){ }, }, {"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"}, - {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, + {"name":"rl","title":"RL","breakpoints":"all","style":{"width":"125px"}}, {"name":"active","filterable": false,"title":lang.active}, {"name":"action","filterable": false,"sortable": false,"style":{"min-width":"290px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} ], From cf4c5df42760ea3102d669e088f7878c12acb595 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 May 2019 20:59:59 +0200 Subject: [PATCH 152/439] [Web] Disable refresh button on reload, re-enable after table init --- data/web/js/site/mailbox.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index cd5bc747..ace0d516 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -215,7 +215,8 @@ jQuery(function($){ .removeAttr("href") .attr("title", "Dual login cannot be used twice") .tooltip(); - } + } + $('.refresh_table').prop("disabled", false); heading = ft.$el.parents('.tab-pane').find('.panel-heading') var ft_paging = ft.use(FooTable.Paging) $(heading).children('.table-lines').text(function(){ @@ -297,6 +298,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'domain_table'); }, @@ -413,6 +417,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'mailbox_table'); }, @@ -481,6 +488,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'resource_table'); }, @@ -546,6 +556,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'bcc_table'); }, @@ -606,6 +619,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'recipient_map_table'); }, @@ -672,6 +688,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'tls_policy_table'); }, @@ -736,6 +755,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'transport_maps_table'); }, @@ -829,6 +851,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'alias_table'); }, @@ -886,6 +911,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'aliasdomain_table'); }, @@ -964,6 +992,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'sync_job_table'); }, @@ -1030,6 +1061,9 @@ jQuery(function($){ "enabled": true }, "on": { + "destroy.ft.table": function(e, ft){ + $('.refresh_table').attr('disabled', 'true'); + }, "ready.ft.table": function(e, ft){ table_mailbox_ready(ft, 'filter_table'); }, From 081feca893ec54988d7413e2e062a4b1d90e82ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 5 May 2019 22:28:55 +0200 Subject: [PATCH 153/439] Replacing trim by function https://perlmaven.com/trim --- data/Dockerfiles/dovecot/imapsync_cron.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 8bc3d976..2b61545e 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -5,11 +5,11 @@ use LockFile::Simple qw(lock trylock unlock); use Proc::ProcessTable; use Data::Dumper qw(Dumper); use IPC::Run 'run'; -use String::Util 'trim'; use File::Temp; use Try::Tiny; use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT); +sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; my $t = Proc::ProcessTable->new; my $imapsync_running = grep { $_->{cmndline} =~ /^\/usr\/bin\/perl \/usr\/local\/bin\/imapsync\s/ } @{$t->table}; if ($imapsync_running eq 1) From 6cf9a0354b8577272584873a34e8ad24b18cbbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 5 May 2019 22:30:11 +0200 Subject: [PATCH 154/439] Trying to avoid non-distro packages --- data/Dockerfiles/dovecot/Dockerfile | 49 ++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 3211d167..f01e0572 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -12,68 +12,76 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ build-essential \ ca-certificates \ cpanminus \ + cron \ curl \ default-libmysqlclient-dev \ dnsutils \ gettext \ jq \ - 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 \ + libcgi-pm-perl \ + libcrypt-openssl-rsa-perl \ libcrypt-ssleay-perl \ libcurl4-openssl-dev \ + libdata-uniqid-perl \ libdbd-mysql-perl \ libdbi-perl \ libdigest-hmac-perl \ + libdist-checkconflicts-perl \ libexpat1-dev \ libfile-copy-recursive-perl \ + libfile-tail-perl \ + libhtml-parser-perl \ libio-compress-perl \ libio-socket-inet6-perl \ libio-socket-ssl-perl \ libio-tee-perl \ libipc-run-perl \ + libjson-webtoken-perl \ libldap2-dev \ liblockfile-simple-perl \ liblz-dev \ liblz4-dev \ liblzma-dev \ + libmail-imapclient-perl \ + libmodule-implementation-perl \ libmodule-scandeps-perl \ libnet-ssleay-perl \ + libpackage-stash-perl \ + libpackage-stash-xs-perl \ libpam-dev \ libpar-packer-perl \ + libparse-recdescent-perl \ + libproc-processtable-perl \ libreadonly-perl \ + libregexp-common-perl \ libssl-dev \ + libsys-meminfo-perl \ libterm-readkey-perl \ + libtest-deep-perl \ + libtest-fatal-perl \ + libtest-mock-guard-perl \ + libtest-mockobject-perl \ + libtest-nowarnings-perl \ libtest-pod-perl \ + libtest-requires-perl \ libtest-simple-perl \ + libtest-warn-perl \ libtry-tiny-perl \ libunicode-string-perl \ - libproc-processtable-perl \ - libtest-nowarnings-perl \ - libtest-deep-perl \ - libtest-warn-perl \ - libregexp-common-perl \ liburi-perl \ + libwww-perl \ lzma-dev \ + make \ + mysql-client \ + procps \ python-html2text \ python-jinja2 \ python-mysql.connector \ python-redis \ - make \ - mysql-client \ - procps \ - supervisor \ - cron \ redis-server \ + supervisor \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ @@ -93,7 +101,6 @@ RUN apt-get update && apt-get -y --no-install-recommends install \ && make clean \ && cd .. \ && rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION \ - && cpanm Data::Uniqid Mail::IMAPClient String::Util File::Tail Dist::CheckConflicts Module::Implementation Package::Stash Package::Stash::XS Test::Fatal Test::Mock::Guard Test::Requires \ && groupadd -g 5000 vmail \ && groupadd -g 401 dovecot \ && groupadd -g 402 dovenull \ From b20ff13e40c45083386bd7a17065749bfc702f3a Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 6 May 2019 12:18:37 +0200 Subject: [PATCH 155/439] [Rspamd] Update to 1.9.2, minor entrypoint changes [Compose] Update Rspamd to 1.9.2 --- data/Dockerfiles/rspamd/docker-entrypoint.sh | 16 +++++++++++++++- docker-compose.yml | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/rspamd/docker-entrypoint.sh b/data/Dockerfiles/rspamd/docker-entrypoint.sh index 6288550d..6facf35a 100755 --- a/data/Dockerfiles/rspamd/docker-entrypoint.sh +++ b/data/Dockerfiles/rspamd/docker-entrypoint.sh @@ -1,7 +1,21 @@ #!/bin/bash -chown -R _rspamd:_rspamd /var/lib/rspamd /etc/rspamd/local.d /etc/rspamd/override.d /etc/rspamd/custom +mkdir -p /etc/rspamd/plugins.d \ + /etc/rspamd/custom + +touch /etc/rspamd/rspamd.conf.local \ + /etc/rspamd/rspamd.conf.override + +chown -R _rspamd:_rspamd /var/lib/rspamd \ + /etc/rspamd/local.d \ + /etc/rspamd/override.d \ + /etc/rspamd/custom \ + /etc/rspamd/rspamd.conf.local \ + /etc/rspamd/rspamd.conf.override \ + /etc/rspamd/plugins.d + chmod 755 /var/lib/rspamd + [[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Placeholder' > /etc/rspamd/override.d/worker-controller-password.inc chown _rspamd:_rspamd /etc/rspamd/override.d/worker-controller-password.inc [[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]] && echo '# to be auto-filled by dovecot-mailcow' > /etc/rspamd/custom/sa-rules-heinlein diff --git a/docker-compose.yml b/docker-compose.yml index 70864056..a55ab5d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.39 + image: mailcow/rspamd:1.40 build: ./data/Dockerfiles/rspamd stop_grace_period: 30s depends_on: From bf3fb0c9d47013fe427e7b7bebb67ec29165944f Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 9 May 2019 11:30:14 +0200 Subject: [PATCH 156/439] [Web] Fix some breakpoints --- data/web/js/site/mailbox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index ace0d516..2ed049f4 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -930,10 +930,10 @@ jQuery(function($){ {"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":"user2","title":lang.owner}, - {"name":"server_w_port","title":"Server","breakpoints":"xs","style":{"word-break":"break-all"}}, + {"name":"server_w_port","title":"Server","breakpoints":"xs sm md","style":{"word-break":"break-all"}}, {"name":"exclude","title":lang.excludes,"breakpoints":"all"}, {"name":"mins_interval","title":lang.mins_interval,"breakpoints":"all"}, - {"name":"last_run","title":lang.last_run,"breakpoints":"sm"}, + {"name":"last_run","title":lang.last_run,"breakpoints":"xs sm md"}, {"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}, From 456e92c83020a197d22ee5e4aba6e0f320ac2399 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 9 May 2019 11:32:16 +0200 Subject: [PATCH 157/439] [Rspamd] Set to to_ip to_ip_from rate buckets to 100 / 1s --- data/conf/rspamd/override.d/ratelimit.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/conf/rspamd/override.d/ratelimit.conf b/data/conf/rspamd/override.d/ratelimit.conf index ccd083d4..f02d2d3c 100644 --- a/data/conf/rspamd/override.d/ratelimit.conf +++ b/data/conf/rspamd/override.d/ratelimit.conf @@ -1,8 +1,8 @@ rates { # Format: "1 / 1h" or "20 / 1m" etc. - global ratelimits are disabled by default - to = "45 / 1m"; - to_ip = "360 / 1m"; - to_ip_from = "180 / 1m"; + to = "100 / 1s"; + to_ip = "100 / 1s"; + to_ip_from = "100 / 1s"; bounce_to = "100 / 1s"; bounce_to_ip = "100 / 1s"; } From 5c07cca529acae330dc80c6066f9dbc11995688a Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 9 May 2019 11:48:38 +0200 Subject: [PATCH 158/439] [Rspamd] Change spoofed mail handling --- data/conf/rspamd/local.d/composites.conf | 2 +- data/conf/rspamd/local.d/multimap.conf | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index 46f06743..1139b490 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -17,6 +17,6 @@ SOGO_CONTACT_SPOOFED { expression = "(R_SPF_PERMFAIL | R_SPF_SOFTFAIL | R_SPF_FAIL) & ~SOGO_CONTACT"; } SPOOFED_UNAUTH { - expression = "UNAUTH_MAILCOW_DOMAIN & !MAILCOW_WHITE & !R_SPF_ALLOW"; + expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !R_SPF_ALLOW & !DMARC_POLICY_ALLOW"; score = 5.0; } diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index 6ce56f7a..7752b813 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -83,11 +83,3 @@ GLOBAL_RCPT_BL { prefilter = true; action = "reject"; } - -UNAUTH_MAILCOW_DOMAIN { - type = "header"; - header = "from"; - filter = "email:domain"; - nflags = ["authenticated"]; - map = "redis://DOMAIN_MAP"; -} From 1c56225b15892dfe722b16e43538a4a6963d3c66 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 9 May 2019 11:49:08 +0200 Subject: [PATCH 159/439] [Compose] Update Dovecot image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a55ab5d4..f6a13178 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -172,7 +172,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.69 + image: mailcow/dovecot:1.70 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 7dc0a05a71d958918644339649ac6874c821eacb Mon Sep 17 00:00:00 2001 From: Harald Glatt Date: Fri, 10 May 2019 15:41:24 +0200 Subject: [PATCH 160/439] Update all helper scripts to '/usr/bin/env bash' shebang --- helper-scripts/backup_and_restore.sh | 2 +- helper-scripts/mailcow-reset-admin.sh | 2 +- helper-scripts/nextcloud.sh | 2 +- helper-scripts/reset-learns.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index 704997c7..93702830 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}" diff --git a/helper-scripts/mailcow-reset-admin.sh b/helper-scripts/mailcow-reset-admin.sh index 74179798..4afd14c9 100755 --- a/helper-scripts/mailcow-reset-admin.sh +++ b/helper-scripts/mailcow-reset-admin.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash [[ -f mailcow.conf ]] && source mailcow.conf [[ -f ../mailcow.conf ]] && source ../mailcow.conf diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 131b7639..b598c4f7 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash for bin in curl dirmngr; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi diff --git a/helper-scripts/reset-learns.sh b/helper-scripts/reset-learns.sh index 647c0e85..9fd11232 100755 --- a/helper-scripts/reset-learns.sh +++ b/helper-scripts/reset-learns.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash read -r -p "Are you sure you want to reset learned hashes from Rspamd (fuzzy, bayes, neural)? [y/N] " response response=${response,,} # tolower From b8b645075346d48c92856e6cad978c5505833829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristia=CC=81n=20Feldsam?= Date: Sat, 11 May 2019 11:16:40 +0200 Subject: [PATCH 161/439] Default quota for mailbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristián Feldsam --- data/web/edit.php | 6 ++++ data/web/inc/functions.mailbox.inc.php | 47 ++++++++++++++++++++++++-- data/web/inc/init_db.inc.php | 3 +- data/web/js/site/mailbox.js | 5 ++- data/web/lang/lang.cs.php | 6 ++++ data/web/lang/lang.en.php | 6 ++++ data/web/modals/mailbox.php | 8 ++++- 7 files changed, 76 insertions(+), 5 deletions(-) diff --git a/data/web/edit.php b/data/web/edit.php index 6d387eb7..1d491f7f 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -273,6 +273,12 @@ if (isset($_SESSION['mailcow_cc_role'])) { +
    + +
    + +
    +
    diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index df4344f8..683bdd5e 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -326,9 +326,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $description = $_data['description']; $aliases = $_data['aliases']; $mailboxes = $_data['mailboxes']; + $defquota = $_data['defquota']; $maxquota = $_data['maxquota']; $restart_sogo = $_data['restart_sogo']; $quota = $_data['quota']; + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + return false; + } if ($maxquota > $quota) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -337,6 +346,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + return false; + } if ($maxquota == "0" || empty($maxquota)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -392,13 +409,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :gal, :active, :relay_all_recipients)"); + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_all_recipients)"); $stmt->execute(array( ':domain' => $domain, ':description' => $description, ':aliases' => $aliases, ':mailboxes' => $mailboxes, + ':defquota' => $defquota, ':maxquota' => $maxquota, ':quota' => $quota, ':backupmx' => $backupmx, @@ -1861,6 +1879,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; $mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; + $defquota = (!empty($_data['defquota'])) ? $_data['defquota'] : ($is_now['def_quota_for_mbox'] / 1048576); $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; @@ -1892,6 +1911,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { )"); $stmt->execute(array(':domain' => $domain)); $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + continue; + } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + continue; + } if ($maxquota > $quota) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1946,6 +1981,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `gal` = :gal, `active` = :active, `quota` = :quota, + `defquota` = :defquota, `maxquota` = :maxquota, `relayhost` = :relayhost, `mailboxes` = :mailboxes, @@ -1958,6 +1994,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':gal' => $gal, ':active' => $active, ':quota' => $quota, + ':defquota' => $defquota, ':maxquota' => $maxquota, ':relayhost' => $relayhost, ':mailboxes' => $mailboxes, @@ -2907,6 +2944,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `description`, `aliases`, `mailboxes`, + `defquota`, `maxquota`, `quota`, `relayhost`, @@ -2938,6 +2976,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); } + $domaindata['def_new_mailbox_quota'] = $domaindata['max_new_mailbox_quota']; + if ($domaindata['def_new_mailbox_quota'] > ($row['defquota'] * 1048576)) { + $domaindata['def_new_mailbox_quota'] = ($row['defquota'] * 1048576); + } $domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use']; $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count']; $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; @@ -2945,6 +2987,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domaindata['description'] = $row['description']; $domaindata['max_num_aliases_for_domain'] = $row['aliases']; $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; + $domaindata['def_quota_for_mbox'] = $row['defquota'] * 1048576; $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; $domaindata['relayhost'] = $row['relayhost']; diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 27db8c1f..1c000204 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -189,7 +189,8 @@ function init_db_schema() { "description" => "VARCHAR(255)", "aliases" => "INT(10) NOT NULL DEFAULT '0'", "mailboxes" => "INT(10) NOT NULL DEFAULT '0'", - "maxquota" => "BIGINT(20) NOT NULL DEFAULT '0'", + "defquota" => "BIGINT(20) NOT NULL DEFAULT '3072'", + "maxquota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'", "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 2ed049f4..5653fdcd 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -62,11 +62,12 @@ $(document).ready(function() { auto_fill_quota = function(domain) { $.get("/api/v1/get/domain/" + domain, function(data){ var result = $.parseJSON(JSON.stringify(data)); + def_new_mailbox_quota = ( result.def_new_mailbox_quota / 1048576); max_new_mailbox_quota = ( result.max_new_mailbox_quota / 1048576); if (max_new_mailbox_quota != '0') { $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": max_new_mailbox_quota}); - $('#addInputQuota').val(max_new_mailbox_quota); + $('#addInputQuota').val(def_new_mailbox_quota); } else { $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); @@ -239,6 +240,7 @@ jQuery(function($){ return Number(res[0]); }, }, + {"name":"def_quota_for_mbox","title":lang.mailbox_defquota,"breakpoints":"xs sm md","style":{"width":"125px"}}, {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}}, {"name":"rl","title":"RL","breakpoints":"xs sm md lg","style":{"maxWidth":"100px","width":"100px"}}, {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm md lg"}, @@ -264,6 +266,7 @@ jQuery(function($){ return e; }).join('/1'); } + item.def_quota_for_mbox = humanFileSize(item.def_quota_for_mbox); item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); item.chkbox = ''; item.action = '
    '; diff --git a/data/web/lang/lang.cs.php b/data/web/lang/lang.cs.php index efcac064..ce57ad2e 100644 --- a/data/web/lang/lang.cs.php +++ b/data/web/lang/lang.cs.php @@ -798,3 +798,9 @@ $lang['warning']['ip_invalid'] = 'Přeskočeno, vadná IP: %s'; $lang['danger']['text_empty'] = 'Text nesmí být prázdný'; $lang['danger']['subject_empty'] = 'Předmět nesmí být prázdný'; $lang['danger']['from_invalid'] = 'Odesílat nesmí být prázdný'; + +$lang['add']['mailbox_quota_def'] = 'Výchozí kvóta schránky'; +$lang['edit']['mailbox_quota_def'] = 'Výchozí kvóta schránky'; +$lang['danger']['mailbox_defquota_exceeds_mailbox_maxquota'] = 'Výchozí kvóta překračuje maximální kvótu schránky"'; +$lang['danger']['defquota_empty'] = 'Výchozí kvóta schránky nesmí být 0.'; +$lang['mailbox']['mailbox_defquota'] = 'Výchozí velikost schránky'; \ No newline at end of file diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 8044b018..d0289797 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -859,3 +859,9 @@ $lang['danger']['text_empty'] = 'Text must not be empty'; $lang['danger']['subject_empty'] = 'Subject must not be empty'; $lang['danger']['from_invalid'] = 'Sender must not be empty'; $lang['danger']['network_host_invalid'] = 'Invalid network or host: %s'; + +$lang['add']['mailbox_quota_def'] = 'Default mailbox quota'; +$lang['edit']['mailbox_quota_def'] = 'Default mailbox quota'; +$lang['danger']['mailbox_defquota_exceeds_mailbox_maxquota'] = 'Default quota exceeds max quota limit"'; +$lang['danger']['defquota_empty'] = 'Default quota per mailbox must not be 0.'; +$lang['mailbox']['mailbox_defquota'] = 'Default mailbox size'; diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 123a5934..67516ac2 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -108,12 +108,18 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
    +
    +
    +
    + +
    +
    - +
    From 8fa60830361ed7578dfd23abbe9ab25750c854d5 Mon Sep 17 00:00:00 2001 From: wucke13 Date: Sat, 11 May 2019 14:15:45 +0200 Subject: [PATCH 162/439] "Repaired" shebang for check_translations.rb Most work was done in 7dc0a05a71d958918644339649ac6874c821eacb, though `check_translation.rb` still had a hardcoded shebang. This is now fixed. #2590 is now fully resolved too. --- helper-scripts/check_translations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper-scripts/check_translations.rb b/helper-scripts/check_translations.rb index 7568a975..af4da1c2 100755 --- a/helper-scripts/check_translations.rb +++ b/helper-scripts/check_translations.rb @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby MASTER="en" From a7bd462d746a2772d2de19ba7bd988cc0f9e2cd4 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 12 May 2019 10:32:06 +0200 Subject: [PATCH 163/439] Update update.sh *curl* on some systems is failing due to no URL specified (1.1.1.1) and ISP blocking *curl* needs an URL for working correctly, so sometimes it is failing as it detects **No Internet connection** but there is. In addition, at some countries some ISP did not update their routers and/or network so the Cloudflare DNS `1.1.1.1` are not working either they are blocked. I suggest using `ping` instead of `curl` with a 3 seconds timeout and pinging to Google DNS (8.8.8.8) instead of Cloudflare ones, as they are universally accepted and used. --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 7fc65578..a503d574 100755 --- a/update.sh +++ b/update.sh @@ -260,7 +260,7 @@ for option in ${CONFIG_ARRAY[@]}; do done echo -en "Checking internet connection... " -curl -o /dev/null 1.1.1.1 -sm3 +timeout 3 ping -c 1 8.8.8.8 > /dev/null if [[ $? != 0 ]]; then echo -e "\e[31mfailed\e[0m" exit 1 From e2389f48165ff9738cfe9c32d6bab1e3c0418384 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 12 May 2019 14:40:54 +0200 Subject: [PATCH 164/439] Update update.sh Updated DNS to Quad9 --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index a503d574..67645b55 100755 --- a/update.sh +++ b/update.sh @@ -260,7 +260,7 @@ for option in ${CONFIG_ARRAY[@]}; do done echo -en "Checking internet connection... " -timeout 3 ping -c 1 8.8.8.8 > /dev/null +timeout 3 ping -c 1 9.9.9.9 > /dev/null if [[ $? != 0 ]]; then echo -e "\e[31mfailed\e[0m" exit 1 From 06193ca6252789aafadc485936ea0c0df6be6e67 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 12 May 2019 15:22:00 +0200 Subject: [PATCH 165/439] [Web] Write API logs when format is data binary --- data/web/json_api.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/data/web/json_api.php b/data/web/json_api.php index 8e6407b7..45963ec6 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -53,8 +53,6 @@ function api_log($_data) { } } -api_log($_POST); - if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) { if (isset($_GET['query'])) { @@ -70,7 +68,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u $requestDecoded = json_decode($request, true); // check for valid json - if($action != 'get' && $requestDecoded === null) { + if ($action != 'get' && $requestDecoded === null) { echo json_encode(array( 'type' => 'error', 'msg' => 'Request body doesn\'t contain valid json!' @@ -79,30 +77,27 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } // add - if($action == 'add') { + if ($action == 'add') { $_POST['attr'] = $request; } // edit - if($action == 'edit') { + if ($action == 'edit') { $_POST['attr'] = json_encode($requestDecoded['attr']); $_POST['items'] = json_encode($requestDecoded['items']); } // delete - if($action == 'delete') { + if ($action == 'delete') { $_POST['items'] = $request; } - unset($_SESSION['return']); - unset($_SESSION['success']); - unset($_SESSION['danger']); - unset($_SESSION['error']); } + api_log($_POST); $request_incomplete = json_encode(array( - 'type' => 'error', - 'msg' => 'Cannot find attributes in post data' + 'type' => 'error', + 'msg' => 'Cannot find attributes in post data' )); switch ($action) { From 84a78dbd0d37f5f1968d3914423076de1a070322 Mon Sep 17 00:00:00 2001 From: Kraeutergarten Date: Thu, 16 May 2019 08:20:21 +0200 Subject: [PATCH 166/439] Adds only existing domains in table to the filter and removes additional ajax request. --- data/web/js/site/mailbox.js | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 2ed049f4..a26874b8 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -3,28 +3,18 @@ $(document).ready(function() { FooTable.domainFilter = FooTable.Filtering.extend({ construct: function(instance){ this._super(instance); - var domain_list = []; - $.ajax({ - dataType: 'json', - url: '/api/v1/get/domain/all', - jsonp: false, - async: true, - error: function () { - domain_list.push('Cannot read domain list'); - }, - success: function (data) { - $.each(data, function (i, item) { - domain_list.push(item.domain_name); - }); - } - }); - this.domains = domain_list; this.def = 'All Domains'; this.$domain = null; }, $create: function(){ this._super(); - var self = this, + var self = this; + var domains = []; + + $.each(self.ft.rows.all, function(i, row){ + if($.inArray(row.val().domain, domains) === -1) domains.push(row.val().domain); + }); + $form_grp = $('
    ', {'class': 'form-group'}) .append($('