diff --git a/.env b/.env new file mode 120000 index 00000000..9400869f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +mailcow.conf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e088e56d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +rebuild-images.sh +data/conf/sogo/sieve.creds +data/conf/dovecot/dovecot-master.passwd +mailcow.conf +mailcow.conf_backup +data/conf/nginx/listen*active +data/web/inc/vars.local.inc.php +site/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..bd305b38 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# mailcow: dockerized - 🐮 + 🐋 = 💕 + +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JWBSYHF4SMC68) + +Please see the official documentation for instructions => [mailcow.email/dockerized](https://mailcow.email/dockerized) diff --git a/css/highlight.css b/css/highlight.css deleted file mode 100644 index 0ae40a72..00000000 --- a/css/highlight.css +++ /dev/null @@ -1,124 +0,0 @@ -/* -This is the GitHub theme for highlight.js - -github.com style (c) Vasily Polovnyov - -*/ - -.hljs { - display: block; - overflow-x: auto; - color: #333; - -webkit-text-size-adjust: none; -} - -.hljs-comment, -.diff .hljs-header, -.hljs-javadoc { - color: #998; - font-style: italic; -} - -.hljs-keyword, -.css .rule .hljs-keyword, -.hljs-winutils, -.nginx .hljs-title, -.hljs-subst, -.hljs-request, -.hljs-status { - color: #333; - font-weight: bold; -} - -.hljs-number, -.hljs-hexcolor, -.ruby .hljs-constant { - color: #008080; -} - -.hljs-string, -.hljs-tag .hljs-value, -.hljs-phpdoc, -.hljs-dartdoc, -.tex .hljs-formula { - color: #d14; -} - -.hljs-title, -.hljs-id, -.scss .hljs-preprocessor { - color: #900; - font-weight: bold; -} - -.hljs-list .hljs-keyword, -.hljs-subst { - font-weight: normal; -} - -.hljs-class .hljs-title, -.hljs-type, -.vhdl .hljs-literal, -.tex .hljs-command { - color: #458; - font-weight: bold; -} - -.hljs-tag, -.hljs-tag .hljs-title, -.hljs-rule .hljs-property, -.django .hljs-tag .hljs-keyword { - color: #000080; - font-weight: normal; -} - -.hljs-attribute, -.hljs-variable, -.lisp .hljs-body, -.hljs-name { - color: #008080; -} - -.hljs-regexp { - color: #009926; -} - -.hljs-symbol, -.ruby .hljs-symbol .hljs-string, -.lisp .hljs-keyword, -.clojure .hljs-keyword, -.scheme .hljs-keyword, -.tex .hljs-special, -.hljs-prompt { - color: #990073; -} - -.hljs-built_in { - color: #0086b3; -} - -.hljs-preprocessor, -.hljs-pragma, -.hljs-pi, -.hljs-doctype, -.hljs-shebang, -.hljs-cdata { - color: #999; - font-weight: bold; -} - -.hljs-deletion { - background: #fdd; -} - -.hljs-addition { - background: #dfd; -} - -.diff .hljs-change { - background: #0086b3; -} - -.hljs-chunk { - color: #aaa; -} diff --git a/css/theme.css b/css/theme.css deleted file mode 100644 index 099a2d82..00000000 --- a/css/theme.css +++ /dev/null @@ -1,12 +0,0 @@ -/* - * This file is copied from the upstream ReadTheDocs Sphinx - * theme. To aid upgradability this file should *not* be edited. - * modifications we need should be included in theme_extra.css. - * - * https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/core/static/core/css/theme.css - */ - -*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-square:before,.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980B9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980B9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:fixed;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center} diff --git a/css/theme_extra.css b/css/theme_extra.css deleted file mode 100644 index b8b06d7f..00000000 --- a/css/theme_extra.css +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Sphinx doesn't have support for section dividers like we do in - * MkDocs, this styles the section titles in the nav - * - * https://github.com/mkdocs/mkdocs/issues/175 - */ -.wy-menu-vertical span { - line-height: 18px; - padding: 0.4045em 1.618em; - display: block; - position: relative; - font-size: 90%; - color: #838383; -} - -.wy-menu-vertical .subnav a { - padding: 0.4045em 2.427em; -} - -/* - * Long navigations run off the bottom of the screen as the nav - * area doesn't scroll. - * - * https://github.com/mkdocs/mkdocs/pull/202 - */ -.wy-nav-side { - height: 100%; - overflow-y: auto; -} - -/* - * readthedocs theme hides nav items when the window height is - * too small to contain them. - * - * https://github.com/mkdocs/mkdocs/issues/#348 - */ -.wy-menu-vertical ul { - margin-bottom: 2em; -} - -/* - * Wrap inline code samples otherwise they shoot of the side and - * can't be read at all. - * - * https://github.com/mkdocs/mkdocs/issues/313 - * https://github.com/mkdocs/mkdocs/issues/233 - * https://github.com/mkdocs/mkdocs/issues/834 - */ -code { - white-space: pre-wrap; - word-wrap: break-word; - padding: 2px 5px; -} - -/** - * Make code blocks display as blocks and give them the appropriate - * font size and padding. - * - * https://github.com/mkdocs/mkdocs/issues/855 - * https://github.com/mkdocs/mkdocs/issues/834 - * https://github.com/mkdocs/mkdocs/issues/233 - */ -pre code { - white-space: pre; - word-wrap: normal; - display: block; - padding: 12px; - font-size: 12px; -} - -/* - * Fix link colors when the link text is inline code. - * - * https://github.com/mkdocs/mkdocs/issues/718 - */ -a code { - color: #2980B9; -} -a:hover code { - color: #3091d1; -} -a:visited code { - color: #9B59B6; -} - -/* - * The CSS classes from highlight.js seem to clash with the - * ReadTheDocs theme causing some code to be incorrectly made - * bold and italic. - * - * https://github.com/mkdocs/mkdocs/issues/411 - */ -pre .cs, pre .c { - font-weight: inherit; - font-style: inherit; -} - -/* - * Fix some issues with the theme and non-highlighted code - * samples. Without and highlighting styles attached the - * formatting is broken. - * - * https://github.com/mkdocs/mkdocs/issues/319 - */ -.no-highlight { - display: block; - padding: 0.5em; - color: #333; -} - - -/* - * Additions specific to the search functionality provided by MkDocs - */ - -.search-results article { - margin-top: 23px; - border-top: 1px solid #E1E4E5; - padding-top: 24px; -} - -.search-results article:first-child { - border-top: none; -} - -form .search-query { - width: 100%; - border-radius: 50px; - padding: 6px 12px; /* csslint allow: box-model */ - border-color: #D1D4D5; -} - -.wy-menu-vertical li ul { - display: inherit; -} - -.wy-menu-vertical li ul.subnav ul.subnav{ - padding-left: 1em; -} - -.wy-menu-vertical .subnav li.current > a { - padding-left: 2.42em; -} -.wy-menu-vertical .subnav li.current > ul li a { - padding-left: 3.23em; -} - -/* - * Improve inline code blocks within admonitions. - * - * https://github.com/mkdocs/mkdocs/issues/656 - */ - .admonition code { - color: #404040; - border: 1px solid #c7c9cb; - border: 1px solid rgba(0, 0, 0, 0.2); - background: #f8fbfd; - background: rgba(255, 255, 255, 0.7); -} - -/* - * Account for wide tables which go off the side. - * Override borders to avoid wierdness on narrow tables. - * - * https://github.com/mkdocs/mkdocs/issues/834 - * https://github.com/mkdocs/mkdocs/pull/1034 - */ -.rst-content .section .docutils { - width: 100%; - overflow: auto; - display: block; - border: none; -} - -td, th { - border: 1px solid #e1e4e5 !important; /* csslint allow: important */ - border-collapse: collapse; -} diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile new file mode 100644 index 00000000..b98cd908 --- /dev/null +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -0,0 +1,87 @@ +From ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN apt-get update +RUN apt-get -y install dovecot-common \ + dovecot-core \ + dovecot-imapd \ + dovecot-lmtpd \ + dovecot-managesieved \ + dovecot-sieve \ + dovecot-mysql \ + dovecot-pop3d \ + dovecot-dev \ + syslog-ng \ + syslog-ng-core \ + ca-certificates \ + supervisor \ + wget \ + curl \ + build-essential \ + autotools-dev \ + automake \ + libauthen-ntlm-perl \ + libcrypt-ssleay-perl \ + libdigest-hmac-perl \ + libfile-copy-recursive-perl \ + libio-compress-perl \ + libio-socket-inet6-perl \ + libio-socket-ssl-perl \ + libio-tee-perl \ + libmodule-scandeps-perl \ + libnet-ssleay-perl \ + libpar-packer-perl \ + libreadonly-perl \ + libterm-readkey-perl \ + libtest-pod-perl \ + libtest-simple-perl \ + libunicode-string-perl \ + liburi-perl \ + libdbi-perl \ + liblockfile-simple-perl \ + libdbd-mysql-perl \ + libipc-run-perl \ + make \ + cpanminus + +RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf +RUN cpanm Data::Uniqid Mail::IMAPClient String::Util +RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync +RUN echo '30 3 * * * vmail /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync + +WORKDIR /tmp + +RUN wget http://hg.dovecot.org/dovecot-antispam-plugin/archive/tip.tar.gz -O - | tar xvz \ + && cd /tmp/dovecot-antispam* \ + && ./autogen.sh \ + && ./configure --prefix=/usr \ + && make \ + && make install + +COPY ./imapsync /usr/local/bin/imapsync +COPY ./postlogin.sh /usr/local/bin/postlogin.sh +COPY ./imapsync_cron.pl /usr/local/bin/imapsync_cron.pl +COPY ./rspamd-pipe /usr/local/bin/rspamd-pipe +COPY ./docker-entrypoint.sh / +COPY ./supervisord.conf /etc/supervisor/supervisord.conf + +RUN chmod +x /usr/local/bin/rspamd-pipe +RUN chmod +x /usr/local/bin/imapsync_cron.pl + +RUN groupadd -g 5000 vmail +RUN useradd -g vmail -u 5000 vmail -d /var/vmail + +EXPOSE 24 10001 + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh new file mode 100755 index 00000000..a015af2b --- /dev/null +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +# Hard-code env vars to imapsync due to cron not passing them to the perl script +sed -i "/^\$DBUSER/c\\\$DBUSER='${DBUSER}';" /usr/local/bin/imapsync_cron.pl +sed -i "/^\$DBPASS/c\\\$DBPASS='${DBPASS}';" /usr/local/bin/imapsync_cron.pl +sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl + +# Set Dovecot config parameters, escape " in db password +DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') +sed -i "/^connect/c\connect = \"host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}\"" /etc/dovecot/sql/* + +[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve +[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo +cat /etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve +sievec /var/vmail/sieve/global.sieve +chown -R vmail:vmail /var/vmail/sieve + +# Do not do this every start-up, it may take a very long time. So we use a stat check here. +if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi + +# 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) +echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /etc/dovecot/dovecot-master.passwd +echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds + +exec "$@" diff --git a/data/Dockerfiles/dovecot/imapsync b/data/Dockerfiles/dovecot/imapsync new file mode 100755 index 00000000..8df547c6 --- /dev/null +++ b/data/Dockerfiles/dovecot/imapsync @@ -0,0 +1,9488 @@ +#!/usr/bin/perl + +# $Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $ +# structure +# pod documentation +# pragmas +# main program +# global variables initialisation +# get_options( ) ; +# default values +# folder loop +# subroutines +# sub usage { +# IMAPClient 3.xx ads + +# pod documentation + +=pod + +=head1 NAME + +imapsync - Email IMAP tool for syncing, copying and migrating email mailboxes. + +The imapsync command synchronises mailboxes between two imap servers. +More than 69 different IMAP server softwares supported with success, +few failures. + +$Revision: 1.727 $ + +=head1 SYNOPSIS + + To synchronize the source imap account + "test1" on server "test1.lamiral.info" with password "secret1" + to the destination imap account + "test2" on server "test2.lamiral.info" with password "secret2" + do: + + imapsync \ + --host1 test1.lamiral.info --user1 test1 --password1 secret1 \ + --host2 test2.lamiral.info --user2 test2 --password2 secret2 + +=head1 REQUIRED ARGUMENTS + +The required argmuments are the six values, three on each sides, +needed to login into the IMAP servers, +a host, a username, and a password, two times. + +=head1 INSTALL + + Imapsync works under any Unix with perl. + Imapsync works under Windows (2000, XP, Vista, Seven) + as a standalone binary software called imapsync.exe + Imapsync works under OS X as a standalone binary + software called imapsync_bin_Darwin. + + Purchase latest imapsync at + http://imapsync.lamiral.info/ + + You'll receive a link to a compressed tarball called imapsync-x.xx.tgz + where x.xx is the version number. Untar the tarball where + you want (on Unix): + + tar xzvf imapsync-x.xx.tgz + + Go into the directory imapsync-x.xx and read the INSTALL file. + As mentioned at http://imapsync.lamiral.info/#install + the INSTALL file can also be found at + http://imapsync.lamiral.info/INSTALL + It is now split in several files for each system + http://imapsync.lamiral.info/INSTALL.d/ + +=head1 CONFIGURATION + +There is no specific configuration file for imapsync, +everything is specified by the command line parameteres +and the default behavior. + +=head1 USAGE + +To get a description of each option just run imapsync +with no argument, like this: + + imapsync + +This description of options is also available at +http://imapsync.lamiral.info/OPTIONS and is +reproduced here: + + usage: ./imapsync [options] + + Several options are mandatory. + str means string + int means integer + reg means regular expression + cmd means command + + --dry : Makes imapsync doing nothing, just print what would + be done without --dry. + + --host1 str : Source or "from" imap server. Mandatory. + --port1 int : Port to connect on host1. Default is 143, 993 if --ssl1 + --user1 str : User to login on host1. Mandatory. + --showpasswords : Shows passwords on output instead of "MASKED". + Useful to restart a complete run by just reading the log. + --password1 str : Password for the user1. + --host2 str : "destination" imap server. Mandatory. + --port2 int : Port to connect on host2. Default is 143, 993 if --ssl2 + --user2 str : User to login on host2. Mandatory. + --password2 str : Password for the user2. + + --passfile1 str : Password file for the user1. It must contain the + password on the first line. This option avoids to show + the password on the command line like --password1 does. + --passfile2 str : Password file for the user2. Contains the password. + + --ssl1 : Use a SSL connection on host1. + --ssl2 : Use a SSL connection on host2. + --tls1 : Use a TLS connection on host1. + --tls2 : Use a TLS connection on host2. + --debugssl int : SSL debug mode from 0 to 4. + --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example: + --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3 + See all possibilities in the new() method of IO::Socket::SSL + http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods + --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection. + See --sslargs1 + + --timeout1 int : Connection timeout in seconds for host1. + Default is 120 and 0 means no timeout at all. + --timeout2 int : Connection timeout in seconds for host2. + Default is 120 and 0 means no timeout at all. + + --authmech1 str : Auth mechanism to use with host1: + PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE. + --authmech2 str : Auth mechanism to use with host2. See --authmech1 + + --authuser1 str : User to auth with on host1 (admin user). + Avoid using --authmech1 SOMETHING with --authuser1. + --authuser2 str : User to auth with on host2 (admin user). + --proxyauth1 : Use proxyauth on host1. Requires --authuser1. + Required by Sun/iPlanet/Netscape IMAP servers to + be able to use an administrative user. + --proxyauth2 : Use proxyauth on host2. Requires --authuser2. + + --authmd51 : Use MD5 authentification for host1. + --authmd52 : Use MD5 authentification for host2. + --domain1 str : Domain on host1 (NTLM authentication). + --domain2 str : Domain on host2 (NTLM authentication). + + + --folder str : Sync this folder. + --folder str : and this one, etc. + --folderrec str : Sync this folder recursively. + --folderrec str : and this one, etc. + + --folderfirst str : Sync this folder first. --folderfirst "Work" + --folderfirst str : then this one, etc. + --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail" + --folderlast str : then this one, etc. + + --nomixfolders : Do not merge folders when host1 is case sensitive + while host2 is not (like Exchange). Only the first + similar folder is synced (ex: Sent SENT sent -> Sent). + + --skipemptyfolders : Empty host1 folders are not created on host2. + + --f1f2 str1=str2 : Force folder str1 to be synced to str2. + --include reg : Sync folders matching this regular expression + --include reg : or this one, etc. + in case both --include --exclude options are + use, include is done before. + --exclude reg : Skips folders matching this regular expression + Several folders to avoid: + --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3. + --exclude reg : or this one, etc. + + --subfolder2 str : Move whole host1 folders hierarchy under this + host2 folder str . + It does it by adding two --regextrans2 options before + all others. Add --debug to see what's really going on. + + --regextrans2 reg : Apply the whole regex to each destination folders. + --regextrans2 reg : and this one. etc. + When you play with the --regextrans2 option, first + add also the safe options --dry --justfolders + Then, when happy, remove --dry, remove --justfolders. + Have in mind that --regextrans2 is applied after prefix + and separator inversion. + + --tmpdir str : Where to store temporary files and subdirectories. + Will be created if it doesn't exist. + Default is system specific, Unix is /tmp but + it's often small and deleted at reboot. + --tmpdir /var/tmp should be better. + --pidfile str : The file where imapsync pid is written. + --pidfilelocking : Abort if pidfile already exists. Usefull to avoid + concurrent transfers on the same mailbox. + + --nolog : Turn off logging on file + --logfile str : Change the default log filename (can be dirname/filename). + --logdir str : Change the default log directory. Default is LOG_imapsync + + --prefix1 str : Remove prefix to all destination folders + (usually INBOX. or INBOX/ or an empty string "") + you have to use --prefix1 if host1 imap server + does not have NAMESPACE capability, so imapsync + suggests to use it. All other cases are bad. + --prefix2 str : Add prefix to all host2 folders. See --prefix1 + --sep1 str : Host1 separator in case NAMESPACE is not supported. + --sep2 str : Host2 separator in case NAMESPACE is not supported. + + --skipmess reg : Skips messages maching the regex. + Example: 'm/[\x80-ff]/' # to avoid 8bits messages. + --skipmess is applied before --regexmess + --skipmess reg : or this one, etc. + + --pipemess cmd : Apply this cmd command to each message content + before the copy. + --pipemess cmd : and this one, etc. + + --disarmreadreceipts : Disarms read receipts (host2 Exchange issue) + + --regexmess reg : Apply the whole regex to each message before transfer. + Example: 's/\000/ /g' # to replace null by space. + --regexmess reg : and this one, etc. + + --regexflag reg : Apply the whole regex to each flags list. + Example: 's/"Junk"//g' # to remove "Junk" flag. + --regexflag reg : and this one, etc. + + --delete : Deletes messages on host1 server after a successful + transfer. Option --delete has the following behavior: + it marks messages as deleted with the IMAP flag + \Deleted, then messages are really deleted with an + EXPUNGE IMAP command. + + --delete2 : Delete messages in host2 that are not in + host1 server. Useful for backup or pre-sync. + --delete2duplicates : Delete messages in host2 that are duplicates. + Works only without --useuid since duplicates are + detected with an header part of each message. + + --delete2folders : Delete folders in host2 that are not in host1 server. + For safety, first try it like this (it is safe): + --delete2folders --dry --justfolders --nofoldersizes + --delete2foldersonly reg : Deleted only folders matching regex. + Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/" + --delete2foldersbutnot reg : Do not delete folders matching regex. + Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/" + --noexpunge : Do not expunge messages on host1. + Expunge really deletes messages marked deleted. + Expunge is made at the beginning, on host1 only. + Newly transferred messages are also expunged if + option --delete is given. + No expunge is done on host2 account (unless --expunge2) + --expunge1 : Expunge messages on host1 after messages transfer. + --expunge2 : Expunge messages on host2 after messages transfer. + --uidexpunge2 : uidexpunge messages on the host2 account + that are not on the host1 account, requires --delete2 + --nomixfolders : Avoid merging folders that are considered different on + host1 but the same on destination host2 because of + case sensitivities and insensitivities. + + --syncinternaldates : Sets the internal dates on host2 same as host1. + Turned on by default. Internal date is the date + a message arrived on a host (mtime). + --idatefromheader : Sets the internal dates on host2 same as the + "Date:" headers. + + --maxsize int : Skip messages larger (or equal) than int bytes + --minsize int : Skip messages smaller (or equal) than int bytes + --maxage int : Skip messages older than int days. + final stats (skipped) don't count older messages + see also --minage + --minage int : Skip messages newer than int days. + final stats (skipped) don't count newer messages + You can do (+ are the messages selected): + past|----maxage+++++++++++++++>now + past|+++++++++++++++minage---->now + past|----maxage+++++minage---->now (intersection) + past|++++minage-----maxage++++>now (union) + + --search str : Selects only messages returned by this IMAP SEARCH + command. Applied on both sides. + --search1 str : Same as --search for selecting host1 messages only. + --search2 str : Same as --search for selecting host2 messages only. + --search CRIT equals --search1 CRIT --search2 CRIT + + --exitwhenover int : Stop syncing when total bytes transferred reached. + Gmail per day allows + 2500000000 = 2.5 GB downloaded from Gmail as host2 + 500000000 = 500 MB uploaded to Gmail as host1. + + --maxlinelength int : skip messages with a line length longer than int bytes. + RFC 2822 says it must be no more than 1000 bytes. + + --useheader str : Use this header to compare messages on both sides. + Ex: Message-ID or Subject or Date. + --useheader str and this one, etc. + + --subscribed : Transfers subscribed folders. + --subscribe : Subscribe to the folders transferred on the + host2 that are subscribed on host1. On by default. + --subscribeall : Subscribe to the folders transferred on the + host2 even if they are not subscribed on host1. + + --nofoldersizes : Do not calculate the size of each folder in bytes + and message counts. Default is to calculate them. + --nofoldersizesatend: Do not calculate the size of each folder in bytes + and message counts at the end. Default is on. + --justfoldersizes : Exit after having printed the folder sizes. + + --syncacls : Synchronises acls (Access Control Lists). + --nosyncacls : Does not synchronize acls. This is the default. + Acls in IMAP are not standardized, be careful. + + --usecache : Use cache to speedup. + --nousecache : Do not use cache. Caveat: --useuid --nousecache creates + duplicates on multiple runs. + --useuid : Use uid instead of header as a criterium to recognize + messages. Option --usecache is then implied unless + --nousecache is used. + + --debug : Debug mode. + --debugfolders : Debug mode for the folders part only. + --debugcontent : Debug content of the messages transfered. Huge ouput. + --debugflags : Debug mode for flags. + --debugimap1 : IMAP debug mode for host1. Very verbose. + --debugimap2 : IMAP debug mode for host2. Very verbose. + --debugimap : IMAP debug mode for host1 and host2. + --debugmemory : Debug mode showing memory consumption after each copy. + + --errorsmax int : Exit when int number of errors is reached. Default is 50. + + --tests : Run local non-regression tests. Exit code 0 means all ok. + --testslive : Run a live test with test1.lamiral.info imap server. + Useful to check the basics. Needs internet connexion. + + --version : Print only software version. + --noreleasecheck : Do not check for new imapsync release (a http request). + --releasecheck : Check for new imapsync release (a http request). + --noid : Do not send/receive ID command to imap servers. + --justconnect : Just connect to both servers and print useful + information. Need only --host1 and --host2 options. + --justlogin : Just login to both host1 and host2 with users + credentials, then exit. + --justfolders : Do only things about folders (ignore messages). + + --help : print this help. + + Example: + To synchronize the source imap account + "test1" on server "test1.lamiral.info" with password "secret1" + to the destination imap account + "test2" on server "test2.lamiral.info" with password "secret2" + do: + + imapsync \ + --host1 test1.lamiral.info --user1 test1 --password1 secret1 \ + --host2 test2.lamiral.info --user2 test2 --password2 secret2 + +=cut +# comment + +=pod + +=head1 DESCRIPTION + +Imapsync command is a tool allowing incremental and +recursive imap transfers from one mailbox to another. + +By default all folders are transferred, recursively, all +possible flags (\Seen \Answered \Flagged etc.) are synced too. + +We sometimes need to transfer mailboxes from one imap server to +another. This is called migration. + +Imapsync reduces the amount +of data transferred by not transferring a given message +if it resides already on both sides. Same specific headers +and the transfer is done only once; taken into account are by default +Message-Id and Received header lines. +All flags are +preserved, unread will stay unread, read will stay read, +deleted will stay deleted. You can stop the transfer at any +time and restart it later, imapsync works well with bad +connections and interruptions. + +You can decide to delete the messages from the source mailbox +after a successful transfer, it can be a good feature when migrating +live mailboxes since messages will be only on one side. +In that case, use the --delete option. Option --delete implies +also option --expunge so all messages marked deleted on host1 +will be really deleted. +(you can use --noexpunge to avoid this but I don't see any +good real world scenario for the combination --delete --noexpunge). + +A different scenario is synchronizing a mailbox B from another mailbox A +in case you just want to keep a "live" copy of A in B. +In that case --delete2 has to be used, it deletes messages in host2 +folder B that are not in host1 folder A. If you also need to destroy +host2 folders that are not in host1 then use --delete2folders (see also +--delete2foldersonly and --delete2foldersbutnot). + +Imapsync is not adequate for maintaining two active imap accounts +in synchronization when the user plays independently on both sides. +Use offlineimap (written by John Goerzen) or mbsync (written by +Michael R. Elkins) for 2 ways synchronizations. + + +=head1 OPTIONS + +To get a description of each option just invoke: + + imapsync + +or read the previous section named USAGE, + +or read http://imapsync.lamiral.info/OPTIONS + +=head1 HISTORY + +I wrote imapsync because an enterprise (basystemes) paid me to install +a new imap server without losing huge old mailboxes located on a far +away remote imap server accessible by a low bandwidth link. The tool +imapcp (written in python) could not help me because I had to verify +every mailbox was well transferred and delete it after a good +transfer. imapsync started its life as a copy_folder.pl patch. +The tool copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl +module tarball source (in the examples/ directory of the tarball). + +=head1 EXAMPLE + +While working on imapsync parameters please run imapsync in +dry mode (no modification induced) with the --dry +option. Nothing bad can be done this way. + +To synchronize the imap account "buddy" (with password "secret1") +on host "imap.src.fr" to the imap account "max" (with password "secret2") +on host "imap.dest.fr": + + imapsync --host1 imap.src.fr --user1 buddy --password1 secret1 \ + --host2 imap.dest.fr --user2 max --password2 secret2 + +Then you will have max's mailbox updated from buddy's +mailbox. + +=head1 SECURITY + +You can use --passfile1 instead of --password1 to give the +password since it is safer. With --password1 option any user +on your host can see the password by using the 'ps auxwwww' +command. Using a variable (like $PASSWORD1) is also +dangerous because of the 'ps auxwwwwe' command. So, saving +the password in a well protected file (600 or rw-------) is +the best solution. + +imasync is not totally protected against sniffers on the +network since passwords may be transferred in plain text +if CRAM-MD5 is not supported by your imap servers. Use +--ssl1 (or --tls1) and --ssl2 (or --tls2) to enable +encryption on host1 and host2. + +You may authenticate as one user (typically an admin user), +but be authorized as someone else, which means you don't +need to know every user's personal password. Specify +--authuser1 "adminuser" to enable this on host1. In this +case, --authmech1 PLAIN will be used by default since it +is the only way to go for now. So don't use --authmech1 SOMETHING +with --authuser1 "adminuser", it will not work. +Same behavior with the --authuser2 option. +Authenticate with an admin account must be supported by your +imap server to work with imapsync. + +When working on Sun/iPlanet/Netscape IMAP servers you must use +--proxyauth1 to enable administrative user to masquerade as another user. +Can also be used on destination server with --proxyauth2 + +You can authenticate with OAUTH when transfering from Google Apps. +The consumer key will be the domain part of the --user, and the +--password will be used as the consumer secret. It does not work +with Google Apps free edition. + +=head1 EXIT STATUS + +imapsync will exit with a 0 status (return code) if everything went good. +Otherwise, it exits with a non-zero status. + +So if you have an unreliable internet connection, you can use this loop +in a Bourne shell: + + while ! imapsync ...; do + echo imapsync not complete + done + +=head1 LICENSE AND COPYRIGHT + +imapsync is free, open, public but not always gratis software +cover by the NOLIMIT Public License. +See the LICENSE file included in the distribution or just read this +simple sentence as it is the licence text: + + "No limit to do anything with this work and this license." + +In case it is not long enough I repeat: + + "No limit to do anything with this work and this license." + +=head1 MAILING-LIST + +The public mailing-list may be the best way to get free support. + +To write on the mailing-list, the address is: + + +To subscribe, send any message (even empty) to: + +then just reply to the confirmation message. + +To unsubscribe, send a message to: + + +To contact the person in charge for the list: + + +The list archives are available at: +http://www.linux-france.org/prj/imapsync_list/ +So consider that the list is public, anyone +can see your post. Use a pseudonym or do not +post to this list if you want to stay private. + +Thank you for your participation. + +=head1 AUTHOR + +Gilles LAMIRAL + +Feedback good or bad is very often welcome. + +Gilles LAMIRAL earns his living by writing, installing, +configuring and teaching free, open and often gratis +softwares. It used to be "always gratis" but now it is +"often" because imapsync is sold by its author, a good +way to stay maintening and supporting free open public +softwares (see the license) over decades. + +=head1 BUGS AND LIMITATIONS + +Help me to help you: follow the following guidelines. + +Report any bugs or feature requests to the public mailing-list +or to the author. + +Before reporting bugs, read the FAQs, the README and the +TODO files. http://imapsync.lamiral.info/ + +Upgrade to last imapsync release, maybe the bug +is already fixed. + +Upgrade to last Mail-IMAPClient Perl module. +http://search.cpan.org/dist/Mail-IMAPClient/ +maybe the bug is already fixed there. + +Make a good title with word "imapsync" in it (my spam filters won't filter it), +Try to write an email title with more words than just "imapsync" or "problem", +a good title is made of keywords summary, but not too long (one visible line). + +Help us to help you: in your report, please include: + + - imapsync version. + + - output near the first failures, a few lines before is good to get the context + of the issue. First failures messages are often more significant than + the last ones. + + - if the issue is always related to the same messages, include the output + with --debug --debugimap, near the failure point. For example, + Isolate a buggy message or two in a folder 'BUG' and use + + imapsync ... --folder 'BUG' --debug --debugimap + + - imap server softwares on both sides and their version number. + + - imapsync with all the options you use, the full command line + you use (except the passwords of course). + + - IMAPClient.pm version. + + - the run context. Do you run imapsync.exe, a unix binary + or the perl script imapsync. + + - operating system running imapsync. + + - virtual software context (vmware, xen etc.) + + - operating systems on both sides and the third side in case + you run imapsync on a foreign host from the both. + +Most of those values can be found as a copy/paste at the begining of the output, +so a carbon copy of the output is a very easy and very good debug report for me. + +One time in your life, read the paper +"How To Ask Questions The Smart Way" +http://www.catb.org/~esr/faqs/smart-questions.html +and then forget it. + +=head1 IMAP SERVERS + +See http://imapsync.lamiral.info/S/imapservers.shtml + +=head1 HUGE MIGRATION + +Pay special attention to options +--subscribed +--subscribe +--delete +--delete2 +--delete2folders +--maxage +--minage +--maxsize +--useuid +--usecache + +If you have many mailboxes to migrate think about a little +shell program. Write a file called file.txt (for example) +containing users and passwords. +The separator used in this example is ';' + +The file.txt file contains: + +user001_1;password001_1;user001_2;password001_2 +user002_1;password002_1;user002_2;password002_2 +user003_1;password003_1;user003_2;password003_2 +user004_1;password004_1;user004_2;password004_2 +user005_1;password005_1;user005_2;password005_2 +... + +On Unix the shell program can be: + + { while IFS=';' read u1 p1 u2 p2; do + imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \ + --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ... + done ; } < file.txt + +On Windows the batch program can be: + + FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^ + --host1 imap.side1.org --user1 %%G --password1 %%H ^ + --host2 imap.side2.org --user2 %%I --password2 %%J ... + +The ... have to be replaced by nothing or any imapsync option. +Welcome in shell programming ! + +You will find already written scripts at +http://imapsync.lamiral.info/examples/ + + +=head1 HACKING + +Feel free to hack imapsync as the NOLIMIT license permits it. + +=head1 LINKS + +Entries for imapsync: +https://web.archive.org/web/20070202005121/http://www.imap.org/products/showall.php + +=head1 SIMILAR SOFTWARES + + imap_tools : http://www.athensfbc.com/imap_tools + offlineimap : https://github.com/nicolas33/offlineimap + 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://home.arcor.de/armin.diehl/imapcopy/imapcopy.html + 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/ + + +Feedback (good or bad) will often be welcome. + +$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $ + +=cut + + +# pragmas + +use strict ; +use warnings ; +++$| ; + +use Carp ; +use Data::Dumper ; +use Digest::HMAC_SHA1 qw( hmac_sha1 ) ; +use Digest::MD5 qw( md5 md5_hex md5_base64 ) ; +use English qw( -no_match_vars ) ; +use Errno qw(EAGAIN EPIPE ECONNRESET) ; +use Fcntl ; +use File::Basename ; +use File::Copy::Recursive ; +use File::Glob qw( :glob ) ; +use File::Path qw( mkpath rmtree ) ; +use File::Spec ; +use File::stat ; +#use Imapsync::Getopt::Long ; +use IO::File ; +use IO::Socket qw(:crlf SOL_SOCKET SO_KEEPALIVE) ; +#use IO::Socket::SSL ; +use IO::Tee ; +use IPC::Open3 'open3' ; +use Mail::IMAPClient 3.30 ; +use MIME::Base64 ; +use POSIX qw(uname SIGALRM) ; +use Term::ReadKey ; +use Test::More ; +use Time::HiRes qw( time sleep ) ; +use Time::Local ; +use Unicode::String ; +use Cwd ; +use Readonly ; + +# constants + +# Let us do like sysexits.h +# /usr/include/sysexits.h + +Readonly my $EX_OK => 0 ; #/* successful termination */ +Readonly my $EX_USAGE => 64 ; #/* command line usage error */ +#Readonly my $EX_DATAERR => 65 ; #/* data format error */ +#Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */ +#Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */ +#Readonly my $EX_NOHOST => 68 ; #/* host name unknown */ +#Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */ +Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */ +#Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */ +#Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */ +#Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */ +#Readonly my $EX_IOERR => 74 ; #/* input/output error */ +#Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */ +#Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */ +#Readonly my $EX_NOPERM => 77 ; #/* permission denied */ +#Readonly my $EX_CONFIG => 78 ; #/* configuration error */ + +# Mine +Readonly my $EXIT_BY_SIGNAL => 6 ; +Readonly my $EXIT_PID_FILE_ALREADY_EXIST => 8 ; +Readonly my $EXIT_WITH_ERRORS => 111 ; +Readonly my $EXIT_WITH_ERRORS_MAX => 112 ; +Readonly my $EXIT_UNKNOWN => 126 ; + +Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors. + + +Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect + +Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more. +Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split ) + # which means default Maxcommandlength is 10*100 = 1000 characters ; + +Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP +Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL + +Readonly my $LAST => -1 ; +Readonly my $MINUS_ONE => -1 ; + +Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ; +Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ; + + +Readonly my $DEFAULT_TIMEOUT => 120 ; +Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ; +Readonly my $DEFAULT_UIDNEXT => 999999 ; +Readonly my $DEFAULT_BUFFER_SIZE => 4096 ; + +Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ; + +Readonly my $PERMISSION_FILTER => 7777 ; + +Readonly my $KIBI => 1024 ; + +Readonly my $NUMBER_10 => 10 ; +Readonly my $NUMBER_42 => 42 ; +Readonly my $NUMBER_100 => 100 ; +Readonly my $NUMBER_200 => 200 ; +Readonly my $NUMBER_300 => 300 ; + +Readonly my $NUMBER_20_000 => 20_000 ; + +Readonly my $QUOTA_PERCENT_LIMIT => 90 ; + +Readonly my $NUMBER_104857600 => 104857600 ; + +Readonly my $SIZE_MAX_STR => 64 ; + +Readonly my $NB_SECONDS_IN_A_DAY => 86400 ; + +Readonly my $STD_CHAR_PER_LINE => 80 ; + +Readonly my $TRUE => 1 ; +Readonly my $FALSE => 0 ; + +Readonly my $LAST_RESSORT_SEPARATOR => q{/} ; + +# global variables + +my( + $sync, + $rcs, + $debug, $debugimap, $debugimap1, $debugimap2, $debugcontent, $debugflags, + $debuglist, $debugdev, $debugmaxlinelength, @debugbasket, $debugcgi, + $host1, $host2, $port1, $port2, + $user1, $user2, $domain1, $domain2, + $password1, $password2, $passfile1, $passfile2, + @folder, @include, @exclude, @folderrec, + @folderfirst, @folderlast, + $prefix1, $prefix2, + $subfolder2, + @regextrans2, @regexmess, @regexflag, @skipmess, @pipemess, $pipemesscheck, + $flagscase, $filterflags, $syncflagsaftercopy, + $sep1, $sep2, + $syncinternaldates, + $idatefromheader, + $syncacls, + $fastio1, $fastio2, + $maxsize, $minsize, $maxage, $minage, + $exitwhenover, + $search, $search1, $search2, + $skipheader, @useheader, + $skipsize, $allowsizemismatch, $foldersizes, $foldersizesatend, $buffersize, + $delete, $delete2, $delete2duplicates, + $expunge, $expunge1, $expunge2, $uidexpunge2, $dry, + $justfoldersizes, + $authmd5, $authmd51, $authmd52, + $subscribed, $subscribe, $subscribeall, + $version, $help, + $justconnect, $justfolders, $justbanner, + $fast, + + $total_bytes_transferred, + $total_bytes_skipped, + $total_bytes_error, + $nb_msg_transferred, + $nb_msg_skipped, + $nb_msg_skipped_dry_mode, + $h1_nb_msg_duplicate, + $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, + $h2_nb_msg_end, $h2_bytes_end, + + $timeout, + $timestart_int, $timeend, + $timebefore, + $ssl1, $ssl2, + $ssl1_ssl_version, $ssl2_ssl_version, + $tls1, $tls2, + $uid1, $uid2, + $authuser1, $authuser2, + $proxyauth1, $proxyauth2, + $authmech1, $authmech2, + $split1, $split2, + $reconnectretry1, $reconnectretry2, + $tests, $test_builder, $testsdebug, $testslive, + $justlogin, + $tmpdir, + $releasecheck, + $max_msg_size_in_bytes, + $modulesversion, + $delete2folders, $delete2foldersonly, $delete2foldersbutnot, + $usecache, $debugcache, $cacheaftercopy, + $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess, + $addheader, + %h1, %h2, + $checkselectable, $checkmessageexists, + $expungeaftereach, + $abletosearch, + $showpasswords, + $fixslash2, + $messageidnodomain, + $fixInboxINBOX, + $maxlinelength, $maxlinelengthcmd, + $minmaxlinelength, + $uidnext_default, + $fixcolonbug, + $create_folder_old, + $maxmessagespersecond, + $maxbytespersecond, + $skipcrossduplicates, $debugcrossduplicates, + $disarmreadreceipts, + $mixfolders, $skipemptyfolders, + $fetch_hash_set, +); + +# main program + +# global variables initialisation + +$rcs = q{$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $} ; + +$total_bytes_transferred = 0; +$total_bytes_skipped = 0; +$total_bytes_error = 0; +$nb_msg_transferred = 0; +$nb_msg_skipped = $nb_msg_skipped_dry_mode = 0; +$h1_nb_msg_deleted = $h2_nb_msg_deleted = 0; +$h1_nb_msg_duplicate = $h2_nb_msg_duplicate = 0; +$h1_nb_msg_noheader = $h2_nb_msg_noheader = 0; +$h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 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 ; + +#$h1_nb_msg_end = $h1_bytes_end = 0 ; +#$h2_nb_msg_end = $h2_bytes_end = 0 ; + +$sync->{nb_errors} = 0; +$max_msg_size_in_bytes = 0; + +my %month_abrev = ( + Jan => '00', + Feb => '01', + Mar => '02', + Apr => '03', + May => '04', + Jun => '05', + Jul => '06', + Aug => '07', + Sep => '08', + Oct => '09', + Nov => '10', + Dec => '11', +); + + + + +# @ARGV will be eat by get_options() +my @argv_copy = @ARGV; + +my $cgi_dir = '/var/tmp/imapsync_cgi' ; + +# Under CGI environment +if ( $ENV{SERVER_SOFTWARE} ) { + myprint( "\n" ) ; + myprint( "
\n" ) ;
+        -d $cgi_dir or mkpath $cgi_dir or die "Can not create $cgi_dir: $!\n" ;
+        chdir  $cgi_dir or die "Can not cd to $cgi_dir: $!\n" ;
+}
+
+get_options(  ) ;
+unsetunsafe(  ) if ( $ENV{SERVER_SOFTWARE} ) ;
+
+# Under CGI environment
+if ( $ENV{SERVER_SOFTWARE} ) {
+        myprint( 'Current directory is ' . getcwd(  ) . "\n" ) ;
+        myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ;
+        myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ;
+}
+
+local $SIG{ INT } = sub {
+        my $signame = shift ;
+        catch_reconnect( $sync, $signame ) ;
+} ;
+
+local $SIG{ QUIT } = local $SIG{ TERM } = sub {
+	my $signame = shift ;
+        catch_exit( $sync, $signame ) ;
+} ;
+
+
+$sync->{timestart} = $BASETIME ; # Never too let reading books and perlvar
+
+$sync->{log}        = defined $sync->{log}        ? $sync->{log}        :  1 ;
+$sync->{errorsdump} = defined $sync->{errorsdump} ? $sync->{errorsdump} :  1 ;
+$sync->{errorsmax}  = defined $sync->{errorsmax}  ? $sync->{errorsmax}  : $ERRORS_MAX ;
+
+$sync->{user2} = $user2 ;
+
+if ( $sync->{log} ) {
+        setlogfile( $sync ) ;
+        teelaunch( $sync ) ;
+}
+
+$timestart_int = int( $sync->{timestart} ) ;
+$timebefore =    $sync->{timestart} ;
+
+my $timestart_str = localtime( $sync->{timestart} ) ;
+myprint( "Transfer started at $timestart_str\n" ) ;
+myprint( "PID is $PROCESS_ID\n" ) ;
+myprint( "Log file is $sync->{logfile} ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) if ( $sync->{log} ) ;
+$modulesversion = defined  $modulesversion  ? $modulesversion : 1 ;
+
+# If you want releasecheck not to be done by default (like the github maintainer),
+# then uncomment the first "$releasecheck =" line, the line ending with "0 ;".
+# The second line (ending with "1 ;") can stay active or be commented,
+# the result will be the same: no releasecheck by default.
+
+$releasecheck = defined  $releasecheck  ? $releasecheck : 0 ;
+#$releasecheck = defined  $releasecheck  ? $releasecheck : 1 ;
+
+my $warn_release = ( $releasecheck ) ? check_last_release(  ) : q{} ;
+
+# default values
+
+$sync->{pidfile} =  defined  $sync->{pidfile}  ? $sync->{pidfile} : $tmpdir . '/imapsync.pid' ;
+
+$sync->{pidfilelocking} = defined  $sync->{pidfilelocking}  ? $sync->{pidfilelocking} : 0 ;
+
+$wholeheaderifneeded  = defined  $wholeheaderifneeded   ? $wholeheaderifneeded  : 1;
+
+# turn on RFC standard flags correction like \SEEN -> \Seen
+$flagscase = defined  $flagscase  ? $flagscase : 1 ;
+
+# Use PERMANENTFLAGS if available
+$filterflags = defined  $filterflags  ? $filterflags : 1 ;
+
+# sync flags just after an APPEND, some servers ignore the flags given in the APPEND
+# like MailEnable IMAP server.
+# Off by default since it takes time.
+$syncflagsaftercopy = defined  $syncflagsaftercopy   ? $syncflagsaftercopy : 0 ;
+
+
+# Activate --usecache if --useuid is set and no --nousecache
+$usecache = 1 if ( $useuid and ( ! defined  $usecache   ) ) ;
+$cacheaftercopy = 1 if ( $usecache and ( ! defined  $cacheaftercopy  ) ) ;
+
+$checkselectable    = defined  $checkselectable  ? $checkselectable : 1 ;
+$checkmessageexists = defined  $checkmessageexists  ? $checkmessageexists : 0 ;
+$expungeaftereach   = defined  $expungeaftereach  ? $expungeaftereach : 1 ;
+$abletosearch       = defined  $abletosearch  ? $abletosearch : 1 ;
+$checkmessageexists = 0 if ( not $abletosearch ) ;
+$showpasswords      = defined  $showpasswords  ? $showpasswords : 0 ;
+$fixslash2          = defined  $fixslash2  ? $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  ) ) ;
+
+$maxmessagespersecond = defined  $maxmessagespersecond  ? $maxmessagespersecond : 0 ;
+$maxbytespersecond    = defined  $maxbytespersecond     ? $maxbytespersecond    : 0 ;
+
+myprint( banner_imapsync( @argv_copy ) ) ;
+
+myprint( "Temp directory is $tmpdir  ( to change it use --tmpdir dirpath )\n") ;
+
+is_valid_directory( $tmpdir ) || croak "Error creating tmpdir $tmpdir : $!" ;
+
+if ( $sync->{pidfile} ) {
+        write_pidfile( $sync->{pidfile}, $sync->{pidfilelocking} ) ;
+}
+
+$fixcolonbug = defined  $fixcolonbug  ? $fixcolonbug : 1 ;
+
+if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug(  ) } ;
+
+$modulesversion and myprint( "Modules version list:\n", modulesversion(), "( use --no-modulesversion to turn off printing this Perl modules list )\n" ) ;
+
+my $DEFAULT_SSL_VERIFY ;
+my %SSL_VERIFY_STR ;
+
+if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
+        Readonly $DEFAULT_SSL_VERIFY => IO::Socket::SSL::SSL_VERIFY_NONE(  ) ;
+        Readonly %SSL_VERIFY_STR => (
+                IO::Socket::SSL::SSL_VERIFY_NONE(  ) => 'SSL_VERIFY_NONE' ,
+                IO::Socket::SSL::SSL_VERIFY_PEER(  ) => 'SSL_VERIFY_PEER' ,
+        ) ;
+        $IO::Socket::SSL::DEBUG = $sync->{debugssl} || 1 ;
+        myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ;
+}
+
+if ( $ssl1 ) {
+        myprint( 'Host1: SSL default mode is like --sslargs1 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host1 (do not check the certificate server)\n" ) ;
+        myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host1\n" ) ;
+}
+if ( $ssl2 ) {
+        myprint( 'Host2: SSL default mode is like --sslargs2 SSL_verify_mode=' . $DEFAULT_SSL_VERIFY . " meaning $SSL_VERIFY_STR{$DEFAULT_SSL_VERIFY} on host2 (do not check the certificate server)\n" ) ;
+        myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER(  ) . " for $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER(  )} on host2\n" ) ;
+}
+
+
+check_lib_version(  ) or
+  croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n";
+
+exit_clean( $sync, $EX_OK ) if ( $justbanner ) ;
+
+
+$split1 ||= $SPLIT ;
+$split2 ||= $SPLIT ;
+
+$host1 || missing_option( '--host1' ) ;
+$port1 ||= ( $ssl1 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
+
+$host2 || missing_option( '--host2' ) ;
+$port2 ||= ( $ssl2 ) ? $IMAP_SSL_PORT : $IMAP_PORT ;
+
+$debugimap1 = $debugimap2 = 1 if ( $debugimap ) ;
+$debug = 1 if ( $debugimap1 or $debugimap2 ) ;
+
+# By default, don't take size to compare
+$skipsize = (defined $skipsize) ? $skipsize : 1;
+
+$uid1 = defined $uid1 ? $uid1 : 1;
+$uid2 = defined $uid2 ? $uid2 : 1;
+
+$subscribe = defined $subscribe ? $subscribe : 1;
+
+# Allow size mismatch by default
+$allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1;
+
+$delete2folders = 1
+    if ( defined  $delete2foldersbutnot  or defined  $delete2foldersonly  ) ;
+
+if ( $justconnect ) {
+	justconnect(  ) ;
+	exit_clean( $sync, $EX_OK ) ;
+}
+
+$user1 || missing_option( '--user1' ) ;
+$user2 || missing_option( '--user2' ) ;
+
+$syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1;
+
+# Turn on expunge if there is not explicit option --noexpunge and option
+# --delete is given.
+# Done because --delete --noexpunge is very dangerous on the second run:
+# the Deleted flag is then synced to all previously transfered messages.
+# So --delete implies --expunge is a better usability default behaviour.
+if ( $delete ) {
+	if ( ! defined  $expunge  ) {
+		myprint( "Info: turning on --expunge1 because --delete --noexpunge1 is very dangerous on the second run.\n" ) ;
+		$expunge = 1 ;
+	}
+		myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ;
+}
+
+if ( $uidexpunge2 and not Mail::IMAPClient->can( 'uidexpunge' ) ) {
+        myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use --expunge2 instead\n" ) ;
+        exit_clean( $sync, $EX_SOFTWARE ) ;
+}
+
+if ( ( $delete2 or $delete2duplicates ) and not defined  $uidexpunge2  ) {
+        if ( Mail::IMAPClient->can( 'uidexpunge' ) ) {
+                myprint( "Info: will act as --uidexpunge2\n" ) ;
+		$uidexpunge2 = 1 ;
+        }elsif ( not defined  $expunge2  ) {
+                 myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ;
+                $expunge2 = 1 ;
+        }
+}
+
+if ( $delete and $delete2 ) {
+	myprint( "Warning: using --delete and --delete2 together is almost always a bad idea, exiting imapsync\n" ) ;
+	exit_clean( $sync, $EX_USAGE ) ;
+}
+
+if ( $idatefromheader ) {
+	myprint( 'Turned ON idatefromheader, ',
+	      "will set the internal dates on host2 from the 'Date:' header line.\n" ) ;
+	$syncinternaldates = 0 ;
+}
+
+if ( $syncinternaldates ) {
+	myprint( 'Info: turned ON syncinternaldates, ',
+	      "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ;
+}else{
+        myprint( "Info: turned OFF syncinternaldates\n" ) ;
+}
+
+if ( defined $authmd5 and $authmd5 ) {
+	$authmd51 = 1 ;
+	$authmd52 = 1 ;
+}
+
+if ( defined $authmd51 and $authmd51 ) {
+	$authmech1 ||= 'CRAM-MD5';
+}
+else{
+	$authmech1 ||= $authuser1 ? 'PLAIN' : 'LOGIN';
+}
+
+if ( defined $authmd52 and $authmd52 ) {
+	$authmech2 ||= 'CRAM-MD5';
+}
+else{
+	$authmech2 ||= $authuser2 ? 'PLAIN' : 'LOGIN';
+}
+
+$authmech1 = uc $authmech1;
+$authmech2 = uc $authmech2;
+
+if (defined $proxyauth1 && !$authuser1) {
+        missing_option( 'With --proxyauth1, --authuser1' ) ;
+}
+
+if (defined $proxyauth2 && !$authuser2) {
+        missing_option( 'With --proxyauth2, --authuser2' ) ;
+}
+
+$authuser1 ||= $user1;
+$authuser2 ||= $user2;
+
+myprint( "Host1: will try to use $authmech1 authentication on host1\n") ;
+myprint( "Host2: will try to use $authmech2 authentication on host2\n") ;
+
+$timeout = defined  $timeout  ? $timeout : $DEFAULT_TIMEOUT ;
+
+$sync->{h1}->{timeout} = defined  $sync->{h1}->{timeout}  ? $sync->{h1}->{timeout} : $timeout ;
+myprint( "Host1: imap connexion timeout is $sync->{h1}->{timeout} seconds\n") ;
+$sync->{h2}->{timeout} = defined  $sync->{h2}->{timeout}  ? $sync->{h2}->{timeout} : $timeout ;
+myprint( "Host2: imap connexion 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 ; }
+
+$foldersizes      = ( defined  $foldersizes       ) ? $foldersizes      : 1 ;
+$foldersizesatend = ( defined  $foldersizesatend  ) ? $foldersizesatend : $foldersizes ;
+
+$fastio1 = defined  $fastio1  ? $fastio1 : 0 ;
+$fastio2 = defined  $fastio2  ? $fastio2 : 0 ;
+
+$reconnectretry1 = defined  $reconnectretry1  ? $reconnectretry1 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
+$reconnectretry2 = defined  $reconnectretry2  ? $reconnectretry2 : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ;
+
+# Since select_msgs() returns no messages when uidnext does not return something
+# then $uidnext_default is never used. So I have to remove it.
+$uidnext_default = $DEFAULT_UIDNEXT ;
+
+@useheader = qw( Message-Id Received ) unless ( @useheader ) ;
+
+my %useheader ;
+
+# Make a hash %useheader of each --useheader 'key' in uppercase
+for ( @useheader ) { $useheader{ uc  $_  } = undef } ;
+
+#myprint( Data::Dumper->Dump( [ \%useheader ] )  ) ;
+#exit ;
+
+myprint( "Host1: IMAP server [$host1] port [$port1] user [$user1]\n" ) ;
+myprint( "Host2: IMAP server [$host2] port [$port2] user [$user2]\n" ) ;
+
+$password1 || $passfile1 || 'PREAUTH' eq $authmech1 || 'EXTERNAL' eq $authmech1 || do {
+	myprint( << 'FIN_PASSFILE'  ) ;
+
+If you are afraid of giving password on the command line arguments, you can put the
+password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it.
+Then give this file restrictive permissions with the command "chmod 600 file1".
+FIN_PASSFILE
+
+	$password1 = ask_for_password( $authuser1 || $user1, $host1 ) ;
+} ;
+
+$password1 = ( defined  $passfile1  ) ? firstline ( $passfile1 ) : $password1 ;
+
+
+$password2 || $passfile2 || 'PREAUTH' eq $authmech2 || 'EXTERNAL' eq $authmech2 || do {
+	myprint( << 'FIN_PASSFILE'  ) ;
+
+If you are afraid of giving password on the command line arguments, you can put the
+password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it.
+Then give this file restrictive permissions with the command "chmod 600 file2".
+FIN_PASSFILE
+
+	$password2 = ask_for_password( $authuser2 || $user2, $host2 ) ;
+} ;
+
+$password2 = ( defined  $passfile2  ) ? firstline ( $passfile2 ) : $password2 ;
+
+
+# need clean up => write methods dry() and dry_message()
+$sync->{dry} = $dry ;
+my $dry_message = q{} ;
+if( $sync->{dry} ) {
+        $dry_message = "\t(not really since --dry mode)" ;
+}
+$sync->{dry_message} = $dry_message ;
+
+
+$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} ;
+}
+
+$pipemesscheck = ( defined  $pipemesscheck  ) ? $pipemesscheck : 1 ;
+
+if ( @pipemess and $pipemesscheck ) {
+	myprint( 'Checking each --pipemess command, ' 
+                . join( q{, }, @pipemess ) 
+                . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ;
+	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" ) ;
+        }
+	myprint( "Ok with each --pipemess @pipemess\n"  ) ;
+}
+
+if ( $maxlinelengthcmd ) {
+	myprint( "Checking  --maxlinelengthcmd command,  $maxlinelengthcmd, with an space string.\n"  ) ;
+	my $string = pipemess( q{ }, $maxlinelengthcmd ) ;
+        # string undef means something was bad.
+        if ( not ( defined  $string  ) ) {
+        	die_clean( "Error: --maxlinelengthcmd command is bad, check it\n" ) ;
+        }
+	myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n"  ) ;
+}
+
+if ( @regexmess ) {
+	my $string = regexmess( q{ } ) ;
+	myprint( "Checking each --regexmess command with an space string.\n"  ) ;
+        # string undef means one of the eval regex was bad.
+        if ( not ( defined  $string  ) ) {
+        	die_clean( 'Error: one of --regexmess option is bad, check it' ) ;
+        }
+	myprint( "Ok with each --regexmess\n"  ) ;
+}
+
+if ( @skipmess ) {
+	myprint( "Checking each --skipmess command with an space string.\n"  ) ;
+	my $match = skipmess( q{ } ) ;
+        # match undef means one of the eval regex was bad.
+        if ( not ( defined  $match  ) ) {
+        	die_clean( 'Error: one of --skipmess option is bad, check it' ) ;
+        }
+	myprint( "Ok with each --skipmess\n"  ) ;
+}
+
+if ( @regexflag ) {
+	myprint( "Checking each --regexflag command with an space string.\n"  ) ;
+	my $string = flags_regex( q{ } ) ;
+	# string undef means one of the eval regex was bad.
+	if ( not ( defined  $string  ) ) {
+		die_clean( 'Error: one of --regexflag option is bad, check it' ) ;
+	}
+	myprint( "Ok with each --regexflag\n"  ) ;
+}
+
+$sync->{imap1} = my $imap1 = login_imap($host1, $port1, $user1, $domain1, $password1,
+		   $debugimap1, $sync->{h1}->{timeout}, $fastio1, $ssl1, $tls1,
+		   $authmech1, $authuser1, $reconnectretry1,
+		   $proxyauth1, $uid1, $split1, 'Host1', $sync->{h1} ) ;
+
+$sync->{imap2} = my $imap2 = login_imap($host2, $port2, $user2, $domain2, $password2,
+		 $debugimap2, $sync->{h2}->{timeout}, $fastio2, $ssl2, $tls2,
+		 $authmech2, $authuser2, $reconnectretry2,
+		 $proxyauth2, $uid2, $split2, 'Host2', $sync->{h2} ) ;
+
+
+$debug and myprint( 'Host1 Buffer I/O: ', $imap1->Buffer(), "\n" ) ;
+$debug and myprint( 'Host2 Buffer I/O: ', $imap2->Buffer(), "\n" ) ;
+
+
+die_clean( 'Not authenticated on host1' ) unless $imap1->IsAuthenticated( ) ;
+myprint( "Host1: state Authenticated\n" ) ;
+die_clean( 'Not authenticated on host2' ) unless   $imap2->IsAuthenticated( ) ;
+myprint( "Host2: state Authenticated\n" ) ;
+
+myprint( 'Host1 capability: ', join(q{ }, @{ $imap1->capability_update() || [] }), "\n" ) ;
+myprint( 'Host2 capability: ', join(q{ }, @{ $imap2->capability_update() || [] }), "\n" ) ;
+
+imap_id_stuff( $sync ) ;
+
+#quota( $imap1, 'host1' ) ; # quota on host1 is useless and pollute host2 output.
+quota( $imap2, 'host2', $sync ) ;
+
+if ( $justlogin ) {
+	$imap1->logout(  ) ;
+	$imap2->logout(  ) ;
+	exit_clean( $sync, $EX_OK ) ;
+}
+
+
+#
+# Folder stuff
+#
+
+my (
+        @h1_folders_all , %h1_folders_all , @h1_folders_wanted , %requested_folder ,
+        %h1_subscribed_folder , %h2_subscribed_folder ,
+        @h2_folders_all , %h2_folders_all , %h2_folders_all_UPPER ,
+        @h2_folders_from_1_wanted , %h2_folders_from_1_wanted ,
+        %h2_folders_from_1_several ,
+        %h2_folders_from_1_all ,
+) ;
+
+my $h1_folders_wanted_nb = 0 ; 
+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(  ) ;
+
+myprint( 'Host1: found ', scalar  @h1_folders_all , " folders.\n"  ) ;
+myprint( 'Host2: found ', scalar  @h2_folders_all , " folders.\n"  ) ;
+
+for ( @h1_folders_all ) { $h1_folders_all{ $_ } = 1 } ;
+for ( @h2_folders_all ) {
+	$h2_folders_all{ $_ } = 1 ;
+	$h2_folders_all_UPPER{ uc  $_  } = 1 ;
+} ;
+
+$sync->{h1_folders_all} = \%h1_folders_all ;
+$sync->{h2_folders_all} = \%h2_folders_all ;
+$sync->{h2_folders_all_UPPER} = \%h2_folders_all_UPPER ;
+
+# Make a hash of subscribed folders in both servers.
+
+for ( $imap1->subscribed(  ) ) { $h1_subscribed_folder{ $_ } = 1 } ;
+for ( $imap2->subscribed(  ) ) { $h2_subscribed_folder{ $_ } = 1 } ;
+
+
+if ( defined  $subfolder2  ) {
+	unshift @regextrans2,
+		q's,^${h2_prefix}(.*),${h2_prefix}${subfolder2}${h2_sep}$1,',
+		q's,^INBOX$,${h2_prefix}${subfolder2}${h2_sep}INBOX,' ;
+
+}
+
+if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) {
+	push @regextrans2, $reg ;
+}
+
+if (scalar @folder or $subscribed or scalar @folderrec) {
+	# folders given by option --folder
+	if (scalar @folder) {
+		add_to_requested_folders(@folder);
+	}
+
+	# option --subscribed
+	if ( $subscribed ) {
+		add_to_requested_folders( keys  %h1_subscribed_folder  ) ;
+	}
+
+	# option --folderrec
+	if (scalar @folderrec) {
+		foreach my $folderrec (@folderrec) {
+			add_to_requested_folders($imap1->folders($folderrec));
+		}
+	}
+}
+else {
+	# no include, no folder/subscribed/folderrec options => all folders
+	if (not scalar @include) {
+		myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n"  ) ;
+		add_to_requested_folders(@h1_folders_all);
+	}
+}
+
+
+# consider (optional) includes and excludes
+if ( scalar  @include  ) {
+	foreach my $include ( @include ) {
+		my @included_folders = grep { /$include/ } @h1_folders_all ;
+		add_to_requested_folders( @included_folders ) ;
+		myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders )  . "\n"  ) ;
+	}
+}
+
+if ( scalar  @exclude  ) {
+	foreach my $exclude ( @exclude ) {
+		my @requested_folder = sort keys %requested_folder ;
+		my @excluded_folders = grep { /$exclude/ } @requested_folder ;
+		remove_from_requested_folders( @excluded_folders ) ;
+		myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n"  ) ;
+	}
+}
+
+
+# sort before is not very powerful
+# it adds --folderfirst and --folderlast even if they don't exist on host1
+@h1_folders_wanted = sort_requested_folders(  ) ;
+
+# Remove no selectable folders
+
+
+my @h1_folders_wanted_exist ;
+myprint( "Host1: checking all wanted folders exist.\n"  ) ;
+foreach my $folder ( @h1_folders_wanted ) {
+	( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n"  ) ;
+	if ( ! exists  $h1_folders_all{ $folder }  ) {
+                myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ;
+		next ;
+	}else{
+		push  @h1_folders_wanted_exist, $folder  ;
+	}
+}
+
+@h1_folders_wanted = @h1_folders_wanted_exist ;
+
+
+
+$checkselectable and do {
+	my @h1_folders_wanted_selectable ;
+        myprint( "Host1: checking all wanted folders are selectable. Use --nocheckselectable to avoid this check.\n"  ) ;
+	foreach my $folder ( @h1_folders_wanted ) {
+        	( $debug or $sync->{debugfolders} ) and myprint( "Checking $folder is selectable on host1\n"  ) ;
+        	if ( ! $imap1->selectable( $folder ) ) {
+                                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->{h1_folders_wanted} = \@h1_folders_wanted ;
+
+
+my( $h1_sep, $h2_sep ) ;
+# what are the private folders separators for each server ?
+
+( $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 ) ;
+
+my( $h1_prefix, $h2_prefix ) ;
+$sync->{ h1_prefix } = $h1_prefix = get_prefix( $imap1, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ;
+$sync->{ h2_prefix } = $h2_prefix = get_prefix( $imap2, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ;
+
+
+myprint( "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n"  ) ;
+myprint( "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n"  ) ;
+
+automap( $sync ) ;
+
+
+foreach my $h1_fold ( @h1_folders_wanted ) {
+	my $h2_fold ;
+	$h2_fold = imap2_folder_name( $h1_fold ) ;
+	$h2_folders_from_1_wanted{ $h2_fold }++ ;
+        if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) {
+        	$h2_folders_from_1_several{ $h2_fold }++ ;
+        }
+}
+@h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted;
+
+foreach my $h1_fold ( @h1_folders_all ) {
+	my $h2_fold ;
+	$h2_fold = imap2_folder_name( $h1_fold ) ;
+	$h2_folders_from_1_all{ $h2_fold }++ ;
+}
+
+
+
+myprint( << 'END_LISTING'  ) ;
+
+++++ Listing folders
+All foldernames are presented between brackets like [X] where X is the foldername.
+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.
+
+END_LISTING
+
+print
+  "Host1 folders list:\n",
+  jux_utf8_list( @h1_folders_all ),
+  "\n",
+  "Host2 folders list:\n",
+  jux_utf8_list( @h2_folders_all ),
+  "\n" ;
+
+print
+  'Host1 subscribed folders list: ',
+  jux_utf8_list( sort keys  %h1_subscribed_folder  ), "\n"
+  if ( $subscribed ) ;
+
+my @h2_folders_not_in_1;
+@h2_folders_not_in_1 = list_folders_in_2_not_in_1(  ) ;
+
+if ( @h2_folders_not_in_1 ) {
+	myprint( "Folders in host2 not in host1:\n",
+	jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ;
+}
+
+
+if ( defined  $sync->{f1f2auto}  ) {
+	myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n"  ) ;
+	foreach my $h1_fold ( keys %{$sync->{f1f2auto}} ) {
+        	my $h2_fold = $sync->{f1f2auto}{$h1_fold} ;
+		myprintf( "%-40s -> %-40s\n",
+		       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
+        }
+        myprint( "\n"  ) ;
+}
+
+if ( defined  $sync->{f1f2}  ) {
+	myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n"  ) ;
+	foreach my $h1_fold ( keys %{$sync->{f1f2}} ) {
+        	my $h2_fold = $sync->{f1f2}{$h1_fold} ;
+                my $warn = q{} ;
+                if ( not exists  $h1_folders_all{ $h1_fold }  ) {
+                        $warn = "BUT $h1_fold does NOT exist on host1!" ;
+                }
+		myprintf( "%-40s -> %-40s %s\n",
+		       jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ;
+        }
+        myprint( "\n"  ) ;
+}
+
+exit_clean( $sync, $EX_OK ) if ( $sync->{justfolderlists} ) ;
+exit_clean( $sync, $EX_OK ) if ( $sync->{justautomap} ) ;
+
+debugsleep( $sync ) ;
+
+if ( $foldersizes ) {
+        foldersizes_on_h1h2(  ) ;
+}
+
+
+exit_clean( $sync, $EX_OK ) if ( $justfoldersizes ) ;
+
+$sync->{stats} = 1 ;
+
+if ( $sync->{'delete1emptyfolders'} ) {
+        delete1emptyfolders( $sync ) ;
+}
+
+delete_folders_in_2_not_in_1(  ) if $delete2folders ;
+
+# folder loop
+$h1_folders_wanted_nb = scalar  @h1_folders_wanted  ;
+
+myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ;
+
+my $begin_transfer_time = time ;
+
+my %uid_candidate_for_deletion ;
+my %uid_candidate_no_deletion ;
+
+my %h2_folders_of_md5 = (  ) ;
+
+FOLDER: foreach my $h1_fold ( @h1_folders_wanted ) {
+
+        last FOLDER if $imap1->IsUnconnected(  ) ;
+        last FOLDER if $imap2->IsUnconnected(  ) ;
+
+	my $h2_fold = imap2_folder_name( $h1_fold ) ;
+
+	$h1_folders_wanted_ct++ ;
+	myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb",
+		jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ;
+        if ( $sync->{debugmemory} ) {
+                myprintf("FL: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+	# host1 can not be fetched read only, select is needed because of expunge.
+	select_folder( $imap1, $h1_fold, 'Host1' ) or next FOLDER ;
+
+        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" ) ;
+
+        if ( $skipemptyfolders and 0 == $h1_fold_nb_messages ) {
+        	myprint( "Skipping empty host1 folder [$h1_fold]\n"  ) ;
+                next FOLDER ;
+        }
+
+	if ( ! exists  $h2_folders_all{ $h2_fold }  ) {
+		create_folder( $imap2, $h2_fold, $h1_fold ) or next FOLDER ;
+	}
+
+	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' ) )
+        or next FOLDER ;
+	my @select_results = $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" ) ;
+
+	my $permanentflags2 = permanentflags( @select_results ) ;
+	( $debug or $debugflags ) and myprint( "Host2 folder [$h2_fold] permanentflags: $permanentflags2\n"  ) ;
+
+	if ( $expunge or $expunge1 ){
+		myprint( "Host1: Expunging $h1_fold $dry_message\n"  ) ;
+		unless( $dry ) { $imap1->expunge(  ) } ;
+	}
+
+	if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall )
+             and not exists  $h2_subscribed_folder{ $h2_fold }  ) {
+		myprint( "Host2: Subscribing to folder $h2_fold\n"  ) ;
+		unless( $dry ) { $imap2->subscribe( $h2_fold ) } ;
+	}
+
+	next FOLDER if ( $justfolders ) ;
+
+        last FOLDER if $imap1->IsUnconnected(  ) ;
+        last FOLDER if $imap2->IsUnconnected(  ) ;
+
+        my $h1_msgs_all_hash_ref = {  } ;
+	my @h1_msgs = select_msgs( $imap1, $h1_msgs_all_hash_ref, $search1, $h1_fold );
+	last FOLDER if $imap1->IsUnconnected(  ) ;
+
+        my $h1_msgs_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" ) ;
+
+        my $h2_msgs_all_hash_ref = {  } ;
+	my @h2_msgs = select_msgs( $imap2, $h2_msgs_all_hash_ref, $search2, $h2_fold ) ;
+	last FOLDER if $imap2->IsUnconnected(  ) ;
+
+        my $h2_msgs_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" ) ;
+
+	my $cache_base = "$tmpdir/imapsync_cache/" ;
+	my $cache_dir = cache_folder( $cache_base, "$host1/$user1/$host2/$user2", $h1_fold, $h2_fold ) ;
+	my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ;
+
+	my $h1_uidvalidity = $imap1->uidvalidity(  ) || q{} ;
+	my $h2_uidvalidity = $imap2->uidvalidity(  ) || q{} ;
+
+        last FOLDER if $imap1->IsUnconnected(  ) ;
+        last FOLDER if $imap2->IsUnconnected(  ) ;
+
+	if ( $usecache ) {
+		myprint( "cache directory: $cache_dir\n"  ) ;
+		mkpath( "$cache_dir" ) ;
+		( $cache_1_2_ref, $cache_2_1_ref )
+                = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
+		myprint( 'CACHE h1 h2: ', scalar  keys %{ $cache_1_2_ref } , " files\n"  ) ;
+		$debug and myprint( '[',
+		    map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ;
+	}
+
+	my %h1_hash = () ;
+	my %h2_hash = () ;
+
+	my ( %h1_msgs, %h2_msgs ) ;
+	@h1_msgs{ @h1_msgs } = ();
+	@h2_msgs{ @h2_msgs } = ();
+
+	my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ;
+	my @h2_msgs_in_cache = keys %{ $cache_2_1_ref } ;
+
+	my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ;
+	%h1_msgs_not_in_cache = %h1_msgs ;
+	%h2_msgs_not_in_cache = %h2_msgs ;
+	delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ;
+	delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ;
+
+	my @h1_msgs_not_in_cache = keys %h1_msgs_not_in_cache ;
+	#myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n"  ) ;
+	my @h2_msgs_not_in_cache = keys %h2_msgs_not_in_cache ;
+
+	my @h2_msgs_delete2_not_in_cache = () ;
+	%h1_msgs_copy_by_uid = (  ) ;
+
+	if ( $useuid ) {
+		# use uid so we have to avoid getting header
+		@h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = (  ) ;
+		@h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ;
+		@h1_msgs_not_in_cache = (  ) ;
+		@h2_msgs_not_in_cache = (  ) ;
+
+		#myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ;
+	}
+
+	$debug and myprint( "Host1 parsing headers of folder [$h1_fold]\n" ) ;
+
+	my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
+	$h1_heads_ref = $imap1->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache);
+	$debug and myprint( "Host1 parsing headers of folder [$h1_fold] took ", timenext(), " s\n" ) ;
+
+	@{ $h1_fir_ref }{@h1_msgs} = ( undef ) ;
+
+	$debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold]\n"  ) ;
+        if ( $abletosearch ) {
+		$h1_fir_ref = $imap1->fetch_hash( \@h1_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
+	  	if ( @h1_msgs ) ;
+        }else{
+		my $uidnext = $imap1->uidnext( $h1_fold ) || $uidnext_default ;
+		my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+		$h1_fir_ref = $imap1->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h1_fir_ref )
+		if ( @h1_msgs ) ;
+        }
+	$debug and myprint( "Host1 getting flags idate and sizes of folder [$h1_fold] took ", timenext(), " s\n"  ) ;
+	unless ($h1_fir_ref) {
+		my $error = join( q{}, "Host1 folder $h1_fold: Could not fetch_hash ",
+			scalar @h1_msgs, ' msgs: ', $imap1->LastError || q{}, "\n" ) ;
+		errors_incr( $sync, $error ) ;
+		next FOLDER ;
+	}
+
+	my @h1_msgs_duplicate;
+	foreach my $m (@h1_msgs_not_in_cache) {
+		my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash);
+		if (! defined $rc) {
+			my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
+			myprint( "Host1 $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n"  ) ;
+			$total_bytes_skipped += $h1_size;
+			$nb_msg_skipped += 1;
+			$h1_nb_msg_noheader +=1;
+                        $h1_nb_msg_processed +=1 ;
+		} elsif(0 == $rc) {
+			# duplicate
+			push @h1_msgs_duplicate, $m;
+			# duplicate, same id same size?
+			my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0;
+			$nb_msg_skipped += 1;
+			$h1_total_bytes_duplicate += $h1_size;
+			$h1_nb_msg_duplicate += 1;
+                        $h1_nb_msg_processed +=1 ;
+		}
+	}
+        my $h1_msgs_duplicate_nb = scalar  @h1_msgs_duplicate  ;
+        $h1{ $h1_fold }{ 'duplicates_nb' } = $h1_msgs_duplicate_nb ;
+
+        $debug and myprint( "Host1 selected: $h1_msgs_nb  duplicates: $h1_msgs_duplicate_nb\n"  ) ;
+	$debug and myprint( 'Host1 whole time parsing headers took ', timenext(), " s\n"  ) ;
+
+	$debug and myprint( "Host2 parsing headers of folder [$h2_fold]\n" ) ;
+
+	my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} );
+	$h2_heads_ref =   $imap2->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache);
+	$debug and myprint( "Host2 parsing headers of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
+
+	$debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold]\n"  ) ;
+	@{ $h2_fir_ref }{@h2_msgs} = (  ); # fetch_hash can select by uid with last arg as ref
+
+
+        if ( $abletosearch ) {
+		$h2_fir_ref = $imap2->fetch_hash( \@h2_msgs, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref)
+		if (@h2_msgs) ;
+        }else{
+		my $uidnext = $imap2->uidnext( $h2_fold ) || $uidnext_default ;
+		my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+		$h2_fir_ref = $imap2->fetch_hash( $fetch_hash_uids, 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE', $h2_fir_ref )
+		if ( @h2_msgs ) ;
+        }
+
+	$debug and myprint( "Host2 getting flags idate and sizes of folder [$h2_fold] took ", timenext(), " s\n"  ) ;
+
+	my @h2_msgs_duplicate;
+	foreach my $m (@h2_msgs_not_in_cache) {
+		my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash) ;
+		my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ;
+		if (! defined  $rc  ) {
+                        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 } ++ ;
+        }
+
+
+        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"  ) ;
+
+	$debug and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ;
+	# messages in host1 that are not in host2
+
+	my @h1_hash_keys_sorted_by_uid
+	  = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash;
+
+	#myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ;
+
+	my @h2_hash_keys_sorted_by_uid
+	  = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash;
+
+
+	if( $delete2duplicates and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+		my @h2_expunge ;
+
+		foreach my $h2_msg ( @h2_msgs_duplicate ) {
+			myprint( "msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $dry_message\n"  ) ;
+			push  @h2_expunge, $h2_msg  if $uidexpunge2 ;
+			unless ( $dry ) {
+				$imap2->delete_message( $h2_msg ) ;
+				$h2_nb_msg_deleted += 1 ;
+			}
+		}
+		my $cnt = scalar @h2_expunge ;
+		if( @h2_expunge and not $expunge2 ) {
+			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
+			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
+		}
+        	if ( $expunge2 ){
+                	myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
+                	$imap2->expunge(  ) if ! $dry ;
+        	}
+	}
+
+	if( $delete2 and not exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+        	# No host1 folders f1a f1b ... going all to same f2 (via --regextrans2)
+		my @h2_expunge;
+		foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
+			#myprint( "$m_id " ) ;
+			unless (exists $h1_hash{$m_id}) {
+				my $h2_msg  = $h2_hash{$m_id}{'m'};
+				my $h2_flags  = $h2_hash{$m_id}{'F'} || q{};
+				my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0;
+				myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $dry_message\n" )
+				  if ! $isdel;
+				push @h2_expunge, $h2_msg if $uidexpunge2;
+				unless ($dry or $isdel) {
+					$imap2->delete_message($h2_msg);
+					$h2_nb_msg_deleted += 1;
+				}
+			}
+		}
+		foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
+			myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $dry_message\n" ) ;
+                        push @h2_expunge, $h2_msg if $uidexpunge2;
+			unless ($dry) {
+				$imap2->delete_message($h2_msg);
+				$h2_nb_msg_deleted += 1;
+			}
+		}
+		my $cnt = scalar @h2_expunge ;
+
+		if( @h2_expunge and not $expunge2 ) {
+			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
+			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
+		}
+        	if ( $expunge2 ) {
+                	myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
+                	$imap2->expunge(  ) if ! $dry ;
+        	}
+	}
+
+	if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  ) {
+        	myprint( "Host2 folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n"  ) ;
+		my @h2_expunge;
+		foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) {
+                	my $h2_msg  = $h2_hash{ $m_id }{ 'm' } ;
+			unless ( exists  $h1_hash{ $m_id }  ) {
+				my $h2_flags  = $h2_hash{ $m_id }{ 'F' } || q{} ;
+				my $isdel  = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ;
+				unless ( $isdel ) {
+                                	$debug and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n"  ) ;
+					$uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
+				}
+			}else{
+                        	$debug and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n"  ) ;
+                        	$uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                        }
+		}
+		foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) {
+			myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ;
+                        $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ;
+		}
+
+		foreach my $h2_msg ( @h2_msgs_in_cache ) {
+			myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ;
+                        $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+		}
+
+
+                if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) {
+                	# last host1 folder going to $h2_fold
+                        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"  ) ;
+                                if ( exists  $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }  ) {
+                                	$debug and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n"  ) ;
+                                }else{
+                                	myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $dry_message\n" ) ;
+                                        push  @h2_expunge, $h2_msg  if $uidexpunge2 ;
+                                        unless ( $dry ) {
+                                        	$imap2->delete_message( $h2_msg ) ;
+                                        	$h2_nb_msg_deleted += 1 ;
+                                        }
+                                }
+                        }
+                }
+
+		my $cnt = scalar @h2_expunge ;
+		if( @h2_expunge and not $expunge2 ) {
+			myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $dry_message\n"  ) ;
+			$imap2->uidexpunge( \@h2_expunge ) if ! $dry ;
+		}
+        	if ( $expunge2 ) {
+                	myprint( "Host2: Expunging host2 folder $h2_fold $dry_message\n"  ) ;
+                	$imap2->expunge(  ) if ! $dry ;
+        	}
+
+                $h2_folders_from_1_several{ $h2_fold }-- ;
+	}
+
+
+	my $h2_uidnext = $imap2->uidnext( $h2_fold ) ;
+        $debug and myprint( "Host2 uidnext: $h2_uidnext\n"  ) ;
+	$h2_uidguess = $h2_uidnext ;
+	MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
+        	last FOLDER if $imap1->IsUnconnected(  ) ;
+                last FOLDER if $imap2->IsUnconnected(  ) ;
+
+		#myprint( "h1_nb_msg_processed: $h1_nb_msg_processed\n"  ) ;
+		my $h1_size  = $h1_hash{$m_id}{'s'};
+		my $h1_msg   = $h1_hash{$m_id}{'m'};
+		my $h1_idate = $h1_hash{$m_id}{'D'};
+
+		if ( ( not exists  $h2_hash{ $m_id }  )
+                	and ( not ( exists $h2_folders_of_md5{ $m_id } )
+                              or not $skipcrossduplicates ) ) {
+			# copy
+			my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
+                        $h2_folders_of_md5{ $m_id }->{ $h2_fold } ++ ;
+                        if( $delete2 and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) {
+                        	myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
+	                        $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                        }
+                        last FOLDER if total_bytes_max_reached(  ) ;
+			next MESS;
+		}
+		else{
+		        # already on host2
+                        if ( exists  $h2_hash{ $m_id }  ) {
+				my $h2_msg   = $h2_hash{$m_id}{'m'} ;
+				$debug and myprint( "Host1 found msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n"  ) ;
+                                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 ;
+                }
+
+                if ( exists  $h2_hash{ $m_id }  ) {
+			#$debug and myprint( "MESSAGE $m_id\n" ) ;
+			my $h2_msg  = $h2_hash{$m_id}{'m'};
+
+                	sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
+	    		# Good
+			my $h2_size = $h2_hash{$m_id}{'s'};
+			$debug and myprint(
+			"Host1 size  msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ;
+		}
+                last FOLDER if $imap2->IsUnconnected(  ) ;
+
+		if ( $delete ) {
+			delete_message_on_host1( $h1_msg, $h1_fold ) ;
+		}
+	}
+	# END MESS: loop
+        last FOLDER if $imap1->IsUnconnected(  ) ;
+        last FOLDER if $imap2->IsUnconnected(  ) ;
+	MESS_IN_CACHE: foreach my $h1_msg ( @h1_msgs_in_cache ) {
+		my $h2_msg = $cache_1_2_ref->{ $h1_msg } ;
+		$debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ;
+		sync_flags_fir( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ;
+		my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ;
+		$total_bytes_skipped += $h1_size;
+		$nb_msg_skipped += 1;
+                $h1_nb_msg_processed +=1 ;
+                last FOLDER if $imap2->IsUnconnected(  ) ;
+	}
+
+	#myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n"  ) ;
+	MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) {
+		#
+		$debug and myprint( "Copy by uid $h1_fold/$h1_msg\n"  ) ;
+                last FOLDER if $imap1->IsUnconnected(  ) ;
+                last FOLDER if $imap2->IsUnconnected(  ) ;
+		my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ;
+                if( $delete2 and exists  $h2_folders_from_1_several{ $h2_fold }  and $h2_msg ) {
+                	myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n"  ) ;
+	                $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ;
+                }
+		last FOLDER if total_bytes_max_reached(  ) ;
+	}
+
+	if ( $expunge or $expunge1 ){
+		myprint( "Host1: Expunging folder $h1_fold $dry_message\n"  ) ;
+		unless( $dry ) { $imap1->expunge(  ) } ;
+	}
+	if ( $expunge2 ){
+		myprint( "Host2: Expunging folder $h2_fold $dry_message\n"  ) ;
+		unless( $dry ) { $imap2->expunge(  ) } ;
+	}
+	$debug and myprint( 'Time: ', timenext(  ), " s\n"  ) ;
+}
+
+
+sub total_bytes_max_reached {
+
+	return( 0 ) if not $exitwhenover ;
+	if ( $total_bytes_transferred >= $exitwhenover ) {
+        	myprint( "Maximum bytes transferred reached, $total_bytes_transferred >= $exitwhenover, ending sync\n"  ) ;
+        	return( 1 ) ;
+        }
+
+}
+
+myprint( "++++ End looping on each folder\n"  ) ;
+( $debug or $sync->{debugfolders} ) and myprint( 'Time: ', timenext(  ), " s\n"  ) ;
+
+
+if ( $foldersizesatend ) {
+	myprint( << 'END_SIZE'  ) ;
+
+Folders sizes after the synchronization.
+You can remove this foldersizes listing by using  "--nofoldersizesatend"
+END_SIZE
+
+	foldersizesatend(  ) ;
+}
+
+$imap1->logout(  ) unless lost_connection( $imap1, "for host1 [$host1]" ) ;
+$imap2->logout(  ) unless lost_connection( $imap2, "for host2 [$host2]" ) ;
+
+stats( $sync ) ;
+myprint( errorsdump( $sync->{nb_errors}, errors_log( $sync ) ) ) if ( $sync->{errorsdump} ) ;
+tests_live_result( $sync->{nb_errors} ) if ( $testslive ) ;
+exit_clean( $sync, $EXIT_WITH_ERRORS ) if ( $sync->{nb_errors} ) ;
+exit_clean( $sync, $EX_OK ) ;
+
+# END of main program
+
+
+# subroutines
+sub  myprint  { return print  @ARG ; } 
+sub  myprintf { return printf @ARG ; } 
+
+sub mysprintf {
+        my( $format, @list ) = @ARG ;
+        return sprintf $format, @list ; 
+}
+
+sub unsetunsafe {
+        # Remove all content in unsafe evalued options
+        @regextrans2 = (  ) ;
+        @regexflag = (  ) ;
+        @regexmess = (  ) ;
+        @skipmess = (  ) ;
+        @pipemess = (  ) ;
+        $delete2foldersonly = undef ;
+        $delete2foldersbutnot = undef ;
+        return ;
+}
+
+sub debugsleep {
+        my $mysync = shift ;
+        if ( defined $mysync->{debugsleep} ) {
+                myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ;
+                sleep $mysync->{debugsleep} ;
+        }
+        return ;
+}
+
+sub foldersizes_on_h1h2 {
+	myprint( << 'END_SIZE'  ) ;
+
+Folders sizes before the synchronization.
+You can remove foldersizes listings by using "--nofoldersizes" and  "--nofoldersizesatend"
+but then you will also loose the ETA (Estimation Time of Arrival) given after each message copy.
+END_SIZE
+
+	( $h1_nb_msg_start, $h1_bytes_start ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted        ) ;
+	( $h2_nb_msg_start, $h2_bytes_start ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
+
+        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 ) ;
+                $foldersizes = 0 ;
+                $foldersizesatend = 0 ;
+                return ;
+        }
+        
+        my $h2_bytes_limit = $sync->{host2}->{quota_limit_bytes} || 0 ;
+        if ( $h2_bytes_limit and ( $h2_bytes_limit < $h1_bytes_start ) ) {
+        	my $quota_percent = mysprintf( '%.0f', $h1_bytes_start/$h2_bytes_limit ) ;
+                my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $h1_bytes_start bytes / $h2_bytes_limit bytes )\n" ;
+                errors_incr( $sync, $error ) ;
+        }
+        return ;
+}
+
+sub all_defined {
+        if ( not @ARG ) {
+                return 0 ;
+        }
+        foreach my $elem ( @ARG ) {
+                if ( not defined $elem ) {
+                        return 0 ;
+                }
+        }
+        return 1 ;
+}
+
+sub tests_all_defined {
+        is( 0, all_defined(  ),             'all_defined: no param  => 0' ) ;
+        is( 0, all_defined( () ),           'all_defined: void list => 0' ) ;
+        is( 0, all_defined( undef ),        'all_defined: undef     => 0' ) ;
+        is( 0, all_defined( undef, undef ), 'all_defined: undef     => 0' ) ;
+        is( 0, all_defined( 1, undef ),     'all_defined: 1 undef   => 0' ) ;
+        is( 0, all_defined( undef, 1 ),     'all_defined: undef 1   => 0' ) ;
+        is( 1, all_defined( 1, 1 ),         'all_defined: 1 1   => 1' ) ;
+        is( 1, all_defined( (1, 1) ),       'all_defined: (1 1) => 1' ) ;
+        return ;
+}
+
+
+sub imap_id_stuff {
+	my $sync = shift ;
+
+	if ( not $sync->{id} ) { return ; } ;
+
+	$sync->{h1_imap_id} = imap_id( $sync->{imap1}, 'Host1' ) ;
+	#myprint( 'Host1: ' . $sync->{h1_imap_id}  ) ;
+	$sync->{h2_imap_id} = imap_id( $sync->{imap2}, 'Host2' ) ;
+	#myprint( 'Host2: ' . $sync->{h2_imap_id}  ) ;
+
+	return ;
+}
+
+sub imap_id {
+	my ( $imap, $Side ) = @_ ;
+
+	$Side ||= q{} ;
+	my $imap_id_response = q{} ;
+
+	if ( not $imap->has_capability( 'ID' ) ) {
+		 $imap_id_response = 'No ID capability' ;
+                 myprint( "$Side: No ID capability\n"  ) ;
+	}else{
+		my $id_inp = imapsync_id( { side => lc $Side } ) ;
+		myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n"
+                . "In order to avoid sending/receiving ID, use option --noid\n" ) ;
+		my $debug_before = $imap->Debug(  ) ;
+		$imap->Debug( 1 ) ;
+		my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ;
+		#my $id_out = $imap->tag_and_run( 'ID NIL' ) ;
+                myprint( "\n"  ) ;
+		$imap->Debug( $debug_before ) ;
+		#$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ;
+	}
+	return( $imap_id_response ) ;
+}
+
+sub imapsync_id {
+	my $overhashref = shift ;
+	# See http://tools.ietf.org/html/rfc2971.html
+
+	my $imapsync_id = { } ;
+
+	my $imapsync_id_lamiral = {
+		name          => 'imapsync',
+		version       => imapsync_version(  ),
+		os            => $OSNAME,
+		vendor        => 'Gilles LAMIRAL',
+		'support-url' => 'http://imapsync.lamiral.info/',
+		# Example of date-time:  19-Sep-2015 08:56:07
+		date          => date_from_rcs( q{$Date: 2016/08/19 10:30:36 $ } ),
+	} ;
+
+	my $imapsync_id_github  = {
+		name          => 'imapsync',
+		version       => imapsync_version(  ),
+		os            => $OSNAME,
+		vendor        => 'github',
+		'support-url' => 'https://github.com/imapsync/imapsync',
+		date          => date_from_rcs( q{$Date: 2016/08/19 10:30:36 $ } ),
+	} ;
+
+	$imapsync_id = $imapsync_id_lamiral ;
+	#$imapsync_id = $imapsync_id_github ;
+	my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ;
+	my $imapsync_id_str = format_for_imap_arg( \%mix ) ;
+	#myprint( "$imapsync_id_str\n"  ) ;
+	return( $imapsync_id_str ) ;
+}
+
+sub tests_imapsync_id {
+	ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "http://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")'
+	eq imapsync_id( {
+		version => 111,
+		os => 'beurk',
+		date => '22-12-1968',
+		side => 'host1' } ),
+	'tests_imapsync_id override' ) ;
+
+	return ;
+}
+
+sub format_for_imap_arg {
+	my $ref = shift ;
+
+	my $string = q{} ;
+	my %terms = %{ $ref } ;
+	my @terms = (  ) ;
+	if ( not ( %terms ) ) { return( 'NIL' ) } ;
+	# sort like in RFC then add extra key/values
+	foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) {
+		if ( $terms{ $key } ) {
+			push  @terms, $key, $terms{ $key }  ;
+			delete $terms{ $key } ;
+		}
+	}
+	push  @terms, %terms  ;
+	$string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms )  . ')' ;
+	return( $string ) ;
+}
+
+
+
+sub tests_format_for_imap_arg {
+	ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ;
+	ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ;
+	ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ;
+	return ;
+}
+
+sub quota {
+	my ( $imap, $side, $sync ) = @_ ;
+
+        my $Side = ucfirst $side ;
+	my $debug_before = $imap->Debug(  ) ;
+	$imap->Debug( 1 ) ;
+	if ( not $imap->has_capability( 'QUOTA' ) ) {
+        	$imap->Debug( $debug_before ) ;
+        	return ;
+        } ;
+	myprint( "\n$Side: found quota, presented in raw IMAP\n"  ) ;
+	my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ;
+        # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n ""
+        #$imap->quota( 'ROOT' ) ;
+        #$imap->quota( '""' ) ;
+	myprint( "\n"  ) ;
+	$imap->Debug( $debug_before ) ;
+        my $quota_limit_bytes   = quota_extract_storage_limit_in_bytes( $getquotaroot ) ;
+        my $quota_current_bytes = quota_extract_storage_current_in_bytes( $getquotaroot ) ;
+        $sync->{$side}->{quota_limit_bytes}   = $quota_limit_bytes ;
+        $sync->{$side}->{quota_current_bytes} = $quota_current_bytes ;
+        my $quota_percent ;
+        if ( $quota_limit_bytes > 0 ) {
+        	$quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ;
+        }else{
+        	$quota_percent = 0 ;
+        }
+        myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n"  ) ;
+        if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) {
+        	my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ;
+                errors_incr( $sync, $error ) ;
+        }
+	return ;
+}
+
+sub tests_quota_extract_storage_limit_in_bytes {
+	my $imap_output = [
+	'* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
+        '* QUOTA "Storage quota" (STORAGE 1 104857600)',
+        '* QUOTA "Messages quota" (MESSAGE 2 100000)',
+        '5 OK Getquotaroot completed.'
+	] ;
+        ok( $NUMBER_104857600 * $KIBI == quota_extract_storage_limit_in_bytes( $imap_output ), 'quota_extract_storage_limit_in_bytes ') ;
+        return ;
+}
+
+sub quota_extract_storage_limit_in_bytes {
+	my $imap_output = shift ;
+
+        my $limit_kb ;
+        $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
+        $limit_kb ||= 0 ;
+        $debug and myprint( "storage_limit_kb = $limit_kb\n"  ) ;
+        return( $KIBI * $limit_kb ) ;
+}
+
+
+sub tests_quota_extract_storage_current_in_bytes {
+	my $imap_output = [
+	'* QUOTAROOT "INBOX" "Storage quota" "Messages quota"',
+        '* QUOTA "Storage quota" (STORAGE 1 104857600)',
+        '* QUOTA "Messages quota" (MESSAGE 2 100000)',
+        '5 OK Getquotaroot completed.'
+	] ;
+        ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ;
+        return ;
+}
+
+sub quota_extract_storage_current_in_bytes {
+	my $imap_output = shift ;
+
+        my $current_kb ;
+        $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/ ? $1 : () } @{ $imap_output } )[0] ;
+        $current_kb ||= 0 ;
+        $debug and myprint( "storage_current_kb = $current_kb\n"  ) ;
+        return( $KIBI * $current_kb ) ;
+
+}
+
+
+sub automap {
+	my ( $sync ) = @_ ;
+
+	if ( $sync->{automap} ) {
+		myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n"  ) ;
+	}else{
+		myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n"  ) ;
+		return ;
+	}
+
+        $sync->{h1_special} = special_from_folders_hash( $sync->{imap1}, 'Host1' ) ;
+        $sync->{h2_special} = special_from_folders_hash( $sync->{imap2}, 'Host2' ) ;
+
+	build_possible_special( $sync ) ;
+        build_guess_special(  $sync ) ;
+	build_automap( $sync ) ;
+
+	return ;
+}
+
+
+
+
+sub build_guess_special {
+	my ( $sync ) = shift ;
+
+        foreach my $h1_fold ( sort keys  %{ $sync->{h1_folders_all} }  ) {
+        	my $special = guess_special( $h1_fold, $sync->{possible_special}, $sync->{h1_prefix} ) ;
+        	if ( $special ) {
+                	$sync->{h1_special_guessed}{$h1_fold} = $special ;
+                        my $already_guessed = $sync->{h1_special_guessed}{$special} ;
+                        if ( $already_guessed ) {
+                        	myprint( "Host1: $h1_fold not $special because set to $already_guessed\n"  ) ;
+                        }else{
+	                        $sync->{h1_special_guessed}{$special} = $h1_fold ;
+                        }
+                }
+        }
+        foreach my $h2_fold ( sort keys  %{ $sync->{h2_folders_all} }  ) {
+        	my $special = guess_special( $h2_fold, $sync->{possible_special}, $sync->{h2_prefix} ) ;
+        	if ( $special ) {
+                	$sync->{h2_special_guessed}{$h2_fold} = $special ;
+                        my $already_guessed = $sync->{h2_special_guessed}{$special} ;
+                        if ( $already_guessed ) {
+                        	myprint( "Host2: $h2_fold not $special because set to $already_guessed\n"  ) ;
+                        }else{
+	                        $sync->{h2_special_guessed}{$special} = $h2_fold ;
+                        }
+                }
+        }
+        return ;
+}
+
+sub guess_special {
+	my( $folder, $possible_special_ref, $prefix ) = @_ ;
+
+        my $folder_no_prefix = $folder ;
+        $folder_no_prefix =~ s/${prefix}// ;
+        #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n"  ) ;
+
+        my $guess_special = $possible_special_ref->{ $folder }
+        	|| $possible_special_ref->{ $folder_no_prefix }
+        	|| q{} ;
+
+        return( $guess_special ) ;
+}
+
+sub tests_guess_special {
+	my $possible_special_ref = build_possible_special( my $sync ) ;
+        ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ;
+        ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ;
+        ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ;
+	return ;
+}
+
+sub build_automap {
+	my ( $sync ) = @_ ;
+
+	foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) {
+		my $h2_fold ;
+		my $h1_special = $sync->{h1_special}{$h1_fold} ;
+                my $h1_special_guessed = $sync->{h1_special_guessed}{$h1_fold} ;
+
+		# Case 1: special on both sides.
+		if ( $h1_special
+                     and exists  $sync->{h2_special}{$h1_special}  ) {
+			$h2_fold = $sync->{h2_special}{$h1_special} ;
+			$sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+			next ;
+		}
+		# Case 2: special on host1, not on host2
+		if ( $h1_special
+                     and ( not exists  $sync->{h2_special}{$h1_special}  )
+                     and ( exists  $sync->{h2_special_guessed}{$h1_special}  )
+                   ) {
+			# special_guessed on host2
+                        $h2_fold = $sync->{h2_special_guessed}{$h1_special} ;
+                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+			next ;
+		}
+		# Case 3: no special on host1, special on host2
+                if ( ( not $h1_special )
+                     and ( $h1_special_guessed )
+                     and ( exists  $sync->{h2_special}{$h1_special_guessed}  )
+                ) {
+                	$h2_fold = $sync->{h2_special}{$h1_special_guessed} ;
+                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+			next ;
+                }
+                # Case 4: no special on both sides.
+                if ( ( not $h1_special )
+                     and ( $h1_special_guessed )
+                     and ( not exists  $sync->{h2_special}{$h1_special_guessed}  )
+                     and ( exists  $sync->{h2_special_guessed}{$h1_special_guessed}  )
+                ) {
+                	$h2_fold = $sync->{h2_special_guessed}{$h1_special_guessed} ;
+                        $sync->{f1f2auto}{ $h1_fold } = $h2_fold ;
+			next ;
+                }
+	}
+	return( $sync->{f1f2auto} ) ;
+}
+
+# I willll probably add what there is at:
+# http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548
+sub build_possible_special {
+	my $sync = shift ;
+	my $possible_special = { } ;
+	# All|Archive|Drafts|Flagged|Junk|Sent|Trash
+
+	$possible_special->{'\All'}     = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ;
+	$possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ;
+	$possible_special->{'\Drafts'}  = [ 'Drafts', '&BCcENQRABD0EPgQyBDgEOgQ4-' ] ;
+	$possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ;
+	$possible_special->{'\Junk'}    = [ 'Junk', 'Spam', '&BCEEPwQwBDw-' ] ;
+	$possible_special->{'\Sent'}    = [ 'Sent', 'Sent Messages', 'Sent Items',
+                                            'Gesendete Elemente', 'Gesendete Objekte',
+                                            '&AMk-l&AOk-ments envoy&AOk-s', 'Envoy&AOk-',
+                                            'Elementos enviados',
+                                            '&kAFP4W4IMH8wojCkMMYw4A-',
+                                            '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-'] ;
+	$possible_special->{'\Trash'}   = [ 'Trash', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-' ] ;
+
+	foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){
+		foreach my $possible_folder ( @{ $possible_special->{$special} } ) {
+			$possible_special->{ $possible_folder } = $special ;
+		} ;
+	}
+        $sync->{possible_special} = $possible_special ;
+	$debug and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] )  ) ;
+        return( $possible_special ) ;
+}
+
+sub special_from_folders_hash {
+	my ( $imap, $side ) = @_ ;
+	my %special = (  ) ;
+        if ( not( Mail::IMAPClient->can( 'folders_hash' ) ) ) {
+        	my $error =  "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ;
+                errors_incr( $sync, $error ) ;
+                return( \%special ) ; # empty hash ref
+        }
+	my $folders_hash = $imap->folders_hash(  ) ;
+	foreach my $fhash (@{ $folders_hash } ) {
+			my @special =  grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/ } @{ $fhash->{attrs} }  ;
+			if ( @special ) {
+				my $special = $special[0] ; # keep first one. Could be not very good.
+				if ( exists  $special{ $special }  ) {
+					myprintf( "%s: special %-20s = %s already asigned to %s\n",
+					        $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ;
+				}else{
+					myprintf( "%s: special %-20s = %s\n",
+					        $side, $fhash->{name}, join( q{ }, @special ) ) ;
+					$special{ $special } = $fhash->{name} ;
+					$special{ $fhash->{name} } = $special ; # double entry value => key
+				}
+			}
+		}
+        myprint( "\n" ) if ( %special ) ;
+	return( \%special ) ;
+}
+
+sub errors_incr {
+	my ( $mysync, @error ) = @ARG ;
+	$sync->{nb_errors}++ ;
+        
+        if ( @error ) {
+		errors_log( $mysync, @error ) ;
+                myprint( @error ) ;
+        }
+        
+        $mysync->{errorsmax} ||= $ERRORS_MAX ;
+	if ( $sync->{nb_errors} >= $mysync->{errorsmax} ) {
+		myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
+                if ( $mysync->{errorsdump} ) {
+                        myprint( errorsdump( $sync->{nb_errors}, errors_log( $mysync ) ) ) ;
+                        # again since errorsdump(  ) can be very verbose and masq previous warning
+		        myprint( "Maximum number of errors $mysync->{errorsmax} reached ( you can change $mysync->{errorsmax} to 100 with --errorsmax 100 ). Exiting.\n"  ) ;
+		}
+                exit_clean( $mysync, $EXIT_WITH_ERRORS_MAX ) ;
+	}
+	return ;
+}
+
+sub errors_log {
+        my ( $mysync, @error ) = @ARG ;
+
+        if ( ! $mysync->{errors_log} ) {
+                $mysync->{errors_log} = [] ;
+        }
+
+        if ( @error ) {
+		push  @{ $mysync->{errors_log} }, join( q{}, @error  ) ;
+        }
+        if ( @{ $mysync->{errors_log} } ) {
+                return @{ $mysync->{errors_log} } ;
+        }
+        else {
+                return ;
+        }
+}
+
+sub tests_errors_log {
+
+
+}
+
+
+sub errorsdump {
+        my( $nb_errors, @errors_log ) = @ARG ;
+	my $error_num = 0 ;
+	my $errors_list = q{} ;
+	if ( @errors_log ) {
+		$errors_list = "++++ Listing $nb_errors errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n" ;
+		foreach my $error ( @errors_log ) {
+			$error_num++ ;
+			$errors_list .= "Err $error_num/$nb_errors: $error" ;
+		}
+	}
+	return( $errors_list ) ;
+}
+
+
+sub tests_live_result {
+	my $nb_errors = shift ;
+	if ( $nb_errors  ) {
+		myprint( "Live tests failed with $nb_errors errors\n"  ) ;
+	} else {
+		myprint( "Live tests ended successfully\n"  ) ;
+	}
+	return ;
+}
+
+sub foldersizesatend {
+	timenext(  ) ;
+	return if ( $imap1->IsUnconnected(  ) ) ;
+	return if ( $imap2->IsUnconnected(  ) ) ;
+	# Get all folders on host2 again since new were created
+	@h2_folders_all = sort $imap2->folders();
+	for ( @h2_folders_all ) {
+        	$h2_folders_all{ $_ } = 1 ;
+        	$h2_folders_all_UPPER{ uc  $_  } = 1 ;
+        } ;
+	( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( 'Host1', $imap1, $search1, @h1_folders_wanted ) ;
+	( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( 'Host2', $imap2, $search2, @h2_folders_from_1_wanted ) ;
+        if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) {
+                my $error = "Failure getting foldersizes, final differences will not be calculated\n" ;
+                errors_incr( $sync, $error ) ;
+        }
+	return ;
+}
+
+sub size_filtered_flag {
+	my $h1_size = shift ;
+
+	if (defined $maxsize and $h1_size >= $maxsize) {
+		return( 1 ) ;
+	}
+	if (defined $minsize and $h1_size <= $minsize) {
+		return( 1 ) ;
+	}
+	return( 0 ) ;
+}
+
+sub sync_flags_fir {
+	my ( $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ;
+
+	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 ) ;
+
+	# 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 ) ;
+
+        return ;
+}
+
+sub sync_flags_after_copy {
+	my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ;
+
+        my @h2_flags = $imap2->flags( $h2_msg ) ;
+        my $h2_flags = "@h2_flags" ;
+        ( $debug or $debugflags ) and myprint( "Host2 flags before resync by STORE on msg $h2_msg: $h2_flags\n"  ) ;
+	sync_flags( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ;
+        return ;
+}
+
+sub sync_flags {
+	my( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $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"  ) ;
+
+	$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"  ) ;
+
+
+	# compare flags - set flags if there a difference
+	my @h1_flags = sort split(q{ }, $h1_flags );
+	my @h2_flags = sort split(q{ }, $h2_flags );
+	my $diff = compare_lists( \@h1_flags, \@h2_flags );
+
+	$diff and ( $debug or $debugflags )
+		and     myprint( "Host2 flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ;
+	# This sets flags so flags can be removed with this
+	# When you remove a \Seen flag on host1 you want to it
+	# to be removed on host2. Just add flags is not what
+	# we need most of the time.
+
+	if ( not $dry and $diff and not $imap2->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) {
+		my $error_msg = join q{}, "Host2 flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ",
+		  $imap2->LastError || q{}, "\n" ;
+		errors_incr( $sync, $error_msg ) ;
+	}
+
+        return ;
+}
+
+
+
+sub _filter {
+	my $str = shift or return q{} ;
+        my $sz  = $SIZE_MAX_STR ;
+        my $len = length $str ;
+        if ( not $debug and $len > $sz*2 ) {
+                my $beg = substr $str, 0, $sz ;
+                my $end = substr $str, -$sz, $sz ;
+                $str = $beg . '...' . $end ;
+        }
+        $str =~ s/\012?\015$//x ;
+        return "(len=$len) " . $str ;
+}
+
+
+
+sub lost_connection {
+	my( $imap, $error_message ) = @_;
+        if ( $imap->IsUnconnected(  ) ) {
+                $sync->{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) ;
+                myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ;
+                return( 1 ) ;
+        }
+        else{
+        	return( 0 ) ;
+        }
+}
+
+sub max {
+	my @list = @_ ;
+	return( undef ) if ( 0 == scalar  @list  ) ;
+	my @sorted = sort { $a <=> $b } @list ;
+	return( pop @sorted ) ;
+}
+
+sub tests_max {
+	ok( 0  == max( 0 ),  'max 0' ) ;
+	ok( 1  == max( 1 ),  'max 1' ) ;
+	ok( $MINUS_ONE == max( $MINUS_ONE ), 'max -1') ;
+	ok( not ( defined max(  ) ), 'max no arg' ) ;
+	ok( $NUMBER_100 == max( 1, $NUMBER_100 ), 'max 1 100' ) ;
+	ok( $NUMBER_100 == max( $NUMBER_100, 1 ), 'max 100 1' ) ;
+	ok( $NUMBER_100 == max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1' ) ;
+	ok( $NUMBER_100 == max( $NUMBER_100, '42', 1 ), 'max 100 42 1' ) ;
+	ok( $NUMBER_100 == max( '100', '42', 1 ), 'max 100 42 1' ) ;
+	#ok( 100 == max( 100, 'haha', 1 ), 'max 100 42 1') ;
+        return ;
+}
+
+
+sub check_lib_version {
+	$debug and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n"  ) ;
+	if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) {
+		myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n"  ) ;
+		return 0 ;
+	}
+	else{
+		# 3.x.x is no longer buggy with imapsync.
+                # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line".
+		return 1 ;
+	}
+        return ;
+}
+
+sub module_version_str {
+	my( $module_name, $module_version ) = @_ ;
+	my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ;
+        return( $str ) ;
+}
+
+sub modulesversion {
+
+	my @list_version;
+
+	my $v ;
+	eval { require Mail::IMAPClient; $v = $Mail::IMAPClient::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Mail::IMAPClient', $v )  ;
+
+	eval { require IO::Socket; $v = $IO::Socket::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'IO::Socket', $v )  ;
+
+	eval { require IO::Socket::INET; $v = $IO::Socket::INET::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'IO::Socket::INET', $v )  ;
+
+	eval { require IO::Socket::INET6; $v = $IO::Socket::INET6::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'IO::Socket::INET6', $v )  ;
+
+	eval { require IO::Socket::SSL ; $v = $IO::Socket::SSL::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'IO::Socket::SSL ', $v )  ;
+
+	eval { require Net::SSLeay ; $v = $Net::SSLeay::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Net::SSLeay ', $v )  ;
+
+	eval { require Compress::Zlib; $v = $Compress::Zlib::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Compress::Zlib', $v )  ;
+
+	eval { require Digest::MD5; $v = $Digest::MD5::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Digest::MD5', $v )  ;
+
+	eval { require Digest::HMAC_MD5; $v = $Digest::HMAC_MD5::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Digest::HMAC_MD5', $v )  ;
+
+	eval { require Digest::HMAC_SHA1; $v = $Digest::HMAC_SHA1::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Digest::HMAC_SHA1', $v )  ;
+
+	eval { require Term::ReadKey; $v = $Term::ReadKey::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Term::ReadKey', $v )  ;
+
+	eval { require File::Spec; $v = $File::Spec::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'File::Spec', $v )  ;
+
+	eval { require Time::HiRes; $v = $Time::HiRes::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Time::HiRes', $v )  ;
+
+	eval { require Unicode::String; $v = $Unicode::String::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Unicode::String', $v )  ;
+
+	eval { require IO::Tee; $v = $IO::Tee::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'IO::Tee', $v )  ;
+
+	eval { require File::Copy::Recursive; $v = $File::Copy::Recursive::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'File::Copy::Recursive', $v )  ;
+
+	eval { require Authen::NTLM; $v = $Authen::NTLM::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Authen::NTLM', $v )  ;
+
+	eval { require URI::Escape; $v = $URI::Escape::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'URI::Escape', $v )  ;
+
+	eval { require Data::Uniqid; $v = $Data::Uniqid::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Data::Uniqid', $v )  ;
+
+	eval { require JSON; $v = $JSON::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'JSON', $v )  ;
+
+	eval { require JSON::WebToken; $v = $JSON::WebToken::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'JSON::WebToken', $v )  ;
+
+	eval { require Crypt::OpenSSL::RSA; $v = $Crypt::OpenSSL::RSA::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Crypt::OpenSSL::RSA', $v )  ;
+
+	eval { require LWP; $v = $LWP::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'LWP', $v )  ;
+
+	eval { require HTML::Entities; $v = $HTML::Entities::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'HTML::Entities', $v )  ;
+
+	#eval { require Filesys::DfPortable; $v = $Filesys::DfPortable::VERSION } or $v = q{?} ;
+	#push  @list_version, module_version_str( 'Filesys::DfPortable', $v )  ;
+
+	eval { require Getopt::Long; $v = $Getopt::Long::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Getopt::Long', $v )  ;
+
+	eval { require Test::MockObject; $v = $Test::MockObject::VERSION } or $v = q{?} ;
+	push  @list_version, module_version_str( 'Test::MockObject', $v )  ;
+
+	return( @list_version ) ;
+}
+
+
+# Construct a command line copy with passwords replaced by MASKED.
+sub command_line_nopassword {
+	my @argv = @_ ;
+	my @argv_nopassword ;
+
+        return( "@argv" ) if $showpasswords ;
+	while ( @argv ) {
+		my $arg = shift @argv ; # option name or value
+		if ( $arg =~ m/-password[12]/x ) {
+			shift @argv ; # password value
+			push  @argv_nopassword, $arg, 'MASKED'  ; # option name and fake value
+		}else{
+			push  @argv_nopassword, $arg ; # same option or value
+		}
+	}
+	return("@argv_nopassword") ;
+}
+
+sub tests_command_line_nopassword {
+
+	ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
+	ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
+	#myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
+	ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
+	ok('--blabla --password1 MASKED --blibli'
+	eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+	$showpasswords = 1 ;
+	ok(q{} eq command_line_nopassword(), 'command_line_nopassword void');
+	ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
+	#myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ;
+	ok('--password1 secret1' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
+	ok('--blabla --password1 secret1 --blibli'
+	eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+        return ;
+}
+
+sub ask_for_password {
+	my ( $user, $host ) = @_ ;
+	myprint( "What's the password for $user" . '@' . "$host? (not visible while you type, then enter RETURN) "  ) ;
+	Term::ReadKey::ReadMode( 2 ) ;
+	my $password = <> ;
+	chomp $password ;
+	myprint( "\nGot it\n" ) ;
+	Term::ReadKey::ReadMode( 0 ) ;
+	return $password ;
+}
+
+sub catch_exit {
+        my $mysync = shift ;
+        my $signame = shift ;
+        if ( $signame ) {
+                myprint( "\nGot a signal $signame\n" ) ;
+        }
+	stats( $mysync ) ;
+        myprint( "Ended by a signal\n" ) ;
+	exit_clean( $mysync, $EXIT_BY_SIGNAL ) ;
+        return ;
+}
+
+sub catch_reconnect {
+	my $mysync = shift ;
+        my $signame = shift ;
+        myprint( "\nGot a signal $signame\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}->reconnect(  ) ;
+        myprint( "Info: reconnecting to host2 imap server\n" ) ;
+        $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ;
+        $mysync->{imap2}->reconnect(  ) ;
+        myprint( "Info: reconnected to both imap servers\n" ) ;
+        return ;
+}
+
+sub here_twice {
+        my $mysync = shift ;
+        my $now = time ;
+        my $previous = $mysync->{lastcatch} || 0 ;
+        $mysync->{lastcatch} = $now ;
+        
+        if ( $INTERVAL_TO_EXIT >= $now - $previous ) {
+                return $TRUE ;
+        }else{
+                return $FALSE ;
+        }
+}
+
+
+
+
+sub justconnect {
+
+	$imap1 = connect_imap( $host1, $port1, $debugimap1, $ssl1, $tls1, 'Host1', $sync->{h1}->{timeout}, $sync->{h1} ) ;
+	myprint( 'Host1 banner: ', $imap1->Banner(  )  ) ;
+	myprint( 'Host1 capability: ', join(q{ }, $imap1->capability(  ) ), "\n"  ) ;
+	$imap2 = connect_imap( $host2, $port2, $debugimap2, $ssl2, $tls2, 'Host2', $sync->{h2}->{timeout}, $sync->{h2} ) ;
+	myprint( 'Host2 banner: ', $imap2->Banner(  )  ) ;
+	myprint( 'Host2 capability: ', join(q{ }, $imap2->capability(  ) ), "\n"  ) ;
+	$imap1->logout(  ) ;
+	$imap2->logout(  ) ;
+        return ;
+}
+
+sub connect_imap {
+	my( $host, $port, $mydebugimap, $ssl, $tls, $Side, $mytimeout, $h ) = @_ ;
+	my $imap = Mail::IMAPClient->new() ;
+	if ( $ssl ) { set_ssl( $imap, $h ) }
+	if ( $tls ) { $imap->Tls( 1 ) }
+	$imap->Server( $host ) ;
+	$imap->Port( $port ) ;
+	$imap->Debug( $mydebugimap ) ;
+        $imap->Timeout( $mytimeout ) ;
+	$imap->connect(  )
+	  or die_clean( "$Side: Can not open imap connection on [$host]: $@\n" ) ;
+
+        my $banner = $imap->Results()->[0] ;
+        $imap->Banner( $banner ) ;
+
+        if ( $imap->Tls(  ) ) {
+        	set_tls( $imap, $h ) ;
+        	$imap->starttls(  )
+                or die_clean("$Side: Can not go to tls encryption on [$host]:", $imap->LastError, "\n" ) ;
+                myprint( "$Side: Socket successfuly converted to SSL\n"  ) ;
+        }
+        return( $imap ) ;
+}
+
+
+sub login_imap {
+
+	my @allargs = @_ ;
+	my(
+		$host, $port, $user, $domain, $password,
+		$mydebugimap, $mytimeout, $fastio,
+		$ssl, $tls, $authmech, $authuser, $reconnectretry,
+		$proxyauth, $uid, $split, $Side, $h ) = @allargs ;
+
+	my $side = lc $Side ;
+	myprint( "$Side: connecting and login on $side [$host] port [$port] with user [$user]\n"  ) ;
+
+	my $imap = init_imap( @allargs ) ;
+
+	$imap->connect()
+	  or die_clean("$Side failure: can not open imap connection on $side [$host] with user [$user]: $@\n") ;
+
+        my $banner = $imap->Results()->[0] ;
+        $imap->Banner( $banner ) ;
+	myprint( "$Side banner: $banner"  ) ;
+
+        if ( $authmech eq 'PREAUTH' ) {
+        	if ( $imap->IsAuthenticated( ) ) {
+        		$imap->Socket ;
+			myprintf("%s: Assuming PREAUTH for %s\n", $Side, $imap->Server ) ;
+        	}else{
+                	die_clean( "$Side failure: error login on $side [$host] with user [$user] auth [PREAUTH]" ) ;
+                }
+        }
+
+        if ( $imap->Tls(  ) ) {
+		set_tls( $imap, $h ) ;
+        	$imap->starttls(  )
+                or die_clean("$Side failure: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ;
+                myprint( "$Side: Socket successfuly converted to SSL\n"  ) ;
+        }
+
+        authenticate_imap( $imap, @allargs ) ;
+
+	myprint( "$Side: success login on [$host] with user [$user] auth [$authmech]\n"  ) ;
+	return( $imap ) ;
+}
+
+
+sub authenticate_imap {
+
+	my($imap,
+           $host, $port, $user, $domain, $password,
+	   $mydebugimap, $mytimeout, $fastio,
+	   $ssl, $tls, $authmech, $authuser, $reconnectretry,
+	   $proxyauth, $uid, $split, $Side, $h ) = @_ ;
+
+	check_capability( $imap, $authmech, $Side ) ;
+
+        if ( $proxyauth ) {
+                $imap->Authmechanism(q{}) ;
+                $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( ) ) {
+		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 ) ;
+                }else{
+			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") ;
+	}
+
+        if ( $proxyauth ) {
+                if ( ! $imap->proxyauth( $user ) ) {
+                        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" ) ;
+                }
+        }
+
+	return ;
+}
+
+sub check_capability {
+
+	my( $imap, $authmech, $Side ) = @_ ;
+
+	if ($imap->has_capability("AUTH=$authmech")
+	    or $imap->has_capability($authmech)
+	   ) {
+		myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n",
+		       $Side, $imap->Server, $authmech);
+	}
+	else {
+		myprintf("%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n",
+		       $Side, $imap->Server, $authmech);
+		if ($authmech eq 'PLAIN') {
+			myprint( "$Side: frequently PLAIN is only supported with SSL, ",
+			  "try --ssl or --tls options\n" ) ;
+		}
+	}
+	return ;
+}
+
+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)
+        #
+
+        my $sslargs_hash = $h->{sslargs} ;
+
+	my $sslargs_default = {
+		SSL_verify_mode => $DEFAULT_SSL_VERIFY,
+        	SSL_verifycn_scheme => 'imap',
+        } ;
+
+        # initiate with default values
+        my %sslargs_mix = %{ $sslargs_default } ;
+        # now override with passed values
+        @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
+        # remove keys with undef values
+        foreach my $key ( keys %sslargs_mix ) {
+                delete $sslargs_mix{ $key } if ( not defined  $sslargs_mix{ $key }  ) ;
+        }
+        # back to an ARRAY
+        my @sslargs_mix = %sslargs_mix ;
+        #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] )  ) ;
+        $imap->Ssl( \@sslargs_mix ) ;
+	return ;
+}
+
+sub set_tls {
+	my ( $imap, $h ) = @_ ;
+
+        my $sslargs_hash = $h->{sslargs} ;
+
+	my $sslargs_default = {
+		SSL_verify_mode => $DEFAULT_SSL_VERIFY,
+        } ;
+
+        # initiate with default values
+        my %sslargs_mix = %{ $sslargs_default } ;
+        # now override with passed values
+        @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ;
+        # remove keys with undef values
+        foreach my $key ( keys %sslargs_mix ) {
+                delete $sslargs_mix{ $key } if ( not defined  $sslargs_mix{ $key } ) ;
+        }
+        # back to an ARRAY
+        my @sslargs_mix = %sslargs_mix ;
+
+        $imap->Starttls( \@sslargs_mix ) ;
+	return ;
+}
+
+
+
+
+sub init_imap {
+	my(
+	   $host, $port, $user, $domain, $password,
+	   $mydebugimap, $mytimeout, $fastio,
+	   $ssl, $tls, $authmech, $authuser, $reconnectretry,
+	   $proxyauth, $uid, $split, $Side, $h ) = @_ ;
+
+	my ( $imap ) ;
+
+	$imap = Mail::IMAPClient->new() ;
+
+	if ( $ssl ) { set_ssl( $imap, $h ) }
+	if ( $tls ) { $imap->Tls( 1 ) } # can not do set_tls() here because connect() will directly do a STARTTLS
+	$imap->Clear(1);
+	$imap->Server($host);
+	$imap->Port($port);
+	$imap->Fast_io($fastio);
+	$imap->Buffer($buffersize || $DEFAULT_BUFFER_SIZE);
+	$imap->Uid($uid);
+
+	$imap->Peek(1);
+	$imap->Debug($mydebugimap);
+	defined  $mytimeout  and $imap->Timeout( $mytimeout ) ;
+
+	$imap->Reconnectretry( $reconnectretry ) if ( $reconnectretry ) ;
+	$imap->Ignoresizeerrors( $allowsizemismatch ) ;
+	$split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ;
+
+
+	return( $imap ) ;
+
+}
+
+sub plainauth {
+        my $code = shift;
+        my $imap = shift;
+
+        my $string = mysprintf("%s\x00%s\x00%s", $imap->User,
+                            $imap->Authuser, $imap->Password);
+        return encode_base64("$string", q{});
+}
+
+# Copy from https://github.com/imapsync/imapsync/pull/25/files
+# Changes "use" pragmas to "require".
+# The openssl system call shall be replaced by pure Perl and
+# https://metacpan.org/pod/Crypt::OpenSSL::PKCS12
+
+# Now the Joaquin Lopez code:
+#
+# Used this as an example: https://gist.github.com/gsainio/6322375
+#
+# And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
+# (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt
+# until I noticed that...)
+#
+# This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated
+# on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol
+# If there are other oauth2 implementations out there, this would need to be modified to be
+# compatible
+#
+# This is a good guide on setting up the google api/apps side of the equation:
+# http://www.limilabs.com/blog/oauth2-gmail-imap-service-account
+#
+# 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to
+# when creating gmail service accounts. They're easier to work with since they neither
+# requiring decrypting nor specifying the oauth2 client id separately.
+#
+# If the password arg ends in .json, it will assume this new json method, otherwise it
+# will fallback to the "oauth client id;.p12" format it was previously using.
+sub xoauth2 {
+	require JSON::WebToken ;
+	require LWP::UserAgent ;
+	require HTML::Entities ;
+	require JSON ;
+	require JSON::WebToken::Crypt::RSA ;
+	require Crypt::OpenSSL::RSA ;
+        require Encode::Byte ;
+        require IO::Socket::SSL ;
+
+        my $code = shift;
+        my $imap = shift;
+
+        my ($iss,$key);
+
+        if( $imap->Password =~ /^(.*\.json)$/ ) {
+            my $json = JSON->new( ) ;
+            my $filename = $1;
+            $debug and myprint( "XOAUTH2 json file: $filename\n" ) ;
+            open( my $FILE, '<', $filename ) or die_clean( "error [$filename]: $! " ) ;
+            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");
+        }
+        else {
+            # Get iss (service account address), keyfile name, and keypassword if necessary
+            ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/ ;
+
+            # 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");
+
+            # 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");
+        }
+
+        # Create jwt of oauth2 request
+        my $time = time ;
+        my $jwt = JSON::WebToken->encode( {
+        'iss' => $iss, # service account
+        'scope' => 'https://mail.google.com/',
+        'aud' => 'https://www.googleapis.com/oauth2/v3/token',
+        'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12,
+        'iat' => $time,
+        'prn' => $imap->User # user to auth as
+        },
+        $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here.
+
+        # Post oauth2 request
+        my $ua = LWP::UserAgent->new(  ) ;
+        $ua->env_proxy(  ) ;
+
+        my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token',
+        { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
+        assertion => $jwt } ) ;
+
+        unless( $response->is_success(  ) ) {
+                die_clean( $response->code, "\n", $response->content, "\n" ) ;
+        }else{
+                $debug and myprint( $response->content  ) ;
+        }
+
+        # access_token in response is what we need
+        my $data = JSON::decode_json( $response->content ) ;
+
+        # 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");
+        return($xoauth2_string);
+}
+
+
+
+
+# xoauth() thanks to Eduardo Bortoluzzi Junior
+sub xoauth {
+        require URI::Escape  ;
+        require Data::Uniqid ;
+
+        my $code = shift;
+        my $imap = shift;
+
+        # The base information needed to construct the OAUTH authentication
+        my $method = 'GET' ;
+        my $url = mysprintf( 'https://mail.google.com/mail/b/%s/imap/', $imap->User ) ;
+        my $urlparm = mysprintf( 'xoauth_requestor_id=%s', URI::Escape::uri_escape( $imap->User ) ) ;
+
+        # 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" ) ;
+
+        # All the parameters needed to be signed on the XOAUTH
+        my %hash = ();
+        $hash { 'xoauth_requestor_id' } = URI::Escape::uri_escape($imap->User);
+        $hash { 'oauth_consumer_key' } = $user_parts[1];
+        $hash { 'oauth_nonce' } = md5_hex(Data::Uniqid::uniqid(rand(), 1==1));
+        $hash { 'oauth_signature_method' } = 'HMAC-SHA1';
+        $hash { 'oauth_timestamp' } = time ;
+        $hash { 'oauth_version' } = '1.0';
+
+        # Base will hold the string to be signed
+        my $base = "$method&" . URI::Escape::uri_escape( $url ) . q{&} ;
+
+        # The parameters must be in dictionary order before signing
+        my $baseparms = q{} ;
+        foreach my $key ( sort keys %hash ) {
+                if ( length( $baseparms ) > 0 ) {
+                        $baseparms .= q{&} ;
+                }
+
+                $baseparms .= "$key=$hash{$key}" ;
+        }
+
+        $base .= URI::Escape::uri_escape($baseparms);
+        $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{&} ) ;
+
+        # The parameters signed become a parameter and...
+        $hash { 'oauth_signature' } = URI::Escape::uri_escape( substr encode_base64( $digest ), 0, $MINUS_ONE ) ;
+
+        # ... we don't need the requestor_id anymore.
+        delete $hash{'xoauth_requestor_id'} ;
+
+        # Create the final authentication string
+        my $string = $method . q{ } . $url . q{?} . $urlparm .q{ } ;
+
+        # All the parameters must be sorted
+        $baseparms = q{};
+        foreach my $key (sort keys %hash) {
+                if(length($baseparms)>0) {
+                        $baseparms .= q{,} ;
+                }
+
+                $baseparms .= "$key=\"$hash{$key}\"";
+        }
+
+        $string .= $baseparms;
+
+        $debug and myprint( "XOAUTH: authentication string: $string\n" ) ;
+
+       # It must be base64 encoded
+        return encode_base64("$string", q{});
+}
+
+sub server_banner {
+	my $imap = shift;
+	my $banner = $imap->Banner() ||  "No banner\n";
+	return $banner;
+ }
+
+
+sub banner_imapsync {
+
+	my @argv = @_ ;
+
+	my $banner_imapsync = join q{},
+		q{$RCSfile: imapsync,v $ },
+		q{$Revision: 1.727 $ },
+		q{$Date: 2016/08/19 10:30:36 $ },
+		"\n", localhost_info(), "\n",
+		"Command line used:\n",
+		"$0 ", command_line_nopassword( @argv ), "\n" ;
+
+        return( $banner_imapsync ) ;
+}
+
+sub is_valid_directory {
+	my $dir = shift;
+
+	# all good => return ok.
+	return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
+
+	# exist but bad
+	if ( -e $dir and not -d _ ) {
+		myprint( "Error: $dir exists but is not a directory\n"  ) ;
+		return( 0 ) ;
+	}
+	if ( -e $dir and not -w _ ) {
+		my $sb = stat $dir ;
+		myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n",
+		         $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid(  ) ) ;
+		return( 0 ) ;
+	}
+	# Trying to create it
+	myprint( "Creating directory $dir\n"  ) ;
+	eval { mkpath( $dir ) } ;
+	myprint( "$@" ) if ( $@ )  ;
+	return( 1 ) if ( -d $dir and -r _ and -w _ ) ;
+	return( 0 ) ;
+}
+
+sub tests_is_valid_directory {
+        Readonly my $NB_UNIX_tests_is_valid_directory => 4 ;
+	SKIP: {
+		skip( 'Tests only for Unix', $NB_UNIX_tests_is_valid_directory ) if ( 'MSWin32' eq $OSNAME ) ;
+		ok( 1 == is_valid_directory( '.'), 'is_valid_directory: . good' ) ;
+		ok( 1 == is_valid_directory( './tmp/tests/valid/sub'), 'is_valid_directory: ./tmp/tests/valid/sub good' ) ;
+		diag( 'Error / not writable is on purpose' ) ;
+		ok( 0 == is_valid_directory( '/'), 'is_valid_directory: / bad' ) ;
+		diag( 'Error permission denied on /noway is on purpose' ) ;
+		ok( 0 == is_valid_directory( '/noway'), 'is_valid_directory: /noway bad' ) ;
+	}
+	return ;
+}
+
+sub write_pidfile {
+	my $pid_filename = shift ;
+        my $lock = shift ;
+        
+	myprint( "PID file is $pid_filename ( to change it use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ;
+	if ( -e $pid_filename and $lock ) {
+		myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n"  ) ;
+                exit $EXIT_PID_FILE_ALREADY_EXIST ;
+	}
+	if ( -e $pid_filename ) {
+		myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n"  ) ;
+	}
+
+	open my $FILE_HANDLE, '>', $pid_filename
+        	or do {
+			myprint( "Could not open $pid_filename for writing. Check permissions or disk space."  ) ;
+		return ;
+	} ;
+        myprint( "Wrinting my PID $PROCESS_ID in $pid_filename\n"  ) ;
+	print $FILE_HANDLE $PROCESS_ID ;
+	close $FILE_HANDLE ;
+
+	return( $PROCESS_ID ) ;
+}
+
+sub remove_tmp_files {
+        my $mysync = shift ;
+	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 ) ;
+	die @messages ;
+}
+
+sub missing_option {
+	my ( $option ) = @_ ;
+	die_clean( "$option option is mandatory, for help run $0 --help\n" ) ;
+	return ;
+}
+
+
+sub fix_Inbox_INBOX_mapping {
+	my( $h1_all, $h2_all ) = @_ ;
+
+	my $regex = q{} ;
+	SWITCH: {
+		if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{INBOX}  ) { $regex = q{} ; last SWITCH ; } ;
+		if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{Inbox}  ) { $regex = q{} ; last SWITCH ; } ;
+		if ( exists  $h1_all->{INBOX}  and exists  $h2_all->{Inbox}  ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ;
+		if ( exists  $h1_all->{Inbox}  and exists  $h2_all->{INBOX}  ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ;
+	} ;
+        return( $regex ) ;
+}
+
+sub tests_fix_Inbox_INBOX_mapping {
+
+	my( $h1_all, $h2_all ) ;
+
+	$h1_all = { 'INBOX' => q{} } ;
+	$h2_all = { 'INBOX' => q{} } ;
+	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ;
+
+	$h1_all = { 'Inbox' => q{} } ;
+	$h2_all = { 'Inbox' => q{} } ;
+	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ;
+
+	$h1_all = { 'INBOX' => q{} } ;
+	$h2_all = { 'Inbox' => q{} } ;
+	ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ;
+
+	$h1_all = { 'Inbox' => q{} } ;
+	$h2_all = { 'INBOX' => q{} } ;
+	ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ;
+
+	$h1_all = { 'INBOX' => q{} } ;
+	$h2_all = { 'rrrrr' => q{} } ;
+	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ;
+
+	$h1_all = { 'rrrrr' => q{} } ;
+	$h2_all = { 'Inbox' => q{} } ;
+	ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ;
+
+	return ;
+}
+
+
+sub jux_utf8_list {
+	my @s_inp = @_ ;
+	my $s_out = q{} ;
+	foreach my $s ( @s_inp ) {
+		$s_out .= jux_utf8( $s ) . "\n" ;
+	}
+	return( $s_out ) ;
+}
+
+sub tests_jux_utf8_list {
+	ok( q{} eq jux_utf8_list(  ), 'jux_utf8_list: void' ) ;
+	ok( "[]\n" eq jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ;
+	ok( "[INBOX]\n" eq jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ;
+	ok( "[&ANY-] = [Ö]\n" eq jux_utf8_list( '&ANY-' ), 'jux_utf8_list: &ANY-' ) ;
+	return( 0 ) ;
+}
+
+sub jux_utf8 {
+	# juxtapose utf8 at the right if different
+        my ( $s_utf7 ) =  shift ;
+        my ( $s_utf8 ) =  imap_utf7_decode( $s_utf7 ) ;
+
+        if ( $s_utf7 eq $s_utf8 ) {
+        	#myprint( "[$s_utf7]\n"  ) ;
+        	return( "[$s_utf7]" ) ;
+        }else{
+        	#myprint( "[$s_utf7] = [$s_utf8]\n"  ) ;
+        	return( "[$s_utf7] = [$s_utf8]" ) ;
+        }
+}
+
+# editing utf8 can be tricky without an utf8 editor
+sub tests_jux_utf8 {
+	ok( '[INBOX]' eq jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ;
+	ok( '[&ZTZO9nux-] = [收件箱]' eq jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [收件箱]' ) ;
+	ok( '[&ANY-] = [Ö]' eq jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ;
+        ok( '[]' eq jux_utf8( q{} ), 'jux_utf8: void => []' ) ;
+        ok( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' eq jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ;
+        ok( '[&BB8EQAQ+BDUEOgRC-] = [Проект]'      eq jux_utf8( '&BB8EQAQ+BDUEOgRC-' ),    'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ;
+
+	return( 0 ) ;
+}
+
+# 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 {
+        my ( $s ) = shift ;
+
+        # Algorithm
+        # On remplace , par / dans les BASE 64 (, entre & et -)
+        # On remplace les &, non suivi d'un - par +
+        # On remplace les &- par &
+        $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/g ;
+        $s =~ s/&(?!\-)/\+/g ;
+        $s =~ s/&\-/&/g ;
+        return( Unicode::String::utf7( $s )->utf8 ) ;
+}
+
+sub imap_utf7_encode {
+	my ( $s ) = @_ ;
+
+	$s = Unicode::String::utf8( $s )->utf7 ;
+
+	$s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/g ;
+	$s =~ s/&/&\-/g ;
+	$s =~ s/\+([^+\-]+)?\-/&$1\-/g ;
+	return( $s ) ;
+}
+
+
+
+
+sub select_folder {
+	my ( $imap, $folder, $hostside ) = @_ ;
+	if ( ! $imap->select( $folder ) ) {
+		my $error = join q{},
+			"$hostside folder $folder: Could not select: ",
+			$imap->LastError,  "\n" ;
+		errors_incr( $sync, $error ) ;
+		return( 0 ) ;
+	}else{
+		# ok select succeeded
+		return( 1 ) ;
+	}
+}
+
+sub examine_folder {
+	my ( $imap, $folder, $hostside ) = @_ ;
+	if ( ! $imap->examine( $folder ) ) {
+		my $error = join q{},
+			"$hostside folder $folder: Could not examine: ",
+			$imap->LastError,  "\n" ;
+		errors_incr( $sync, $error ) ;
+		return( 0 ) ;
+	}else{
+		# ok select succeeded
+		return( 1 ) ;
+	}
+}
+
+
+
+
+sub count_from_select {
+	my @lines = @_ ;
+        my $count ;
+        foreach my $line ( @lines ) {
+        	#myprint( "line = [$line]\n"  ) ;
+                if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/ ) {
+                	$count = $1 ;
+                        return( $count ) ;
+                }
+        }
+        return( undef ) ;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+sub create_folder_old {
+	my( $imap, $h2_fold, $h1_fold ) = @_ ;
+
+	myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ;
+        if ( ( 'INBOX' eq uc  $h2_fold )
+         and ( $imap->exists( $h2_fold ) ) ) {
+                myprint( "Folder [$h2_fold] already exists\n"  ) ;
+                return( 1 ) ;
+        }
+	if ( ! $dry ){
+		if ( ! $imap->create( $h2_fold ) ) {
+			my $error = join q{},
+				"Could not create folder [$h2_fold] from [$h1_fold]: ",
+				$imap->LastError(  ), "\n" ;
+			errors_incr( $sync, $error ) ;
+                        # success if folder exists ("already exists" error)
+                        return( 1 ) if $imap->exists( $h2_fold ) ;
+                        # failure since create failed
+			return( 0 ) ;
+		}else{
+			#create succeeded
+                        myprint( "Created ( the old way ) folder [$h2_fold] on host2\n"  ) ;
+			return( 1 ) ;
+		}
+	}else{
+		# dry mode, no folder so many imap will fail, assuming failure
+                myprint( "Created ( the old way ) folder [$h2_fold] on host2 $dry_message\n"  ) ;
+		return( 0 ) ;
+	}
+}
+
+
+sub create_folder {
+        my( $imap2 , $h2_fold , $h1_fold ) = @_ ;
+        my( @parts , $parent ) ;
+
+        if ( $imap2->IsUnconnected(  ) ) {
+                myprint( "Host2: Unconnected state\n"  ) ;
+                return( 0 ) ;
+        }
+
+	if ( $create_folder_old ) {
+        	return( create_folder_old( $imap2 , $h2_fold , $h1_fold ) ) ;
+	}
+        myprint( "Creating folder [$h2_fold] on host2\n"  ) ;
+        if ( ( 'INBOX' eq uc  $h2_fold  )
+         and ( $imap2->exists( $h2_fold ) ) ) {
+                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"  ) ;
+                return( 1 ) ;
+        }
+
+
+        if ( ( not $mixfolders ) and ( $imap2->exists( $h2_fold ) ) ) {
+                myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n"  ) ;
+                return( 0 ) ;
+        }
+
+        @parts = split /\Q$h2_sep\E/, $h2_fold ;
+        pop @parts ;
+        $parent = join $h2_sep, @parts ;
+        $parent =~ s/^\s+|\s+$//g ;
+        if ( ( $parent ne q{} ) and ( ! $imap2->exists( $parent ) ) ) {
+                create_folder( $imap2 , $parent , $h1_fold ) ;
+        }
+
+        if ( ! $dry ) {
+                if ( ! $imap2->create( $h2_fold ) ) {
+			my $error = join q{},
+				"Could not create folder [$h2_fold] from [$h1_fold]: " ,
+				$imap2->LastError(  ), "\n" ;
+			errors_incr( $sync, $error ) ;
+                        # success if folder exists ("already exists" error)
+                        return( 1 ) if $imap2->exists( $h2_fold ) ;
+                        # failure since create failed
+                        return( 0 ) ;
+                }else{
+                        #create succeeded
+                        myprint( "Created folder [$h2_fold] on host2\n"  ) ;
+                        return( 1 ) ;
+                }
+        }else{
+                # dry mode, no folder so many imap will fail, assuming failure
+                myprint( "Created  folder [$h2_fold] on host2 $dry_message\n"  ) ;
+                if ( ! $justfolders ) {
+			myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n"
+			. "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ;
+                }
+		return( 0 ) ;
+        }
+}
+
+
+
+sub tests_folder_routines {
+	ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1'               );
+	ok(  add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo'       );
+	ok(  is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2'               );
+	ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST'       );
+	ok( !remove_from_requested_folders('folder_foo'), 'removed folder_foo'                   );
+	ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3'               );
+	my @f ;
+	ok(  @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f"        );
+	ok(  is_requested_folder('folder_bar'), 'is_requested_folder 4'                          );
+	ok(  is_requested_folder('folder_toto'), 'is_requested_folder 5'                         );
+	ok(  remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: '       );
+	ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6'                         );
+	ok( !remove_from_requested_folders('folder_bar'), 'remove_from_requested_folders: empty' ) ;
+
+        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' ) ;
+	@folderfirst = ( 'Z_11' ) ;
+        ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [ 'Z_11', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ;
+	@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' ) ;
+	@folderfirst = qw( Z_22  Z_11 ) ;
+	@folderlast  = qw( A_99  A_88 ) ;
+        ok( 0 == compare_lists( [ sort_requested_folders(  ) ], [  'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ;
+
+	return ;
+}
+
+
+sub sort_requested_folders {
+	my @requested_folders_sorted = () ;
+
+	foreach my $folder ( @folderfirst ) {
+        	remove_from_requested_folders( $folder ) ;
+        }
+
+	foreach my $folder ( @folderlast ) {
+        	remove_from_requested_folders( $folder ) ;
+        }
+
+	my @middle = sort keys %requested_folder ;
+
+        @requested_folders_sorted = ( @folderfirst, @middle, @folderlast ) ;
+
+	return( @requested_folders_sorted ) ;
+}
+
+sub is_requested_folder {
+	my ( $folder ) = @_;
+
+	return( defined  $requested_folder{ $folder }  ) ;
+}
+
+
+sub add_to_requested_folders {
+	my @wanted_folders = @_ ;
+
+	foreach my $folder ( @wanted_folders ) {
+	 	++$requested_folder{ $folder } ;
+	}
+	return( keys  %requested_folder  ) ;
+}
+
+sub remove_from_requested_folders {
+	my @wanted_folders = @_ ;
+
+	foreach my $folder ( @wanted_folders ) {
+	 	delete $requested_folder{ $folder } ;
+	}
+	return( keys %requested_folder ) ;
+}
+
+sub compare_lists {
+	my ($list_1_ref, $list_2_ref) = @_;
+
+	return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref);
+	return(0)  if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list
+	return(1)  if (not defined $list_2_ref); # end if only one list
+
+	if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]};
+	if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]};
+
+
+	my $last_used_indice = $MINUS_ONE;
+
+
+	ELEMENT:
+	foreach my $indice ( 0 .. $#{ $list_1_ref } ) {
+		$last_used_indice = $indice ;
+
+		# End of list_2
+		return 1 if ($indice > $#{ $list_2_ref } ) ;
+
+		my $element_list_1 = $list_1_ref->[$indice] ;
+		my $element_list_2 = $list_2_ref->[$indice] ;
+		my $balance = $element_list_1 cmp $element_list_2 ;
+		next ELEMENT if ($balance == 0) ;
+		return $balance ;
+	}
+	# each element equal until last indice of list_1
+	return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ;
+
+	# same size, each element equal
+	return 0 ;
+}
+
+sub tests_compare_lists {
+
+
+	my $empty_list_ref = [];
+
+	ok( 0 == compare_lists()               , 'compare_lists, no args');
+	ok( 0 == compare_lists(undef)          , 'compare_lists, undef = nothing');
+	ok( 0 == compare_lists(undef, undef)   , 'compare_lists, undef = undef');
+	ok($MINUS_ONE == compare_lists(undef , [])     , 'compare_lists, undef < []');
+	ok($MINUS_ONE == compare_lists(undef , [1])    , 'compare_lists, undef < [1]');
+	ok($MINUS_ONE == compare_lists(undef , [0])    , 'compare_lists, undef < [0]');
+      	ok(+1 == compare_lists([])             , 'compare_lists, [] > nothing');
+        ok(+1 == compare_lists([], undef)      , 'compare_lists, [] > undef');
+	ok( 0 == compare_lists([] , [])        , 'compare_lists, [] = []');
+
+	ok($MINUS_ONE == compare_lists([] , [1])        , 'compare_lists, [] < [1]');
+	ok(+1 == compare_lists([1] , [])        , 'compare_lists, [1] > []');
+
+
+	ok( 0 == compare_lists([1],  1 )          , 'compare_lists, [1] =  1 ') ;
+	ok( 0 == compare_lists( 1 , [1])          , 'compare_lists,  1  = [1]') ;
+	ok( 0 == compare_lists( 1 ,  1 )          , 'compare_lists,  1  =  1 ') ;
+	ok($MINUS_ONE == compare_lists( 0 ,  1 )          , 'compare_lists,  0  <  1 ') ;
+	ok($MINUS_ONE == compare_lists($MINUS_ONE ,  0 )          , 'compare_lists, -1  <  0 ') ;
+	ok($MINUS_ONE == compare_lists( 1 ,  2 )          , 'compare_lists,  1  <  2 ') ;
+	ok(+1 == compare_lists( 2 ,  1 )          , 'compare_lists,  2  >  1 ') ;
+
+
+	ok( 0 == compare_lists([1,2], [1,2])   , 'compare_lists,  [1,2] = [1,2]' ) ;
+	ok($MINUS_ONE == compare_lists([1], [1,2])     , 'compare_lists,    [1] < [1,2]' ) ;
+	ok(+1 == compare_lists([2], [1,2])     , 'compare_lists,    [2] > [1,2]' ) ;
+	ok($MINUS_ONE == compare_lists([1], [1,1])     , 'compare_lists,    [1] < [1,1]' ) ;
+	ok(+1 == compare_lists([1, 1], [1])    , 'compare_lists, [1, 1] >   [1]' ) ;
+	ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000])
+                                               , 'compare_lists, [1..20_000] = [1..20_000]' ) ;
+	ok($MINUS_ONE == compare_lists([1], [2])       , 'compare_lists, [1] < [2]') ;
+	ok( 0 == compare_lists([2], [2])       , 'compare_lists, [0] = [2]') ;
+	ok(+1 == compare_lists([2], [1])       , 'compare_lists, [2] > [1]') ;
+
+	ok($MINUS_ONE == compare_lists(['a'],  ['b'])   , 'compare_lists, ["a"] < ["b"]') ;
+	ok( 0 == compare_lists(['a'],  ['a'])   , 'compare_lists, ["a"] = ["a"]') ;
+	ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ;
+	ok(+1 == compare_lists(['b'],  ['a'])   , 'compare_lists, ["b"] > ["a"]') ;
+	ok($MINUS_ONE == compare_lists(['a'],  ['aa'])  , 'compare_lists, ["a"] < ["aa"]') ;
+	ok($MINUS_ONE == compare_lists(['a'],  ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ;
+	ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ;
+	ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ;
+        return ;
+}
+
+
+sub guess_prefix {
+	my @foldernames = @_ ;
+
+	return( undef ) unless ( @foldernames ) ;
+
+	my $prefix_guessed = q{} ;
+	foreach my $folder ( @foldernames ) {
+		next if ( $folder =~ m{^INBOX$}i ) ; # no guessing from INBOX
+		if ( $folder !~ m{^INBOX}i ) {
+			$prefix_guessed = q{} ; # prefix empty guessed
+			last ;
+		}
+		if ( $folder =~ m{^(INBOX(?:\.|\/))}i ) {
+			$prefix_guessed = $1 ;  # prefix Inbox/ or INBOX. guessed
+		}
+	}
+	return( $prefix_guessed ) ;
+}
+
+sub tests_guess_prefix {
+
+	ok( not( defined guess_prefix(  ) ), 'guess_prefix: no args' ) ;
+	ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
+	ok( q{} eq guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ;
+	ok( q{} eq guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ;
+	ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ;
+	ok( 'INBOX.' eq guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ;
+	ok( 'Inbox/' eq guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ;
+	ok( 'Inbox.' eq guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ;
+	ok( 'INBOX/' eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ;
+	ok( q{} eq guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ;
+	ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
+	ok( q{} eq guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ;
+
+	return ;
+}
+
+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"  ) ;
+	$prefix_guessed = guess_prefix( @{ $folders_ref } ) ;
+	myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n"  ) ;
+	( $debug or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n"  ) ;
+	if ( $imap->has_capability( 'namespace' ) ) {
+		my $r_namespace = $imap->namespace(  ) ;
+		$prefix_out = $r_namespace->[0][0][0] ;
+                myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n"  ) ;
+		if ( defined  $prefix_in  ) {
+                	myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n"  ) ;
+                	$prefix_out = $prefix_in ;
+                	return( $prefix_out ) ;
+                }else{
+                	# all good
+	                return( $prefix_out ) ;
+                }
+	}
+	else{
+        	if ( defined  $prefix_in  ) {
+                	myprint( "$Side: using [$prefix_in] given by $prefix_opt\n"  ) ;
+                	$prefix_out = $prefix_in ;
+                	return( $prefix_out ) ;
+                }else{
+			myprint(
+			  "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n",
+			  help_to_guess_prefix( $imap, $prefix_opt ) ) ;
+			return( $prefix_guessed ) ;
+                }
+	}
+        return ;
+}
+
+
+sub guess_separator {
+	my @foldernames = @_ ;
+
+	#return( undef ) unless ( @foldernames ) ;
+
+	my $sep_guessed ;
+	my %counter ;
+	foreach my $folder ( @foldernames ) {
+		$counter{'/'}++  while ( $folder =~ m{/}g ) ;  # count /
+		$counter{'.'}++  while ( $folder =~ m{\.}g ) ; # count .
+		$counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}g ) ; # count \\
+	}
+	my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys  %counter  ;
+	#myprint( "@race_sorted\n"  ) ;
+	$sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found.
+	return( $sep_guessed ) ;
+}
+
+sub tests_guess_separator {
+	ok( '/' eq  guess_separator(  ), 'guess_separator: no args' ) ;
+	ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ;
+	ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ;
+	ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ;
+	ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ;
+	return ;
+}
+
+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"  ) ;
+	$sep_guessed = guess_separator( @{ $folders_ref } ) ;
+	myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n"  ) ;
+
+	( $debug or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n"  ) ;
+	if ( $imap->has_capability( 'namespace' ) ) {
+		$sep_out = $imap->separator(  ) ;
+		if ( defined  $sep_out  ) {
+                	myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n"  ) ;
+                        if ( defined  $sep_in  ) {
+                		myprint( "$Side: but using [$sep_in] given by $sep_opt\n"  ) ;
+                        	$sep_out = $sep_in ;
+                        	return( $sep_out ) ;
+                        }else{
+                        	return( $sep_out ) ;
+                        }
+		}else{
+                	if ( defined  $sep_in  ) {
+                        	myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n"  ) ;
+                        	$sep_out = $sep_in ;
+                        	return( $sep_out ) ;
+                        }else{
+				myprint(
+		  		"$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n",
+                  		help_to_guess_sep( $imap, $sep_opt ) ) ;
+				return( $sep_guessed ) ;
+                        }
+                }
+	}
+	else{
+        	if ( defined  $sep_in  ) {
+                	myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n"  ) ;
+                	$sep_out = $sep_in ;
+                	return( $sep_out ) ;
+                }else{
+			myprint(
+		  	"$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n",
+		      	help_to_guess_sep( $imap, $sep_opt ) ) ;
+			return( $sep_guessed ) ;
+                }
+	}
+        return ;
+}
+
+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"
+	. "the complete listing of folders may help you to find it\n"
+	. folders_list_to_help( $imap ) ;
+
+	return( $help_to_guess_sep ) ;
+}
+
+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"
+	. "the folowing listing of folders may help you to find it:\n"
+	. folders_list_to_help( $imap ) ;
+
+	return( $help_to_guess_prefix ) ;
+}
+
+
+sub folders_list_to_help {
+	my($imap) = @_ ;
+
+	my @folders = $imap->folders ;
+	my $listing = join q{}, map { "[$_]\n" } @folders ;
+	return( $listing ) ;
+}
+
+
+sub tests_separator_invert {
+	$fixslash2 = 0 ;
+	ok( not( defined separator_invert(  )  ), 'separator_invert: no args' ) ;
+	ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ;
+	ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ;
+
+	ok( q{} eq separator_invert( q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ;
+	ok( 'lalala' eq separator_invert( 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ;
+	ok( 'lalala' eq separator_invert( 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ;
+	ok( 'lal/ala' eq separator_invert( 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ;
+	ok( 'lal.ala' eq separator_invert( 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+	ok( 'lal/ala' eq separator_invert( 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
+	ok( 'la.l/ala' eq separator_invert( 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ;
+
+	ok( 'l/al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+        $fixslash2 = 1 ;
+	ok( 'l_al.ala' eq separator_invert( 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ;
+
+	return ;
+}
+
+sub separator_invert {
+	my( $h1_fold, $h1_separator, $h2_separator ) = @_ ;
+
+	return( undef ) if ( not defined  $h1_fold  or not defined  $h1_separator  or not defined  $h2_separator  ) ;
+	# The separator we hope we'll never encounter: 00000000 == 0x00
+	my $o_sep = "\000" ;
+
+	my $h2_fold = $h1_fold ;
+	$h2_fold =~ s,\Q$h2_separator,$o_sep,xg ;
+	$h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ;
+	$h2_fold =~ s,\Q$o_sep,$h1_separator,xg ;
+        $h2_fold =~ s,/,_,xg if( $fixslash2 and '/' ne $h2_separator and '/' eq $h1_separator ) ;
+	return( $h2_fold ) ;
+}
+
+
+sub tests_imap2_folder_name {
+
+$h1_prefix = $h2_prefix = q{};
+$h1_sep = '/';
+$h2_sep = '.';
+
+$debug and myprint( <<"EOS"
+prefix1: [$h1_prefix]
+prefix2: [$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->{f1f2}{ 'auto' } = 'moto' ;
+ok( 'moto' eq imap2_folder_name( 'auto' ), 'imap2_folder_name: auto' ) ;
+$sync->{f1f2}{ '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');
+
+
+
+$fixslash2 = 0 ;
+$h1_prefix = q{ };
+
+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');
+
+$h1_sep = '.' ;
+$h2_sep = '/' ;
+$h1_prefix = 'INBOX.' ;
+$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' ) ;
+
+
+return ;
+
+}
+
+sub imap2_folder_name {
+	my ( $h1_fold ) = @_ ;
+	my ( $h2_fold ) ;
+	if ( $sync->{f1f2}{ $h1_fold } ) {
+		$h2_fold = $sync->{f1f2}{ $h1_fold } ;
+		( $debug or $sync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n"  ) ;
+		return( $h2_fold ) ;
+	}
+	if ( $sync->{f1f2auto}{ $h1_fold } ) {
+		$h2_fold = $sync->{f1f2auto}{ $h1_fold } ;
+		( $debug or $sync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n"  ) ;
+		return( $h2_fold ) ;
+	}
+
+	$h2_fold = prefix_seperator_invertion( $h1_fold ) ;
+	$h2_fold = regextrans2( $h2_fold ) ;
+	return( $h2_fold ) ;
+}
+
+sub prefix_seperator_invertion {
+	my ( $h1_fold ) = @_ ;
+	my ( $h2_fold ) ;
+
+	# first we remove the prefix
+	$h1_fold =~ s/^\Q$h1_prefix\E//x ;
+	( $debug or $sync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n"  ) ;
+	$h2_fold = separator_invert( $h1_fold, $h1_sep, $h2_sep ) ;
+	( $debug or $sync->{debugfolders} ) and myprint( "inverted  separators: [$h2_fold]\n"  ) ;
+	# Adding the prefix supplied by namespace or the --prefix2 option
+	$h2_fold = $h2_prefix . $h2_fold
+	  unless( ( $h2_prefix eq 'INBOX' . $h2_sep ) and ( $h2_fold =~ m/^INBOX$/xi ) ) ;
+	( $debug or $sync->{debugfolders} ) and myprint( "added   host2 prefix: [$h2_fold]\n"  ) ;
+	return( $h2_fold ) ;
+}
+
+sub regextrans2 {
+	my( $h2_fold ) = @_ ;
+	# Transforming the folder name by the --regextrans2 option(s)
+	foreach my $regextrans2 ( @regextrans2 ) {
+	        my $h2_fold_before = $h2_fold ;
+		my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ;
+		( $debug or $sync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n"  ) ;
+                if ( not ( defined  $ret  ) or $@ ) {
+			die_clean( "error: eval regextrans2 '$regextrans2': $@\n" ) ;
+                }
+	}
+	return( $h2_fold ) ;
+}
+
+
+sub tests_decompose_regex {
+	ok( 1, 'decompose_regex 1' ) ;
+	ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ;
+	ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ;
+	return ;
+}
+
+sub decompose_regex {
+	my $regex = shift ;
+	my( $left_part, $right_part ) ;
+
+	( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x;
+        return( q{}, q{} ) if not $left_part ;
+	return( $left_part, $right_part ) ;
+}
+
+
+sub foldersizes {
+
+	my ( $side, $imap, $search_cmd, @folders ) = @_ ;
+	my $total_size = 0 ;
+	my $total_nb = 0 ;
+	my $biggest_in_all = 0 ;
+
+	my $nb_folders = scalar  @folders  ;
+	my $ct_folders = 0 ; # folder counter.
+	myprint( "++++ Calculating sizes of $nb_folders folders on $side\n"  ) ;
+	foreach my $folder ( @folders )     {
+		my $stot = 0 ;
+		my $nb_msgs = 0 ;
+		$ct_folders++ ;
+		myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ;
+                if ( 'Host2' eq $side and not exists  $h2_folders_all_UPPER{ uc  $folder  }  ) {
+		        myprint( " does not exist yet\n") ;
+			next ;
+		}
+                if ( 'Host1' eq $side and not exists  $h1_folders_all{ $folder }  ) {
+		        myprint( " does not exist\n" ) ;
+			next ;
+		}
+
+		last if $imap->IsUnconnected(  ) ;
+		# FTGate is RFC buggy with EXAMINE it does not act as SELECT
+		#unless ( $imap->examine( $folder ) ) {
+		unless ( $imap->select( $folder ) ) {
+			my $error = join q{},
+				"$side Folder $folder: Could not select: ",
+				$imap->LastError,  "\n"  ;
+			errors_incr( $sync, $error ) ;
+			next ;
+		}
+		last if $imap->IsUnconnected(  ) ;
+
+		my $hash_ref = { } ;
+		my @msgs = select_msgs( $imap, undef, $search_cmd, $folder ) ;
+		$nb_msgs = scalar  @msgs  ;
+		my $biggest_in_folder = 0 ;
+		@{ $hash_ref }{ @msgs } = ( undef ) if @msgs ;
+
+		last if $imap->IsUnconnected(  ) ;
+		if ( $nb_msgs > 0 and @msgs ) {
+                	if ( $abletosearch ) {
+				if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) {
+                                        my $error = "$side failure with fetch_hash: $@" ;
+                                        errors_incr( $sync, $error ) ;
+                                        return ;
+                                }
+                        }else{
+				my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
+				my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+				if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) {
+                                        my $error = "$side failure with fetch_hash: $@" ;
+                                        errors_incr( $sync, $error ) ;
+                                        return ;
+                                }
+                        }
+			for ( keys %{ $hash_ref } ) {
+                        	my $size =  $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ;
+                        	$stot    += $size ;
+                                $biggest_in_folder =  max( $biggest_in_folder, $size ) ;
+                        }
+		}
+
+		myprintf( ' Size: %9s', $stot ) ;
+		myprintf( ' Messages: %5s', $nb_msgs ) ;
+		myprintf( " Biggest: %9s\n", $biggest_in_folder ) ;
+		$total_size += $stot ;
+		$total_nb += $nb_msgs ;
+                $biggest_in_all =  max( $biggest_in_all, $biggest_in_folder ) ;
+	}
+	myprintf( "%s Nb folders:      %11s folders\n",    $side, $nb_folders ) ;
+	myprintf( "%s Nb messages:     %11s messages\n",   $side, $total_nb ) ;
+	myprintf( "%s Total size:      %11s bytes (%s)\n", $side, $total_size, bytes_display_string( $total_size ) ) ;
+	myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string( $biggest_in_all ) ) ;
+	myprintf( "%s Time spent:      %11.1f seconds\n",  $side, timenext(  ) ) ;
+        return( $total_nb, $total_size ) ;
+}
+
+sub timenext {
+	my ( $timenow, $timediff ) ;
+	# $timebefore is global, beurk !
+	$timenow    = time ;
+	$timediff   = $timenow - $timebefore ;
+	$timebefore = $timenow ;
+	return( $timediff ) ;
+}
+
+sub timesince {
+	my $timeinit = shift ;
+	my ( $timenow, $timediff ) ;
+	$timenow    = time ;
+	$timediff   = $timenow - $timeinit ;
+	return( $timediff ) ;
+}
+
+
+
+
+sub tests_flags_regex {
+
+	ok( q{} eq flags_regex(q{} ), 'flags_regex, null string q{}' ) ;
+	ok( q'\Seen NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), 'flags_regex, nothing to do');
+
+	@regexflag = ('I am BAD' ) ;
+        ok( not ( defined flags_regex( q{} ) ), 'flags_regex, bad regex' ) ;
+
+	@regexflag = ( 's/NonJunk//g' ) ;
+	ok( q'\Seen  $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove NonJunk: 's/NonJunk//g'} ) ;
+	@regexflag = ( q's/\$Spam//g' ) ;
+	ok( '\Seen NonJunk ' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove $Spam: 's/\$Spam//g'} ) ;
+
+	@regexflag = ( 's/\\\\Seen//g' ) ;
+
+	ok( q' NonJunk $Spam' eq flags_regex( q'\Seen NonJunk $Spam' ), q{flags_regex, remove \Seen: 's/\\\\\\\\Seen//g'} ) ;
+
+	@regexflag = ( 's/(\s|^)[^\\\\]\w+//g' ) ;
+	ok( '\Seen \Middle \End'   eq flags_regex( q'\Seen NonJunk \Middle $Spam \End' ), q{flags_regex: only \word among \Seen NonJunk \Middle $Spam \End} ) ;
+	ok( ' \Seen \Middle \End1' eq flags_regex( q'Begin \Seen NonJunk \Middle $Spam \End1 End' ), 
+                     q'flags_regex: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End' ) ;
+
+	@regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g' ) ;
+	ok('Keep1 Keep2  ReB' eq flags_regex('ReA Keep1 REM Keep2 ReB'), 'Keep only regex' ) ;
+	
+	ok('Keep1 Keep2 ' eq flags_regex( 'REM REM Keep1 Keep2'), 'Keep only regex' ) ;
+	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 REM REM Keep2'), 'Keep only regex' ) ;
+	ok('Keep1 Keep2 ' eq flags_regex( 'REM Keep1 REM REM  Keep2'), 'Keep only regex' ) ;
+	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2'), 'Keep only regex' ) ;
+	ok('Keep1 ' eq flags_regex( 'REM Keep1'), 'Keep only regex' ) ;
+
+	@regexflag = ( q's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ;
+	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 ReB'), 'Keep only regex' ) ;
+	ok('Keep1 Keep2 ' eq flags_regex( 'Keep1 Keep2 REM REM  REM'), 'Keep only regex' ) ;
+	ok('Keep2 ' eq flags_regex('Keep2 REM REM  REM'), 'Keep only regex' ) ;
+	
+
+	@regexflag = ( q's/.*?(Keep1|Keep2|Keep3)/$1 /g',
+	   's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g');
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), 'Keep only regex');
+	ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
+	ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
+	ok('Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex');
+	ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
+
+	@regexflag = ('s/(.*)/$1 jrdH8u/');
+	ok('REM  REM  REM REM REM jrdH8u' eq flags_regex('REM  REM  REM REM REM'), q{Keep only regex 's/(.*)/\$1 jrdH8u/'} ) ;
+	@regexflag = ('s/jrdH8u *//');
+	ok('REM  REM  REM REM REM ' eq flags_regex('REM  REM  REM REM REM jrdH8u'), q{Keep only regex s/jrdH8u *//} ) ;
+
+	@regexflag = (
+	's/(.*)/$1 jrdH8u/',
+	's/.*?(Keep1|Keep2|Keep3|jrdH8u)/$1 /g',
+	's/(Keep1|Keep2|Keep3|jrdH8u) (?!(Keep1|Keep2|Keep3|jrdH8u)).*/$1 /g',
+	's/jrdH8u *//'
+	);
+
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2 REM'), q{Keep only regex 'REM Keep1 REM Keep2 REM'} ) ;
+	ok('Keep1 Keep2 ' eq flags_regex('Keep1 REM Keep2 REM'), 'Keep only regex');
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 Keep2 REM'), 'Keep only regex');
+	ok('Keep1 Keep2 ' eq flags_regex('REM Keep1 REM Keep2'), 'Keep only regex');
+	ok('Keep1 Keep2 Keep3 ' eq flags_regex('REM Keep1 REM Keep2 REM REM Keep3 REM'), 'Keep only regex');
+	ok('Keep1 ' eq flags_regex('REM  REM Keep1 REM REM REM '), 'Keep only regex');
+	ok('Keep1 Keep3 ' eq flags_regex('RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 '), 'Keep only regex');
+	ok(q{} eq flags_regex('REM  REM REM REM REM'), 'Keep only regex');
+
+	@regexflag = (
+	's/(.*)/$1 jrdH8u/',
+	's/.*?(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)/$1 /g',
+	's/(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u) (?!(\\\\Seen|\\\\Answered|\\\\Flagged|\\\\Deleted|\\\\Draft|jrdH8u)).*/$1 /g',
+	's/jrdH8u *//'
+	);
+
+	ok('\\Deleted \\Answered '
+	    eq flags_regex('Blabla $Junk \\Deleted machin \\Answered truc'), 'Keep only regex: Exchange case' ) ;
+	ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string' ) ;
+	ok( q{}
+	   eq flags_regex('Blabla $Junk  machin  truc'), 'Keep only regex: Exchange case, no accepted flags' ) ;
+	ok( '\\Deleted \\Answered \\Draft \\Flagged '
+	    eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '), 'Keep only regex: Exchange case' ) ;
+
+
+	@regexflag = (
+	's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg'
+	);
+
+	ok( '\\Deleted \\Answered '
+	eq flags_regex('Blabla \$Junk \\Deleted machin \\Answered truc'),
+	'Keep only regex: Exchange case (Phil)' ) ;
+
+	ok( q{} eq flags_regex( q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ;
+
+	ok( q{}
+	eq flags_regex('Blabla $Junk  machin  truc'),
+	'Keep only regex: Exchange case, no accepted flags (Phil)' ) ;
+
+	ok('\\Deleted \\Answered \\Draft \\Flagged '
+	eq flags_regex('\\Deleted    \\Answered  \\Draft \\Flagged '),
+	'Keep only regex: Exchange case (Phil)' ) ;
+
+	return ;
+}
+
+sub flags_regex {
+	my ( $h1_flags ) = @_ ;
+	foreach my $regexflag ( @regexflag ) {
+		my $h1_flags_orig = $h1_flags ;
+		$debugflags and myprint( "eval \$h1_flags =~ $regexflag\n"  ) ;
+		my $ret = eval "\$h1_flags =~ $regexflag ; 1 " ;
+		$debugflags and myprint( "regexflag $regexflag [$h1_flags_orig] -> [$h1_flags]\n"  ) ;
+                if( not ( defined $ret ) or $@ ) {
+			myprint( "Error: eval regexflag '$regexflag': $@\n"  ) ;
+                        return( undef ) ;
+                }
+	}
+	return( $h1_flags ) ;
+}
+
+sub acls_sync {
+	my($h1_fold, $h2_fold) = @_ ;
+	if ( $syncacls ) {
+		my $h1_hash = $imap1->getacl($h1_fold)
+		  or myprint( "Could not getacl for $h1_fold: $@\n" ) ;
+		my $h2_hash = $imap2->getacl($h2_fold)
+		  or myprint( "Could not getacl for $h2_fold: $@\n" ) ;
+		my %users = map { ($_, 1) } ( keys  %{ $h1_hash} , keys %{ $h2_hash }  ) ;
+		foreach my $user (sort keys %users ) {
+			my $acl = $h1_hash->{$user} || 'none' ;
+			myprint( "acl $user: [$acl]\n" ) ;
+			next if ($h1_hash->{$user} && $h2_hash->{$user} &&
+				 $h1_hash->{$user} eq $h2_hash->{$user});
+			unless ($dry) {
+				myprint( "setting acl $h2_fold $user $acl\n" ) ;
+				$imap2->setacl($h2_fold, $user, $acl)
+				  or myprint( "Could not set acl: $@\n" ) ;
+			}
+		}
+	}
+        return ;
+}
+
+
+sub tests_permanentflags {
+
+	my $string;
+	ok(q{} eq permanentflags(' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited'),
+	   'permanentflags \*');
+	ok('\Draft \Answered' eq permanentflags(' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited'),
+	   'permanentflags \Draft \Answered');
+	ok('\Draft \Answered'
+	   eq permanentflags('Blabla',
+	                     ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited',
+			     'Blabla'),
+	   'permanentflags \Draft \Answered'
+	);
+	ok(q{} eq permanentflags('Blabla'), 'permanentflags nothing');
+        return ;
+}
+
+sub permanentflags {
+	my @lines = @_ ;
+
+	foreach my $line (@lines) {
+		if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) {
+			( $debugflags or $debug ) and myprint( "permanentflags: $line"  ) ;
+			my $permanentflags = $1 ;
+			if ( $permanentflags =~ m{\\\*}x ) {
+				$permanentflags = q{} ;
+			}
+			return($permanentflags) ;
+		} ;
+	}
+        return( q{} ) ;
+}
+
+sub tests_flags_filter {
+
+	ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' );
+	ok( q{} eq flags_filter('\Seen', '\Draft  \Answered'), 'flags_filter ' );
+	ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' );
+	ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' );
+	ok( '\Seen \Draft'
+	   eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' );
+	ok( '\Seen \Draft'
+	   eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' );
+        return ;
+}
+
+sub flags_filter {
+	my( $flags, $allowed_flags ) = @_ ;
+
+	my @flags = split  /\s+/x, $flags ;
+	my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ;
+	my @flags_out     = map { exists $allowed_flags{$_} ? $_ : () } @flags ;
+
+	my $flags_out = join q{ }, @flags_out ;
+
+	return( $flags_out ) ;
+}
+
+sub flagscase {
+	my $flags = shift ;
+
+	my @flags = split /\s+/x, $flags ;
+	my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ;
+	my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ;
+
+	my $flags_out = join q{ }, @flags_out ;
+
+	return( $flags_out ) ;
+}
+
+sub tests_flagscase {
+	ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ;
+	ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ;
+
+	ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ;
+	ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ;
+
+	ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT  LALA \SEEN' ), 'flagscase: \DRAFT  LALA \SEEN -> \Draft LALA \Seen' ) ;
+	ok( '\Draft lala \Seen' eq flagscase( '\DRAFT  lala \SEEN' ), 'flagscase: \DRAFT  lala \SEEN -> \Draft lala \Seen' ) ;
+        return ;
+}
+
+
+
+sub ucsecond {
+	my $string = shift ;
+	my $output ;
+
+	return( $string )  if ( 1 >= length $string ) ;
+	
+	$output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ;
+	#myprint( "UUU $string -> $output\n"  ) ;
+	return( $output ) ;
+}
+
+
+sub tests_ucsecond {
+	ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ;
+	ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE'  ) ;
+	ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE'  ) ;
+	ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde'  ) ;
+	ok( 'A'     eq ucsecond( 'A' ),     'ucsecond: A  -> A'  ) ;
+	ok( 'AB'    eq ucsecond( 'Ab' ),    'ucsecond: Ab -> AB' ) ;
+	ok( '\B'    eq ucsecond( '\b' ),    'ucsecond: \b -> \B' ) ;
+	ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ;
+        return ;
+}
+
+
+sub select_msgs {
+	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
+	my ( @msgs ) ;
+
+	if ( $abletosearch ) {
+		@msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
+	}else{
+		@msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ;
+	}
+	return(  @msgs ) ;
+
+}
+
+sub select_msgs_by_search {
+	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
+	my ( @msgs, @msgs_all ) ;
+
+        # Need to have the whole list in msgs_all_hash_ref
+        # without calling messages() several times.
+        # Need all messages list to avoid deleting useful cache part
+        # in case of --search or --minage or --maxage
+
+	if ( ( defined  $msgs_all_hash_ref  and $usecache )
+        or ( not defined  $maxage  and not defined  $minage  and not defined  $search_cmd  )
+        ) {
+
+       		$debugdev and myprint( "Calling messages()\n"  ) ;
+		@msgs_all = $imap->messages(  ) ;
+
+                return if ( $#msgs_all == 0 && !defined  $msgs_all[0]  ) ;
+
+                if ( defined  $msgs_all_hash_ref  ) {
+                        @{ $msgs_all_hash_ref }{ @msgs_all } =  () ;
+                }
+                # return all messages
+                if ( not defined  $maxage  and not defined  $minage  and not defined  $search_cmd  ) {
+                        return( @msgs_all ) ;
+                }
+	}
+
+        if ( defined  $search_cmd  ) {
+        	@msgs = $imap->search( $search_cmd ) ;
+                return( @msgs ) ;
+        }
+
+	# we are here only if $maxage or $minage is defined
+        @msgs = select_msgs_by_age( $imap ) ;
+	return( @msgs );
+}
+
+
+sub select_msgs_by_fetch {
+	my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ;
+	my ( @msgs, @msgs_all, %fetch ) ;
+
+        # Need to have the whole list in msgs_all_hash_ref
+        # without calling messages() several times.
+        # Need all messages list to avoid deleting useful cache part
+        # in case of --search or --minage or --maxage
+
+
+	$debugdev and myprint( "Calling fetch_hash()\n"  ) ;
+	my $uidnext = $imap->uidnext( $folder ) || $uidnext_default ;
+	my $fetch_hash_uids = $fetch_hash_set || "1:$uidnext" ;
+	%fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ;
+
+        @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  ) {
+                 @{ $msgs_all_hash_ref }{ @msgs_all } =  () ;
+        }
+        # return all messages
+        if ( not defined  $maxage  and not defined  $minage  and not defined  $search_cmd  ) {
+                return( @msgs_all ) ;
+        }
+
+        if ( defined  $search_cmd  ) {
+		myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n"  ) ;
+        	@msgs = $imap->search( $search_cmd ) ;
+                return( @msgs ) ;
+        }
+
+	# we are here only if $maxage or $minage is defined
+	my( @max, @min, $maxage_epoch, $minage_epoch ) ;
+	if ( defined  $maxage  ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; }
+	if ( defined  $minage  ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; }
+	foreach my $msg ( @msgs_all ) {
+		my $idate = $fetch{ $msg }->{'INTERNALDATE'} ;
+		#myprint( "$idate\n"  ) ;
+		if ( defined  $maxage  and ( epoch( $idate ) >= $maxage_epoch ) ) {
+			push  @max, $msg  ;
+		}
+		if ( defined  $minage  and ( epoch( $idate ) <= $minage_epoch ) ) {
+			push  @min, $msg  ;
+		}
+	}
+        @msgs = msgs_from_maxmin( \@max, \@min ) ;
+	return( @msgs ) ;
+}
+
+sub select_msgs_by_age {
+	my( $imap ) = @_ ;
+
+	my( @max, @min, @msgs, @inter, @union ) ;
+
+	if ( defined  $maxage  ) {
+		@max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ;
+	}
+	if ( defined  $minage  ) {
+		@min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ;
+	}
+
+	@msgs = msgs_from_maxmin( \@max, \@min ) ;
+	return( @msgs ) ;
+}
+
+sub msgs_from_maxmin {
+	my( $max_ref, $min_ref ) = @_ ;
+	my( @max, @min, @msgs, @inter, @union ) ;
+
+	@max = @{ $max_ref } ;
+	@min = @{ $min_ref } ;
+
+	SWITCH: {
+		unless( defined  $minage  ) { @msgs = @max ; last SWITCH } ;
+		unless( defined  $maxage  ) { @msgs = @min ; last SWITCH } ;
+		my ( %union, %inter ) ;
+		foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ }
+		@inter = sort { $a <=> $b } keys  %inter  ;
+		@union = sort { $a <=> $b } keys  %union  ;
+		# normal case
+		if ( $minage <= $maxage )  { @msgs = @inter ; last SWITCH } ;
+		# just exclude messages between
+		if ( $minage > $maxage )  { @msgs = @union ; last SWITCH } ;
+
+	}
+	return( @msgs ) ;
+}
+
+sub tests_msgs_from_maxmin {
+	my @msgs ;
+	$maxage = $NUMBER_200 ;
+	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+	ok( 0 == compare_lists( [ '1', '2' ], \@msgs ), 'msgs_from_maxmin: maxage++' ) ;
+	$minage = $NUMBER_100 ;
+	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+	ok( 0 == compare_lists( [ '2' ], \@msgs ), 'msgs_from_maxmin:  -maxage++minage-' ) ;
+	$minage = $NUMBER_300 ;
+	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+	ok( 0 == compare_lists( [ '1', '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++maxage-minage++' ) ;
+	$maxage = undef ;
+	@msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ;
+	ok( 0 == compare_lists( [ '2', '3' ], \@msgs ), 'msgs_from_maxmin:  ++minage-' ) ;
+	return ;
+}
+
+
+sub lastuid {
+	my $imap   = shift ;
+	my $folder = shift ;
+	my $lastuid_guess  = shift ;
+	my $lastuid ;
+
+	# rfc3501: The only reliable way to identify recent messages is to
+	#          look at message flags to see which have the \Recent flag
+	#          set, or to do a SEARCH RECENT.
+	# SEARCH RECENT doesn't work this way on courrier.
+
+	my @recent_messages ;
+	# SEARCH RECENT for each transfer can be expensive with a big folder
+	# Call commented for now
+	#@recent_messages = $imap->recent(  ) ;
+	#myprint( "Recent: @recent_messages\n" ) ;
+
+	my $max_recent ;
+	$max_recent = max( @recent_messages ) ;
+
+	if ( defined  $max_recent  and ($lastuid_guess <= $max_recent ) ) {
+		$lastuid = $max_recent ;
+	}else{
+		$lastuid = $lastuid_guess
+	}
+	return( $lastuid ) ;
+}
+
+sub size_filtered {
+	my( $h1_size, $h1_msg, $h1_fold, $h2_fold  ) = @_ ;
+
+        $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef
+	if (defined $maxsize and $h1_size > $maxsize) {
+		myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $maxsize bytes)\n" ) ;
+		$total_bytes_skipped += $h1_size;
+		$nb_msg_skipped += 1;
+		return( 1 ) ;
+	}
+	if (defined $minsize and $h1_size <= $minsize) {
+		myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ;
+		$total_bytes_skipped += $h1_size;
+		$nb_msg_skipped += 1;
+		return( 1 ) ;
+	}
+	return( 0 ) ;
+}
+
+sub message_exists {
+	my( $imap, $msg ) = @_ ;
+	return( 1 ) if not $imap->Uid(  ) ;
+
+	my $search_uid ;
+        ( $search_uid ) = $imap->search( "UID $msg" ) ;
+        #myprint( "$search ? $msg\n"  ) ;
+        return( 1 ) if ( $search_uid eq $msg ) ;
+        return( 0 ) ;
+}
+
+sub copy_message {
+	# copy
+
+	my ( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ;
+	( $debug or $dry) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $dry_message\n" ) ;
+
+	my $h1_size  = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'}  || 0 ;
+	my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'}        || q{} ;
+	my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ;
+
+
+        if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold  ) ) {
+        	$h1_nb_msg_processed +=1 ;
+                return ;
+        }
+
+	debugsleep( $sync ) ;
+	myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size )   ;
+
+
+        if ( $checkmessageexists and not message_exists( $imap1, $h1_msg ) ) {
+		$total_bytes_skipped += $h1_size;
+		$nb_msg_skipped += 1;
+        	$h1_nb_msg_processed +=1 ;
+                return ;
+        }
+        if ( $sync->{debugmemory} ) {
+                myprintf("C1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+	my ( $string, $string_len ) ;
+        ( $string_len ) = message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ;
+
+        if ( $sync->{debugmemory} ) {
+                myprintf("C2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+        # not defined or empty $string
+        if ( ( not $string ) and ( not $string_len ) ) {
+		myprint( "- msg $h1_fold/$h1_msg skipped.\n"  ) ;
+		$total_bytes_skipped += $h1_size;
+		$nb_msg_skipped += 1;
+                $h1_nb_msg_processed +=1 ;
+                return ;
+        }
+
+        # Lines too long (or not enough) => do no copy or fix
+        if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) {
+		$string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ;
+		if ( not defined  $string  ) {
+			$h1_nb_msg_processed +=1 ;
+			$total_bytes_skipped += $h1_size ;
+			$nb_msg_skipped += 1 ;
+			return ;
+		}
+	}
+
+	my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ;
+
+	( $debug or $debugflags ) and
+        myprint( "Host1 flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
+
+	$h1_flags = flags_for_host2( $h1_flags, $permanentflags2 ) ;
+
+	( $debug or $debugflags ) and
+        myprint( "Host1 flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n"  ) ;
+
+	$h1_date = undef if ($h1_date eq q{});
+
+	my $new_id = append_message_on_host2( \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ;
+
+	if ( $new_id and $syncflagsaftercopy ) {
+        	sync_flags_after_copy( $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ;
+        }
+
+	if ( $sync->{debugmemory} ) {
+        	myprintf("C3: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+        return $new_id ;
+}
+
+
+
+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"  ) ;
+
+        if ( ( defined $minmaxlinelength )  and ( $maxlinelength_string <= $minmaxlinelength ) ) {
+		my $subject = subject( $string ) ;
+         	$debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
+                      	. "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ;
+         	return ;
+        }
+
+        if ( ( defined $maxlinelength )  and ( $maxlinelength_string > $maxlinelength ) ) {
+         	my $subject = subject( $string ) ;
+		if ( $maxlinelengthcmd ) {
+			$string = pipemess( $string, $maxlinelengthcmd ) ;
+			# string undef means something was bad.
+			if ( not ( defined  $string  ) ) {
+				myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] "
+				      . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ;
+				return ;
+			}else{
+				return $string ;
+			}
+		}
+         	myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] "
+                      . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ;
+		return ;
+	}
+	return $string ;
+}
+
+
+sub message_for_host2 {
+
+# global variable list: 
+# @skipmess
+# @regexmess
+# @pipemess
+# $addheader
+# $debugcontent
+# $debug
+# 
+# API current
+#
+# at failure: 
+#   * return nothing ( will then be undef or () )
+#   * $string_ref content is undef or empty
+# at success:
+#   * return string length ($string_ref content length)
+#   * $string_ref content filled with message
+
+# API future
+# 
+# 
+	my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ;
+
+        # abort when missing a parameter
+        if ( (!$sync) or  (!$h1_msg) or (!$h1_fold) or (!$h1_size) or (!defined $h1_flags) or (!$h1_idate) or (!$h1_fir_ref) or (!$string_ref) ) {
+                return ;
+        }
+
+        if ( $sync->{debugmemory} ) {
+                myprintf("M1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+        my $imap1 = $sync->{imap1} ;
+	my $string_ok = $imap1->message_to_file( $string_ref, $h1_msg ) ;
+
+        if ( $sync->{debugmemory} ) {
+                myprintf("M2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+	my $string_len = length_ref( $string_ref  ) ;
+
+
+	unless ( defined  $string_ok  and $string_len ) {
+		# undef or 0 length
+		my $error = join q{},
+			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ",
+			$imap1->LastError || q{}, "\n"  ;
+		errors_incr( $sync, $error ) ;
+		$total_bytes_error += $h1_size if ( $h1_size ) ;
+                $h1_nb_msg_processed +=1 ;
+		return ;
+	}
+
+	if ( @skipmess ) {
+		my $match = skipmess( ${ $string_ref } ) ;
+                # string undef means the eval regex was bad.
+                if ( not ( defined  $match  ) ) {
+                	myprint(
+			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        . " could not be skipped by --skipmess option, bad regex\n" ) ;
+                	return ;
+                }
+                if ( $match ) {
+                        my $subject = subject( ${ $string_ref } ) ;
+                        myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                            . " (Subject:[$subject]) skipped by --skipmess\n" ) ;
+                	return ;
+                }
+	}
+
+	if ( @regexmess ) {
+		${ $string_ref } = regexmess( ${ $string_ref } ) ;
+                # string undef means the eval regex was bad.
+                if ( not ( defined  ${ $string_ref }  ) ) {
+                	myprint(
+			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        . " could not be transformed by --regexmess\n" ) ;
+                	return ;
+                }
+	}
+
+	if ( @pipemess ) {
+		${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ;
+                # string undef means something was bad.
+                if ( not ( defined  ${ $string_ref }  ) ) {
+                	myprint(
+			"- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]"
+                        . " could not be successfully transformed by --pipemess option\n" ) ;
+                	return ;
+                }
+	}
+
+        if ( $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"  ) ;
+                ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ;
+        }
+
+        $string_len = length_ref( $string_ref  ) ;
+
+	$debugcontent and myprint(
+		q{=} x $STD_CHAR_PER_LINE, "\n",
+		"F message content begin next line ($string_len characters long)\n",
+		${ $string_ref },
+		"F message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n" ) ;
+
+        if ( $sync->{debugmemory} ) {
+                myprintf("M3: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+	return $string_len ;
+}
+
+sub tests_message_for_host2 {
+        
+        my ( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ;
+        
+        is( undef, message_for_host2(  ), q{message_for_host2: no args} ) ;
+        is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ;
+
+        require Test::MockObject ;
+        my $imapT = Test::MockObject->new(  ) ;
+        $sync->{imap1} = $imapT ;
+        my $string ;
+        
+        $h1_msg = 1 ;
+        $h1_fold = 'FoldFoo';
+        $h1_size =  9 ; 
+        $h1_flags = '' ; 
+        $h1_idate = '10-Jul-2015 09:00:00 +0200' ;
+        $h1_fir_ref = {} ;
+        $string_ref = \$string ;
+        $imapT->mock( 'message_to_file',   
+                sub {
+                        my ( $imap, $string_ref, $msg ) = @_ ;
+                        ${$string_ref} = 'blablabla' ;
+                        return length ${$string_ref} ;
+                }
+        ) ;
+        is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+        q{message_for_host2: msg 1 == "blablabla", length} ) ;
+        is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ;
+ 
+        # so far so good
+        # now the --pipemess stuff
+
+	SKIP: {
+                Readonly my $NB_WIN_tests_message_for_host2 => 0 ;
+		skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ;
+		# Windows
+		# "type" command does not accept redirection of STDIN with <
+		# "sort" does
+
+	} ;
+
+	SKIP: {
+                Readonly my $NB_UNX_tests_message_for_host2 => 6 ;
+		skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ;
+		# Unix
+                
+                # no change by cat
+                @pipemess = ( 'cat' ) ;
+                is( 9, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                q{message_for_host2: --pipemess 'cat', length} ) ;
+                is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ;
+
+                
+                # failure by false
+                @pipemess = ( 'false' ) ;
+                is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                q{message_for_host2: --pipemess 'false', length} ) ;
+                is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ;
+
+                # failure by true since no output
+                @pipemess = ( 'true' ) ;
+                is( undef, message_for_host2( $sync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), 
+                q{message_for_host2: --pipemess 'true', length} ) ;
+                is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ;
+        }
+        return ;
+}
+
+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 {
+        my $notdefined ;
+        is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ;
+        my $notref ;
+        is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ;
+
+        my $lala = 'lala' ;
+        is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ;
+        is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ;
+        return ;
+}
+
+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"  ) ;
+		$h1_date = good_date( $h1_date ) ;
+		$debug and myprint( "internal date from host1: [$h1_date] (fixed)\n"  ) ;
+	}
+
+	if ( $idatefromheader ) {
+		$h1_date = $imap1->get_header( $h1_msg, 'Date' ) ;
+		$debug and myprint( "header date from host1: [$h1_date]\n"  ) ;
+		$h1_date = good_date( $h1_date ) ;
+		$debug and myprint( "header date from host1: [$h1_date] (fixed)\n"  ) ;
+	}
+
+	return( $h1_date ) ;
+}
+
+sub flags_for_host2 {
+	my( $h1_flags, $permanentflags2 ) = @_ ;
+	# RFC 2060: This flag can not be altered by any client
+	$h1_flags =~ s@\\Recent\s?@@xgi ;
+        my $h1_flags_re ;
+        if ( @regexflag and defined( $h1_flags_re = flags_regex( $h1_flags ) ) ) {
+                $h1_flags = $h1_flags_re ;
+        }
+	$h1_flags = flagscase( $h1_flags ) if $flagscase ;
+        $h1_flags = flags_filter( $h1_flags, $permanentflags2) if ( $permanentflags2 and $filterflags ) ;
+
+	return( $h1_flags ) ;
+}
+
+sub subject {
+	my $string = shift ;
+	my $subject = q{} ;
+
+        my $header = extract_header( $string ) ;
+
+        if( $header =~ m/^Subject:\s*([^\n\r]*)\r?$/msx ) {
+        	#myprint( "MMM[$1]\n"  ) ;
+        	$subject = $1 ;
+        }
+	return( $subject ) ;
+}
+
+sub tests_subject {
+	ok( q{} eq subject( q{} ), 'subject: null') ;
+	ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'subject: toto le hero') ;
+	ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'subject: toto le hero blank') ;
+	ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'subject: toto le hero\r\n') ;
+
+        my $MESS ;
+	$MESS = <<'EOF';
+From: lalala
+Subject: toto le hero
+Date: zzzzzz
+
+Boogie boogie
+EOF
+	ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ;
+
+	$MESS = <<'EOF';
+Subject: toto le hero
+From: lalala
+Date: zzzzzz
+
+Boogie boogie
+EOF
+	ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ;
+
+
+	$MESS = <<'EOF';
+From: lalala
+Subject: cuicui
+Date: zzzzzz
+
+Subject: toto le hero
+EOF
+	ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ;
+
+	$MESS = <<'EOF';
+From: lalala
+Date: zzzzzz
+
+Subject: toto le hero
+EOF
+	ok( q{} eq subject( $MESS ), 'subject: null but body could') ;
+
+	return ;
+}
+
+
+# GlobVar
+# $dry
+# $max_msg_size_in_bytes
+# $imap2
+# $imap1
+# $total_bytes_error
+# $h1_nb_msg_processed
+# $h2_uidguess
+# $total_bytes_transferred
+# $nb_msg_transferred
+# $begin_transfer_time
+# $time_spent
+# ...
+#
+#
+sub append_message_on_host2 {
+	my( $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ;
+	if ( $sync->{debugmemory} ) {
+        	myprintf("A1: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+        }
+
+	my $new_id ;
+	if ( ! $dry ) {
+		$max_msg_size_in_bytes = max( $h1_size, $max_msg_size_in_bytes ) ;
+		$new_id = $imap2->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ;
+	        if ( $sync->{debugmemory} ) {
+        	        myprintf("A2: Memory consumption: %.1f MiB\n", memory_consumption(  ) / $KIBI / $KIBI) ;
+                }
+		if ( ! $new_id){
+                	my $subject = subject( ${ $string_ref } ) ;
+                        my $error_imap = $imap2->LastError || q{} ;
+			my $error = "- msg $h1_fold/$h1_msg {$string_len} couldn't append  (Subject:[$subject]) to folder $h2_fold: $error_imap\n" ;
+			errors_incr( $sync, $error ) ;
+			$total_bytes_error += $h1_size;
+                        $h1_nb_msg_processed +=1 ;
+			return ;
+		}
+		else{
+			# good
+			# $new_id is an id if the IMAP server has the
+			# UIDPLUS capability else just a ref
+			if ( $new_id !~ m{^\d+$}x ) {
+				$new_id = lastuid( $imap2, $h2_fold, $h2_uidguess ) ;
+			}
+			$h2_uidguess += 1 ;
+			$total_bytes_transferred += $h1_size ;
+			$nb_msg_transferred += 1 ;
+                        $h1_nb_msg_processed +=1 ;
+
+                        my $time_spent = timesince( $begin_transfer_time ) ;
+                        my $rate = bytes_display_string( $total_bytes_transferred / $time_spent ) ;
+                        my $eta = eta( $time_spent,
+                                       $h1_nb_msg_processed, $h1_nb_msg_start, $nb_msg_transferred ) ;
+                        my $amount_transferred = bytes_display_string( $total_bytes_transferred ) ;
+			myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s  %s/s %s copied  %s\n",
+                        $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $nb_msg_transferred/$time_spent, $rate,
+                        $amount_transferred,
+                        $eta );
+                        sleep_if_needed( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) ;
+                        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 ( $delete ) {
+				delete_message_on_host1( $h1_msg, $h1_fold ) ;
+			}
+			#myprint( "PRESS ENTER" ) and my $a = <> ;
+                        return( $new_id ) ;
+		}
+	}
+	else{
+		# NOOP to avoid timeout on large folders.
+		$imap2->noop(  ) ;
+		$nb_msg_skipped_dry_mode += 1 ;
+                $h1_nb_msg_processed +=1 ;
+	}
+
+	return ;
+}
+
+sub sleep_if_needed {
+	my( $time_spent, $total_bytes_transferred, $nb_msg_transferred ) = @_ ;
+        my $sleep_max_messages = sleep_max_messages( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) ;
+        my $sleep_max_bytes = sleep_max_bytes( $total_bytes_transferred, $time_spent, $maxbytespersecond  ) ;
+        my $sleep_max = max( $sleep_max_messages, $sleep_max_bytes ) ;
+        if ( $sleep_max > 0 ) {
+        	myprintf( "sleeping %.2f s\n", $sleep_max ) ;
+                sleep $sleep_max ;
+        }
+	return ;
+}
+
+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 ) } ;
+        my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ;
+        # the sleep must be positive
+        return( max( 0, $sleep ) ) ;
+}
+
+
+sub tests_sleep_max_messages {
+	ok( 0 == sleep_max_messages( 4, 2, undef ),  'sleep_max_messages: maxmessagespersecond = undef') ;
+	ok( 0 == sleep_max_messages( 4, 2, 0 ),  'sleep_max_messages: maxmessagespersecond = 0') ;
+	ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ;
+	ok( 0 == sleep_max_messages( 4, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max reached') ;
+	ok( 2 == sleep_max_messages( 8, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max over') ;
+	ok( 0 == sleep_max_messages( 2, 2, 2 ),  'sleep_max_messages: maxmessagespersecond = 2 max not reached') ;
+	return ;
+}
+
+
+sub sleep_max_bytes {
+	# how long we have to sleep to go under max_bytes_per_second
+        my( $total_bytes_transferred, $time_spent, $maxbytespersecond ) = @_ ;
+        if ( ( not defined  $maxbytespersecond  ) or $maxbytespersecond <= 0 ) { return( 0 ) } ;
+        my $sleep = ( $total_bytes_transferred / $maxbytespersecond ) - $time_spent ;
+        # the sleep must be positive
+        return( max( 0, $sleep ) ) ;
+}
+
+
+sub tests_sleep_max_bytes {
+	ok( 0 == sleep_max_bytes( 4000, 2, undef ),  'sleep_max_bytes: maxbytespersecond = undef') ;
+	ok( 0 == sleep_max_bytes( 4000, 2, 0 ),  'sleep_max_bytes: maxbytespersecond = 0') ;
+	ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1') ;
+	ok( 0 == sleep_max_bytes( 4000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max reached') ;
+	ok( 2 == sleep_max_bytes( 8000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max over') ;
+	ok( 0 == sleep_max_bytes( 2000, 2, 2000 ),  'sleep_max_bytes: maxbytespersecond = 2 max not reached') ;
+	return ;
+}
+
+
+
+
+# 6 GlobVar: $dry_message $dry $imap1 $h1_nb_msg_deleted $expunge $expunge1
+sub delete_message_on_host1  {
+	my( $h1_msg, $h1_fold ) = @_ ;
+	my $expunge_message = q{} ;
+	$expunge_message = 'and expunged' if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
+	myprint( "Host1 msg $h1_fold/$h1_msg marked deleted $expunge_message $dry_message\n"  ) ;
+        if ( ! $dry ) {
+        	$imap1->delete_message( $h1_msg ) ;
+        	$h1_nb_msg_deleted += 1 ;
+        	$imap1->expunge(  ) if ( $expungeaftereach and ( $expunge or $expunge1 ) ) ;
+        }
+        return ;
+}
+
+
+sub eta {
+	my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
+	return( q{} ) if not $foldersizes ;
+
+        my $time_remaining = time_remaining( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) ;
+        my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ;
+        my $eta_date = localtime( time + $time_remaining ) ;
+        return( mysprintf( 'ETA: %s  %1.0f s  %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ;
+}
+
+sub time_remaining {
+
+	my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ;
+
+	my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ;
+	return( $time_remaining ) ;
+}
+
+
+sub tests_time_remaining {
+
+	ok( 1 == time_remaining( 1, 1,  2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1'  ) ;
+	ok( 1 == time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ;
+	ok( 9 == time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 1' ) ;
+	return ;
+}
+
+
+sub cache_map {
+	my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_;
+	my ( %map1_2, %map2_1, %done2 ) ;
+
+	my $h1_msgs_hash_ref = {  } ;
+	my $h2_msgs_hash_ref = {  } ;
+
+	@{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = (  ) ;
+	@{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = (  ) ;
+
+	foreach my $file ( sort @{ $cache_files_ref } ) {
+		$debugcache and myprint( "C12: $file\n"  ) ;
+		( $uid1, $uid2 ) = match_a_cache_file( $file ) ;
+
+		if (  exists( $h1_msgs_hash_ref->{ defined  $uid1  ? $uid1 : q{} } )
+		  and exists( $h2_msgs_hash_ref->{ defined  $uid2  ? $uid2 : q{} } ) ) {
+		  	# keep only the greatest uid2
+			# 130_2301 and
+			# 130_231  => keep only 130 -> 2301
+
+			# keep only the greatest uid1
+			# 1601_260 and
+			#  161_260 => keep only 1601 -> 260
+		  	my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ;
+			if ( exists $done2{ $max_uid2 } ) {
+				if ( $done2{ $max_uid2 } < $uid1 )  {
+					$map1_2{ $uid1 } = $max_uid2 ;
+					delete $map1_2{ $done2{ $max_uid2 } } ;
+					$done2{ $max_uid2 } = $uid1 ;
+				}
+			}else{
+				$map1_2{ $uid1 } = $max_uid2 ;
+				$done2{ $max_uid2 } = $uid1 ;
+			}
+		};
+
+	}
+	%map2_1 = reverse %map1_2 ;
+	return( \%map1_2, \%map2_1) ;
+}
+
+sub tests_cache_map {
+	#$debugcache = 1 ;
+	my @cache_files = qw (
+	100_200
+	101_201
+	120_220
+	142_242
+	143_243
+	177_277
+	177_278
+	177_279
+	155_255
+	180_280
+	181_280
+	182_280
+	130_231
+	130_2301
+	161_260
+	1601_260
+	) ;
+
+	my $msgs_1 = [120, 142, 143, 144, 161, 1601,           177,      182, 130 ];
+	my $msgs_2 = [     242, 243,       260,      299, 377, 279, 255, 280, 231, 2301 ];
+
+	my( $c12, $c21 ) ;
+	ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' );
+	my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+	my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+	ok( 0 == compare_lists( [ 130, 142, 143,      177, 182, 1601      ], $a1 ), 'cache_map: 03' );
+	ok( 0 == compare_lists( [      242, 243, 260, 279, 280,      2301 ], $a2 ), 'cache_map: 04' );
+	ok( ! $c12->{161},        'cache_map: ! 161 ->  260' );
+	ok( 260  == $c12->{1601}, 'cache_map:  1601 ->  260' );
+	ok( 2301 == $c12->{130},  'cache_map:   130 -> 2301' );
+	#myprint( $c12->{1601}, "\n" ) ;
+	return ;
+
+}
+
+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 {
+	ok( 'lalala' eq  cache_dir_fix('lalala'),  'cache_dir_fix: lalala -> lalala' );
+	ok( 'ii\\\\ii' eq  cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' );
+	ok( 'ii@ii' eq  cache_dir_fix('ii@ii'),  'cache_dir_fix: ii@ii -> ii@ii' );
+	ok( 'ii@ii\\:ii' eq  cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' );
+	ok( 'i\\\\i\\\\ii' eq  cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' );
+	ok( 'i\\\\ii' eq  cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' );
+	ok( '\\\\ ' eq  cache_dir_fix('\\ '), 'cache_dir_fix: \\  -> \\\\\ ' );
+	ok( '\\\\ ' eq  cache_dir_fix('\ '), 'cache_dir_fix: \  -> \\\\\ ' );
+	ok( '\[bracket\]' eq  cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' );
+	return ;
+}
+
+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 {
+	ok( 'lalala' eq  cache_dir_fix_win('lalala'),  'cache_dir_fix_win: lalala -> lalala' );
+	ok( '[[]bracket[]]' eq  cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' );
+	return ;
+}
+
+
+
+
+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" ) ;
+
+
+        if ( 'MSWin32' ne $OSNAME ) {
+        	$cache_dir = cache_dir_fix( $cache_dir ) ;
+        }else{
+        	$cache_dir = cache_dir_fix_win( $cache_dir ) ;
+        }
+
+	$debugcache and myprint( "cache_dir_fix: $cache_dir\n"  ) ;
+
+	my @cache_files = bsd_glob( "$cache_dir/*" ) ;
+	#$debugcache and myprint( "cache_files: [@cache_files]\n"  ) ;
+
+	$debugcache and myprint( 'cache_files: ', scalar  @cache_files , " files found\n" ) ;
+
+	my( $cache_1_2_ref, $cache_2_1_ref )
+	  = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ;
+
+	clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ;
+
+	$debugcache and myprint( "Exiting get_cache\n" ) ;
+	return( $cache_1_2_ref, $cache_2_1_ref ) ;
+}
+
+
+sub tests_get_cache {
+
+	ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' );
+	ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' )), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ;
+	ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ;
+
+	my @test_files_cache = ( qw(
+	W/tmp/cache/F1/F2/100_200
+	W/tmp/cache/F1/F2/101_201
+	W/tmp/cache/F1/F2/120_220
+	W/tmp/cache/F1/F2/142_242
+	W/tmp/cache/F1/F2/143_243
+	W/tmp/cache/F1/F2/177_277
+	W/tmp/cache/F1/F2/177_377
+	W/tmp/cache/F1/F2/177_777
+	W/tmp/cache/F1/F2/155_255
+	) ) ;
+	ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
+
+
+	# on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
+	# on live:
+	my $msgs_1 = [120, 142, 143, 144,          177      ];
+	my $msgs_2 = [     242, 243,     299, 377, 777, 255 ];
+
+        my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ;
+        my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ;
+
+	my( $c12, $c21 ) ;
+	ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
+	my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+	my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+	ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' );
+	ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' );
+	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
+	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
+	ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200');
+	ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201');
+
+	# test clean_cache executed
+	$maxage = 2 ;
+	ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ;
+	ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' );
+	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242');
+	ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243');
+	ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200');
+	ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201');
+
+
+	# strange files
+	#$debugcache = 1 ;
+	$maxage = undef ;
+	ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ;
+	ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ;
+
+	@test_files_cache = ( qw(
+	W/tmp/cache/rr\uee/100_200
+	W/tmp/cache/rr\uee/101_201
+	W/tmp/cache/rr\uee/120_220
+	W/tmp/cache/rr\uee/142_242
+	W/tmp/cache/rr\uee/143_243
+	W/tmp/cache/rr\uee/177_277
+	W/tmp/cache/rr\uee/177_377
+	W/tmp/cache/rr\uee/177_777
+	W/tmp/cache/rr\uee/155_255
+	) ) ;
+	ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ;
+
+	# on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255
+	# on live:
+	$msgs_1 = [120, 142, 143, 144,          177      ] ;
+	$msgs_2 = [     242, 243,     299, 377, 777, 255 ] ;
+
+        $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ;
+        $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ;
+
+	ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/rr\uee', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2), 'get_cache: strange path 02' );
+	$a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ;
+	$a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ;
+	ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' );
+	ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' );
+	ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242');
+	ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243');
+	ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200');
+	ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201');
+	return ;
+}
+
+sub match_a_cache_file {
+	my $file = shift ;
+	my ( $cache_uid1, $cache_uid2 ) ;
+
+	return( ( undef, undef ) ) if ( ! $file ) ;
+	if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) {
+		$cache_uid1 = $1 ;
+		$cache_uid2 = $2 ;
+	}
+	return( $cache_uid1, $cache_uid2 ) ;
+}
+
+sub tests_match_a_cache_file {
+	my ( $tuid1, $tuid2 ) ;
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file(  ), 'match_a_cache_file: no arg' ) ;
+	ok( ! defined  $tuid1 , 'match_a_cache_file: no arg 1' ) ;
+	ok( ! defined  $tuid2 , 'match_a_cache_file: no arg 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ;
+	ok( ! defined  $tuid1 , 'match_a_cache_file: empty arg 1' ) ;
+	ok( ! defined  $tuid2 , 'match_a_cache_file: empty arg 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ;
+	ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ;
+	ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ;
+	ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ;
+	ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ;
+	ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ;
+	ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ;
+	ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ;
+	ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ;
+
+	ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ;
+	ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ;
+	ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ;
+
+	return ;
+}
+
+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" ) ;
+
+	$debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref }  ) ;
+	foreach my $file ( @{ $cache_files_ref } ) {
+		$debugcache and myprint( "$file\n"  ) ;
+		my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ;
+		$debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ;
+#		  or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) )
+#		  or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) )
+		if ( ( not defined  $cache_uid1  )
+		  or ( not defined  $cache_uid2  )
+                  or ( not exists  $h1_msgs_all_hash_ref->{ $cache_uid1 }  )
+                  or ( not exists  $h2_msgs_all_hash_ref->{ $cache_uid2 }  )
+                ) {
+			$debugcache and myprint( "remove $file\n"  ) ;
+			unlink $file or myprint( "$!"  ) ;
+		}
+	}
+
+	$debugcache and myprint( "Exiting clean_cache\n" ) ;
+	return( 1 ) ;
+}
+
+sub tests_clean_cache {
+
+	ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ;
+	ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ;
+
+	my @test_files_cache = ( qw(
+	W/tmp/cache/G1/G2/100_200
+	W/tmp/cache/G1/G2/101_201
+	W/tmp/cache/G1/G2/120_220
+	W/tmp/cache/G1/G2/142_242
+	W/tmp/cache/G1/G2/143_243
+	W/tmp/cache/G1/G2/177_277
+	W/tmp/cache/G1/G2/177_377
+	W/tmp/cache/G1/G2/177_777
+	W/tmp/cache/G1/G2/155_255
+	) ) ;
+	ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ;
+
+	ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' );
+	ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' );
+	ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' );
+
+	my $cache = {
+		142 => 242,
+		177 => 777,
+	} ;
+
+        my $all_1 = {
+                142 => q{},
+                177 => q{},
+        } ;
+
+        my $all_2 = {
+                200 => q{},
+                242 => q{},
+                777 => q{},
+        } ;
+	ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ;
+
+	ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' );
+	ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' );
+	ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' );
+	return ;
+}
+
+sub tests_clean_cache_2 {
+
+	ok( ( not -d  'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ;
+	ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ;
+
+	my @test_files_cache = ( qw(
+	W/tmp/cache/G1/G2/100_200
+	W/tmp/cache/G1/G2/101_201
+	W/tmp/cache/G1/G2/120_220
+	W/tmp/cache/G1/G2/142_242
+	W/tmp/cache/G1/G2/143_243
+	W/tmp/cache/G1/G2/177_277
+	W/tmp/cache/G1/G2/177_377
+	W/tmp/cache/G1/G2/177_777
+	W/tmp/cache/G1/G2/155_255
+	) ) ;
+	ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ;
+
+	ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' );
+	ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' );
+	ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' );
+	ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' );
+
+	my $cache = {
+		142 => 242,
+		177 => 777,
+	} ;
+
+        my $all_1 = {
+                $NUMBER_100 => q{},
+                142 => q{},
+                177 => q{},
+        } ;
+
+        my $all_2 = {
+                200 => q{},
+                242 => q{},
+                777 => q{},
+        } ;
+
+
+
+	ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ;
+
+	ok(   -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' );
+	ok(   -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' );
+	ok(   -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' );
+	ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' );
+	return ;
+}
+
+
+
+sub tests_mkpath {
+
+	ok( 1 == 1, 'tests_mkpath: 1 == 1' ) ;
+
+	SKIP: {
+		skip( 'Tests only for Unix', 2   ) if ( 'MSWin32' eq $OSNAME ) ;
+		my $long_path_unix = '123456789/' x 30 ;
+		ok( (-d "W/tmp/tests/long/$long_path_unix" or  mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'tests_mkpath: mkpath > 300 char' ) ;
+		ok( (-d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'tests_mkpath: rmtree > 300 char' ) ;
+        } ;
+
+	SKIP: {
+		skip( 'Tests only for MSWin32', 6  ) if ( 'MSWin32' ne $OSNAME ) ;
+		my $long_path_2_prefix =  "$tmpdir\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests'  ;
+		myprint( "long_path_2_prefix: $long_path_2_prefix\n"  ) ;
+
+		my $long_path_2   = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ;
+		my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ;
+
+		myprint( "$long_path_2\n"  ) ;
+
+		#ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
+		#ok( ( -d $long_path_2_prefix or mkpath( "\\\\\?\\E:\\\\TEMP\\imapsync_tests" ) ), 'tests_mkpath: -d  small path 1' ) ;
+
+		ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'tests_mkpath: -d mkpath small path' ) ;
+		ok( ( -d $long_path_2_prefix ), 'tests_mkpath: -d mkpath small path done' ) ;
+		ok( ( -d $long_path_2        or mkpath( $long_path_2 ) ),        'tests_mkpath: mkpath > 200 char' ) ;
+		ok( ( -d $long_path_2 ), 'tests_mkpath: -d mkpath > 200 char done' ) ;
+		ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'tests_mkpath: rmtree > 200 char' ) ;
+		ok( (! -d $long_path_2_prefix ), 'tests_mkpath: ! -d rmtree done' ) ;
+
+		myprint( "$long_path_300\n"  ) ;
+		# This one just kill the whole process without a whisper:
+		#ok( ( -d $long_path_300        or mkpath( $long_path_300 ) ),        'tests_mkpath: mkpath fails > 300 char' ) ;
+		#ok( ( -d $long_path_300 and rmtree( $long_path_300 ) ), 'tests_mkpath: rmtree \ > 300 char' ) ;
+	} ;
+
+	return 1 ;
+}
+
+sub tests_touch {
+
+	ok( (-d 'W/tmp/tests/' or  mkpath( 'W/tmp/tests/' )), 'tests_touch: mkpath W/tmp/tests/' ) ;
+	ok( 1 == touch( 'W/tmp/tests/lala'), 'tests_touch: W/tmp/tests/lala') ;
+	ok( 1 == touch( 'W/tmp/tests/\y'), 'tests_touch: W/tmp/tests/\y') ;
+	ok( 0 == touch( '/no/no/no/aaa'), 'tests_touch: not /aaa') ;
+	ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'tests_touch: 2 files') ;
+	ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'tests_touch: 2 files, 1 fails' ) ;
+	return ;
+}
+
+
+sub touch {
+	my @files = @_ ;
+	my $failures = 0 ;
+
+	foreach my $file ( @files ) {
+		my  $fh = IO::File->new ;
+		if ( $fh->open(">> $file" ) ) {
+			$fh->close ;
+		}else{
+                	myprint( "Could not open file $file in write/append mode\n"  ) ;
+                	$failures++ ;
+                }
+	}
+	return( ! $failures );
+}
+
+
+sub tests_tmpdir_has_colon_bug {
+
+	ok( 0 == tmpdir_has_colon_bug( q{} ),        'tmpdir_has_colon_bug: ' ) ;
+	ok( 0 == tmpdir_has_colon_bug( '/tmp' ),    'tmpdir_has_colon_bug: /tmp' ) ;
+	ok( 1 == tmpdir_has_colon_bug( 'C:' ),      'tmpdir_has_colon_bug: C:' ) ;
+	ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ;
+
+        return( 0 ) ;
+}
+
+sub tmpdir_has_colon_bug {
+	my $path = shift ;
+
+	my $path_filtered = filter_forbidden_characters( $path ) ;
+	if ( $path_filtered ne $path ) {
+        	( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n"  ) ;
+        	return( 1 ) ;
+        }
+        return( 0 ) ;
+}
+
+sub tmpdir_fix_colon_bug {
+
+        my $err = 0 ;
+        if ( not (-d $tmpdir and -r _ and -w _) ) {
+                myprint( "tmpdir $tmpdir is not valid\n"  ) ;
+                return( 0 ) ;
+        }
+        my $cachedir_new = "$tmpdir/imapsync_cache" ;
+
+        if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ;
+
+        # check if old cache directory already exists
+        my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ;
+        if ( not ( -d $cachedir_old ) ) {
+                myprint( "Old cache directory $cachedir_new no exists, nothing to do\n"  ) ;
+                return( 1 ) ;
+        }
+        # check if new cache directory already exists
+        if ( -d $cachedir_new ) {
+                myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n"  ) ;
+                return( 0 ) ;
+        }else{
+                # move the old one to the new place
+                myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n"  ) ;
+                File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new )
+                or do {
+                        myprint( "Could not move $cachedir_old to $cachedir_new\n"  ) ;
+                        $err++ ;
+                } ;
+                # check it succeeded
+                if ( -d $cachedir_new and -r _ and -w _ ) {
+                        myprint( "New fixed cache directory $cachedir_new ok\n"  ) ;
+                }else{
+                        myprint( "New fixed cache directory $cachedir_new does not exist\n"  ) ;
+                        $err++ ;
+                }
+                if ( -d $cachedir_old ) {
+                        myprint( "Old cache directory $cachedir_old still exists\n"  ) ;
+                        $err++ ;
+                }else{
+                        myprint( "Old cache directory $cachedir_old successfuly moved\n"  ) ;
+                }
+        }
+        return( not $err ) ;
+}
+
+
+sub 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' ) ;
+
+	ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ;
+	ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ;
+	ok( 'D:/_p_a__th/fol_d1/fold2' eq cache_folder( 'D:', '/>pp /path/fol_d1/fold2' ) ;
+	ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder:  -> //' ) ;
+	ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ;
+	return ;
+}
+
+sub cache_folder {
+	my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ;
+
+	my $sep_1 = $h1_sep || '/';
+	my $sep_2 = $h2_sep || '/';
+
+	#myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ;
+	$h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ;
+	$h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ;
+
+        my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ;
+	#myprint( "cache_folder [$cache_folder]\n"  ) ;
+        return( $cache_folder ) ;
+}
+
+sub filter_forbidden_characters  {
+	my $string = shift ;
+
+        if ( 'MSWin32' eq $OSNAME ) {
+        	# Move trailing whitespace to _ " a b /c d " -> " a b_/c d_"
+        	$string =~ s{\ (/|$)}{_$1}xg ;
+        }
+        $string =~ s{[\Q*|?:"<>\E]}{_}xg ;
+        #myprint( "[$string]\n"  ) ;
+	return( $string ) ;
+}
+
+sub tests_filter_forbidden_characters  {
+
+	ok( 'a_b' eq filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ;
+	ok( 'a_b' eq filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ;
+	ok( 'a_b' eq filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ;
+	ok( 'a_b' eq filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ;
+	ok( 'a_______b' eq filter_forbidden_characters( 'a*|?:"<>b' ), 'filter_forbidden_characters: a*|?:"<>b -> a_______b' ) ;
+
+	SKIP: {
+		skip( 'Not on MSWin32', 1 ) if ( 'MSWin32' eq $OSNAME ) ;
+		ok( ( 'a b ' eq filter_forbidden_characters( 'a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b "' ) ;
+	} ;
+
+	SKIP: {
+		skip( 'Only on MSWin32', 2 ) if ( 'MSWin32' ne $OSNAME ) ;
+		ok( ( ' a b_' eq filter_forbidden_characters( ' a b ' ) ), 'filter_forbidden_characters: "a b " -> "a b_"' ) ;
+		ok( ( ' a b_/ c d_' eq filter_forbidden_characters( ' a b / c d ' ) ), 'filter_forbidden_characters: " a b / c d " -> "a b_/ c d_"' ) ;
+        } ;
+
+	return ;
+}
+
+sub convert_sep_to_slash {
+	my ( $folder, $sep ) = @_ ;
+
+	$folder =~ s{\Q$sep\E}{/}xg ;
+	return( $folder ) ;
+}
+
+sub tests_convert_sep_to_slash {
+
+	ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder');
+	ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX');
+	ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo');
+	ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo');
+	ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob');
+	ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo');
+	ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi');
+	return ;
+}
+
+
+sub tests_regexmess {
+
+	ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess, no regexmess, nothing to do' ) ;
+
+	@regexmess = ( 'lalala' ) ;
+	ok( not( defined regexmess( 'popopo' ) ), 'regexmess, bad regex lalala' ) ;
+
+	@regexmess = ( 's/p/Z/g' ) ;
+	ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess, s/p/Z/g' ) ;
+
+	@regexmess = ( 's{c}{C}gxms' ) ;
+	ok("H1: abC\nH2: Cde\n\nBody abC"
+		   eq regexmess( "H1: abc\nH2: cde\n\nBody abc"),
+	   'regexmess, c->C');
+
+	@regexmess = ( 's{\AFrom\ }{From:}gxms' ) ;
+	ok(          q{}
+	eq regexmess(q{}),
+	'From mbox 1 add colon blank');
+
+	ok(          'From:'
+	eq regexmess('From '),
+	'From mbox 2 add colo');
+
+	ok(          "\n" . 'From '
+	eq regexmess("\n" . 'From '),
+	'From mbox 3 add colo') ;
+
+	ok(          "From: zzz\n" . 'From '
+	eq regexmess("From  zzz\n" . 'From '),
+	'From mbox 4 add colo') ;
+
+	@regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ;
+	ok(          q{}
+	eq regexmess(q{}),
+	'From mbox 1 remove, blank');
+
+	ok(          q{}
+	eq regexmess('From '),
+	'From mbox 2 remove');
+
+	ok(          "\n" . 'From '
+	eq regexmess("\n" . 'From '),
+	'From mbox 3 remove');
+
+	#myprint( "[", regexmess("From  zzz\n" . 'From '), "]" ) ;
+	ok(          q{}            . 'From '
+	eq regexmess("From  zzz\n" . 'From '),
+	'From mbox 4 remove');
+
+
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+From  zzz
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+), 'From mbox 5 remove');
+
+
+@regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST!
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Disposition-Notification-To: Gilles LAMIRAL 
+From:
+
+Hello,
+Bye.
+EOM
+	),
+	'regexmess: 1 Delete header Disposition-Notification-To:');
+
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Hello,
+Bye.
+EOM
+),
+	'regexmess: 2 Delete header Disposition-Notification-To:');
+
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Disposition-Notification-To: Gilles LAMIRAL 
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+),
+	'regexmess: 3 Delete header Disposition-Notification-To:');
+
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Disposition-Notification-To: Gilles LAMIRAL 
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+),
+	'regexmess: 4 Delete header Disposition-Notification-To:');
+
+
+	ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+),
+	'regexmess: 5 Delete header Disposition-Notification-To:');
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+),
+	'regexmess: 6 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+),
+	'regexmess: 7 Delete header Disposition-Notification-To:');
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.
+EOM
+),
+	'regexmess: 8 Delete header Disposition-Notification-To:');
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+Bye.
+EOM
+),
+	'regexmess: 9 Delete header Disposition-Notification-To:');
+
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Disposition-Notification-To: Gilles LAMIRAL 
+
+
+Bye.
+EOM
+),
+	'regexmess: 10 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+),
+	'regexmess: 11 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+),
+	'regexmess: 12 Delete header Disposition-Notification-To:');
+
+
+@regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD!
+@regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ;
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+),
+	'regexmess: 13 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+X-Disposition-Notification-To: Gilles LAMIRAL 
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Disposition-Notification-To: Gilles LAMIRAL 
+From:
+
+Hello,
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Bye.
+EOM
+),
+	'regexmess: 14 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+X-Disposition-Notification-To: Gilles LAMIRAL 
+From:
+
+Hello,
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Disposition-Notification-To: Gilles LAMIRAL 
+From:
+
+Hello,
+
+Bye.
+EOM
+),
+	'regexmess: 15 Delete header Disposition-Notification-To:');
+
+
+ok(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+X-Disposition-Notification-To: Gilles LAMIRAL 
+
+Hello,
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+Disposition-Notification-To: Gilles LAMIRAL 
+
+Hello,
+
+Bye.
+EOM
+),
+	'regexmess: 16 Delete header Disposition-Notification-To:');
+
+ok(
+<<'EOM'
+X-Disposition-Notification-To: Gilles LAMIRAL 
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Bye.
+EOM
+	eq regexmess(
+<<'EOM'
+Disposition-Notification-To: Gilles LAMIRAL 
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+
+Bye.
+EOM
+),
+	'regexmess: 17 Delete header Disposition-Notification-To:');
+
+
+
+# regex to play with Date: from the FAQ
+#@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms'
+
+return ;
+
+}
+
+sub regexmess {
+	my ( $string ) = @_ ;
+	foreach my $regexmess ( @regexmess ) {
+		$debug and myprint( "eval \$string =~ $regexmess\n"  ) ;
+		my $ret = eval "\$string =~ $regexmess ; 1" ;
+                #myprint( "eval [$ret]\n"  ) ;
+                if ( ( not $ret ) or $@ ) {
+			myprint( "Error: eval regexmess '$regexmess': $@"  ) ;
+                        return( undef ) ;
+                }
+	}
+        $debug and myprint( "$string\n" ) ;
+	return( $string ) ;
+}
+
+
+sub tests_skipmess {
+
+	ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ;
+
+	@skipmess = ('[') ;
+	ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ;
+
+	@skipmess = ('lalala') ;
+	ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ;
+
+	@skipmess = ('/popopo/') ;
+	ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ;
+
+	@skipmess = ('/popopo/') ;
+	ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ;
+
+	@skipmess = ('m{^$}') ;
+	ok( 1 == skipmess( q{} ),    'skipmess: empty string yes' ) ;
+	ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ;
+
+	@skipmess = ('m{i}') ;
+	ok( 1 == skipmess( 'Hi!' ),  'skipmess: i string yes' ) ;
+	ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ;
+
+	@skipmess = ('m{[\x80-\xff]}') ;
+	ok( 0 == skipmess( 'Hi!' ),  'skipmess: i 8bit no' ) ;
+	ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ;
+
+	@skipmess = ('m{A}', 'm{B}') ;
+	ok( 0 == skipmess( 'Hi!' ),  'skipmess: A or B no' ) ;
+	ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ;
+	ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ;
+	ok( 1 == skipmess( 'AB' ),   'skipmess: A or B yes' ) ;
+	ok( 1 == skipmess( 'BA' ),   'skipmess: A or B yes' ) ;
+	ok( 1 == skipmess( 'AA' ),   'skipmess: A or B yes' ) ;
+	ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ;
+
+
+	@skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST!
+
+
+
+	ok( 1 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Content-Type: Message/Partial; blabla
+From:
+
+Hello!
+Bye.
+EOM
+),
+    'skipmess: 1 match Content-Type: Message/Partial' ) ;
+
+	ok( 0 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello!
+Bye.
+EOM
+),
+    'skipmess: 2 not match Content-Type: Message/Partial' ) ;
+
+
+	ok( 1 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+Content-Type: Message/Partial; blabla
+
+Hello!
+Bye.
+EOM
+),
+    'skipmess: 3 match Content-Type: Message/Partial' ) ;
+
+	ok( 0 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello!
+Content-Type: Message/Partial; blabla
+Bye.
+EOM
+),
+    'skipmess: 4 not match Content-Type: Message/Partial' ) ;
+
+
+	ok( 0 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello!
+Content-Type: Message/Partial; blabla
+
+Bye.
+EOM
+),
+    'skipmess: 5 not match Content-Type: Message/Partial' ) ;
+
+
+	ok( 1 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Content-Type: Message/Partial; blabla
+From:
+
+Hello!
+
+Content-Type: Message/Partial; blabla
+
+Bye.
+EOM
+),
+    'skipmess: 6 match Content-Type: Message/Partial' ) ;
+
+	ok( 1 == skipmess(
+<<'EOM'
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+Content-Type: Message/Partial;
+From:
+
+Hello!
+Bye.
+EOM
+),
+    'skipmess: 7 match Content-Type: Message/Partial' ) ;
+
+	ok( 1 == skipmess(
+<<'EOM'
+Date: Wed, 2 Jul 2014 02:26:40 +0000
+MIME-Version: 1.0
+Content-Type: message/partial;
+	id="TAN_U_P<1404267997.00007489ed17>";
+	number=3;
+	total=3
+
+6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G
+
+
+Hello!
+Bye.
+EOM
+),
+    'skipmess: 8 match Content-Type: Message/Partial' ) ;
+
+
+ok( 1 == skipmess(
+<<'EOM'
+Return-Path: 
+Received: by lamiral.info (Postfix, from userid 1000)
+        id 21EB12443BF; Mon,  2 Mar 2015 15:38:35 +0100 (CET)
+Subject: test: aethaecohngiexao
+To: 
+X-Mailer: mail (GNU Mailutils 2.2)
+Message-Id: <20150302143835.21EB12443BF@lamiral.info>
+Content-Type: message/partial;
+        id="TAN_U_P<1404267997.00007489ed17>";
+        number=3;
+        total=3
+Date: Mon,  2 Mar 2015 15:38:34 +0100 (CET)
+From: gilles@lamiral.info (Gilles LAMIRAL)
+
+test: aethaecohngiexao
+EOM
+),
+    'skipmess: 9 match Content-Type: Message/Partial' ) ;
+
+ok( 1 == skipmess(
+<<'EOM'
+Date: Mon,  2 Mar 2015 15:38:34 +0100 (CET)
+From: gilles@lamiral.info (Gilles LAMIRAL)
+Content-Type: message/partial;
+        id="TAN_U_P<1404267997.00007489ed17>";
+        number=3;
+        total=3
+
+test: aethaecohngiexao
+EOM
+. "lalala\n" x 3000000
+),
+    'skipmess: 10 match Content-Type: Message/Partial' ) ;
+
+ok( 0 == skipmess(
+<<'EOM'
+Date: Mon,  2 Mar 2015 15:38:34 +0100 (CET)
+From: gilles@lamiral.info (Gilles LAMIRAL)
+
+test: aethaecohngiexao
+EOM
+. "lalala\n" x 3000000
+),
+    'skipmess: 11 match Content-Type: Message/Partial' ) ;
+
+
+ok( 0 == skipmess(
+<<"EOM"
+From: fff\r
+To: fff\r
+Subject: Testing imapsync --skipmess\r
+Date: Mon, 22 Aug 2011 08:40:20 +0800\r
+Mime-Version: 1.0\r
+Content-Type: text/plain; charset=iso-8859-1\r
+Content-Transfer-Encoding: 7bit\r
+\r
+EOM
+. qq{!#"$%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32730
+),
+    'skipmess: 12 not match Content-Type: Message/Partial' ) ;
+        # Complex regular subexpression recursion limit (32766) exceeded with more lines
+        # exit;
+	return ;
+}
+
+sub skipmess {
+	my ( $string ) = @_ ;
+	my $match ;
+	#myprint( "$string\n"  ) ;
+	foreach my $skipmess ( @skipmess ) {
+		$debug and myprint( "eval \$match = \$string =~ $skipmess\n"  ) ;
+		my $ret = eval "\$match = \$string =~ $skipmess ; 1"  ;
+		#myprint( "eval [$ret]\n"  ) ;
+		$debug and myprint( "match [$match]\n"  ) ;
+		if ( ( not $ret ) or $@ ) {
+			myprint( "Error: eval skipmess '$skipmess': $@"  ) ;
+			return( undef ) ;
+		}
+		return( $match ) if ( $match ) ;
+	}
+	return( $match ) ;
+}
+
+
+
+
+sub tests_bytes_display_string {
+
+        is(    'NA', bytes_display_string(       ), 'bytes_display_string: no args => NA' ) ;
+        is(    'NA', bytes_display_string( undef ), 'bytes_display_string: undef   => NA' ) ;
+        is(    'NA', bytes_display_string( 'blabla' ), 'bytes_display_string: blabla   => NA' ) ;
+        
+	ok(    '0.000 KiB' eq bytes_display_string(       0 ), 'bytes_display_string:       0' ) ;
+	ok(    '0.001 KiB' eq bytes_display_string(       1 ), 'bytes_display_string:       1' ) ;
+	ok(    '0.010 KiB' eq bytes_display_string(      10 ), 'bytes_display_string:      10' ) ;
+	ok(    '1.000 MiB' eq bytes_display_string( 1048575 ), 'bytes_display_string: 1048575' ) ;
+	ok(    '1.000 MiB' eq bytes_display_string( 1048576 ), 'bytes_display_string: 1048576' ) ;
+
+	ok(    '1.000 GiB' eq bytes_display_string( 1073741823 ), 'bytes_display_string: 1073741823 ' ) ;
+	ok(    '1.000 GiB' eq bytes_display_string( 1073741824 ), 'bytes_display_string: 1073741824 ' ) ;
+
+	ok(    '1.000 TiB' eq bytes_display_string( 1099511627775 ), 'bytes_display_string: 1099511627775' ) ;
+	ok(    '1.000 TiB' eq bytes_display_string( 1099511627776 ), 'bytes_display_string: 1099511627776' ) ;
+
+	ok(    '1.000 PiB' eq bytes_display_string( 1125899906842623 ), 'bytes_display_string: 1125899906842623' ) ;
+	ok(    '1.000 PiB' eq bytes_display_string( 1125899906842624 ), 'bytes_display_string: 1125899906842624' ) ;
+
+	ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846975 ), 'bytes_display_string: 1152921504606846975' ) ;
+	ok( '1024.000 PiB' eq bytes_display_string( 1152921504606846976 ), 'bytes_display_string: 1152921504606846976' ) ;
+
+	ok( '1048576.000 PiB' eq bytes_display_string( 1180591620717411303424 ), 'bytes_display_string: 1180591620717411303424' ) ;
+
+        #myprint(  bytes_display_string( 1180591620717411303424 ), "\n"  ) ;
+	return ;
+}
+
+sub bytes_display_string {
+	my ( $bytes ) = @_ ;
+
+	my $readable_value = q{} ;
+
+        if ( ! defined( $bytes ) ) {
+                return( 'NA' ) ;
+        }
+
+        if ( not match_number( $bytes ) ) {
+                return( 'NA' ) ;
+        }
+
+        
+
+	SWITCH: {
+        	if ( abs( $bytes ) < ( 1000 * $KIBI ) ) {
+        		$readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ;
+                	last SWITCH ;
+        	}
+        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) {
+        		$readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ;
+        	        last SWITCH ;
+        	}
+        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) {
+			$readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ;
+        	        last SWITCH ;
+        	}
+        	if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) {
+			$readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ;
+        	        last SWITCH ;
+        	} else {
+			$readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ;
+        	}
+		# if you have exabytes (EiB) of email to transfer, you have too much email!
+	}
+        #myprint( "$bytes = $readable_value\n"  ) ;
+        return( $readable_value ) ;
+}
+
+sub stats {
+        my $sync_loc = shift ;
+
+        if ( ! $sync_loc->{stats} ) {
+                return ;
+        }
+        
+	$timeend = time ;
+	my $timediff = $timeend - $sync_loc->{timestart} ;
+
+	my $timeend_str   = localtime $timeend ;
+
+	my $memory_consumption = 0 ;
+        $memory_consumption = memory_consumption(  ) || 0 ;
+	my $memory_ratio = ($max_msg_size_in_bytes) ?
+		mysprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : 'NA' ;
+
+	my $host1_reconnect_count = $imap1->Reconnect_counter() || 0 ;
+	my $host2_reconnect_count = $imap2->Reconnect_counter() || 0 ;
+
+	myprint(  "++++ Statistics\n"  ) ;
+	myprint(  "Transfer started on               : $timestart_str\n"  ) ;
+	myprint(  "Transfer ended on                 : $timeend_str\n"  ) ;
+	myprintf( "Transfer time                     : %.1f sec\n", $timediff ) ;
+	myprint(  "Folders synced                    : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n"  ) ;
+	myprint(  "Messages transferred              : $nb_msg_transferred "  ) ;
+	myprint(  "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $dry ) ;
+	myprint(  "\n" ) ;
+	myprint(  "Messages skipped                  : $nb_msg_skipped\n"  ) ;
+	myprint(  "Messages found duplicate on host1 : $h1_nb_msg_duplicate\n"  ) ;
+	myprint(  "Messages found duplicate on host2 : $h2_nb_msg_duplicate\n"  ) ;
+	myprint(  "Messages void (noheader) on host1 : $h1_nb_msg_noheader\n"  ) ;
+	myprint(  "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n"  ) ;
+	myprint(  "Messages deleted on host1         : $h1_nb_msg_deleted\n"  ) ;
+	myprint(  "Messages deleted on host2         : $h2_nb_msg_deleted\n"  ) ;
+        myprintf( "Total bytes transferred           : %s (%s)\n",
+                $total_bytes_transferred,
+                bytes_display_string( $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 ) ) ;
+	$timediff ||= 1 ; # No division per 0
+	myprintf("Message rate                      : %.1f messages/s\n", $nb_msg_transferred / $timediff ) ;
+	myprintf("Average bandwidth rate            : %.1f KiB/s\n", $total_bytes_transferred / $KIBI / $timediff ) ;
+	#myprint(  "Reconnections to host1            : $host1_reconnect_count\n"  ) ;
+	#myprint(  "Reconnections to host2            : $host2_reconnect_count\n"  ) ;
+	myprintf("Memory consumption                : %.1f MiB\n", $memory_consumption / $KIBI / $KIBI ) ;
+        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"  ) ;
+        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,
+                                                        $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,
+                                                        $bytes_end_diff,
+                                                        bytes_display_string( $bytes_end_diff ) ) ;
+        }
+	myprint(  "Detected $sync->{nb_errors} errors\n\n"  ) ;
+
+	myprint(  $warn_release, "\n"  ) ;
+	myprint(  thank_author(  )  ) ;
+	return ;
+}
+
+sub diff_or_NA {
+        my( $n1, $n2 ) = @ARG ;
+        
+        if ( not defined $n1 or not defined $n2 ) {
+                return 'NA' ;
+        }
+        
+        if ( not match_number( $n1 ) 
+          or not match_number( $n2 ) ) {
+                 return 'NA' ;
+        }
+        
+        return( $n1 - $n2 ) ;
+}
+
+sub match_number {
+        my $n = shift @ARG ;
+        
+        if ( not defined $n ) {
+                return 0 ;
+        }
+        if ( $n =~  /[0-9]+\.?[0-9]?/ ) {
+                return 1 ;
+        }
+        else {
+                return 0 ;
+        }
+}
+
+
+sub tests_match_number {
+
+        is( 0, match_number(   ),        'match_number: no parameters => 0' ) ;
+        is( 0, match_number( undef ),    'match_number:         undef => 0' ) ;
+        is( 0, match_number( 'blabla' ), 'match_number:        blabla => 0' ) ;
+        is( 1, match_number( 0 ),        'match_number:             0 => 1' ) ;
+        is( 1, match_number( 1 ),        'match_number:             1 => 1' ) ;
+        is( 1, match_number( 1.0 ),      'match_number:           1.0 => 1' ) ;
+        is( 1, match_number( 0.0 ),      'match_number:           0.0 => 1' ) ;
+        return ;
+}
+
+
+
+sub tests_diff_or_NA {
+
+        is( 'NA', diff_or_NA(  ),             'diff_or_NA: no parameters => NA' ) ;
+        is( 'NA', diff_or_NA( undef ),        'diff_or_NA: undef         => NA' ) ;
+        is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef  undef  => NA' ) ;
+        is( 'NA', diff_or_NA( undef, 1 ),     'diff_or_NA: undef  1      => NA' ) ;
+        is( 'NA', diff_or_NA( 1, undef ),     'diff_or_NA: 1      undef  => NA' ) ;
+        is( 'NA', diff_or_NA( 'blabla', 1 ),  'diff_or_NA: blabla 1      => NA' ) ;
+        is( 'NA', diff_or_NA( 1, 'blabla' ),  'diff_or_NA: 1      blabla => NA' ) ;
+        is( 0, diff_or_NA( 1, 1 ),            'diff_or_NA: 1      1      =>  0' ) ;
+        is( 1, diff_or_NA( 1, 0 ),            'diff_or_NA: 1      0      =>  1' ) ;
+        is( -1, diff_or_NA( 0, 1 ),           'diff_or_NA: 0      1      => -1' ) ;
+        is( 0, diff_or_NA( 1.0, 1 ),          'diff_or_NA: 1.0    1      =>  0' ) ;
+        is( 1, diff_or_NA( 1.0, 0 ),          'diff_or_NA: 1.0    0      =>  1' ) ;
+        is( -1, diff_or_NA( 0, 1.0 ),         'diff_or_NA: 0      1.0    => -1' ) ;
+        return ;
+}
+
+sub thank_author {
+	return( "Homepage: http://imapsync.lamiral.info/\n" ) ;
+}
+
+
+sub load_modules {
+
+	if ( $ssl1 or $ssl2 or $tls1 or $tls2) {
+        	# not yet a "use" statement
+        	require IO::Socket::SSL ;
+		if ( $sync->{inet4} ) {
+		        IO::Socket::SSL->import( 'inet4' ) ;
+		}
+		if ( $sync->{inet6} ) {
+		        IO::Socket::SSL->import( 'inet6' ) ;
+		}
+        }
+
+       if ( ( ( not( $password1 or $passfile1 ) )
+	   or (not ( $password2 or $passfile2 ) )
+            )
+	and ( not $help ) ) {
+        	# now a "use" statement
+        	#require Term::ReadKey ;
+        }
+
+	return ;
+}
+
+
+
+sub parse_header_msg {
+	my ( $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ;
+
+	my $head = $s_heads->{$m_uid} ;
+	my $headnum =  scalar keys  %{ $head }   ;
+	$debug and myprint( "$side uid $m_uid head nb pass one: ", $headnum, "\n"  ) ;
+
+	if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){
+		myprint( "$side uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n"  ) ;
+		$imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ;
+		my $whole_header = $imap->_transaction_literals ;
+
+                #myprint( $whole_header ) ;
+                $head = decompose_header( $whole_header ) ;
+
+                $headnum =  scalar  keys  %{ $head }   ;
+	        $debug and myprint( "$side uid $m_uid head nb pass two: ", $headnum, "\n" ) ;
+	}
+
+        #myprint( Data::Dumper->Dump( [ $head, \%useheader ] )  ) ;
+
+	my $headstr ;
+
+        $headstr = header_construct( $head, $side, $m_uid ) ;
+
+	if ( ( ! $headstr) and ( $addheader ) and ( $side eq 'Host1' ) ) {
+        	my $header = add_header( $m_uid ) ;
+		myprint( "Host1 uid $m_uid no header found so adding our own [$header]\n" ) ;
+		$headstr .= uc  $header  ;
+		$s_fir->{$m_uid}->{NO_HEADER} = 1;
+	}
+
+	return if ( ! $headstr ) ;
+
+	my $size  = $s_fir->{$m_uid}->{'RFC822.SIZE'} ;
+	my $flags = $s_fir->{$m_uid}->{'FLAGS'} ;
+	my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ;
+	$size = length $headstr  unless ( $size ) ;
+	my $m_md5 = md5_base64( $headstr ) ;
+	$debug and myprint( "$side uid $m_uid sig $m_md5 size $size idate $idate\n"  ) ;
+	my $key ;
+        if ($skipsize) {
+                $key = "$m_md5";
+        }
+	else {
+                $key = "$m_md5:$size";
+        }
+	# 0 return code is used to identify duplicate message hash
+	return 0 if exists $s_hash->{"$key"};
+	$s_hash->{"$key"}{'5'} = $m_md5;
+	$s_hash->{"$key"}{'s'} = $size;
+	$s_hash->{"$key"}{'D'} = $idate;
+	$s_hash->{"$key"}{'F'} = $flags;
+	$s_hash->{"$key"}{'m'} = $m_uid;
+
+	return( 1 ) ;
+}
+
+sub header_construct {
+
+	my( $head, $side, $m_uid ) = @_ ;
+
+        my $headstr ;
+	foreach my $h ( sort keys  %{ $head }  ) {
+                next if ( not ( exists $useheader{ uc  $h  } )
+                      and ( not exists  $useheader{ 'ALL' } )
+                ) ;
+		foreach my $val ( sort @{$head->{$h}} ) {
+
+                        my $H = header_line_normalize( $h, $val ) ;
+
+			# show stuff in debug mode
+			$debug and myprint( "$side uid $m_uid header [$H]", "\n"  ) ;
+
+			if ($skipheader and $H =~ m/$skipheader/xi) {
+				$debug and myprint( "$side uid $m_uid skipping header [$H]\n"  ) ;
+				next ;
+			}
+			$headstr .= "$H" ;
+		}
+	}
+	return( $headstr ) ;
+}
+
+
+sub header_line_normalize {
+	my( $header_key,  $header_val ) = @_ ;
+
+        # no 8-bit data in headers !
+        $header_val =~ s/[\x80-\xff]/X/xog;
+
+        # change tabulations to space (Gmail bug on with "Received:" on multilines)
+        $header_val =~ s/\t/\ /xgo ;
+
+        # remove the first blanks ( dbmail bug? )
+        $header_val =~ s/^\s*//xo;
+
+        # remove the last blanks ( Gmail bug )
+        $header_val =~ s/\s*$//xo;
+
+        # remove successive blanks ( Mailenable does it )
+        $header_val =~ s/\s+/ /xgo;
+
+        # remove Message-Id value domain part ( Mailenable changes it )
+        if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc  $header_key  ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; }
+
+        # and uppercase header line
+        # (dbmail and dovecot)
+
+        my $header_line = uc "$header_key: $header_val" ;
+
+	return( $header_line ) ;
+}
+
+sub tests_header_line_normalize {
+
+	ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ;
+	ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ;
+	ok( 'HHH: VVV' eq header_line_normalize( 'hhh', '  vvv' ), 'header_line_normalize: remove first blancs' ) ;
+	ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa  bb   ccc d' ), 'header_line_normalize: remove succesive blanks' ) ;
+	ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa  bb   ccc   ' ), 'header_line_normalize: remove last blanks' ) ;
+	ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ;
+	ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ;
+
+	return ;
+}
+
+
+sub firstline {
+        # extract the first line of a file (without \n)
+
+        my( $file ) = @_ ;
+        my $line  = q{} ;
+        my $FILE ;
+        open $FILE, '<', $file or do {
+                myprint( "Error opening file $file : $!\n" ) ;
+                return ;
+        } ;
+        $line = <$FILE> || q{} ;
+        close $FILE ;
+        chomp $line ;
+        return $line ;
+}
+
+sub tests_firstline {
+        is( 1 , string_to_file( "blabla\n", 'tmp/firstline.txt' ), 'tests_firstline: put blabla in tmp/firstline.txt' ) ;
+        is( 'blabla' , firstline( 'tmp/firstline.txt' ), 'tests_firstline: get blabla from tmp/firstline.txt' ) ;
+        is( undef , firstline( 'tmp/noexist.txt' ), 'tests_firstline: get blabla from tmp/noexist.txt' ) ;
+        is( 1 , string_to_file( q{}, 'tmp/firstline2.txt' ), 'tests_firstline: put empty string in tmp/firstline2.txt' ) ;
+        is( q{} , firstline( 'tmp/firstline2.txt' ), 'tests_firstline: get empty string from tmp/firstline2.txt' ) ;
+        is( 1 , string_to_file( "\n", 'tmp/firstline3.txt' ), 'tests_firstline: put CR in tmp/firstline3.txt' ) ;
+        is( q{} , firstline( 'tmp/firstline3.txt' ), 'tests_firstline: get empty string from tmp/firstline3.txt' ) ;
+
+        return ;
+}
+
+
+sub file_to_string {
+	my( $file ) = @_ ;
+	my @string ;
+	open my $FILE, '<', $file or die_clean( "Error with file $file : $! " ) ;
+	@string = <$FILE> ;
+	close $FILE ;
+	return( join q{}, @string ) ;
+}
+
+
+sub string_to_file {
+	my( $string, $file ) = @_ ;
+	sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) or die_clean( "$! $file" ) ;
+	print FILE $string ;
+	close FILE ;
+	return 1 ;
+}
+
+q^
+This is a multiline comment.
+Based on David Carter discussion, to do:
+* Call parameters stay the same.
+* Now always "return( $string, $error )". Descriptions below.
+OK * Still    capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )"
+OK * Now also capture STDERR via "2> $error_tmpfile"  to finish in $error  and "return( $string, $error )"
+OK * in case of CHILD_ERROR, return( undef, $error ) 
+  and print $error, with folder/UID/maybeSubject context,
+  on console and at the end with the final error listing. Count this as a sync error.
+* in case of good command, take final $string as is, unless void. In case $error with value then print it.
+* in case of good command and final $string empty, consider it like CHILD_ERROR =>
+  return( undef, $error ) and print $error, with folder/UID/maybeSubject context,
+  on console and at the end with the final error listing. Count this as a sync error. 
+^ if 0 ; # End of multiline comment.
+
+sub pipemess {
+	my ( $string, @commands ) = @_ ;
+	my $error = q{} ;
+        foreach my $command ( @commands ) {
+                my $input_tmpfile  = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.inp.txt" ;
+                my $output_tmpfile = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.out.txt" ;
+                my $error_tmpfile  = "$tmpdir/imapsync_tmp_file.$PROCESS_ID.err.txt" ;
+                string_to_file( $string, $input_tmpfile  ) ;
+                ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ;
+                my $is_command_ko = $CHILD_ERROR ;
+                my $error_cmd = file_to_string( $error_tmpfile ) ;
+                chomp( $error_cmd ) ;
+		$string = file_to_string( $output_tmpfile ) ;
+                my $string_len = length( $string ) ;
+                unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ;
+
+		if ( $is_command_ko or ( ! $string_len ) ) {
+			my $cmd_exit_value = $CHILD_ERROR >> 8 ;
+			my $cmd_end_signal = $CHILD_ERROR & 127 ;
+                        my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ;
+                        my $error_log = qq{Failure: --pipemess command "$command" ended with$signal_log "$string_len" characters exit value "$cmd_exit_value" and STDERR "$error_cmd"\n} ;
+			myprint( $error_log ) ;
+			if ( wantarray ) {
+                                return @{ [ undef, $error_log ] }
+                        }else{
+                                return ;
+                        }
+		}
+                if ( $error_cmd ) {
+                        $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ;
+                        myprint(  qq{STDERR of --pipemess "$command": $error_cmd\n} ) ;
+                }
+        }
+        #myprint( "[$string]\n"  ) ;
+        if ( wantarray ) {
+                return ( $string, $error ) ;
+        }else{
+                return $string ;
+        }
+}
+
+
+
+sub tests_pipemess {
+
+	SKIP: {
+                Readonly my $NB_WIN_tests_pipemess => 3 ;
+		skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ;
+		# Windows
+		# "type" command does not accept redirection of STDIN with <
+		# "sort" does
+		ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ;
+		ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ;
+		# command not found
+		#diag( 'Warning and failure about cacaprout are on purpose' ) ;
+		ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ;
+
+	} ;
+
+        my ( $stringT, $errorT ) ;
+
+	SKIP: {
+                Readonly my $NB_UNX_tests_pipemess => 25 ;
+		skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ;
+		# Unix
+		ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ;
+
+		ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ;
+
+		ok( "     1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
+		ok( "     1\tnumberize\n     2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ;
+
+		ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ;
+
+		# command not found
+		#diag( 'Warning and failure about cacaprout are on purpose' ) ;
+		is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ;
+
+                # success with true but no output at all
+                is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ;
+
+                # failure with false and no output at all
+                is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ;
+
+		# Failure since pipemess is not a real pipe, so first cat wait for standard input
+		is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ;
+
+
+                ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ;
+                is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ;
+                is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ;
+                
+                ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ;
+                is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ;
+                like( $errorT, qr{Failure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""},  'pipemess: list context, true but no output, error' ) ;
+
+                ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ;
+                is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ;
+                like( $errorT, qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""},  'pipemess: list context, false and no output, error' ) ;
+
+                ( $stringT, $errorT ) = pipemess( 'dontcare', 'echo -n blablabla' ) ;
+                is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ;
+                is( $errorT, q{},  'pipemess: list context, "echo blablabla", error' ) ;
+
+                
+                ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
+                is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ;
+                like( $errorT,  qr{blablabla"$},  'pipemess: list context, "no output STDERR blablabla", error' ) ;
+
+
+                ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ;
+                is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ;
+                like( $errorT,  qr{blablabla"$},  'pipemess: list context, "no output STDERR blablabla then false", error' ) ;
+
+                ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ;
+                is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ;
+                like( $errorT,  qr{Failure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""},  'pipemess: list context, "false then STDERR blablabla", error' ) ;
+
+                ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ;
+                like( $stringT, qr{rrrrr}, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ;
+                like( $errorT,  qr{STDERR.*error_blablabla},  'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ;
+
+	}
+
+        ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ;
+        is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ;
+        like( $errorT, qr{Failure: --pipemess command "cacaprout" ended with "0" characters exit value.*}, 'pipemess: list context, cacaprout not found, error' ) ;
+
+	return ;
+}
+
+sub tests_is_a_release_number {
+	ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_1), 'is_a_release_number 1.351') ;
+	ok(is_a_release_number($RELEASE_NUMBER_EXAMPLE_2), 'is_a_release_number 42.4242') ;
+	ok(is_a_release_number(imapsync_version()), 'is_a_release_number imapsync_version()') ;
+	ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla') ;
+	return ;
+}
+
+sub is_a_release_number {
+	my $number = shift;
+
+	return( $number =~ m{^\d+\.\d+$}xo ) ;
+}
+
+sub check_last_release {
+
+	my $public_release = not_long_imapsync_version_public(  ) ;
+	$debug and myprint( "check_last_release: [$public_release]\n"  ) ;
+	return('unknown') if ($public_release eq 'unknown') ;
+	return('timeout') if ($public_release eq 'timeout') ;
+	return('unknown') if (! is_a_release_number( $public_release ) ) ;
+
+	my $imapsync_here  = imapsync_version();
+
+	if ($public_release > $imapsync_here) {
+		return("New imapsync release $public_release available");
+	}else{
+		return( 'This imapsync is up to date') ;
+	}
+}
+
+sub imapsync_version  {
+	my $rcs_imapsync = '$Id: imapsync,v 1.727 2016/08/19 10:30:36 gilles Exp gilles $ ' ;
+        my $imapsync_version ;
+
+	if ( $rcs_imapsync =~ m{,v\s+(\d+\.\d+)}xo ) {
+		$imapsync_version = $1
+        } else {
+                $imapsync_version = 'UNKNOWN' ;
+        }
+	return( $imapsync_version ) ;
+}
+
+sub tests_imapsync_basename {
+	ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync');
+	ok( 'blabla'   ne imapsync_basename(), 'imapsync_basename: do not equal blabla');
+	return ;
+}
+
+sub imapsync_basename {
+
+	return basename($0);
+
+}
+
+sub imapsync_version_public {
+
+	my $local_version = imapsync_version();
+	my $imapsync_basename = imapsync_basename();
+	my $agent_info = "$OSNAME system, perl "
+		. mysprintf( '%vd', $PERL_VERSION)
+		. ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
+		. " $imapsync_basename";
+	my $sock = IO::Socket::INET->new(
+		PeerAddr => 'imapsync.lamiral.info',
+		PeerPort => 80,
+		Proto    => 'tcp',
+                ) ;
+	return( 'unknown' ) if not $sock ;
+	print $sock
+		"GET /prj/imapsync/VERSION HTTP/1.0\n",
+		"User-Agent: imapsync/$local_version ($agent_info)\n",
+		"Host: ks.lamiral.info\n\n";
+	my @line = <$sock>;
+	close $sock ;
+	my $last_release = $line[$LAST];
+	chomp $last_release ;
+	return($last_release) ;
+}
+
+sub not_long_imapsync_version_public {
+	#myprint( "Entering not_long_imapsync_version_public\n" ) ;
+
+	my $val;
+
+	# Doesn't work with gethostbyname (see perlipc)
+	#local $SIG{ALRM} = sub { die "alarm\n" };
+
+	if ('MSWin32' eq $OSNAME) {
+		local $SIG{ALRM} = sub { die "alarm\n" };
+	}else{
+
+        	POSIX::sigaction(SIGALRM,
+                         POSIX::SigAction->new(sub { croak 'alarm' } ) )
+        		or myprint( "Error setting SIGALRM handler: $!\n"  ) ;
+	}
+
+	my $ret = eval {
+		alarm 3 ;
+		{
+			$val = imapsync_version_public(  ) ;
+                        #sleep 4 ;
+			#myprint( "End of imapsync_version_public\n"  ) ;
+		}
+		alarm 0 ;
+                1 ;
+	} ;
+        #myprint( "eval [$ret]\n"  ) ;
+	if ( ( not $ret ) or $@ ) {
+		#myprint( "$@" ) ;
+		if ($@ =~ /alarm/) {
+		# timed out
+			return('timeout');
+		}else{
+			alarm 0 ;
+			return('unknown'); # propagate unexpected errors
+		}
+	}else {
+	# Good!
+		return($val);
+	}
+}
+
+sub localhost_info {
+
+	my($infos) = join q{},
+	    "Here is a [$OSNAME] system (",
+	    join(q{ },
+	         uname(),
+	         ),
+                 ")\n",
+	         'with Perl ',
+	         mysprintf( '%vd', $PERL_VERSION),
+	         " Mail::IMAPClient $Mail::IMAPClient::VERSION",
+             ;
+	return($infos) ;
+}
+
+sub memory_consumption {
+	# memory consumed by imapsync until now in bytes
+	return( ( memory_consumption_of_pids(  ) )[0] );
+}
+
+sub tests_memory_consumption {
+
+	like( memory_consumption(  ),  qr{\d+},'memory_consumption no args') ;
+	like( memory_consumption( 1 ), qr{\d+},'memory_consumption 1') ;
+	like( memory_consumption( $PROCESS_ID ), qr{\d+},"memory_consumption_of_pids $PROCESS_ID") ;
+
+	like( memory_consumption_ratio(), qr{\d+},   'memory_consumption_ratio' ) ;
+	like( memory_consumption_ratio(1), qr{\d+},  'memory_consumption_ratio 1' ) ;
+	like( memory_consumption_ratio(10), qr{\d+}, 'memory_consumption_ratio 10' ) ;
+
+	like( memory_consumption(), qr{\d+}, "memory_consumption\n" ) ;
+	return ;
+}
+
+
+
+sub memory_consumption_of_pids {
+
+	my @pid = @_;
+	@pid = (@pid) ? @pid : ($PROCESS_ID) ;
+
+	#myprint( "PIDs: @pid\n" ) ;
+	my @val;
+	if ('MSWin32' eq $OSNAME) {
+		@val = memory_consumption_of_pids_win32(@pid);
+	}else{
+		# Unix
+		my @ps = qx{ ps -o vsz -p @pid } ;
+                #myprint( @ps ) ;
+                #my @ps = backtick( "ps -o vsz -p @pid" ) ;
+		shift @ps; # First line is column name "VSZ"
+		chomp @ps;
+		# convert to octets
+                
+		@val = map { $_ * $KIBI } @ps;
+	}
+	return( @val ) ;
+}
+
+sub memory_consumption_of_pids_win32 {
+	# Windows
+	my @PID = @_;
+	my %PID;
+	# hash of pids as key values
+	map { $PID{$_}++ } @PID;
+
+	# Does not work but should reading the tasklist documentation
+	#@ps = qx{ tasklist /FI "PID eq @PID" };
+
+	my @ps = qx{ tasklist /NH /FO CSV } ;
+        #my @ps = backtick( 'tasklist /NH /FO CSV' ) ;
+	#myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ;
+	my @val;
+	foreach my $line (@ps) {
+		my($name, $pid, $mem) = (split ',', $line )[0,1,4];
+		next if (! $pid);
+		#myprint( "[$name][$pid][$mem]" ) ;
+		if ($PID{remove_qq($pid)}) {
+			#myprint( "MATCH !\n" ) ;
+			chomp $mem ;
+			$mem = remove_qq($mem);
+			$mem = remove_Ko($mem);
+			$mem = remove_not_num($mem);
+			#myprint( "[$mem]\n" ) ;
+			push @val, $mem * $KIBI;
+		}
+	}
+	return(@val);
+}
+
+sub backtick {
+	my $command = shift ;
+	my ( $writer, $reader, $err ) ;
+        my @output ;
+        open3( $writer, $reader, $err, $command ) ;
+        @output = <$reader>;  #Output here
+        #my @errors = <$err>;    #Errors here, instead of the console
+        $debugdev and myprint( @output  ) ;
+        return( @output ) ;
+}
+
+sub tests_backtick {
+
+        SKIP: {
+		skip( 'Tests for MSWin32', 3 ) if ('MSWin32' ne $OSNAME) ;
+		my @output ;
+		@output = backtick( 'echo Hello World!' ) ;
+		# Add \r on Windows.
+		ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ;
+		$debug and myprint( "[@output]"  ) ;
+		@output = backtick( 'echo Hello & echo World!' ) ;
+		ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World!' ) ;
+		ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World!' ) ;
+		$debug and myprint( "[@output][$output[0]][$output[1]]"  ) ;
+        } ;
+	SKIP: {
+		skip( 'Tests for Unix', 3 ) if ('MSWin32' eq $OSNAME) ;
+		my @output ;
+		@output = backtick( 'echo Hello World!' ) ;
+		ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ;
+		$debug and myprint( "[@output]"  ) ;
+		@output = backtick( "echo Hello\necho World!" ) ;
+		ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World!' ) ;
+		ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World!' ) ;
+		$debug and myprint( "[@output]"  ) ;
+	}
+        return ;
+}
+
+sub remove_not_num {
+
+	my $string = shift;
+	$string =~ tr/0-9//cd;
+	#myprint( "tr [$string]\n" ) ;
+	return($string);
+}
+
+sub tests_remove_not_num {
+
+	ok('123' eq remove_not_num(123), 'remove_not_num( 123 )' ) ;
+	ok('123' eq remove_not_num('123'), q{remove_not_num( '123' )} ) ;
+	ok('123' eq remove_not_num('12 3'), q{remove_not_num( '12 3' )} ) ;
+	ok('123' eq remove_not_num('a 12 3 Ko'), q{remove_not_num( 'a 12 3 Ko' )} ) ;
+	return ;
+}
+
+sub remove_Ko {
+	my $string = shift;
+	if ($string =~ /^(.*)\sKo$/xo) {
+		return($1);
+	}else{
+		return($string);
+	}
+}
+
+sub remove_qq {
+	my $string = shift;
+	if ($string =~ /^"(.*)"$/xo) {
+		return($1);
+	}else{
+		return($string);
+	}
+}
+
+sub memory_consumption_ratio {
+
+	my ($base) = @_;
+	$base ||= 1;
+	my $consu = memory_consumption();
+	return($consu / $base);
+}
+
+
+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"  ) ;
+                my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
+                $month = $num2mon{$month} ;
+                $d = "$day-$month-$year $hour:$min:$sec +0000" ;
+		#myprint( "$d\n"  ) ;
+	}
+	return( $d ) ;
+}
+
+sub tests_date_from_rcs {
+	ok('19-Sep-2015 16:11:07 +0000'
+	eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ;
+	return ;
+}
+
+sub good_date {
+        # two incoming formats:
+        # header    Tue, 24 Aug 2010 16:00:00 +0200
+	# internal       24-Aug-2010 16:00:00 +0200
+
+        # outgoing format: internal date format
+        #   24-Aug-2010 16:00:00 +0200
+
+    my $d = shift ;
+    return(q{}) if not defined $d;
+
+	SWITCH: {
+    	if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) {
+		#myprint( "internal: [$1][$2][$3][$4]\n"  ) ;
+		my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ;
+		$day_1 = '0' if ($day_1 eq q{}) ;
+		$zone  = ' +0000'  if not defined $zone ;
+		$d = $day_1 . $date_rest . $hour . $zone ;
+                last SWITCH ;
+        }
+
+	if ($d =~ m{(?:\w{3,},\s)?(\d{1,2}),?\s+(\w{3,})\s+(\d{2,4})\s+(\d{1,2})(?::|\.)(\d{1,2})(?:(?::|\.)(\d{1,2}))?\s*((?:\+|-)\d{4})?}xo ) {
+        	# Handles any combination of following formats
+                # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard
+                # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week
+                # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year
+                # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons
+                # Tue, 24 Aug 1997  16:00:00 +0200 -- Extra whitespace between year and hour
+                # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second
+                # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma
+
+                #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ;
+                my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
+                $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo;
+                $year = '20' . $year if length($year) == 2;
+
+                $month = substr $month, 0, 3 if length($month) > 4;
+                $day  = mysprintf( '%02d', $day);
+                $hour = mysprintf( '%02d', $hour);
+                $min  = mysprintf( '%02d', $min);
+                $sec  = '00' if not defined  $sec  ;
+                $sec  = mysprintf( '%02d', $sec ) ;
+                $zone = '+0000' if not defined  $zone  ;
+                $d    = "$day-$month-$year $hour:$min:$sec $zone" ;
+		last SWITCH ;
+	}
+
+	if ($d =~ m{(?:.{3})\s(...)\s+(\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})\s(?:\w{3})?\s?(\d{4})}xo ) {
+        	# Handles any combination of following formats
+                # Sun Aug 20 11:55:09 2006
+                # Wed Jan 24 11:58:38 MST 2007
+                # Wed Jan  2 08:40:57 2008
+
+                #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
+                my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6);
+                $day  = mysprintf( '%02d', $day  ) ;
+                $hour = mysprintf( '%02d', $hour ) ;
+                $min  = mysprintf( '%02d', $min  ) ;
+                $sec  = mysprintf( '%02d', $sec  ) ;
+                $d    = "$day-$month-$year $hour:$min:$sec +0000" ;
+		last SWITCH ;
+	}
+        my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ;
+
+        if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
+                # Handles the following format
+                # 2015/07/10 11:05:59 -- Generated by RCS Date tag.
+		#myprint( "$d\n"  ) ;
+                #myprint( "header: [$1][$2][$3][$4][$5][$6]\n"  ) ;
+                my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ;
+                $month = $num2mon{$month} ;
+                $d = "$day-$month-$year $hour:$min:$sec +0000" ;
+		#myprint( "$d\n"  ) ;
+		last SWITCH ;
+	}
+
+        if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) {
+                # Handles the following format
+                # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices
+
+                #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ;
+                my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6);
+                $year = '20' . $year;
+                $month = $num2mon{$month};
+                $d = "$day-$month-$year $hour:$min:$sec +0000";
+		last SWITCH ;
+	}
+
+	if ($d =~ m{\w{6,},\s(\w{3})\w+\s+(\d{1,2}),\s(\d{4})\s(\d{2}):(\d{2})\s(AM|PM)}xo ) {
+        	# Handles the following format
+                # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations
+
+                my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6);
+
+                $hour += 12 if $apm eq 'PM' ;
+                $day = mysprintf( '%02d', $day ) ;
+                $d = "$day-$month-$year $hour:$min:00 +0000" ;
+                last SWITCH ;
+	}
+
+	if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) {
+        	# Handles the following format
+                # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations
+
+                my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7);
+
+                $day = mysprintf( '%02d', $day ) ;
+                $d = "$day-$month-$year $hour:$min:$sec $zone";
+                last SWITCH ;
+	}
+
+	if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) {
+        	# Handles the following format
+                # 21-Jun-2001 - register.com domain transfer email circa 2001
+
+                my ($day, $month, $year) = ($1,$2,$3);
+                $day = mysprintf( '%02d', $day);
+                $d = "$day-$month-$year 11:11:11 +0000";
+		last SWITCH ;
+	}
+
+    	# unknown or unmatch => return same string
+    	return($d);
+    }
+
+    $d = qq("$d") ;
+    return( $d ) ;
+}
+
+
+sub tests_good_date {
+
+	ok(q{} eq good_date(), 'good_date no arg');
+	ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone');
+	ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone');
+	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit');
+	ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone');
+	ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone');
+	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200'), 'good_date header SP 1digit zone');
+	ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200 (CEST)'), 'good_date header SP 1digit zone');
+        ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR');
+        ok('"02-Jan-2008 08:40:57 +0000"' eq good_date('Wed Jan  2 08:40:57 2008'), 'good_date header dice.com support 1digit day');
+        ok('"20-Aug-2006 11:55:09 +0000"' eq good_date('Sun Aug 20 11:55:09 2006'), 'good_date header dice.com support 2digit day');
+        ok('"24-Jan-2007 11:58:38 +0000"' eq good_date('Wed Jan 24 11:58:38 MST 2007'), 'good_date header status-now.com');
+        ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24 Aug 2010 16:00:00 +0200'), 'good_date header missing date of week');
+        ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year');
+        ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year');
+        ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year');
+        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 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('"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('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ;
+	return ;
+}
+
+
+sub tests_list_keys_in_2_not_in_1 {
+
+	my @list;
+	ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
+	ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
+	ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}');
+	ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}');
+	ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}');
+	ok( 0 == compare_lists( [],        [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+	ok( 0 == compare_lists( ['b'],     [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+
+	return ;
+}
+
+sub list_keys_in_2_not_in_1 {
+
+	my $folders1_ref = shift;
+	my $folders2_ref = shift;
+	my @list;
+
+	foreach my $folder ( sort keys %{ $folders2_ref } ) {
+		next if exists $folders1_ref->{$folder};
+		push @list, $folder;
+	}
+	return(@list);
+}
+
+
+sub list_folders_in_2_not_in_1 {
+
+	my (@h2_folders_not_in_h1, %h2_folders_not_in_h1) ;
+	@h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all) ;
+	map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ;
+	@h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1) ;
+
+	return( reverse @h2_folders_not_in_h1 );
+}
+
+sub delete_folders_in_2_not_in_1 {
+
+	foreach my $folder (@h2_folders_not_in_1) {
+		if ( defined  $delete2foldersonly  and eval "\$folder !~ $delete2foldersonly" ) {
+			myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n"  ) ;
+			next ;
+		}
+		if ( defined  $delete2foldersbutnot  and eval "\$folder =~ $delete2foldersbutnot" ) {
+			myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n"  ) ;
+			next ;
+		}
+		my $res = $dry ; # always success in dry mode!
+		$imap2->unsubscribe( $folder ) if ( ! $dry ) ;
+		$res = $imap2->delete( $folder ) if ( ! $dry ) ;
+		if ( $res ) {
+			myprint( "Deleted $folder", "$dry_message", "\n"  ) ;
+		}else{
+			myprint( "Deleting $folder failed", "\n"  ) ;
+		}
+	}
+	return ;
+}
+
+sub delete_folder {
+        my ( $sync, $imap, $folder, $Side ) = @_ ;
+        if ( ! $sync )   { return ; }
+        if ( ! $imap )   { return ; }
+        if ( ! $folder ) { return ; }
+        $Side ||= 'HostX' ;
+        
+        my $res = $sync->{dry} ; # always success in dry mode!
+        if ( ! $sync->{dry} ) {
+                $imap->unsubscribe( $folder ) ;
+                $res = $imap->delete( $folder ) ;
+        }
+        if ( $res ) {
+        	myprint( "$Side deleted $folder", $sync->{dry_message}, "\n"  ) ;
+                return 1 ;
+        }else{
+        	myprint( "$Side deleting $folder failed", "\n"  ) ;
+                return ;
+        }
+}
+
+sub delete1emptyfolders {
+        my $sync = shift ;
+        if ( ! $sync ) { return ; } # abort if no parameter
+        if ( ! $sync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off
+        my $imap = $sync->{imap1} ;
+        if ( ! $imap ) { return ; } # abort if no imap
+        if ( $imap->IsUnconnected(  ) ) { return ; } # abort if diesconnected
+        
+        my %folders_kept ;
+        myprint( qq{Host1 deleting empty folders\n} ) ;
+        foreach my $folder ( reverse sort @{ $sync->{h1_folders_wanted} } ) {
+                my $parenthood = $imap->is_parent( $folder ) ;
+                if ( defined $parenthood and $parenthood ) {
+                        myprint( "Host1 folder $folder has subfolders\n" ) ;
+                        $folders_kept{ $folder }++ ;
+                        next ;
+                }
+                my $nb_messages_select = examine_folder_and_count( $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" ) ;
+                        $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" ) ;
+                        $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" ) ;
+                        $folders_kept{ $folder }++ ;
+                        next ; 
+                }
+                myprint( "Host1 deleting empty folder $folder\n" ) ;
+                # can not delete a SELECTed or EXAMINEd folder so closing it
+                # could changed be SELECT INBOX
+                $imap->close(  ) ; # close after examine does not expunge; anyway expunging an empty folder... 
+                if ( delete_folder( $sync, $imap, $folder, 'Host1' ) ) {
+                        next ; # Deleted, good!
+                }else{
+                        $folders_kept{ $folder }++ ;
+                        next ; # Not deleted, bad!
+                }
+        }
+        remove_deleted_folders_from_wanted_list( $sync, %folders_kept ) ;
+        myprint( qq{Host1 ended deleting empty folders\n} ) ;
+        return ;
+}
+
+sub remove_deleted_folders_from_wanted_list {
+        my ( $sync, %folders_kept ) = @ARG ;
+        
+        my @h1_folders_wanted_init = @{ $sync->{h1_folders_wanted} } ;
+        my @h1_folders_wanted_last ;
+        foreach my $folder ( @h1_folders_wanted_init ) {
+                if ( $folders_kept{ $folder } ) {
+                        push @h1_folders_wanted_last, $folder ;
+                }
+        }
+        @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_last ;
+        return ;
+}
+
+sub examine_folder_and_count {
+        my ( $imap, $folder, $Side ) = @_ ;
+        $Side ||= 'HostX' ;
+        
+        if ( ! examine_folder( $imap, $folder, $Side ) ) {
+                return ;
+        }
+        my $nb_messages_select = count_from_select( $imap->History ) ;
+        return $nb_messages_select ;
+}
+
+
+sub tests_delete1emptyfolders {
+
+        is( undef, delete1emptyfolders(  ), q{delete1emptyfolders: undef} ) ;
+        my $syncT ;
+        is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ;
+        my $imapT ;
+        $syncT->{imap1} = $imapT ;
+        is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ;
+        
+        require Test::MockObject ;
+        $imapT = Test::MockObject->new(  ) ;
+        $syncT->{imap1} = $imapT ;
+
+        $imapT->set_true( 'IsUnconnected' ) ;
+        is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ;
+
+        # Now connected tests
+        $imapT->set_false( 'IsUnconnected' ) ;
+        $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ;
+        
+        $syncT->{delete1emptyfolders} = 0 ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX DELME1 DELME2 } ],
+                q{tests_delete1emptyfolders: --delete1emptyfolders OFF}
+        ) ;
+
+        # All are parents => no deletion at all
+        $imapT->set_true( 'is_parent' ) ;
+        $syncT->{delete1emptyfolders} = 1 ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX DELME1 DELME2 } ],
+                q{tests_delete1emptyfolders: --delete1emptyfolders ON}
+        ) ;
+
+        # No parents but examine false for all => skip all
+        $imapT->set_false( 'is_parent', 'examine' ) ;
+        
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [  ],
+                q{tests_delete1emptyfolders: EXAMINE fails}
+        ) ;
+
+        # examine ok for all but History bad => skip all
+        $imapT->set_true( 'examine' ) ;
+        $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [  ],
+                q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails}
+        ) ;
+
+        # History good but some messages EXISTS == messages() => no deletion
+        $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ;
+        $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX DELME1 DELME2 } ],
+                q{tests_delete1emptyfolders: History EXAMINE ok, several messages}
+        ) ;
+
+        # 0 EXISTS but != messages() => no deletion
+        $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
+        $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX DELME1 DELME2 } ],
+                q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()}
+        ) ;
+
+        # 1 EXISTS but != 0 == messages() => no deletion
+        $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ;
+        $imapT->mock( 'messages', sub { [ ] } ) ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX DELME1 DELME2 } ],
+                q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()}
+        ) ;
+
+        # 0 EXISTS and 0 == messages() => deletion except INBOX
+        $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ;
+        $imapT->mock( 'messages', sub { [ ] } ) ;
+        $imapT->set_true( qw{ delete close unsubscribe } ) ;
+        $syncT->{dry_message} = q{ (not really since in a mocked test)} ;
+        tests_delete1emptyfolders_unit(
+                $syncT,
+                [ qw{ INBOX DELME1 DELME2 } ],
+                [ qw{ INBOX } ],
+                q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX}
+        ) ;
+
+
+
+
+        return ;
+}
+
+sub tests_delete1emptyfolders_unit {
+        my $syncT  = shift ;
+        my $folders1wanted_init_ref = shift ;
+        my $folders1wanted_after_ref = shift ;
+        my $comment = shift || q{delete1emptyfolders:} ;
+        
+        my @folders1wanted_init  = @{ $folders1wanted_init_ref } ;
+        my @folders1wanted_after = @{ $folders1wanted_after_ref } ;
+
+        @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ;
+        
+        is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ;
+        delete1emptyfolders( $syncT ) ;
+        is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ;
+        return ;
+}
+
+sub extract_header {
+        my $string = shift ;
+
+        my ( $header ) = split  /\n\n/x, $string ;
+        if ( ! $header ) { return( q{} ) ; }
+        #myprint( "[$header]\n"  ) ;
+        return( $header ) ;
+}
+
+sub tests_extract_header {
+
+
+my $h = <<'EOM';
+Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
+Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
+From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
+EOM
+chomp $h ;
+ok( $h eq extract_header(
+<<'EOM'
+Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
+Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
+From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
+
+body
+lalala
+EOM
+), 'extract_header: 1') ;
+
+
+
+	return ;
+}
+
+sub decompose_header{
+        my $string = shift ;
+
+        # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc]
+        # Think of multiple "Received:" header lines.
+        my $header = {  } ;
+
+        my ($key, $val ) ;
+        my @line = split /\n|\r\n/x, $string ;
+        foreach my $line ( @line ) {
+                #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"  ) ;
+                        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"  ) ;
+                        @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ;
+                # dirty line?
+                }else{
+                        next ;
+                }
+        }
+
+        #myprint( Data::Dumper->Dump( [ $header ] )  ) ;
+
+        return( $header ) ;
+}
+
+
+sub tests_decompose_header{
+
+        my $header_dec ;
+
+        $header_dec = decompose_header(
+<<'EOH'
+KEY_1: VAL_1
+KEY_2: VAL_2
+  VAL_2_+
+        VAL_2_++
+KEY_3: VAL_3
+KEY_1: VAL_1_other
+KEY_4: VAL_4
+	VAL_4_+
+KEY_5 BLANC:  VAL_5
+
+KEY_6_BAD_BODY: VAL_6
+EOH
+        ) ;
+
+        ok( 'VAL_3'
+        eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ;
+
+        ok( 'VAL_1'
+        eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ;
+
+        ok( 'VAL_1_other'
+        eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ;
+
+        ok( 'VAL_2 VAL_2_+ VAL_2_++'
+        eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ;
+
+        ok( 'VAL_4 VAL_4_+'
+        eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ;
+
+        ok( ' VAL_5'
+        eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ;
+
+        ok( not( defined  $header_dec->{ 'KEY_6_BAD_BODY' }[0]  ), 'decompose_header: KEY_6_BAD_BODY' ) ;
+
+
+        $header_dec = decompose_header(
+<<'EOH'
+Message-Id: <20100428101817.A66CB162474E@plume.est.belle>
+Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST)
+From: gilles@louloutte.dyndns.org (Gilles LAMIRAL)
+EOH
+        ) ;
+
+        ok( '<20100428101817.A66CB162474E@plume.est.belle>'
+        eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ;
+
+        $header_dec = decompose_header(
+<<'EOH'
+Return-Path: 
+Received: by plume.est.belle (Postfix, from userid 1000)
+        id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)
+Subject: test:eekahceishukohpe
+EOH
+) ;
+        ok(
+'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)'
+        eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ;
+
+        $header_dec = decompose_header(
+<<'EOH'
+Received: from plume (localhost [127.0.0.1])
+        by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9
+        for ; Mon, 26 Nov 2007 10:39:06 +0100 (CET)
+Received: from plume [192.168.68.7]
+        by plume with POP3 (fetchmail-6.3.6)
+        for  (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)
+EOH
+        ) ;
+        ok(
+        'from plume (localhost [127.0.0.1]) by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9 for ; Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
+        eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ;
+        ok(
+        'from plume [192.168.68.7] by plume with POP3 (fetchmail-6.3.6) for  (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)'
+        eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ;
+
+# Bad header beginning with a blank character
+        $header_dec = decompose_header(
+<<'EOH'
+ KEY_1: VAL_1
+KEY_2: VAL_2
+  VAL_2_+
+        VAL_2_++
+KEY_3: VAL_3
+KEY_1: VAL_1_other
+EOH
+        ) ;
+
+        ok( 'VAL_3'
+        eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ;
+
+        ok( 'VAL_1_other'
+        eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ;
+
+        ok( 'VAL_2 VAL_2_+ VAL_2_++'
+        eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ;
+
+	return ;
+}
+
+sub epoch {
+        # incoming format:
+	# internal date 24-Aug-2010 16:00:00 +0200
+
+        # outgoing format: epoch
+
+
+        my $d = shift ;
+        return(q{}) if not defined $d;
+
+        my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ;
+        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"  ) ;
+                ( $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"  ) ;
+
+                $sign = +1 if ( '+' eq $sign ) ;
+                $sign = $MINUS_ONE if ( '-' eq $sign ) ;
+
+                $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year )
+                        - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ;
+
+                #myprint( "$time ", scalar localtime($time), "\n");
+        }
+        return( $time ) ;
+}
+
+sub tests_epoch {
+        ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ;
+        ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ;
+
+        ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ;
+        ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ;
+	return ;
+}
+
+sub add_header {
+	my $header_uid = shift || 'mistake' ;
+	my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ;
+        return( $header_Message_Id ) ;
+}
+
+sub tests_add_header {
+	ok( 'Message-Id: ' eq add_header(), 'add_header no arg' ) ;
+	ok( 'Message-Id: <123456789@imapsync>' eq add_header(123456789), 'add_header 123456789' ) ;
+
+	return ;
+}
+
+sub tests_Banner{
+
+	my $imap = Mail::IMAPClient->new(  ) ;
+        ok( 'lalala' eq $imap->Banner('lalala'), 'Banner set lalala' ) ;
+        ok( 'lalala' eq $imap->Banner(), 'Banner returns lalala' ) ;
+	return ;
+}
+
+
+
+
+sub max_line_length {
+	my $string = shift ;
+        my $max = 0 ;
+
+        while ( $string =~ m/([^\n]*\n?)/msxg ) {
+        	$max = max( $max, length $1 ) ;
+        }
+	return( $max ) ;
+}
+
+sub tests_max_line_length {
+	ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ;
+	ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ;
+	ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ;
+	ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ;
+	ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ;
+	ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ;
+	ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ;
+	ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ;
+	ok( 3 == max_line_length( "a\nab\n" x 10000 ), 'max_line_length: 3 == 10000 a\nab\n' ) ;
+	ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ;
+
+	ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ;
+	ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ;
+	ok( 5 == max_line_length( "a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd" ), 'max_line_length: 5 == a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd' ) ;
+	return ;
+}
+
+sub setlogfile {
+        my( $mysync ) = shift ;
+        $mysync->{logdir}  = defined $mysync->{logdir}  ? $mysync->{logdir}  : 'LOG_imapsync' ;
+        $mysync->{logfile} = defined $mysync->{logfile} ? "$mysync->{logdir}/$mysync->{logfile}" :
+                logfile( $mysync->{timestart}, $mysync->{user2}, $mysync->{logdir} ) ;
+        #myprint( "logdir  = $mysync->{logdir}\n"  ) ;
+        #myprint( "logfile = $mysync->{logfile}\n"  ) ;
+        return( $mysync->{logfile} ) ;
+}
+
+sub tests_setlogfile {
+        my $mysync = {
+                timestart => 2,
+                user2     => 'user2',
+        } ;
+
+        ok( 'LOG_imapsync/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
+                'setlogfile: default is like LOG_imapsync/1970_01_01_01_00_02_user2.txt' ) ;
+
+        $mysync->{logdir}  = undef ;
+        $mysync->{logfile} = undef ;
+        ok( 'LOG_imapsync/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
+                'setlogfile: logdir undef, LOG_imapsync/1970_01_01_01_00_02_user2.txt' ) ;
+
+        $mysync->{logdir} = q{} ;
+        $mysync->{logfile} = undef ;
+        ok( '1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
+                'setlogfile: logdir empty, 1970_01_01_01_00_02_user2.txt' ) ;
+
+        $mysync->{logdir} = 'vallogdir' ;
+        $mysync->{logfile} = undef ;
+        ok( 'vallogdir/1970_01_01_01_00_02_user2.txt' eq setlogfile( $mysync ),
+                'setlogfile: logdir vallogdir, vallogdir/1970_01_01_01_00_02_user2.txt' ) ;
+
+        $mysync->{logdir}  = 'vallogdir' ;
+        $mysync->{logfile} = 'vallogfile.txt' ;
+        ok( 'vallogdir/vallogfile.txt' eq setlogfile( $mysync ),
+                'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ;
+
+        return ;
+}
+
+
+sub logfile {
+	my ( $time, $suffix, $dir ) = @_ ;
+
+	$time   ||= 0 ;
+	$suffix ||= q{} ;
+	my $sep_suffix = ( $suffix ) ? '_' : q{} ;
+        $dir    ||= q{} ;
+	my $sep_dir = ( $dir ) ? '/' : q{} ;
+
+	my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ;
+        my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ;
+	$debug and myprint( "date_str: $date_str\n"  ) ;
+	$debug and myprint( "logfile : $logfile\n"  ) ;
+	return( $logfile ) ;
+}
+
+sub tests_logfile {
+	SKIP: {
+		# Too hard to have a well known timezone on Windows
+		skip( 'Too hard to have a well known timezone on Windows', 6 ) if ( 'MSWin32' eq $OSNAME ) ;
+
+		local $ENV{TZ} = 'GMT' ;
+		{ POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
+			ok( '1970_01_01_00_00_00.txt' eq logfile(  ),           'logfile: no args    => 1970_01_01_00_00_00.txt' ) ;
+			ok( '1970_01_01_00_00_00.txt' eq logfile( 0 ),          'logfile: 0          => 1970_01_01_00_00_00.txt' ) ;
+			ok( '1970_01_01_00_01_01.txt' eq logfile( 61 ),         'logfile: 0          => 1970_01_01_00_01_01.txt' ) ;
+			ok( '2010_08_24_14_00_00.txt' eq logfile( 1282658400 ), 'logfile: 1282658400 => 2010_08_24_14_00_00.txt' ) ;
+			ok( '2010_08_24_14_01_01.txt' eq logfile( 1282658461 ), 'logfile: 1282658461 => 2010_08_24_14_01_01.txt' ) ;
+			ok( '2010_08_24_14_01_01_poupinette.txt' eq logfile( 1282658461, 'poupinette' ), 'logfile: 1282658461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ;
+                }
+		POSIX::tzset unless ('MSWin32' eq $OSNAME) ;
+	} ;
+	return ;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+sub tests_million_folders_baby_2 {
+	my %long ;
+	@long{ 1 .. 900_000 } = (1) x 900_000 ;
+	#myprint( %long, "\n"  ) ;
+	my $pasglop = 0 ;
+	foreach my $elem (  1 .. 900_000 ) {
+		#$debug and myprint( "$elem "  ) ;
+		if ( not exists  $long{ $elem }  ) {
+			$pasglop++ ;
+		}
+	}
+        ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ;
+	# myprint( "$pasglop\n"  ) ;
+        return ;
+}
+
+
+
+sub tests_always_fail {
+	ok( 0 == 1, '0 == 1' ) ;
+	ok( 1 == 1, '1 == 1' ) ;
+        return ;
+}
+
+sub logfileprepa {
+	my $logfile = shift ;
+
+	my $dirname = dirname( $logfile ) ;
+	is_valid_directory( $dirname ) || return( 0 ) ;
+	return( 1 ) ;
+}
+
+sub teelaunch {
+        my $mysync = shift ;
+	my $logfile = $mysync->{logfile} ;
+	logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $!" ;
+	my $logfile_handle ;
+	open $logfile_handle, '>', $logfile
+	  or croak( "Can not open $logfile for write: $!" ) ;
+	my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ;
+	*STDERR = *$tee{IO} ;
+	select $tee ;
+        $tee->autoflush( 1 ) ;
+        $mysync->{logfile_handle} = $logfile_handle ;
+        $mysync->{tee} = $tee ;
+	return $logfile_handle ;
+}
+
+sub getpwuid_any_os {
+        my $uid = shift ;
+
+        return( scalar  getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system
+        return( scalar  getpwuid $uid ) ; # Unix system
+}
+
+
+
+sub usage {
+	my $localhost_info = localhost_info();
+	my $thank = thank_author();
+	my $imapsync_release = q{};
+	$imapsync_release = check_last_release() if (not defined $releasecheck);
+        my $escape_char = ( 'MSWin32' eq $OSNAME ) ? '^' : '\\';
+        myprint( <<"EOF" ) ;
+
+ usage: $0 [options]
+
+ Several options are mandatory.
+ str means string
+ int means integer
+ reg means regular expression
+ cmd means command
+
+ --dry               : Makes imapsync doing nothing, just print what would
+                       be done without --dry.
+
+ --host1        str  : Source or "from" imap server. Mandatory.
+ --port1        int  : Port to connect on host1. Default is 143, 993 if --ssl1
+ --user1        str  : User to login on host1. Mandatory.
+ --showpasswords     : Shows passwords on output instead of "MASKED".
+                       Useful to restart a complete run by just reading the log.
+ --password1    str  : Password for the user1.
+ --host2        str  : "destination" imap server. Mandatory.
+ --port2        int  : Port to connect on host2. Default is 143, 993 if --ssl2
+ --user2        str  : User to login on host2. Mandatory.
+ --password2    str  : Password for the user2.
+
+ --passfile1    str  : Password file for the user1. It must contain the
+                       password on the first line. This option avoids to show
+                       the password on the command line like --password1 does.
+ --passfile2    str  : Password file for the user2. Contains the password.
+
+ --ssl1              : Use a SSL connection on host1.
+ --ssl2              : Use a SSL connection on host2.
+ --tls1              : Use a TLS connection on host1.
+ --tls2              : Use a TLS connection on host2.
+ --debugssl     int  : SSL debug mode from 0 to 4.
+ --sslargs1     str  : Pass any ssl parameter for host1 ssl or tls connection. Example:
+                       --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3
+                       See all possibilities in the new() method of IO::Socket::SSL
+                       http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods
+ --sslargs2     str  : Pass any ssl parameter for host2 ssl or tls connection.
+                       See --sslargs1
+
+ --timeout1     int  : Connection timeout in seconds for host1.
+                       Default is 120 and 0 means no timeout at all.
+ --timeout2     int  : Connection timeout in seconds for host2.
+                       Default is 120 and 0 means no timeout at all.
+
+ --authmech1    str  : Auth mechanism to use with host1:
+                       PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE.
+ --authmech2    str  : Auth mechanism to use with host2. See --authmech1
+
+ --authuser1    str  : User to auth with on host1 (admin user).
+                       Avoid using --authmech1 SOMETHING with --authuser1.
+ --authuser2    str  : User to auth with on host2 (admin user).
+ --proxyauth1        : Use proxyauth on host1. Requires --authuser1.
+                       Required by Sun/iPlanet/Netscape IMAP servers to
+                       be able to use an administrative user.
+ --proxyauth2        : Use proxyauth on host2. Requires --authuser2.
+
+ --authmd51          : Use MD5 authentification for host1.
+ --authmd52          : Use MD5 authentification for host2.
+ --domain1      str  : Domain on host1 (NTLM authentication).
+ --domain2      str  : Domain on host2 (NTLM authentication).
+
+
+ --folder       str  : Sync this folder.
+ --folder       str  : and this one, etc.
+ --folderrec    str  : Sync this folder recursively.
+ --folderrec    str  : and this one, etc.
+
+ --folderfirst  str  : Sync this folder first. --folderfirst "Work"
+ --folderfirst  str  : then this one, etc.
+ --folderlast   str  : Sync this folder last. --folderlast "[Gmail]/All Mail"
+ --folderlast   str  : then this one, etc.
+
+ --nomixfolders      : Do not merge folders when host1 is case sensitive
+                       while host2 is not (like Exchange). Only the first
+                       similar folder is synced (ex: Sent SENT sent -> Sent).
+
+ --skipemptyfolders  : Empty host1 folders are not created on host2.
+
+ --include      reg  : Sync folders matching this regular expression
+ --include      reg  : or this one, etc.
+                       in case both --include --exclude options are
+                       use, include is done before.
+ --exclude      reg  : Skips folders matching this regular expression
+                       Several folders to avoid:
+                        --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3.
+ --exclude      reg  : or this one, etc.
+
+ --subfolder2   str  : Move whole host1 folders hierarchy under this
+                       host2 folder  str    .
+                       It does it by adding two --regextrans2 options before
+                       all others. Add --debug to see what's really going on.
+
+ --automap           : guesses folders mapping, for folders like
+                       "Sent", "Junk", "Drafts", "All", "Archive", "Flagged".
+ --f1f2    str1=str2 : Force folder str1 to be synced to str2,
+                       --f1f2 overrides --automap and --regextrans2.
+ --regextrans2  reg  : Apply the whole regex to each destination folders.
+ --regextrans2  reg  : and this one. etc.
+                       When you play with the --regextrans2 option, first
+                       add also the safe options --dry --justfolders
+                       Then, when happy, remove --dry, remove --justfolders.
+                       Have in mind that --regextrans2 is applied after prefix
+                       and separator inversion. For examples see
+                       http://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt
+
+ --tmpdir       str  : Where to store temporary files and subdirectories.
+                       Will be created if it doesn't exist.
+                       Default is system specific, Unix is /tmp but
+                       it's often small and deleted at reboot.
+                       --tmpdir /var/tmp should be better.
+ --pidfile      str  : The file where imapsync pid is written.
+ --pidfilelocking    : Abort if pidfile already exists. Usefull to avoid
+                       concurrent transfers on the same mailbox.
+
+ --nolog             : Turn off logging on file
+ --logfile      str  : Change the default log filename (can be dirname/filename).
+ --logdir       str  : Change the default log directory. Default is LOG_imapsync
+
+ --prefix1      str  : Remove prefix to all destination folders
+                       (usually INBOX. or INBOX/ or an empty string "")
+                       you have to use --prefix1 if host1 imap server
+                       does not have NAMESPACE capability, so imapsync
+                       suggests to use it. All other cases are bad.
+ --prefix2      str  : Add prefix to all host2 folders. See --prefix1
+ --sep1         str  : Host1 separator in case NAMESPACE is not supported.
+ --sep2         str  : Host2 separator in case NAMESPACE is not supported.
+
+ --skipmess     reg  : Skips messages maching the regex.
+                       Example: 'm/[\\x80-ff]/' # to avoid 8bits messages.
+                       --skipmess is applied before --regexmess
+ --skipmess     reg  : or this one, etc.
+
+ --pipemess     cmd  : Apply this cmd command to each message content
+                       before the copy.
+ --pipemess     cmd  : and this one, etc.
+
+ --disarmreadreceipts : Disarms read receipts (host2 Exchange issue)
+
+ --regexmess    reg  : Apply the whole regex to each message before transfer.
+                       Example: 's/\\000/ /g' # to replace null by space.
+ --regexmess    reg  : and this one, etc.
+
+ --regexflag    reg  : Apply the whole regex to each flags list.
+                       Example: 's/\"Junk"//g' # to remove "Junk" flag.
+ --regexflag    reg  : and this one, etc.
+
+ --delete            : Deletes messages on host1 server after a successful
+                       transfer. Option --delete has the following behavior:
+                       it marks messages as deleted with the IMAP flag
+                       \\Deleted, then messages are really deleted with an
+                       EXPUNGE IMAP command.
+
+ --delete2           : Delete messages in host2 that are not in
+                       host1 server. Useful for backup or pre-sync.
+ --delete2duplicates : Delete messages in host2 that are duplicates.
+                       Works only without --useuid since duplicates are
+                       detected with an header part of each message.
+
+ --delete2folders    : Delete folders in host2 that are not in host1 server.
+                       For safety, first try it like this (it is safe):
+                       --delete2folders --dry --justfolders --nofoldersizes
+ --delete2foldersonly   reg : Deleted only folders matching regex.
+                              Example: --delete2foldersonly "/^Junk\$|^INBOX.Junk\$/"
+ --delete2foldersbutnot reg : Do not delete folders matching regex.
+                              Example: --delete2foldersbutnot "/Tasks\$|Contacts\$|Foo\$/"
+ --noexpunge         : Do not expunge messages on host1.
+                       Expunge really deletes messages marked deleted.
+                       Expunge is made at the beginning, on host1 only.
+                       Newly transferred messages are also expunged if
+                       option --delete is given.
+                       No expunge is done on host2 account (unless --expunge2)
+ --expunge1          : Expunge messages on host1 after messages transfer.
+ --expunge2          : Expunge messages on host2 after messages transfer.
+ --uidexpunge2       : uidexpunge messages on the host2 account
+                       that are not on the host1 account, requires --delete2
+ --nomixfolders      : Avoid merging folders that are considered different on
+                       host1 but the same on destination host2 because of
+                       case sensitivities and insensitivities.
+
+ --syncinternaldates : Sets the internal dates on host2 same as host1.
+                       Turned on by default. Internal date is the date
+                       a message arrived on a host (mtime).
+ --idatefromheader   : Sets the internal dates on host2 same as the
+                       "Date:" headers.
+
+ --maxsize      int  : Skip messages larger  (or equal) than  int  bytes
+ --minsize      int  : Skip messages smaller (or equal) than  int  bytes
+ --maxage       int  : Skip messages older than  int  days.
+                       final stats (skipped) don't count older messages
+                       see also --minage
+ --minage       int  : Skip messages newer than  int  days.
+                       final stats (skipped) don't count newer messages
+                       You can do (+ are the messages selected):
+                       past|----maxage+++++++++++++++>now
+                       past|+++++++++++++++minage---->now
+                       past|----maxage+++++minage---->now (intersection)
+                       past|++++minage-----maxage++++>now (union)
+
+ --search       str  : Selects only messages returned by this IMAP SEARCH
+                       command. Applied on both sides.
+ --search1      str  : Same as --search for selecting host1 messages only.
+ --search2      str  : Same as --search for selecting host2 messages only.
+                       --search CRIT equals --search1 CRIT --search2 CRIT
+
+ --exitwhenover int  : Stop syncing when total bytes transferred reached.
+                       Gmail per day allows
+                       2500000000 = 2.5 GB downloaded from Gmail as host2
+                        500000000 = 500 MB uploaded to Gmail as host1.
+
+ --maxlinelength int : skip messages with a line length longer than  int  bytes.
+                       RFC 2822 says it must be no more than 1000 bytes.
+
+ --useheader    str  : Use this header to compare messages on both sides.
+                       Ex: Message-ID or Subject or Date.
+ --useheader    str    and this one, etc.
+
+ --subscribed        : Transfers subscribed folders.
+ --subscribe         : Subscribe to the folders transferred on the
+                       host2 that are subscribed on host1. On by default.
+ --subscribeall      : Subscribe to the folders transferred on the
+                       host2 even if they are not subscribed on host1.
+
+ --nofoldersizes     : Do not calculate the size of each folder in bytes
+                       and message counts. Default is to calculate them.
+ --nofoldersizesatend: Do not calculate the size of each folder in bytes
+                       and message counts at the end. Default is on.
+ --justfoldersizes   : Exit after having printed the folder sizes.
+
+ --syncacls          : Synchronises acls (Access Control Lists).
+ --nosyncacls        : Does not synchronize acls. This is the default.
+                       Acls in IMAP are not standardized, be careful.
+
+ --usecache          : Use cache to speedup.
+ --nousecache        : Do not use cache. Caveat: --useuid --nousecache creates
+                       duplicates on multiple runs.
+ --useuid            : Use uid instead of header as a criterium to recognize
+                       messages. Option --usecache is then implied unless
+                       --nousecache is used.
+
+ --debug             : Debug mode.
+ --debugfolders      : Debug mode for the folders part only.
+ --debugcontent      : Debug content of the messages transfered. Huge ouput.
+ --debugflags        : Debug mode for flags.
+ --debugimap1        : IMAP debug mode for host1. Very verbose.
+ --debugimap2        : IMAP debug mode for host2. Very verbose.
+ --debugimap         : IMAP debug mode for host1 and host2.
+ --debugmemory       : Debug mode showing memory consumption after each copy.
+
+ --errorsmax     int : Exit when int number of errors is reached. Default is 50.
+
+ --tests             : Run local non-regression tests. Exit code 0 means all ok.
+ --testslive         : Run a live test with test1.lamiral.info imap server.
+                       Useful to check the basics. Needs internet connexion.
+
+ --version           : Print only software version.
+ --noreleasecheck    : Do not check for new imapsync release (a http request).
+ --releasecheck      : Check for new imapsync release (a http request).
+ --noid              : Do not send/receive ID command to imap servers.
+ --justconnect       : Just connect to both servers and print useful
+                       information. Need only --host1 and --host2 options.
+ --justlogin         : Just login to both host1 and host2 with users
+                       credentials, then exit.
+ --justfolders       : Do only things about folders (ignore messages).
+
+ --help              : print this help.
+
+ Example: to synchronize imap account "test1" on "test1.lamiral.info"
+                     to  imap account "test2" on "test2.lamiral.info"
+                     with test1 password "secret1"
+                     and  test2 password "secret2"
+
+ $0 $escape_char
+    --host1 test1.lamiral.info --user1 test1 --password1 secret1 $escape_char
+    --host2 test2.lamiral.info --user2 test2 --password2 secret2
+
+$localhost_info
+$rcs
+$imapsync_release
+
+$thank
+EOF
+	return( 1 ) ;
+}
+
+
+sub usage_complete {
+	myprint( <<'EOF'  ) ;
+--skipheader   reg     : Don't take into account header keyword
+                         matching  reg    ex: --skipheader 'X.*'
+
+--skipsize             : Don't take message size into account to compare
+                         messages on both sides. On by default.
+			 Use --no-skipsize for using size comparaison.
+--allowsizemismatch    : allow RFC822.SIZE != fetched msg size
+                         consider also --skipsize to avoid duplicate messages
+                         when running syncs more than one time per mailbox
+
+--reconnectretry1  int : reconnect to host1 if connection is lost up to
+                          int  times per imap command (default is 3)
+--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.
+--split2      int      : same thing on host2.
+--nofixInboxINBOX      : Don't fix Inbox INBOX mapping.
+EOF
+	return ;
+}
+
+
+
+sub get_options {
+	# In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET).
+	my $numopt = scalar  @ARGV  || length $ENV{'QUERY_STRING'} ;
+	my $argv   = join "\x00", @ARGV ;
+
+	if ( $argv =~ m/-delete\x002/x ) {
+		myprint( "May be you mean --delete2 instead of --delete 2\n"  ) ;
+		exit 1 ;
+	}
+	$sync->{f1f2} = {} ;
+        my $opt_ret = Imapsync::Getopt::Long::GetOptions(
+        'debug!'        => \$debug,
+        'debuglist!'    => \$debuglist,
+        'debugcontent!' => \$debugcontent,
+        'debugsleep=f'  => \$sync->{debugsleep},
+        'debugflags!'   => \$debugflags,
+        'debugimap!'    => \$debugimap,
+        'debugimap1!'   => \$debugimap1,
+        'debugimap2!'   => \$debugimap2,
+        'debugdev!'     => \$debugdev,
+        'debugmemory!'  => \$sync->{debugmemory},
+        'debugfolders!' => \$sync->{debugfolders},
+        'debugssl=i'    => \$sync->{debugssl},
+	'debugbasket=s' => \@debugbasket,
+	'debugcgi!'     => \$debugcgi,
+        'host1=s'     => \$host1,
+        'host2=s'     => \$host2,
+        'port1=i'     => \$port1,
+        'port2=i'     => \$port2,
+	'inet4'       => \$sync->{inet4},
+	'inet6'       => \$sync->{inet6},
+        'user1=s'     => \$user1,
+        'user2=s'     => \$user2,
+        'domain1=s'   => \$domain1,
+        'domain2=s'   => \$domain2,
+        'password1=s' => \$password1,
+        'password2=s' => \$password2,
+        'passfile1=s' => \$passfile1,
+        'passfile2=s' => \$passfile2,
+        'authmd5!'    => \$authmd5,
+        'authmd51!'   => \$authmd51,
+        'authmd52!'   => \$authmd52,
+        'sep1=s'      => \$sep1,
+        'sep2=s'      => \$sep2,
+        'folder=s'    => \@folder,
+        'folderrec=s' => \@folderrec,
+        'include=s'   => \@include,
+        'exclude=s'   => \@exclude,
+        'folderfirst=s' => \@folderfirst,
+        'folderlast=s' => \@folderlast,
+        'prefix1=s'   => \$prefix1,
+        'prefix2=s'   => \$prefix2,
+	'subfolder2=s' => \$subfolder2,
+        'fixslash2!'   => \$fixslash2,
+        'fixInboxINBOX!' => \$fixInboxINBOX,
+        'regextrans2=s' => \@regextrans2,
+        'mixfolders!' => \$mixfolders,
+        'skipemptyfolders!' => \$skipemptyfolders,
+        'regexmess=s' => \@regexmess,
+        'skipmess=s' => \@skipmess,
+        'pipemess=s' => \@pipemess,
+	'pipemesscheck!' => \$pipemesscheck,
+        'disarmreadreceipts!' => \$disarmreadreceipts,
+        'regexflag=s' => \@regexflag,
+        'filterflags!' => \$filterflags,
+        'flagscase!'  => \$flagscase,
+        'syncflagsaftercopy!' => \$syncflagsaftercopy,
+        'delete|delete1!' => \$delete,
+        'delete2!'    => \$delete2,
+        'delete2duplicates!' => \$delete2duplicates,
+        'delete2folders!'    => \$delete2folders,
+        'delete2foldersonly=s' => \$delete2foldersonly,
+        'delete2foldersbutnot=s' => \$delete2foldersbutnot,
+        'syncinternaldates!' => \$syncinternaldates,
+        'idatefromheader!'   => \$idatefromheader,
+        'syncacls!'   => \$syncacls,
+        'maxsize=i'   => \$maxsize,
+        'minsize=i'   => \$minsize,
+        'maxage=i'    => \$maxage,
+        'minage=i'    => \$minage,
+        'search=s'    => \$search,
+        'search1=s'   => \$search1,
+        'search2=s'   => \$search2,
+        'foldersizes!' => \$foldersizes,
+        'foldersizesatend!' => \$foldersizesatend,
+        'dry!'        => \$dry,
+        'expunge!'    => \$expunge,
+        'expunge1!'    => \$expunge1,
+        'expunge2!'    => \$expunge2,
+        'uidexpunge2!' => \$uidexpunge2,
+        'subscribed!' => \$subscribed,
+        'subscribe!'  => \$subscribe,
+        'subscribeall|subscribe_all!'  => \$subscribeall,
+        'justbanner!' => \$justbanner,
+        'justconnect!'=> \$justconnect,
+        'justfolders!'=> \$justfolders,
+        'justfoldersizes!' => \$justfoldersizes,
+        'fast!'       => \$fast,
+        'version'     => \$version,
+        'help'        => \$help,
+        'timeout=i'   => \$timeout,
+        'timeout1=i'   => \$sync->{h1}->{timeout},
+        'timeout2=i'   => \$sync->{h2}->{timeout},
+        'skipheader=s' => \$skipheader,
+        'useheader=s' => \@useheader,
+        'wholeheaderifneeded!'   => \$wholeheaderifneeded,
+        'messageidnodomain!' => \$messageidnodomain,
+        'skipsize!'   => \$skipsize,
+        'allowsizemismatch!' => \$allowsizemismatch,
+        'fastio1!'     => \$fastio1,
+        'fastio2!'     => \$fastio2,
+        'ssl1!'        => \$ssl1,
+        'ssl2!'        => \$ssl2,
+        'ssl1_ssl_version=s' => \$sync->{h1}->{sslargs}->{SSL_version},
+        'ssl2_ssl_version=s' => \$sync->{h2}->{sslargs}->{SSL_version},
+        'sslargs1=s%'        => \$sync->{h1}->{sslargs},
+        'sslargs2=s%'        => \$sync->{h2}->{sslargs},
+        'tls1!'        => \$tls1,
+        'tls2!'        => \$tls2,
+        'uid1!'        => \$uid1,
+        'uid2!'        => \$uid2,
+        'authmech1=s' => \$authmech1,
+        'authmech2=s' => \$authmech2,
+        'authuser1=s' => \$authuser1,
+        'authuser2=s' => \$authuser2,
+        'proxyauth1'  => \$proxyauth1,
+        'proxyauth2'  => \$proxyauth2,
+        'split1=i'    => \$split1,
+        'split2=i'    => \$split2,
+        'buffersize=i' => \$buffersize,
+        'reconnectretry1=i' => \$reconnectretry1,
+        'reconnectretry2=i' => \$reconnectretry2,
+        'tests!'       => \$tests,
+        'testsdebug|tests_debug!' => \$testsdebug,
+        'testslive!'   => \$testslive,
+        'justlogin!'  => \$justlogin,
+        'tmpdir=s'    => \$tmpdir,
+        'pidfile=s'    => \$sync->{pidfile},
+        'pidfilelocking!' => \$sync->{pidfilelocking},
+        'releasecheck!' => \$releasecheck,
+        'modulesversion|modules_version!' => \$modulesversion,
+        'usecache!'    => \$usecache,
+        'cacheaftercopy!' => \$cacheaftercopy,
+        'debugcache!' => \$debugcache,
+        'useuid!'     => \$useuid,
+        'addheader!'  => \$addheader,
+        'exitwhenover=i' => \$exitwhenover,
+        'checkselectable!' => \$checkselectable,
+        'checkmessageexists!' => \$checkmessageexists,
+        'expungeaftereach!' => \$expungeaftereach,
+        'abletosearch!' => \$abletosearch,
+        'showpasswords!' => \$showpasswords,
+        'maxlinelength=i' => \$maxlinelength,
+        'maxlinelengthcmd=s' => \$maxlinelengthcmd,
+        'minmaxlinelength=i' => \$minmaxlinelength,
+        'debugmaxlinelength!' => \$debugmaxlinelength,
+        'fixcolonbug!'           => \$fixcolonbug,
+        'create_folder_old!'     => \$create_folder_old,
+        'maxmessagespersecond=f' => \$maxmessagespersecond,
+        'maxbytespersecond=i'    => \$maxbytespersecond,
+        'skipcrossduplicates!'   => \$skipcrossduplicates,
+        'debugcrossduplicates!'  => \$debugcrossduplicates,
+        'log!'                   => \$sync->{log},
+        'logfile=s'        => \$sync->{logfile},
+        'logdir=s'         => \$sync->{logdir},
+        'errorsmax=i'      => \$sync->{errorsmax},
+        'errorsdump!'      => \$sync->{errorsdump},
+        'fetch_hash_set=s' => \$fetch_hash_set,
+        'automap!'         => \$sync->{automap},
+        'justautomap!'     => \$sync->{justautomap},
+        'id!'              => \$sync->{id},
+        'f1f2=s%'          => \$sync->{f1f2},
+        'justfolderlists!' => \$sync->{justfolderlists},
+        'delete1emptyfolders' => \$sync->{delete1emptyfolders},
+        ) ;
+
+
+	$debugcgi and myprint( map { "$_ => $ENV{$_}\n" } sort keys  %ENV   ) ;
+	$debugcgi and myprint( "@debugbasket\n"  ) ;
+        $debug and myprint( "get options: [$opt_ret]\n"  ) ;
+
+        # just the version
+        myprint( imapsync_version(  ), "\n" ) and exit 0 if ( $version ) ;
+        # $tmpdir is used in tests_pipemess()
+	$tmpdir ||= File::Spec->tmpdir(  ) ;
+	if ( $tests or $testsdebug ) {
+		$test_builder = Test::More->builder ;
+		if ( $tests ) { tests(  ) ; }
+		if ( $testsdebug ) { testsdebug(  ) ; }
+		#$test_builder->reset(  ) ;
+		exit ;
+	}
+
+	#$help = 1 if ! $numopt;
+	load_modules(  );
+
+	# exit with --help option or no option at all
+	$debug and myprint( "numopt:$numopt\n"  ) ;
+        usage(  ) and exit  if ( $help or not $numopt ) ;
+
+	# don't go on if options are not all known.
+        exit $EX_USAGE unless ( $opt_ret ) ;
+
+	# init live varaiables
+	testslive(  ) if ( $testslive ) ;
+
+	return ;
+}
+
+sub testslive {
+	$host1 = 'test1.lamiral.info' ;
+	$user1 = 'test1' ;
+	$password1 = 'secret1' ;
+	$host2 = 'test2.lamiral.info' ;
+	$user2 = 'test2' ;
+	$password2 ='secret2' ;
+	return ;
+}
+
+sub testsdebug {
+      SKIP: {
+                skip 'No test in normal run' if ( not $testsdebug ) ;
+                #tests_bytes_display_string(  ) ;
+                #tests_ucsecond(  ) ;
+                #tests_mkpath(  ) ;
+                #eval { tests_mkpath(  ) ; } or ok( 0 == 1,  'tests_mkpath fail badly?' ) ;
+                #tests_format_for_imap_arg(  ) ;
+                #tests_is_a_release_number(  ) ;
+                #tests_delete1emptyfolders(  ) ;
+                #tests_memory_consumption(  ) ;
+                #tests_imap2_folder_name() ;
+                #tests_length_ref(  ) ;
+		#tests_is_valid_directory(  ) ;
+                #tests_firstline(  ) ;
+                #tests_diff_or_NA(  ) ;
+                #tests_match_number(  ) ;
+                #tests_all_defined(  ) ;
+                #tests_guess_separator(  ) ;
+                tests_pipemess(  ) ;
+                #tests_message_for_host2(  ) ;
+                done_testing(  ) ;
+                note('End of imapsync --tests_debug') ;
+        }
+        return ;
+}
+
+sub tests {
+
+      SKIP: {
+                skip 'No test in normal run' if ( not $tests ) ;
+                tests_folder_routines(  ) ;
+                tests_compare_lists(  ) ;
+                tests_regexmess();
+                tests_skipmess(  ) ;
+                tests_flags_regex();
+                tests_ucsecond(  ) ;
+                tests_permanentflags();
+                tests_flags_filter(  ) ;
+                tests_separator_invert(  ) ;
+                tests_imap2_folder_name() ;
+                tests_command_line_nopassword();
+                tests_good_date(  ) ;
+                tests_max();
+                tests_remove_not_num();
+                tests_memory_consumption( ) ;
+                tests_is_a_release_number();
+                tests_imapsync_basename();
+                tests_list_keys_in_2_not_in_1();
+                tests_convert_sep_to_slash(  ) ;
+                tests_match_a_cache_file(  ) ;
+                tests_cache_map(  ) ;
+                tests_get_cache(  ) ;
+                tests_clean_cache(  ) ;
+                tests_clean_cache_2(  ) ;
+                tests_touch(  ) ;
+                tests_flagscase(  ) ;
+                eval { tests_mkpath(  ) ; } or ok( 0 == 1,  'tests_mkpath fail badly?' ) ;
+                tests_extract_header(  ) ;
+                tests_decompose_header(  ) ;
+                tests_epoch(  ) ;
+                tests_add_header(  ) ;
+                tests_cache_dir_fix(  ) ;
+                tests_cache_dir_fix_win(  ) ;
+                tests_filter_forbidden_characters(  ) ;
+                tests_cache_folder(  ) ;
+                tests_time_remaining(  ) ;
+                tests_decompose_regex(  ) ;
+                tests_Banner(  ) ;
+                tests_backtick(  ) ;
+                tests_bytes_display_string(  ) ;
+                tests_header_line_normalize(  ) ;
+                tests_fix_Inbox_INBOX_mapping(  ) ;
+                tests_max_line_length(  ) ;
+                tests_subject(  ) ;
+                tests_msgs_from_maxmin(  ) ;
+                tests_tmpdir_has_colon_bug(  ) ;
+                tests_sleep_max_messages(  ) ;
+                tests_sleep_max_bytes(  ) ;
+                tests_logfile(  ) ;
+                tests_setlogfile(  ) ;
+                tests_jux_utf8(  ) ;
+                tests_pipemess(  ) ;
+                tests_jux_utf8_list(  ) ;
+                tests_guess_prefix(  ) ;
+                tests_guess_separator(  ) ;
+                tests_format_for_imap_arg(  ) ;
+                tests_imapsync_id(  ) ;
+                tests_date_from_rcs(  ) ;
+                tests_quota_extract_storage_limit_in_bytes(  ) ;
+                tests_quota_extract_storage_current_in_bytes(  ) ;
+                tests_guess_special(  ) ;
+		tests_is_valid_directory(  ) ;
+                tests_delete1emptyfolders(  ) ;
+                tests_message_for_host2(  ) ;
+                tests_length_ref(  ) ;
+                tests_firstline(  ) ;               
+                tests_diff_or_NA(  ) ;
+                #tests_always_fail(  ) ;
+                tests_match_number(  ) ;
+                tests_all_defined(  ) ;
+                done_testing( 693 ) ;
+                note('End of imapsync --tests') ;
+        }
+        return ;
+}
+
+
+
+# IMAPClient 3.xx ads
+
+package Mail::IMAPClient;
+
+sub Tls {
+	my $self  = shift ;
+	my $value = shift ;
+	if ( defined  $value  ) { $self->{TLS} = $value }
+	return $self->{TLS};
+}
+
+sub Reconnect_counter {
+	my $self  = shift ;
+        my $value = shift ;
+	$self->{Reconnect_counter} = 0 if ( not defined  $self->{Reconnect_counter}  ) ;
+	if ( defined  $value  ) { $self->{Reconnect_counter} = $value }
+	return( $self->{Reconnect_counter} ) ;
+}
+
+
+sub Banner {
+	my $self  = shift ;
+	my $value = shift ;
+	if ( defined $value ) { $self->{ BANNER } = $value }
+	return $self->{ BANNER };
+}
+
+sub capability_update {
+	my $self = shift ;
+
+	delete $self->{CAPABILITY} ;
+	return( $self->capability ) ;
+}
+
+
+package Imapsync::Getopt::Long ;
+# Started as a copy of Luke Ross Getopt::Long::CGI
+# https://metacpan.org/release/Getopt-Long-CGI
+# So this section is under the same license as Getopt-Long-CGI Luke Ross wants it,
+# which was Perl 5.6 or later licenses at the date of the copy.
+
+use strict ;
+use warnings ;
+
+use Getopt::Long(  ) ;
+
+
+sub GetOptions {
+    my %options = @_ ;
+
+    if ( not $ENV{SERVER_SOFTWARE} ) {
+        # Not CGI - pass upstream for normal command line handling
+        return Getopt::Long::GetOptions( %options ) ;
+    }
+    my $b_ref = $options{'debugbasket=s'} ;
+    require CGI ;
+    require CGI::Carp ;
+    CGI::Carp->import( 'fatalsToBrowser' ) ;
+
+    my $cgi = CGI->new(  ) ;
+    $cgi->param( 'debugcgi' ) and myprint( "

Current Values

\n" . $cgi->Dump ) ; + + foreach my $key (sort keys %options) { + my $val = $options{$key}; + #push( @{$b_ref}, "opt:[$key] val:[$val]" . ( ('SCALAR' eq ref($val) and defined $$val ) ? " [$$val]" : q{} ) . "\n" ) ; + if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/ ) { + push @{$b_ref}, "Unknown opt: [$key]\n" ; + next ; # Unknown item + } + + my $name = [split '|', $1, 1 ]->[0]; + + if (($3 || q{}) eq '+') { + ${ $val } = $cgi->param($name); # "Incremental" integer + } elsif ($2) { + my @values = $cgi->param($name); + my $type = $2; + if (($3 || q{}) eq '%' or ref($val) eq 'HASH') { + my %values = map { split /=/, $_, 1 } @values; + if ($type =~ m/i$/) { + foreach my $k (keys %values) { + $values{$k} = int $values{$k} ; + } + } elsif ($type =~ m/f$/) { + foreach my $k (keys %values) { + $values{$k} = 0 + $values{$k} + } + } + if ( ref($val) eq 'CODE') { + while(my($k, $v) = each %values) { + $val->($name, $k, $v); + } + } elsif ( 'REF' eq ref $val ) { + #push( @{$b_ref}, "refref($$val): " . ref($$val) . " %values= ", %values, "\n\n" ) ; + %{ ${ $val } } = %values; + } else { + #push( @{$b_ref}, "ref($val): " . ref($val) . " %values= ", %values, "\n\n" ) ; + %{ $val } = %values; + } + } else { + if ($type =~ m/i$/) { + @values = map { int $_ } @values; + } elsif ($type =~ m/f$/) { + @values = map { 0 + $_ } @values; + } + if (($3 || q{}) eq '@' or ref($val) eq 'ARRAY') { + if (ref($val) eq 'CODE') { + $val->($name, \@values) + } else { + @{ $val } = @values ; + } + } else { + if (ref($val) eq 'CODE') { + $val->($name, $values[0]); + } else { + ${ $val } = $values[0]; + } + } + } + } else { + # Checkbox + ${ $val } = $cgi->param($name) ? 1 : undef ; + #push( @{$b_ref}, "param($name) ref($val): " . ref($val) . " val=[$$val]\n\n" ) ; + } + } + return( 1 ) ; +} + + diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl new file mode 100755 index 00000000..5c47eb47 --- /dev/null +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +use DBI; +use File::Temp qw/ mkstemp /; +use LockFile::Simple qw(lock trylock unlock); +use Data::Dumper qw(Dumper); +use IPC::Run 'run'; +use String::Util 'trim'; + +$DBNAME = ''; +$DBUSER = ''; +$DBPASS = ''; + +$run_dir="/tmp"; +$dsn = "DBI:mysql:database=" . $DBNAME . ";host=mysql"; +$lock_file = $run_dir . "/imapsync_busy"; +$lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); +$lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; +$dbh = DBI->connect($dsn, $DBUSER, $DBPASS); +open my $file, '<', "/etc/sogo/sieve.creds"; +my $creds = <$file>; +close $file; +my ($master_user, $master_pass) = split /:/, $creds; +my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL)"); +$sth->execute(); +my $row; + +while ($row = $sth->fetchrow_arrayref()) { + + $id = @$row[0]; + $user1 = @$row[1]; + $user2 = @$row[2]; + $host1 = @$row[3]; + $authmech1 = @$row[4]; + $password1 = @$row[5]; + $exclude = @$row[6]; + $port1 = @$row[7]; + $enc1 = @$row[8]; + $delete2duplicates = @$row[9]; + $maxage = @$row[10]; + $subfolder2 = @$row[11]; + + if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } + + run [ "/usr/local/bin/imapsync", + "--timeout1", "10", + "--tmpdir", "/tmp", + "--subscribeall", + ($exclude eq "" ? () : ("--exclude", $exclude)), + ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), + ($maxage eq "0" ? () : ('--maxage', $maxage)), + ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), + (!defined($enc1) ? () : ($enc1)), + "--host1", $host1, + "--user1", $user1, + "--password1", $password1, + "--port1", $port1, + "--host2", "localhost", + "--user2", $user2 . '*' . trim($master_user), + "--password2", trim($master_pass), + '--no-modulesversion'], ">", \my $stdout; + + $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW() WHERE id = ?"); + $update->bind_param( 1, ${stdout} ); + $update->bind_param( 2, ${id} ); + $update->execute(); +} + +$sth->finish(); +$dbh->disconnect(); + +$lockmgr->unlock($lock_file); diff --git a/data/Dockerfiles/dovecot/postlogin.sh b/data/Dockerfiles/dovecot/postlogin.sh new file mode 100755 index 00000000..343910ff --- /dev/null +++ b/data/Dockerfiles/dovecot/postlogin.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +export MASTER_USER=$USER +exec "$@" diff --git a/data/Dockerfiles/dovecot/rspamd-pipe b/data/Dockerfiles/dovecot/rspamd-pipe new file mode 100755 index 00000000..f9236e17 --- /dev/null +++ b/data/Dockerfiles/dovecot/rspamd-pipe @@ -0,0 +1,8 @@ +#!/bin/bash +if [[ ${2} == "learn_spam" ]]; then +/usr/bin/curl --data-binary @- http://rspamd:11334/learnspam < /dev/stdin +elif [[ ${2} == "learn_ham" ]]; then +/usr/bin/curl --data-binary @- http://rspamd:11334/learnham < /dev/stdin +fi +# Always return 0 to satisfy Dovecot... +exit 0 diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf new file mode 100644 index 00000000..45f9ddd5 --- /dev/null +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -0,0 +1,21 @@ +[supervisord] +nodaemon=true + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +redirect_stderr=true +autostart=true +stdout_syslog=true + +[program:dovecot] +command=/usr/sbin/dovecot -F +autorestart=true + +[program:logfiles] +command=/usr/bin/tail -f /var/log/mail.log /var/log/syslog +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 + +[program:cron] +command=/usr/sbin/cron -f +autorestart=true diff --git a/.nojekyll b/data/Dockerfiles/memcached/.empty similarity index 100% rename from .nojekyll rename to data/Dockerfiles/memcached/.empty diff --git a/data/Dockerfiles/mysql/.empty b/data/Dockerfiles/mysql/.empty new file mode 100644 index 00000000..e69de29b diff --git a/data/Dockerfiles/nginx/.empty b/data/Dockerfiles/nginx/.empty new file mode 100644 index 00000000..e69de29b diff --git a/data/Dockerfiles/pdns/Dockerfile b/data/Dockerfiles/pdns/Dockerfile new file mode 100644 index 00000000..b56dcf9c --- /dev/null +++ b/data/Dockerfiles/pdns/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN echo 'deb http://repo.powerdns.com/ubuntu xenial-rec-40 main' > /etc/apt/sources.list.d/pdns.list + +RUN echo 'Package: pdns-*\n\ +Pin: origin repo.powerdns.com\n\ +Pin-Priority: 600\n' > /etc/apt/preferences.d/pdns + +RUN apt-key adv --fetch-keys http://repo.powerdns.com/FD380FBB-pub.asc \ + && apt-get update \ + && apt-get install -y --force-yes pdns-recursor + +CMD ["/usr/sbin/pdns_recursor"] + +EXPOSE 53/udp + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/php-fpm/Dockerfile b/data/Dockerfiles/php-fpm/Dockerfile new file mode 100644 index 00000000..ad4b105d --- /dev/null +++ b/data/Dockerfiles/php-fpm/Dockerfile @@ -0,0 +1,18 @@ +FROM php:7.1-fpm +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update \ + && apt-get install -y zlib1g-dev libicu-dev g++ libidn11-dev libxml2-dev + +RUN docker-php-ext-configure intl +RUN docker-php-ext-install intl pdo pdo_mysql xmlrpc +RUN pear install channel://pear.php.net/Net_IDNA2-0.1.1 Auth_SASL Net_IMAP NET_SMTP Net_IDNA2 Mail_mime + +COPY ./docker-entrypoint.sh / + +EXPOSE 9000 + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["php-fpm"] diff --git a/data/Dockerfiles/php-fpm/docker-entrypoint.sh b/data/Dockerfiles/php-fpm/docker-entrypoint.sh new file mode 100755 index 00000000..8f57a6d9 --- /dev/null +++ b/data/Dockerfiles/php-fpm/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +if [[ ! -d "/data/dkim/txt" || ! -d "/data/dkim/keys" ]] ; then mkdir -p /data/dkim/{txt,keys} ; chown -R www-data:www-data /data/dkim; fi +if [[ $(stat -c %U /data/dkim/) != "www-data" ]] ; then chown -R www-data:www-data /data/dkim ; fi + +exec "$@" diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile new file mode 100644 index 00000000..a3781bd4 --- /dev/null +++ b/data/Dockerfiles/postfix/Dockerfile @@ -0,0 +1,33 @@ +From ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN apt-get update +RUN apt-get install -y --no-install-recommends supervisor \ + postfix \ + sasl2-bin \ + libsasl2-modules \ + postfix \ + postfix-mysql \ + postfix-pcre \ + syslog-ng \ + syslog-ng-core \ + ca-certificates + +RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf + +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY postfix.sh /opt/postfix.sh + +EXPOSE 588 + +CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh new file mode 100755 index 00000000..3dd108d0 --- /dev/null +++ b/data/Dockerfiles/postfix/postfix.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +trap "postfix stop" EXIT + +sed -i "/^user/c\user = ${DBUSER}" /opt/postfix/conf/sql/* +sed -i "/^password/c\password = ${DBPASS}" /opt/postfix/conf/sql/* +sed -i "/^dbname/c\dbname = ${DBNAME}" /opt/postfix/conf/sql/* + +postconf -c /opt/postfix/conf +if [[ $? != 0 ]]; then + echo "Postfix configuration error, refusing to start." + exit 1 +else + postfix -c /opt/postfix/conf start + sleep infinity +fi diff --git a/data/Dockerfiles/postfix/supervisord.conf b/data/Dockerfiles/postfix/supervisord.conf new file mode 100644 index 00000000..4268899d --- /dev/null +++ b/data/Dockerfiles/postfix/supervisord.conf @@ -0,0 +1,17 @@ +[supervisord] +nodaemon=true + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +redirect_stderr=true +autostart=true +stdout_syslog=true + +[program:postfix] +command=/opt/postfix.sh +autorestart=true + +[program:postfix-maillog] +command=/usr/bin/tail -f /var/log/mail.log +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 diff --git a/data/Dockerfiles/redis/.empty b/data/Dockerfiles/redis/.empty new file mode 100644 index 00000000..e69de29b diff --git a/data/Dockerfiles/rmilter/Dockerfile b/data/Dockerfiles/rmilter/Dockerfile new file mode 100644 index 00000000..364c78c1 --- /dev/null +++ b/data/Dockerfiles/rmilter/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \ + && echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ + && apt-get update \ + && apt-get --no-install-recommends -y --force-yes install rmilter cron syslog-ng syslog-ng-core supervisor + +COPY supervisord.conf /etc/supervisor/supervisord.conf + +EXPOSE 9000 + +RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf +RUN touch /var/log/mail.log && chmod 640 /var/log/mail.log && chown root:adm /var/log/mail.log + +CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/rmilter/supervisord.conf b/data/Dockerfiles/rmilter/supervisord.conf new file mode 100644 index 00000000..41e1328f --- /dev/null +++ b/data/Dockerfiles/rmilter/supervisord.conf @@ -0,0 +1,18 @@ +[supervisord] +nodaemon=true + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +redirect_stderr=true +autostart=true +stdout_syslog=true + +[program:rmilter] +command=/usr/sbin/rmilter -n -c /etc/rmilter.conf.d/rmilter.conf +user=_rmilter +autorestart=true + +[program:rmilter-syslog] +command=/usr/bin/tail -f /var/log/mail.log +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile new file mode 100644 index 00000000..d4d4508d --- /dev/null +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \ + && echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \ + && apt-get update \ + && apt-get -y install rspamd ca-certificates + +RUN echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local +# "Hardcoded" - we need them +RUN echo 'settings = "http://nginx:8081/settings.php";' > /etc/rspamd/modules.d/settings.conf + +CMD ["/usr/bin/rspamd","-f", "-u", "_rspamd", "-g", "_rspamd"] + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +USER _rspamd + +EXPOSE 11333 11334 diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile new file mode 100644 index 00000000..43960438 --- /dev/null +++ b/data/Dockerfiles/sogo/Dockerfile @@ -0,0 +1,51 @@ +FROM ubuntu:xenial +MAINTAINER Andre Peters + +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL C +ENV GOSU_VERSION 1.9 + +RUN dpkg-divert --local --rename --add /sbin/initctl \ + && ln -sf /bin/true /sbin/initctl \ + && dpkg-divert --local --rename --add /usr/bin/ischroot \ + && ln -sf /bin/true /usr/bin/ischroot + +RUN apt-get update \ + && apt-get install -y --no-install-recommends apt-transport-https \ + ca-certificates \ + wget \ + syslog-ng \ + syslog-ng-core \ + supervisor \ + mysql-client \ + cron \ + && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ + && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc" \ + && export GNUPGHOME="$(mktemp -d)" \ + && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \ + && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \ + && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true + +RUN apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \ + && echo "deb http://packages.inverse.ca/SOGo/nightly/3/ubuntu/ xenial xenial" > /etc/apt/sources.list.d/sogo.list \ + && apt-get update \ + && apt-get -y --force-yes install sogo sogo-activesync + +RUN sed -i -E 's/^(\s*)system\(\);/\1unix-stream("\/dev\/log");/' /etc/syslog-ng/syslog-ng.conf +RUN echo '* * * * * sogo /usr/sbin/sogo-ealarms-notify' > /etc/cron.d/sogo +RUN echo '* * * * * sogo /usr/sbin/sogo-tool expire-sessions 60' >> /etc/cron.d/sogo +RUN echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo + +COPY ./reconf-domains.sh / +COPY supervisord.conf /etc/supervisor/supervisord.conf + +#EXPOSE 20000 +#EXPOSE 9191 +#EXPOSE 9192 + +CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf + +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/data/Dockerfiles/sogo/reconf-domains.sh b/data/Dockerfiles/sogo/reconf-domains.sh new file mode 100755 index 00000000..7ec4e173 --- /dev/null +++ b/data/Dockerfiles/sogo/reconf-domains.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# Wait for MySQL to warm-up +while mysqladmin ping --host mysql --silent; do + +# Recreate view + +mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" + +mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF +CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS +SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox +LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username +LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username +WHERE mailbox.active = '1'; +EOF + + +mkdir -p /var/lib/sogo/GNUstep/Defaults/ + +# Generate plist header with timezone data +cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist + + + + + OCSAclURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_acl + OCSCacheFolderURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_cache_folder + OCSEMailAlarmsFolderURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_alarms_folder + DomainFieldName + domain + OCSFolderInfoURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_folder_info + OCSSessionsFolderURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_sessions_folder + OCSStoreURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_store + SOGoProfileURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_user_profile + SOGoTimeZone + ${TZ} + domains + +EOF + +# Generate multi-domain setup +while read line + do + echo " ${line} + + SOGoMailDomain + ${line} + SOGoUserSources + + + MailFieldNames + + aliases + ad_aliases + + KindFieldName + kind + MultipleBookingsFieldName + multiple_bookings + canAuthenticate + YES + displayName + GAL + id + ${line} + isAddressBook + YES + type + sql + userPasswordAlgorithm + ssha256 + viewURL + mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_view + + + " >> /var/lib/sogo/GNUstep/Defaults/sogod.plist +done < <(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain;" -B -N) + +# Generate footer +echo ' + +' >> /var/lib/sogo/GNUstep/Defaults/sogod.plist + +# Fix permissions +chown sogo:sogo -R /var/lib/sogo/ +chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist + +sleep 99999 + +done diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf new file mode 100644 index 00000000..5f2d4d28 --- /dev/null +++ b/data/Dockerfiles/sogo/supervisord.conf @@ -0,0 +1,46 @@ +[supervisord] +nodaemon=true + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +redirect_stderr=true +autostart=true +stdout_syslog=true + +[group:sogo-group] +programs=reconf-domains,sogo + +[program:sogo] +command=/usr/sbin/sogod +user=sogo +autorestart=true +priority=20 + +[program:reconf-domains] +command=/reconf-domains.sh +autorestart=true +priority=10 + +[program:sogo-syslog] +command=/usr/bin/tail -f /var/log/syslog -f /var/log/sogo/sogo.log +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 + +[program:cron] +command=/usr/sbin/cron -f +autorestart=true + +[program:sogo-webres] +command=/usr/bin/python -u -m SimpleHTTPServer 9192 +directory=/usr/lib/GNUstep/SOGo/ +user=sogo +autorestart=true + +[inet_http_server] +port=9191 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=http://localhost:9191 diff --git a/data/assets/passwd/generate_passwords.sh b/data/assets/passwd/generate_passwords.sh new file mode 100755 index 00000000..78613150 --- /dev/null +++ b/data/assets/passwd/generate_passwords.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo DBPASS=$(openssl rand -base64 32 | tr -dc _A-Z-a-z-0-9) +echo DBROOT=$(openssl rand -base64 32 | tr -dc _A-Z-a-z-0-9) diff --git a/data/assets/ssl/ca-key.pem b/data/assets/ssl/ca-key.pem new file mode 100644 index 00000000..ce767c9c --- /dev/null +++ b/data/assets/ssl/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAr+geb+b9p5PgNMDUwtK3NxtmUJrHvnCSxkkLEBPFp/vVvEQV +eJQN3WNQVXjkqaAXQzWSkTz/URiKAKMSL/nx1+Es6HRXdR79GMc6kY1GBFMfhSwt +BzblNZ++WpD4JB3bL5prMrf2HiTqe617dlENoqK0kBEDGZq3En07aV+MIRuT8HSW +B4j1/St2DAKJYGCGVqPDgpFt7+aR8aXGkvoCMKpgiB32tNkmdDfzOdWmAdFS1KaV +RK+ap9WmwNdMH0BzCud9vhuJjdUZlhfmWuuT5XaGU+fftIlI191XYOEZHe44TLy1 +xWwXUhc+BmhhSj3k75lNvtAxydVrZIyoVxpLHwIDAQABAoIBAARMxkGyAc1Q3hAs +Dodco0Hjl5Ks1ekf01apfm28Lf63NzhM6cFyzQv2W4ZbWCuVUDxCWPzX4t3Wnbj6 +Q32MvI6sYG6mOWURhtpONG5OZ8G/Tmvw8oDUpLG03/BSzt4DJNJ7EdfBi3CdMmYn +jXcM8Cpjk8pZwBumHoeDLCqdPU2p1onq+iwMdQDKfq1ESBwEuXIO82x9y/O6zxNZ +HlWFDS5hLEt0UVWIV5aGK0/M/kzOCKwy7MMbJhHPYKy7Yvno2t+fJ6jiwswyEsCz +M2Z3DUWzgrT4DdWSV2Bns0X/xID5kH7EXm7GqsudbHHJNnr7LWuowFSOzAX47t+N +JgrRIaECgYEA1iFfpgO/awiNQLV1VpgA3pqzmFddEh74sFqILkSGd2fxH6BbgXJB +z+7Cu6jij8Odvf48suKsG9K0OwAehnR/ZgkrQmnKftf/pEZ+iLdsS/T8IZAXjQ4g +cLlYOOMuqIi8Ev1nT0/++IaSKro0CSDuuNtkqRCQ/ZMJtWzFxw3yZRcCgYEA0k1m +vaxvkSXqrYvFjDkPM5gUXcE4wx9t6Y4iwmRISLe6bJcfVfaT9oCz4o6p4dxZsoXw +SFSazFedqwQlzXMrrBd2rHYgg13N9LU9TggZXtSMOdye+dxk24P7GmJdWH5+S5vm +TqATs105AD95p7X8+H1TpW4liwW+eNjFqqO8HzkCgYBIuVj01z0BqwveOEK7wDA7 +aisoyDMR3nbz3it2G0vX7fNUnG+3jQiRCDQW7ArWbMd8KYaP8rAlWvBfQXEclSBX +lTGeArQFVHK8Zjy/Thx3x6KB+6AkBfI6lphB6daE4ruNb4bQxwh/e6TU4hyeJRMu +sUSErt2vYWrgtSqOqkvyzQKBgBKExFZRd+WVLCwqEbQ+VgtaCfkTibcM7nXRkVgC +0qasnxru19CPDQp43N5HZ8g+yhtBVh5YbOUvle+4Rsfnq2HVExsur8BBo2A4EXTs +m6dRGiQCPHGOKcd2wMbbAJNJWD+6M7aavAFgZSOTc1gEW4laJ+J6Z43tbI9hr05O +asNpAoGAFje1QFMYw8s8ZTYVrfwafgDB/BNnQRVnQwTZRXcabAT5O0Wc8hTDPOun +aBNJ3oKSwBvt7Snsn1G/oeH0aZ6cI8kkYE+dyQ5YqSZZUbJF6g771iNv4lMvxRBN +BT/ihKzXb/t47z6W8dL5N1BSZ0KrwIrEkqCeHBlAW+G/FArrbZU= +-----END RSA PRIVATE KEY----- diff --git a/data/assets/ssl/ca.pem b/data/assets/ssl/ca.pem new file mode 100644 index 00000000..505f5698 --- /dev/null +++ b/data/assets/ssl/ca.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIICzjCCAbigAwIBAgIRALtgn1eSIUIhoGv9nq94NYEwCwYJKoZIhvcNAQELMBIx +EDAOBgNVBAoTB21haWxjb3cwHhcNMTYxMjEzMTAxMTAwWhcNMTkxMTI4MTAxMTAw +WjASMRAwDgYDVQQKEwdtYWlsY293MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAr+geb+b9p5PgNMDUwtK3NxtmUJrHvnCSxkkLEBPFp/vVvEQVeJQN3WNQ +VXjkqaAXQzWSkTz/URiKAKMSL/nx1+Es6HRXdR79GMc6kY1GBFMfhSwtBzblNZ++ +WpD4JB3bL5prMrf2HiTqe617dlENoqK0kBEDGZq3En07aV+MIRuT8HSWB4j1/St2 +DAKJYGCGVqPDgpFt7+aR8aXGkvoCMKpgiB32tNkmdDfzOdWmAdFS1KaVRK+ap9Wm +wNdMH0BzCud9vhuJjdUZlhfmWuuT5XaGU+fftIlI191XYOEZHe44TLy1xWwXUhc+ +BmhhSj3k75lNvtAxydVrZIyoVxpLHwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAKww +DwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQsDggEBAF36qnigBDvvqRI0xksc +NkGFMB3JLKznOZ0DrWUx121/GHFaNNNeI7ECmyk2eRYKCEZNnxWa1/LJ7GWn7lRU +JD3OeWbhBUgA+HXoKl/jzXokuXMjYi/eFrgOofk2AqNDA5ioduS6A4vL8UDQc+74 +WSS8za4zoVR4GtxqDG+msRzTNVWXRcaaaWSrWMfZtQcEKIeQGDkcccvZ+mzlFUsH +G1xxKuOPGjAwrxda4x+FY/dYdPbRV8ua0RQmYUMnROv507QnGZ9FdzdrvisZ67xx +5BfxbApAyxDD/p7B4Zh1daga2LYGRwMJuwYjXlw9uNxJVQwcxg4nppWF1KZ4Fz0c +EjY= +-----END CERTIFICATE----- diff --git a/data/assets/ssl/cert.pem b/data/assets/ssl/cert.pem new file mode 100644 index 00000000..96d16bec --- /dev/null +++ b/data/assets/ssl/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAe6gAwIBAgIQeJMoL/3dxhxhT9EwuRTL/DALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHbWFpbGNvdzAeFw0xNjEyMTMxMDExMDBaFw0xOTExMjgxMDExMDBa +MC0xEDAOBgNVBAoTB21haWxjb3cxGTAXBgNVBAMTEG1haWwuZXhhbXBsZS5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRg0xT3At9DSb3H5OMp3K1 +MpXAgYyotSK6TS61fC0QEHy2fMXiws7Agcye6Ln7CG63Fe1eN2jkdlefy9xJivS8 +y5w0M8i168v5znzC8fnylL2iOiSYfK/B/oEqfU7YH4RcegO53oDDIUZmi4Frgnu7 +39VVOU1ZyHEVqGJ2H2aAIkoZRjGzumD9Ym4LWGidtKJzBgFt/qmhUeWXipM8w281 +XkQnJU79+x2ywnJSvEZ3r/ZVJC7kbjiVw+/k15k9Cxk6Ik8wmJ0X/+xWxoZomHQI +1LM0VKAS/iaU95dn2bplvL6jTiiyWAbrMjSKs4XbPt/fIbOicNkj6+CFy0MVfyyH +AgMBAAGjPzA9MA4GA1UdDwEB/wQEAwIAqDAdBgNVHSUEFjAUBggrBgEFBQcDAgYI +KwYBBQUHAwEwDAYDVR0TAQH/BAIwADALBgkqhkiG9w0BAQsDggEBAI/jBJa1P8nB +eHUN5muQmjBVDVOYyWAAEapOe2HYsBcpjaB2H8Iw3DQzJtz6peYeYSCmHRVqFLCm +VPrq36l9mPUotyPDPlQQAxCj9R2+WbGaJO+N/E1F8FQ94dr3jqwUyfjVPoqEjmIH +NFkvbA0RJOeBm9oYGdhM0wjOBV9c9MTHFG82nQ/zQeTuPb7GXuKIOXYCxoLNOZMw +UJ02Cqjv5ImrgOhcstAKX3Ip0urSvZUGvtPla4CGh+M6yDFJ08GzX6OiMIH207RW +jAbUXXERSUv/7hysdDjGo5HZjCeMzVu9KAxoZXqnmvkk8g2swKWtWBRcoeU1VGx0 +Bx4Q4KMjuYQ= +-----END CERTIFICATE----- diff --git a/data/assets/ssl/dhparams.pem b/data/assets/ssl/dhparams.pem new file mode 100644 index 00000000..b245f051 --- /dev/null +++ b/data/assets/ssl/dhparams.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA9iHB0CRDhV8wfBgqnmvuJpl0fzL3qL75R4ZvQHlfMNLrxuIz2x9D +9zcDhPcBTVzV5Ay0AAkke4wP6r6wDQqXqBP4Y8IOkYAyLh3jM40jfHQzQt+5JdQl +ond3kiscBsFOch/vMfSLMu3lAb0YhPNTvrxhMz7LcVAWYl82swASupdiKR+MgaQr +XsugpmDKsHW60VmIM9B7K9Y+rNHwvMWkmISd0KxA8oOy1WJvsVEissMALZDE3c4w +2xHmO2lXxgEx3aez28736t4m/KW3g9Zr31a1M0KusmfY//fGkPk4NUrLBOS2xrgp +Y/rG1qSBdcVyerM0Ki93qCyHKYu4ene0OwIBAg== +-----END DH PARAMETERS----- diff --git a/data/assets/ssl/key.pem b/data/assets/ssl/key.pem new file mode 100644 index 00000000..cedf35a0 --- /dev/null +++ b/data/assets/ssl/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0YNMU9wLfQ0m9x+TjKdytTKVwIGMqLUiuk0utXwtEBB8tnzF +4sLOwIHMnui5+whutxXtXjdo5HZXn8vcSYr0vMucNDPItevL+c58wvH58pS9ojok +mHyvwf6BKn1O2B+EXHoDud6AwyFGZouBa4J7u9/VVTlNWchxFahidh9mgCJKGUYx +s7pg/WJuC1honbSicwYBbf6poVHll4qTPMNvNV5EJyVO/fsdssJyUrxGd6/2VSQu +5G44lcPv5NeZPQsZOiJPMJidF//sVsaGaJh0CNSzNFSgEv4mlPeXZ9m6Zby+o04o +slgG6zI0irOF2z7f3yGzonDZI+vghctDFX8shwIDAQABAoIBAQC9kiLnIgxXGyZt +pmmYdA6re1jatZ2zLSp+DcY8ul3/0hs195IKCyCOOSQPiR520Pt0t+duP46uYZIJ +aakp9gxaI5Vz+oMacH/AyaBDuDTj1Mf9WMSyIOfbDVCMRJOppGLcVh62+Gfjp2EO ++h2hTJBuvypFkbK2kVIZOaHVpbXWKw1oYuEcTftk9XfxxvfSMw1HQ12/P2CAcbaa +jPmVbisunv6kpXtewSBTcaLSYWJf1MYD5Hi8fzkD2FJSXYbfQd8RKvT2rj6FA7ux +CDMzbYhdnd7lc63OARCIjfCRNtDT1cZ3gR1CQHD98lWxmPQIZukv+w7s/bSrFgnQ +ROZ0ghBJAoGBAOmE/3d5FDmp0aJNxXynKcRGdpEEM4O40RIdqa2eR6Pa7aTRosao +z0qVgdFuJrqjlB3jgedxXEX1M0abCUzzM9Q5F7JLl+KsjwRwpkIOkPiyUncLp7LK +QbY3tvYBIdpjlF1USOMGRL4j11hqr4vQC/yPBF7jj81kCZDTbmZhp82jAoGBAOWu +ql5QFUOlmqkuWIAFkiLEZhOu+ptqkE+zG50CCGMJIX0dJ2PHXFyNGInomAeT0nbI +pbnK3x7KeEKiGrAqZFNCTHhApTwkrIj0L/RQbMDZ7u7j1AEUVNFEhIm62kg84FtG +xtfxVxredE+NQc/tyV3hXegdNZxegALirlcMKIvNAoGAWFwIxk48Ru1o8z72QQqH +lUsMRicOzwK5qV8r+xPvC6MlVL42F3F8rj4QFwzU/r4yp3SUjNyqC5aSRl8Xj9Re +gijwPHi6Cf09SHLPliMo29GtvnnchJxfbPF7+23GP3p6gy4HPk/65u9s5nnH3uFk +B7ad8sGsgg0eSXyXQ4okEn0CgYEAnogPuedGthlxBgMiPMMbmfm7hyyId4t3Ljuu +/JExnsHnpobf8EPjoVIWNOIhRWGnrCtUEEhR9tvDZCKljyDDfKBPTdU496lMmX8K +NnToi7gg7iy84T3aSVMktDgPgDrclMPmbZh8CeSvnVUfrtgu3Ci4+4Rlw5eKffNe +aGDQ/6UCgYAbUq9mRT2WOXIo+Dchi9VzDWgtfOw5VEyqkSpb7hPiIYx5jNaENnVK +cAi3iqbBgPJBuMlTrKmmaxdmssGOEZNJLuuXLDbCU+f5cpu5PQ4crC6UtRI5rlhp +8Yc+oiv3HWbSw3sVRpMFB6NP4DnvgFW3B2Wdfb/lNzPCKWqBsX7gWw== +-----END RSA PRIVATE KEY----- diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf new file mode 100644 index 00000000..0565b99e --- /dev/null +++ b/data/conf/dovecot/dovecot.conf @@ -0,0 +1,247 @@ +auth_mechanisms = plain login +#mail_debug = yes +log_path = /var/log/mail.log +disable_plaintext_auth = yes +# Uncomment on NFS share +#mmap_disable = yes +#mail_fsync = always +#mail_nfs_index = yes +#mail_nfs_storage = yes +login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" +mail_home = /var/vmail/%d/%n +mail_location = maildir:~/ +mail_plugins = quota acl zlib antispam +auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@ +ssl_protocols = !SSLv3 !SSLv2 +ssl_prefer_server_ciphers = yes +ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA +ssl_options = no_compression +# Automatically regenerates every week +ssl_dh_parameters_length = 2048 +log_timestamp = "%Y-%m-%d %H:%M:%S " +recipient_delimiter = + +auth_master_user_separator = * +mail_prefetch_count = 30 +passdb { + driver = passwd-file + args = /etc/dovecot/dovecot-master.passwd + master = yes + pass = yes +} +passdb { + args = /etc/dovecot/sql/dovecot-mysql.conf + driver = sql +} +namespace inbox { + inbox = yes + location = + separator = / + mailbox "Trash" { + auto = subscribe + special_use = \Trash + } + mailbox "Deleted Messages" { + special_use = \Trash + } + mailbox "Deleted Items" { + special_use = \Trash + } + mailbox "Gelöschte Objekte" { + special_use = \Trash + } + mailbox "Papierkorb" { + special_use = \Trash + } + mailbox "Itens Excluidos" { + special_use = \Trash + } + mailbox "Itens Excluídos" { + special_use = \Trash + } + mailbox "Lixeira" { + special_use = \Trash + } + mailbox "Prullenbak" { + special_use = \Trash + } + mailbox "Verwijderde items" { + special_use = \Trash + } + mailbox "Archive" { + auto = subscribe + special_use = \Archive + } + mailbox "Archiv" { + special_use = \Archive + } + mailbox "Archives" { + special_use = \Archive + } + mailbox "Arquivo" { + special_use = \Archive + } + mailbox "Arquivos" { + special_use = \Archive + } + mailbox "Archief" { + special_use = \Archive + } + mailbox "Sent" { + auto = subscribe + special_use = \Sent + } + mailbox "Sent Messages" { + special_use = \Sent + } + mailbox "Sent Items" { + special_use = \Sent + } + mailbox "Gesendet" { + special_use = \Sent + } + mailbox "Gesendete Objekte" { + special_use = \Sent + } + mailbox "Itens Enviados" { + special_use = \Sent + } + mailbox "Enviados" { + special_use = \Sent + } + mailbox "Verzonden items" { + special_use = \Sent + } + mailbox "Verzonden" { + special_use = \Sent + } + mailbox "Drafts" { + auto = subscribe + special_use = \Drafts + } + mailbox "Entwürfe" { + special_use = \Drafts + } + mailbox "Rascunhos" { + special_use = \Drafts + } + mailbox "Concepten" { + special_use = \Drafts + } + mailbox "Junk" { + auto = subscribe + special_use = \Junk + } + mailbox "Junk E-mail" { + special_use = \Junk + } + mailbox "Spam" { + special_use = \Junk + } + mailbox "Lixo Eletrônico" { + special_use = \Junk + } + mailbox "Ongewenste e-mail" { + special_use = \Junk + } + prefix = +} +namespace { + type = shared + separator = / + prefix = Shared/%%u/ + location = maildir:%%h/:INDEXPVT=~/Shared/%%u + subscriptions = no + list = yes +} +protocols = imap sieve lmtp pop3 +service dict { + unix_listener dict { + mode = 0660 + user = vmail + group = vmail + } +} +service auth { + inet_listener auth-inet { + port = 10001 + } + unix_listener auth-master { + mode = 0600 + user = vmail + } + unix_listener auth-userdb { + mode = 0600 + user = vmail + } + user = root +} +service managesieve-login { + inet_listener sieve { + port = 4190 + } + service_count = 1 + process_min_avail = 2 + vsz_limit = 128M +} +service imap { + executable = imap imap-postlogin +} +service managesieve { + process_limit = 256 +} +service lmtp { + inet_listener lmtp-inet { + port = 24 + } + user = vmail +} +listen = *,[::] +ssl_cert = = UNIX_TIMESTAMP() diff --git a/data/conf/rmilter/rmilter.conf b/data/conf/rmilter/rmilter.conf new file mode 100644 index 00000000..f84408cd --- /dev/null +++ b/data/conf/rmilter/rmilter.conf @@ -0,0 +1,42 @@ +bind_socket = inet:9900; +spamd { + servers = r:rspamd:11333; + connect_timeout = 1s; + results_timeout = 20s; + error_time = 10; + dead_time = 300; + maxerrors = 10; + reject_message = "Spam or virus message rejected due to high detection score"; + whitelist = 127.0.0.1/32, [::1]/128; + spamd_soft_fail = yes; + rspamd_metric = "default"; + extended_spam_headers = yes; + spam_header = "X-Spam-Flag"; + spam_header_value = "YES"; +}; +redis { + servers_grey = redis:6379; + servers_limits = redis:6379; + servers_id = redis:6379; + id_prefix = "message_id."; + grey_prefix = "grey."; + white_prefix = "white."; + connect_timeout = 1s; + error_time = 10; + dead_time = 300; + maxerrors = 10; +}; +tempdir = /tmp; +tempfiles_mode = 00600; +max_size = 20M; +strict_auth = yes; +use_dcc = no; +limits { + enable = false; +}; +greylisting { + enable = false; +} +dkim { + enable = false; +}; diff --git a/data/conf/rspamd/dynmaps/authoritative.php b/data/conf/rspamd/dynmaps/authoritative.php new file mode 100644 index 00000000..b2c101f7 --- /dev/null +++ b/data/conf/rspamd/dynmaps/authoritative.php @@ -0,0 +1,22 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +$pdo = new PDO($dsn, $database_user, $database_pass, $opt); +$stmt = $pdo->query("SELECT `domain` FROM `domain`"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + echo strtolower(trim($row['domain'])) . PHP_EOL; +} +$stmt = $pdo->query("SELECT `alias_domain` FROM `alias_domain`"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + echo strtolower(trim($row['alias_domain'])) . PHP_EOL; +} +?> \ No newline at end of file diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php new file mode 100644 index 00000000..a281b981 --- /dev/null +++ b/data/conf/rspamd/dynmaps/settings.php @@ -0,0 +1,224 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +$pdo = new PDO($dsn, $database_user, $database_pass, $opt); +?> +settings { +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + score_ { + priority = low; +prepare("SELECT `option`, `value` FROM `filterconf` + WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') + AND `object`= :object"); + $stmt->execute(array(':object' => $row['object'])); + $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP); + + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + WHERE (`object`= :object OR `object`= :object_domain) + AND (`option` = 'blacklist_from' OR `option` = 'whitelist_from')"); + $stmt->execute(array(':object' => $row['object'], ':object_domain' => substr(strrchr($row['object'], "@"), 1))); + $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); + $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); +?> + from = "/^((?!).)*$/"; + rcpt = ""; +prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address"); + $stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object'])); + $rows_aliases_1 = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row_aliases_1 = array_shift($rows_aliases_1)) { +?> + rcpt = ""; +prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain` + WHERE `mailbox`.`username` = :object"); + $stmt->execute(array(':object' => $row['object'])); + $rows_aliases_2 = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_aliases_2); + while ($row_aliases_2 = array_shift($rows_aliases_2)) { + if (!empty($row_aliases_2['aliases'])) { +?> + rcpt = ""; + + apply "default" { + actions { + reject = ; + greylist = ; + "add header" = ; + } + } + } +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + whitelist_ { +prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + WHERE `object`= :object + AND `option` = 'whitelist_from'"); + $stmt->execute(array(':object' => $row['object'])); + $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); + $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); +?> + from = "/()/"; + + priority = medium; + rcpt = "/.*@/"; +prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `target_domain` = :object"); + $stmt->execute(array(':object' => $row['object'])); + $rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_domain_aliases); + while ($row_domain_aliases = array_shift($rows_domain_aliases)) { +?> + rcpt = "/.*@/"; + + priority = high; + rcpt = ""; +prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address"); + $stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object'])); + $rows_aliases_wl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_aliases_wl_1); + while ($row_aliases_wl_1 = array_shift($rows_aliases_wl_1)) { +?> + rcpt = ""; +prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain` + WHERE `mailbox`.`username` = :object"); + $stmt->execute(array(':object' => $row['object'])); + $rows_aliases_wl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_aliases_wl_2); + while ($row_aliases_wl_2 = array_shift($rows_aliases_wl_2)) { + if (!empty($row_aliases_wl_2['aliases'])) { +?> + rcpt = ""; + + apply "default" { + MAILCOW_MOO = -999.0; + } + } +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + blacklist_ { +prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf` + WHERE `object`= :object + AND `option` = 'blacklist_from'"); + $stmt->execute(array(':object' => $row['object'])); + $grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN); + $value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0]))); +?> + from = "/()/"; + + priority = medium; + rcpt = "/.*@/"; +prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `target_domain` = :object"); + $stmt->execute(array(':object' => $row['object'])); + $rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_domain_aliases); + while ($row_domain_aliases = array_shift($rows_domain_aliases)) { +?> + rcpt = "/.*@/"; + + priority = high; + rcpt = ""; +prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address"); + $stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object'])); + $rows_aliases_bl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_aliases_bl_1); + while ($row_aliases_bl_1 = array_shift($rows_aliases_bl_1)) { +?> + rcpt = ""; +prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain` + WHERE `mailbox`.`username` = :object"); + $stmt->execute(array(':object' => $row['object'])); + $rows_aliases_bl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($rows_aliases_bl_2); + while ($row_aliases_bl_2 = array_shift($rows_aliases_bl_2)) { + if (!empty($row_aliases_bl_2['aliases'])) { +?> + rcpt = ""; + + apply "default" { + MAILCOW_MOO = 999.0; + } + } + +} \ No newline at end of file diff --git a/data/conf/rspamd/dynmaps/tags.php b/data/conf/rspamd/dynmaps/tags.php new file mode 100644 index 00000000..a084d141 --- /dev/null +++ b/data/conf/rspamd/dynmaps/tags.php @@ -0,0 +1,22 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +$pdo = new PDO($dsn, $database_user, $database_pass, $opt); +$stmt = $pdo->query("SELECT `username` FROM `mailbox` WHERE `wants_tagged_subject` = '1'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + echo strtolower(trim($row['username'])) . PHP_EOL; +} +$stmt = $pdo->query("SELECT CONCAT(mailbox.local_part, '@', alias_domain.alias_domain) as `tag_ad` FROM `mailbox` INNER JOIN `alias_domain` ON mailbox.domain = alias_domain.target_domain WHERE mailbox.wants_tagged_subject='1';"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + echo strtolower(trim($row['tag_ad'])) . PHP_EOL; +} +?> diff --git a/data/conf/rspamd/dynmaps/vars.inc.php b/data/conf/rspamd/dynmaps/vars.inc.php new file mode 120000 index 00000000..f59ab89b --- /dev/null +++ b/data/conf/rspamd/dynmaps/vars.inc.php @@ -0,0 +1 @@ +../../../web/inc/vars.inc.php \ No newline at end of file diff --git a/data/conf/rspamd/local.d/dkim.conf b/data/conf/rspamd/local.d/dkim.conf new file mode 100644 index 00000000..c199c6ae --- /dev/null +++ b/data/conf/rspamd/local.d/dkim.conf @@ -0,0 +1,34 @@ +sign_condition =<= 0.95 + else + cl = 'ham' + in_class = prob <= 0.05 + end + + if in_class then + return false,string.format('already in class %s; probability %.2f%%', + cl, math.abs((prob - 0.5) * 200.0)) + end + end + + return true +end +EOD +} diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua new file mode 100644 index 00000000..0d74e9d0 --- /dev/null +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -0,0 +1,75 @@ +rspamd_config.MAILCOW_AUTH = { + callback = function(task) + local uname = task:get_user() + if uname then + return 1 + end + end +} + +rspamd_config.MAILCOW_MOO = function (task) + return true +end + +local modify_subject_map = rspamd_config:add_map({ + url = 'http://nginx:8081/tags.php', + type = 'map', + description = 'Map of users to use subject tags for' +}) + +local auth_domain_map = rspamd_config:add_map({ + url = 'http://nginx:8081/authoritative.php', + type = 'map', + description = 'Map of domains we are authoritative for' +}) + +rspamd_config.ADD_DELIMITER_TAG = { + callback = function(task) + local util = require("rspamd_util") + local rspamd_logger = require "rspamd_logger" + + local user_env_tagged = task:get_recipients(1)[1]['user'] + local user_to_tagged = task:get_recipients(2)[1]['user'] + + local domain = task:get_recipients(1)[1]['domain'] + + local user_env, tag_env = user_env_tagged:match("([^+]+)+(.*)") + local user_to, tag_to = user_to_tagged:match("([^+]+)+(.*)") + + local authdomain = auth_domain_map:get_key(domain) + + if tag_env then + tag = tag_env + user = user_env + elseif tag_to then + tag = tag_to + user = user_env + end + + if tag and authdomain then + rspamd_logger.infox("Domain %s is part of mailcow, start reading tag settings", domain) + local user_untagged = user .. '@' .. domain + rspamd_logger.infox("Querying tag settings for user %1", user_untagged) + if modify_subject_map:get_key(user_untagged) then + rspamd_logger.infox("User wants subject modified for tagged mail") + local sbj = task:get_header('Subject') + if tag then + rspamd_logger.infox("Found tag %1, will modify subject header", tag) + new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?=' + task:set_rmilter_reply({ + remove_headers = {['Subject'] = 1}, + add_headers = {['Subject'] = new_sbj} + }) + end + else + rspamd_logger.infox("Add X-Moo-Tag header") + task:set_rmilter_reply({ + add_headers = {['X-Moo-Tag'] = 'YES'} + }) + end + else + rspamd_logger.infox("Skip delimiter handling for untagged message or authenticated user") + end + return false + end +} diff --git a/data/conf/rspamd/override.d/logging.inc b/data/conf/rspamd/override.d/logging.inc new file mode 100644 index 00000000..64a2b7d4 --- /dev/null +++ b/data/conf/rspamd/override.d/logging.inc @@ -0,0 +1,3 @@ +type = "console"; +systemd = false; +.include "$CONFDIR/logging.inc" diff --git a/data/conf/rspamd/override.d/worker-controller.inc b/data/conf/rspamd/override.d/worker-controller.inc new file mode 100644 index 00000000..3845bc0d --- /dev/null +++ b/data/conf/rspamd/override.d/worker-controller.inc @@ -0,0 +1,7 @@ +bind_socket = "*:11334"; +enable_password = "$2$pppq86q9uns51zd5ekfxecj7bxwaefo3$p7f9xdhamydjhtypcr639it3kqeiknx3dk9on7skjypyi8uwwcmy"; +secure_ip = "192.168.0.0/16"; +secure_ip = "172.16.0.0/12"; +secure_ip = "10.0.0.0/8"; +secure_ip = "127.0.0.1"; +secure_ip = "::1"; diff --git a/data/conf/rspamd/override.d/worker-normal.inc b/data/conf/rspamd/override.d/worker-normal.inc new file mode 100644 index 00000000..aac8fc17 --- /dev/null +++ b/data/conf/rspamd/override.d/worker-normal.inc @@ -0,0 +1 @@ +bind_socket = "*:11333"; diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf new file mode 100644 index 00000000..9a5bd29a --- /dev/null +++ b/data/conf/sogo/sogo.conf @@ -0,0 +1,76 @@ +{ + SOGoCalendarDefaultRoles = ( + PublicViewer, + ConfidentialDAndTViewer, + PrivateDAndTViewer + ); + + WOWorkersCount = "20"; + SOGoACLsSendEMailNotifications = YES; + SOGoAppointmentSendEMailNotifications = YES; + SOGoDraftsFolderName = "Drafts"; + SOGoJunkFolderName= "Junk"; + SOGoMailDomain = "sogo.local"; + SOGoEnableEMailAlarms = NO; + SOGoFoldersSendEMailNotifications = YES; + SOGoForwardEnabled = YES; + + // Multi-domain setup + // Domains are isolated, you can define visibility options here. + // Example: + + // SOGoDomainsVisibility = ( + // (domain1.tld, domain5.tld), + // (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"; + SOGoMemcachedHost = "memcached"; + + SOGoLanguage = English; + SOGoMailAuxiliaryUserAccountsEnabled = YES; + SOGoMailCustomFromEnabled = YES; + SOGoMailingMechanism = smtp; + SOGoSMTPAuthenticationType = plain; + + SxVMemLimit = 512; + + SOGoMaximumPingInterval = 354; + + SOGoInternalSyncInterval = 30; + SOGoMaximumSyncInterval = 354; + + SOGoMaximumSyncWindowSize = 0; + SOGoMaximumSyncResponseSize = 1024; + + WOWatchDogRequestTimeout = 10; + WOListenQueueSize = 300; + WONoDetach = YES; + + SOGoIMAPAclConformsToIMAPExt = Yes; + SOGoPageTitle = "SOGo Groupware"; + SOGoFirstDayOfWeek = "1"; + + SOGoSieveFolderEncoding = "UTF-8"; + SOGoPasswordChangeEnabled = NO; + SOGoSentFolderName = "Sent"; + SOGoMailShowSubscribedFoldersOnly = NO; + NGImap4ConnectionStringSeparator = "/"; + SOGoSieveScriptsEnabled = YES; + SOGoTrashFolderName = "Trash"; + SOGoVacationEnabled = YES; + + MySQL4Encoding = "utf8mb4"; + //SOGoDebugRequests = YES; + //SoDebugBaseURL = YES; + //ImapDebugEnabled = YES; + //SOGoEASDebugEnabled = YES; + //LDAPDebugEnabled = YES; + //PGDebugEnabled = YES; + //MySQL4DebugEnabled = YES; + //SOGoUIxDebugEnabled = YES; + //WODontZipResponse = YES; +} diff --git a/data/web/add.php b/data/web/add.php new file mode 100644 index 00000000..664d96ad --- /dev/null +++ b/data/web/add.php @@ -0,0 +1,385 @@ + +
+
+
+
+
+

+
+
+ +

+
"> +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +

+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+

+
+ +

+

+
"> +
+ +
+ +

+
+
+
+ +
+ +

+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +

+
"> +
+ +
+ +

+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +

+
"> +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +

+
"> +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + +

+

+
"> +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + +
+
+
+
+ +
+ + diff --git a/data/web/admin.php b/data/web/admin.php new file mode 100644 index 00000000..5377616f --- /dev/null +++ b/data/web/admin.php @@ -0,0 +1,311 @@ + +
+

+ +
+
+
+
+
+ +
+ +
+ + ↳ a-z A-Z - _ . +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
:
+
+

+
+ +
+ +
+ + [] +
+
+ +
+
+
+
+
+
:
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ '; + } + ?> + +
+ + +
+
+
+
+ + +
+
+ +
+ + ↳ a-z A-Z - _ . +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +

+
+
+
+
+

+ +
+
+

Domain:
+ + bit +

+
+
+
+
+
+
+ + + +
+
+
+ +
+
+

Domain:

+
+
-
+
 
+
+ +
+
+

↳ Alias-Domain:
+ + bit +

+
+
+
+
+
+
+ + + +
+
+
+ +
+
+

↳ Alias-Domain:

+
+
-
+
 
+
+ +
+
+

Domain:

+
+
+
+
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+ + + + diff --git a/data/web/autoconfig.php b/data/web/autoconfig.php new file mode 100644 index 00000000..563df5d5 --- /dev/null +++ b/data/web/autoconfig.php @@ -0,0 +1,69 @@ + +';?> + + + + A mailcow mail server + mail server + + + + 993 + SSL + %EMAILADDRESS% + password-cleartext + + + + 143 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + + 995 + SSL + %EMAILADDRESS% + password-cleartext + + + + 110 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + + 465 + SSL + %EMAILADDRESS% + password-cleartext + + + + + 587 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now. + Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden. + + + + + + + + diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php new file mode 100644 index 00000000..d729306f --- /dev/null +++ b/data/web/autodiscover.php @@ -0,0 +1,142 @@ + 'yes', + 'autodiscoverType' => 'activesync', + 'imap' => array( + 'server' => $mailcow_hostname, + 'port' => '993', + 'ssl' => 'on', + ), + 'smtp' => array( + 'server' => $mailcow_hostname, + 'port' => '465', + 'ssl' => 'on' + ), + 'activesync' => array( + 'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync' + ) +); + +if(file_exists('inc/vars.local.inc.php')) { + include_once 'inc/vars.local.inc.php'; +} + +/* ---------- DO NOT MODIFY ANYTHING BEYOND THIS LINE. IGNORE AT YOUR OWN RISK. ---------- */ + +if ($config['useEASforOutlook'] == 'no') { + if (strpos($_SERVER['HTTP_USER_AGENT'], 'Outlook')) { + $config['autodiscoverType'] = 'imap'; + } +} + +$dsn = "$database_type:host=$database_host;dbname=$database_name"; +$opt = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +$pdo = new PDO($dsn, $database_user, $database_pass, $opt); +$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); +$as = check_login($login_user, $_SERVER['PHP_AUTH_PW']); + +if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { + header('WWW-Authenticate: Basic realm=""'); + header('HTTP/1.0 401 Unauthorized'); + exit; +} else { + if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { + if ($as === "user") { + header("Content-Type: application/xml"); + echo ''; + + $data = trim(file_get_contents("php://input")); + if(!$data) { + list($usec, $sec) = explode(' ', microtime()); + echo ''; + echo ''; + echo '600Invalid Request'; + echo ''; + echo ''; + exit(0); + } + $discover = new SimpleXMLElement($data); + $email = $discover->Request->EMailAddress; + + if ($config['autodiscoverType'] == 'imap') { + ?> + + + email + settings + + IMAP + + + off + + off + + on + + + SMTP + + + off + + off + + on + on + off + + + + prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); + $stmt->execute(array(':username' => $username)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + die("Failed to determine name from SQL"); + } + if (!empty($MailboxData['name'])) { + $displayname = utf8_encode($MailboxData['name']); + } + else { + $displayname = $email; + } + ?> + + en:en + + + + + + + + MobileSync + + + + + + + + + diff --git a/data/web/call_sogo_ctrl.php b/data/web/call_sogo_ctrl.php new file mode 100644 index 00000000..c548bd56 --- /dev/null +++ b/data/web/call_sogo_ctrl.php @@ -0,0 +1,40 @@ +'utf-8')); + $context = stream_context_create(array('http' => array( + 'method' => "POST", + 'header' => "Content-Length: " . strlen($request), + 'content' => $request + ))); + $file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port"); + $response = xmlrpc_decode($file); + if (isset($response['faultString'])) { + echo '' . $response['faultString'] . ''; + } + else { + echo 'OK'; + } +} +elseif ($_GET['ACTION'] == "stop") { + $request = xmlrpc_encode_request("supervisor.stopProcessGroup", 'sogo-group', array('encoding'=>'utf-8')); + $context = stream_context_create(array('http' => array( + 'method' => "POST", + 'header' => "Content-Length: " . strlen($request), + 'content' => $request + ))); + $file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port"); + $response = xmlrpc_decode($file); + if (isset($response['faultString'])) { + echo '' . $response['faultString'] . ''; + } + else { + echo 'OK'; + } +} +?> \ No newline at end of file diff --git a/data/web/css/bootstrap-select.min.css b/data/web/css/bootstrap-select.min.css new file mode 100644 index 00000000..d178d824 --- /dev/null +++ b/data/web/css/bootstrap-select.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap-select v1.12.2 (http://silviomoreto.github.io/bootstrap-select) + * + * Copyright 2013-2017 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + */select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\9}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px;z-index:1}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-group-lg .bootstrap-select.btn-group.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.btn-group.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute;height:0!important;padding:0!important}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/data/web/css/bootstrap-slider.min.css b/data/web/css/bootstrap-slider.min.css new file mode 100644 index 00000000..e55300b2 --- /dev/null +++ b/data/web/css/bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 9.7.2 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#0480be;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#0480be;border-right-color:#0480be;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-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);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-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-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:-o-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:linear-gradient(to bottom,#149bdf 0,#0480be 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-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-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0);opacity:1} \ No newline at end of file diff --git a/data/web/css/bootstrap-switch.min.css b/data/web/css/bootstrap-switch.min.css new file mode 100644 index 00000000..cbfa013b --- /dev/null +++ b/data/web/css/bootstrap-switch.min.css @@ -0,0 +1,10 @@ +/** + * bootstrap-switch - Turn checkboxes and radio buttons into toggle switches. + * + * @version v3.3.3 + * @homepage http://www.bootstrap-switch.org + * @author Mattia Larentis (http://larentis.eu) + * @license Apache-2.0 + */ + +.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,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}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;margin:0;z-index:-1;opacity:0;filter:alpha(opacity=0)}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px} \ No newline at end of file diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css new file mode 100644 index 00000000..b5c69343 --- /dev/null +++ b/data/web/css/mailbox.css @@ -0,0 +1,19 @@ +.panel-heading div { + margin-top: -18px; + font-size: 15px; +} +.panel-heading div span { + margin-left:5px; +} +.panel-body { + display: none; +} +.clickable { + cursor: pointer; +} +.progress { + margin-bottom: 0px; +} +.table>thead>tr>th { + vertical-align: top !important; +} \ No newline at end of file diff --git a/data/web/css/mailcow.css b/data/web/css/mailcow.css new file mode 100644 index 00000000..a47252bc --- /dev/null +++ b/data/web/css/mailcow.css @@ -0,0 +1,46 @@ +#maxmsgsize { min-width: 80px; } +#slider1 .slider-selection { + background: #FFD700; +} +#slider1 .slider-track-high { + background: #FF4500; +} +#slider1 .slider-track-low { + background: #66CD00; +} +.striped:nth-child(odd) { + background-color: #fff; +} +.striped:nth-child(even) { + background-color: #fafafa; + border:1px solid white; +} +.btn { + text-transform: none; +} +.glyphicon-spin { + font-size:12px; + -webkit-animation: spin 2000ms infinite linear; + animation: spin 2000ms infinite linear; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} \ No newline at end of file diff --git a/data/web/css/tables.css b/data/web/css/tables.css new file mode 100644 index 00000000..651e1665 --- /dev/null +++ b/data/web/css/tables.css @@ -0,0 +1,79 @@ +ul[id*="sortable"] { word-wrap: break-word; list-style-type: none; float: left; padding: 0 15px 0 0; width: 48%; cursor:move} +ul[id$="sortable-active"] li {cursor:move; } +ul[id$="sortable-inactive"] li {cursor:move } +.list-heading { cursor:default !important} +.ui-state-disabled { cursor:no-drop; color:#ccc; } +.ui-state-highlight {background: #F5F5F5 !important; height: 41px !important; cursor:move } +table[data-sortable] { + border-collapse: collapse; + border-spacing: 0; +} +table[data-sortable] th { + vertical-align: bottom; + font-weight: bold; +} +table[data-sortable] th, table[data-sortable] td { + text-align: left; + padding: 10px; +} +table[data-sortable] th:not([data-sortable="false"]) { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-touch-callout: none; + cursor: pointer; +} +table[data-sortable] th:after { + content: ""; + visibility: hidden; + display: inline-block; + vertical-align: inherit; + height: 0; + width: 0; + border-width: 5px; + border-style: solid; + border-color: transparent; + margin-right: 1px; + margin-left: 10px; + float: right; +} +table[data-sortable] th[data-sortable="false"]:after { + display: none; +} +table[data-sortable] th[data-sorted="true"]:after { + visibility: visible; +} +table[data-sortable] th[data-sorted-direction="descending"]:after { + border-top-color: inherit; + margin-top: 8px; +} +table[data-sortable] th[data-sorted-direction="ascending"]:after { + border-bottom-color: inherit; + margin-top: 3px; +} +table[data-sortable].sortable-theme-bootstrap thead th { + border-bottom: 2px solid #e0e0e0; +} +table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"] { + color: #3a87ad; + background: #d9edf7; + border-bottom-color: #bce8f1; +} +table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="descending"]:after { + border-top-color: #3a87ad; +} +table[data-sortable].sortable-theme-bootstrap th[data-sorted="true"][data-sorted-direction="ascending"]:after { + border-bottom-color: #3a87ad; +} +table[data-sortable].sortable-theme-bootstrap.sortable-theme-bootstrap-striped tbody > tr:nth-child(odd) > td { + background-color: #f9f9f9; +} +#data td, #no-data td { + vertical-align: middle; +} +.sort-table:hover { + border-bottom-color: #00B7DC !important; +} \ No newline at end of file diff --git a/data/web/delete.php b/data/web/delete.php new file mode 100644 index 00000000..6ba93d31 --- /dev/null +++ b/data/web/delete.php @@ -0,0 +1,212 @@ + +
+
+
+
+
+

+
+
+ + +

+
+ +
+
+ +
+
+
+ + +

+
+ "> +
+
+ +
+
+
+ + + + +
+ +
+
+ +
+
+
+ + + + +
+ +
+
+ +
+
+
+ + +

+
+ +
+
+ +
+
+
+ + + + +

+
+ +
+
+ +
+
+
+ + + + + + +

+
+ +
+
+ "> + +
+
+
+ + + + + + + +
+
+
+
+ +
+ diff --git a/data/web/edit.php b/data/web/edit.php new file mode 100644 index 00000000..996c21f8 --- /dev/null +++ b/data/web/edit.php @@ -0,0 +1,664 @@ + +
+
+
+
+
+

+
+
+ +

+
+
"> + +
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + +

+
+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + +

+
"> + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +

+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+

Domain: (dkim._domainkey)

+
+
+
+
+
+ +
+
+
+

+

+
+
+
+
+ +
+
+
+ +
+
+
+
+ + + + + + + + +
+
+
+ +
+
+
+
+ + + +
+
+ +
+
+
+
+
+

+

+
+
+
+
+ +
+
+
+ +
+
+
+
+ + + + + + + + +
+
+
+ +
+
+
+
+ + + +
+
+ +
+
+
+
+
+ + + +

+
"> + +
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+

Domain: (dkim._domainkey)

+
+
+
+
+
+ + + +

+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +

+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + +

+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + +
+
+
+
+ +
+ diff --git a/data/web/favicon.png b/data/web/favicon.png new file mode 100644 index 00000000..6390041d Binary files /dev/null and b/data/web/favicon.png differ diff --git a/data/web/img/cow_mailcow.svg b/data/web/img/cow_mailcow.svg new file mode 100644 index 00000000..bea3f826 --- /dev/null +++ b/data/web/img/cow_mailcow.svg @@ -0,0 +1,197 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/data/web/img/yubi.ico b/data/web/img/yubi.ico new file mode 100644 index 00000000..126c2b17 Binary files /dev/null and b/data/web/img/yubi.ico differ diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php new file mode 100644 index 00000000..5523ec58 --- /dev/null +++ b/data/web/inc/footer.inc.php @@ -0,0 +1,225 @@ + + + +
+ + + + + + + +
+
+
class="alert alert-" role="alert"> + × + +
+
+
+ + + + diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php new file mode 100644 index 00000000..fef6760e --- /dev/null +++ b/data/web/inc/functions.inc.php @@ -0,0 +1,4922 @@ +prepare("SELECT `domain` FROM `domain_admins` + WHERE ( + `active`='1' + AND `username` = :username + AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)) + ) + OR 'admin' = :role"); + $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain, ':role' => $role)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (!empty($num_results)) { + return true; + } + return false; +} +function hasMailboxObjectAccess($username, $role, $object) { + global $pdo; + if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + return false; + } + if ($username == $object) { + return true; + } + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object"); + $stmt->execute(array(':object' => $object)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { + return true; + } + } + catch(PDOException $e) { + error_log($e); + return false; + } + return false; +} +function init_db_schema() { + // This will be much better in future releases... + global $pdo; + try { + $stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`, `tfa`"); + $stmt->execute(); + } + catch (Exception $e) { + $lines = file('/web/inc/init.sql'); + $data = ''; + foreach ($lines as $line) { + if (substr($line, 0, 2) == '--' || $line == '') { + continue; + } + $data .= $line; + if (substr(trim($line), -1, 1) == ';') { + $pdo->query($data); + $data = ''; + } + } + // Create index if not exists + $stmt = $pdo->query("SHOW INDEX FROM sogo_acl WHERE KEY_NAME = 'sogo_acl_c_folder_id_idx'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("CREATE INDEX sogo_acl_c_folder_id_idx ON sogo_acl(c_folder_id)"); + } + $stmt = $pdo->query("SHOW INDEX FROM sogo_acl WHERE KEY_NAME = 'sogo_acl_c_uid_idx'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("CREATE INDEX sogo_acl_c_uid_idx ON sogo_acl(c_uid)"); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Database initialization completed.' + ); + } + // Add newly added columns + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'kind'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("ALTER TABLE `mailbox` ADD `kind` VARCHAR(100) NOT NULL DEFAULT ''"); + } + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'multiple_bookings'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("ALTER TABLE `mailbox` ADD `multiple_bookings` tinyint(1) NOT NULL DEFAULT '0'"); + } + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'wants_tagged_subject'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("ALTER TABLE `mailbox` ADD `wants_tagged_subject` tinyint(1) NOT NULL DEFAULT '0'"); + } + $stmt = $pdo->query("SHOW COLUMNS FROM `tfa` LIKE 'key_id'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("ALTER TABLE `tfa` ADD `key_id` VARCHAR(255) DEFAULT 'unidentified'"); + } +} +function verify_ssha256($hash, $password) { + // Remove tag if any + $hash = ltrim($hash, '{SSHA256}'); + // Decode hash + $dhash = base64_decode($hash); + // Get first 32 bytes of binary which equals a SHA256 hash + $ohash = substr($dhash, 0, 32); + // Remove SHA256 hash from decoded hash to get original salt string + $osalt = str_replace($ohash, '', $dhash); + // Check single salted SHA256 hash against extracted hash + if (hash('sha256', $password . $osalt, true) == $ohash) { + return true; + } + else { + return false; + } +} +function doveadm_authenticate($hash, $algorithm, $password) { + $descr = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $pipes = array(); + $process = proc_open("/usr/bin/doveadm pw -s ".$algorithm." -t '".$hash."'", $descr, $pipes); + if (is_resource($process)) { + fputs($pipes[0], $password); + fclose($pipes[0]); + while ($f = fgets($pipes[1])) { + if (preg_match('/(verified)/', $f)) { + proc_close($process); + return true; + } + return false; + } + fclose($pipes[1]); + while ($f = fgets($pipes[2])) { + proc_close($process); + return false; + } + fclose($pipes[2]); + proc_close($process); + } + return false; +} +function check_login($user, $pass) { + global $pdo; + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + return false; + } + $user = strtolower(trim($user)); + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `superadmin` = '1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + if (verify_ssha256($row['password'], $pass)) { + if (get_tfa($user)['name'] != "none") { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "admin"; + $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + unset($_SESSION['ldelay']); + return "pending"; + } + else { + unset($_SESSION['ldelay']); + return "admin"; + } + } + } + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `superadmin` = '0' + AND `active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + if (verify_ssha256($row['password'], $pass) !== false) { + if (get_tfa($user)['name'] != "none") { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; + $_SESSION['pending_tfa_method'] = get_tfa($user)['name']; + unset($_SESSION['ldelay']); + return "pending"; + } + else { + unset($_SESSION['ldelay']); + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + return "domainadmin"; + } + } + } + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + if (verify_ssha256($row['password'], $pass) !== false) { + unset($_SESSION['ldelay']); + return "user"; + } + } + if (!isset($_SESSION['ldelay'])) { + $_SESSION['ldelay'] = "0"; + } + elseif (!isset($_SESSION['mailcow_cc_username'])) { + $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; + } + sleep($_SESSION['ldelay']); +} +function formatBytes($size, $precision = 2) { + if(!is_numeric($size)) { + return "0"; + } + $base = log($size, 1024); + $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB'); + if ($size == "0") { + return "0"; + } + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; +} +function edit_admin_account($postarray) { + global $lang; + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $username = $postarray['admin_user']; + $username_now = $_SESSION['mailcow_cc_username']; + + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + + if (!empty($postarray['admin_pass']) && !empty($postarray['admin_pass2'])) { + if ($postarray['admin_pass'] != $postarray['admin_pass2']) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + $password_hashed = hash_password($postarray['admin_pass']); + try { + $stmt = $pdo->prepare("UPDATE `admin` SET + `modified` = :modified, + `password` = :password_hashed, + `username` = :username1 + WHERE `username` = :username2"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':modified' => date('Y-m-d H:i:s'), + ':username1' => $username, + ':username2' => $username_now + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + try { + $stmt = $pdo->prepare("UPDATE `admin` SET + `modified` = :modified, + `username` = :username1 + WHERE `username` = :username2"); + $stmt->execute(array( + ':username1' => $username, + ':modified' => date('Y-m-d H:i:s'), + ':username2' => $username_now + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + try { + $stmt = $pdo->prepare("UPDATE `domain_admins` SET `domain` = 'ALL', `username` = :username1 WHERE `username` = :username2"); + $stmt->execute(array(':username1' => $username, ':username2' => $username_now)); + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username1 WHERE `username` = :username2"); + $stmt->execute(array(':username1' => $username, ':username2' => $username_now)); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['mailcow_cc_username'] = $username; + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['admin_modified']) + ); +} +function set_time_limited_aliases($postarray) { + global $lang; + global $pdo; + (isset($postarray['username'])) ? $username = $postarray['username'] : $username = $_SESSION['mailcow_cc_username']; + + if ($_SESSION['mailcow_cc_role'] != "user" && + $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + switch ($postarray["set_time_limited_aliases"]) { + case "generate": + if (!is_numeric($postarray["validity"]) || $postarray["validity"] > 672) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['validity_missing']) + ); + return false; + } + $validity = strtotime("+".$postarray["validity"]." hour"); + $letters = 'abcefghijklmnopqrstuvwxyz1234567890'; + $random_name = substr(str_shuffle($letters), 0, 24); + try { + $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES + (:address, :goto, :validity)"); + $stmt->execute(array( + ':address' => $random_name . '@' . $domain, + ':goto' => $username, + ':validity' => $validity + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + break; + case "deleteall": + try { + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username"); + $stmt->execute(array( + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + break; + case "delete": + if (empty($postarray['item']) || !filter_var($postarray['item'], FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $item = $postarray['item']; + try { + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username AND `address` = :item"); + $stmt->execute(array( + ':username' => $username, + ':item' => $item + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + break; + case "extendall": + try { + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = (`validity` + 3600) WHERE + `goto` = :username AND + `validity` >= :validity"); + $stmt->execute(array( + ':username' => $username, + ':validity' => time(), + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + break; + case "extend": + if (empty($postarray['item']) || !filter_var($postarray['item'], FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $item = $postarray['item']; + try { + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = (`validity` + 3600) WHERE + `goto` = :username AND + `address` = :item AND + `validity` >= :validity"); + $stmt->execute(array( + ':username' => $username, + ':item' => $item, + ':validity' => time(), + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + break; + } +} +function get_time_limited_aliases($username = null) { + // 'username' can be be set, if not, default to mailcow_cc_username + global $lang; + global $pdo; + $data = array(); + if (isset($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + try { + $stmt = $pdo->prepare("SELECT `address`, + `goto`, + `validity` + FROM `spamalias` + WHERE `goto` = :username + AND `validity` >= :unixnow"); + $stmt->execute(array(':username' => $username, ':unixnow' => time())); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $data; +} +function edit_user_account($postarray) { + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + $password_old = $postarray['user_old_pass']; + + if (isset($postarray['user_new_pass']) && isset($postarray['user_new_pass2'])) { + $password_new = $postarray['user_new_pass']; + $password_new2 = $postarray['user_new_pass2']; + } + + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!verify_ssha256($row['password'], $password_old)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if (isset($password_new) && isset($password_new2)) { + if (!empty($password_new2) && !empty($password_new)) { + if ($password_new2 != $password_new) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + if (strlen($password_new) < "6" || + !preg_match('/[A-Za-z]/', $password_new) || + !preg_match('/[0-9]/', $password_new)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_complexity']) + ); + return false; + } + $password_hashed = hash_password($password_new); + try { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `modified` = :modified, `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':modified' => date('Y-m-d H:i:s'), + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); +} +function get_spam_score($username = null) { + global $pdo; + $default = "5, 15"; + if (isset($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + return false; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + try { + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object` = :username AND + (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (empty($num_results)) { + return $default; + } + else { + try { + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'highspamlevel' AND `object` = :username"); + $stmt->execute(array(':username' => $username)); + $highspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'lowspamlevel' AND `object` = :username"); + $stmt->execute(array(':username' => $username)); + $lowspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); + + return $lowspamlevel['value'].', '.$highspamlevel['value']; + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } +} +function edit_spam_score($postarray) { + // Array items + // 'username' can be set, defaults to mailcow_cc_username + // 'lowspamlevel' + // 'highspamlevel' + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + $lowspamlevel = explode(',', $postarray['score'])[0]; + $highspamlevel = explode(',', $postarray['score'])[1]; + + if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'highspamlevel', :highspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':highspamlevel' => $highspamlevel + )); + + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'lowspamlevel', :lowspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':lowspamlevel' => $lowspamlevel + )); + } + catch (PDOException $e) { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); +} +function get_policy_list($object = null) { + // 'object' can be be set, if not, default to mailcow_cc_username + global $lang; + global $pdo; + if (isset($object)) { + if (!filter_var($object, FILTER_VALIDATE_EMAIL) && is_valid_domain_name($object)) { + $object = idn_to_ascii(strtolower(trim($object))); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + elseif (filter_var($object, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + } + else { + $object = $_SESSION['mailcow_cc_username']; + } + try { + // WHITELIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND (`object` = :username OR `object` = SUBSTRING_INDEX(:username_domain, '@' ,-1))"); + $stmt->execute(array(':username' => $object, ':username_domain' => $object)); + $rows['whitelist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + // BLACKLIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND (`object` = :username OR `object` = SUBSTRING_INDEX(:username_domain, '@' ,-1))"); + $stmt->execute(array(':username' => $object, ':username_domain' => $object)); + $rows['blacklist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $rows; +} +function add_policy_list_item($postarray) { + // Array data + // Either 'domain' or 'username' can be be set + // If none of the above is set, default to mailcow_cc_username + // + // If 'delete_prefid' then delete item id + global $lang; + global $pdo; + (isset($postarray['username'])) ? $object = $postarray['username'] : null; + (isset($postarray['domain'])) ? $object = $postarray['domain'] : null; + (!isset($object)) ? $object = $_SESSION['mailcow_cc_username'] : null; + + if (is_valid_domain_name($object)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $object = idn_to_ascii(strtolower(trim($object))); + } + else { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + + ($postarray['object_list'] == "bl") ? $object_list = "blacklist_from" : null; + ($postarray['object_list'] == "wl") ? $object_list = "whitelist_from" : null; + $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($postarray['object_from']))), '.')); + if (!ctype_alnum(str_replace(array('@', '.', '-', '*'), '', $object_from))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['policy_list_from_invalid']) + ); + return false; + } + if ($object_list != "blacklist_from" && $object_list != "whitelist_from") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` + WHERE (`option` = 'whitelist_from' OR `option` = 'blacklist_from') + AND `object` = :object + AND `value` = :object_from"); + $stmt->execute(array(':object' => $object, ':object_from' => $object_from)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['policy_list_from_exists']) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + try { + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`) + VALUES (:object, :object_list, :object_from)"); + $stmt->execute(array( + ':object' => $object, + ':object_list' => $object_list, + ':object_from' => $object_from + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], $object) + ); +} +function delete_policy_list_item($postarray) { + // Array data + // Either 'domain' or 'username' can be be set + // If none of the above is set, default to mailcow_cc_username + // + // 'delete_prefid' is item to be deleted + global $lang; + global $pdo; + (isset($postarray['username'])) ? $object = $postarray['username'] : null; + (isset($postarray['domain'])) ? $object = $postarray['domain'] : null; + (!isset($object)) ? $object = $_SESSION['mailcow_cc_username'] : null; + + if (is_valid_domain_name($object)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $object = idn_to_ascii(strtolower(trim($object))); + } + else { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + + if (!is_numeric($postarray['delete_prefid'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :object AND `prefid` = :prefid"); + $stmt->execute(array( + ':object' => $object, + ':prefid' => $postarray['delete_prefid'] + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], $object) + ); + return true; +} +function get_syncjobs($username = null) { + // 'username' can be be set, if not, default to mailcow_cc_username + global $lang; + global $pdo; + $data = array(); + if (isset($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + try { + $stmt = $pdo->prepare("SELECT *, CONCAT(LEFT(`password1`, 3), '…') as `password1_short` + FROM `imapsync` + WHERE `user2` = :username"); + $stmt->execute(array(':username' => $username)); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $data; +} +function get_syncjob_details($id) { + global $lang; + global $pdo; + $syncjobdetails = array(); + if ($_SESSION['mailcow_cc_role'] != "user" && + $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT * FROM `imapsync` WHERE (`user2` = :username OR 'admin' = :role) AND id = :id"); + $stmt->execute(array(':id' => $id, ':role' => $_SESSION['mailcow_cc_role'], ':username' => $_SESSION['mailcow_cc_username'])); + $syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $syncjobdetails; +} +function delete_syncjob($postarray) { + // Array items + // 'username' can be set, defaults to mailcow_cc_username + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + $id = $postarray['id']; + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username AND `id`= :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id, + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username)) + ); + return true; +} +function add_syncjob($postarray) { + // Array items + // 'username' can be set, defaults to mailcow_cc_username + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + isset($postarray['active']) ? $active = '1' : $active = '0'; + isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0'; + $port1 = $postarray['port1']; + $host1 = $postarray['host1']; + $password1 = $postarray['password1']; + $exclude = $postarray['exclude']; + $maxage = $postarray['maxage']; + $subfolder2 = $postarray['subfolder2']; + $user1 = $postarray['user1']; + $mins_interval = $postarray['mins_interval']; + $enc1 = $postarray['enc1']; + + if (empty($subfolder2)) { + $subfolder2 = ""; + } + if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) { + $maxage = "0"; + } + if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 10, 'max_range' => 3600)))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!is_valid_domain_name($host1)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (@preg_match("/" . $exclude . "/", null) === false) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `user2`, `user1` FROM `imapsync` + WHERE `user2` = :user2 AND `user1` = :user1"); + $stmt->execute(array(':user1' => $user1, ':user2' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($host1 . ' / ' . $user1)) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`) + VALUES (:user2, :exclude, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)"); + $stmt->execute(array( + ':user2' => $username, + ':exclude' => $exclude, + ':maxage' => $maxage, + ':subfolder2' => $subfolder2, + ':host1' => $host1, + ':authmech1' => 'PLAIN', + ':user1' => $user1, + ':password1' => $password1, + ':mins_interval' => $mins_interval, + ':port1' => $port1, + ':enc1' => $enc1, + ':delete2duplicates' => $delete2duplicates, + ':active' => $active, + )); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); + return true; +} +function edit_syncjob($postarray) { + // Array items + // 'username' can be set, defaults to mailcow_cc_username + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + isset($postarray['active']) ? $active = '1' : $active = '0'; + isset($postarray['delete2duplicates']) ? $delete2duplicates = '1' : $delete2duplicates = '0'; + $id = $postarray['id']; + $port1 = $postarray['port1']; + $host1 = $postarray['host1']; + $password1 = $postarray['password1']; + $exclude = $postarray['exclude']; + $maxage = $postarray['maxage']; + $subfolder2 = $postarray['subfolder2']; + $user1 = $postarray['user1']; + $mins_interval = $postarray['mins_interval']; + $enc1 = $postarray['enc1']; + + if (empty($subfolder2)) { + $subfolder2 = ""; + } + if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) { + $maxage = "0"; + } + if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 10, 'max_range' => 3600)))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!is_valid_domain_name($host1)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (@preg_match("/" . $exclude . "/", null) === false) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` + WHERE `user2` = :user2 AND `id` = :id"); + $stmt->execute(array(':user2' => $username, ':id' => $id)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (empty($num_results)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `imapsync` set `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active + WHERE `user2` = :user2 AND `id` = :id"); + $stmt->execute(array( + ':user2' => $username, + ':id' => $id, + ':exclude' => $exclude, + ':maxage' => $maxage, + ':subfolder2' => $subfolder2, + ':host1' => $host1, + ':user1' => $user1, + ':password1' => $password1, + ':mins_interval' => $mins_interval, + ':port1' => $port1, + ':enc1' => $enc1, + ':delete2duplicates' => $delete2duplicates, + ':active' => $active, + )); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); + return true; +} +function edit_tls_policy($postarray) { + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + isset($postarray['tls_in']) ? $tls_in = '1' : $tls_in = '0'; + isset($postarray['tls_out']) ? $tls_out = '1' : $tls_out = '0'; + $username = $_SESSION['mailcow_cc_username']; + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `tls_enforce_out` = :tls_out, `tls_enforce_in` = :tls_in WHERE `username` = :username"); + $stmt->execute(array( + ':tls_out' => $tls_out, + ':tls_in' => $tls_in, + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); +} +function get_tls_policy($username = null) { + global $lang; + global $pdo; + $data = array(); + if (isset($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + try { + $stmt = $pdo->prepare("SELECT `tls_enforce_out`, `tls_enforce_in` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $data; +} +function edit_delimiter_action($postarray) { + // Array items + // 'username' can be set, defaults to mailcow_cc_username + global $lang; + global $pdo; + if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + else { + $username = $postarray['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + ($postarray['tagged_mail_handler'] == "subject") ? $wants_tagged_subject = '1' : $wants_tagged_subject = '0'; + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `wants_tagged_subject` = :wants_tagged_subject WHERE `username` = :username"); + $stmt->execute(array(':username' => $username, ':wants_tagged_subject' => $wants_tagged_subject)); + $SelectData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); + return true; +} +function get_delimiter_action($username = null) { + // 'username' can be set, defaults to mailcow_cc_username + global $lang; + global $pdo; + $data = array(); + if (isset($username) && filter_var($username, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + return false; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + try { + $stmt = $pdo->prepare("SELECT `wants_tagged_subject` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $data; +} +function user_get_alias_details($username) { + global $lang; + global $pdo; + if ($_SESSION['mailcow_cc_role'] == "user") { + $username = $_SESSION['mailcow_cc_username']; + } + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + return false; + } + try { + $data['address'] = $username; + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `aliases` FROM `alias` WHERE `goto` = :username_goto AND `address` NOT LIKE '@%' AND `address` != :username_address"); + $stmt->execute(array(':username_goto' => $username, ':username_address' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['aliases'] = $row['aliases']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '✘') AS `ad_alias` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` + WHERE `username` = :username ;"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['ad_alias'] = $row['ad_alias']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['aliases_also_send_as'] = $row['send_as']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['aliases_send_as_all'] = $row['send_as']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') as `address` FROM `alias` WHERE `goto` = :username AND `address` LIKE '@%';"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['is_catch_all'] = $row['address']; + } + return $data; + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function is_valid_domain_name($domain_name) { + if (empty($domain_name)) { + return false; + } + $domain_name = idn_to_ascii($domain_name); + return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) + && preg_match("/^.{1,253}$/", $domain_name) + && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); +} +function add_domain_admin($postarray) { + global $lang; + global $pdo; + $username = strtolower(trim($postarray['username'])); + $password = $postarray['password']; + $password2 = $postarray['password2']; + isset($postarray['active']) ? $active = '1' : $active = '0'; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (empty($postarray['domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + foreach ($num_results as $num_results_each) { + if ($num_results_each != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username)) + ); + return false; + } + } + if (!empty($password) && !empty($password2)) { + if ($password != $password2) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + $password_hashed = hash_password($password); + foreach ($postarray['domain'] as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username, :domain, :created, :active)"); + $stmt->execute(array( + ':username' => $username, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + catch (PDOException $e) { + delete_domain_admin(array('username' => $username)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + try { + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`) + VALUES (:username, :password_hashed, '0', :created, :modified, :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_empty']) + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username)) + ); +} +function delete_domain_admin($postarray) { + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $username = $postarray['username']; + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_admin_removed'], htmlspecialchars($username)) + ); +} +function get_domain_admins() { + global $pdo; + global $lang; + $domainadmins = array(); + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->query("SELECT DISTINCT + `username` + FROM `domain_admins` + WHERE `username` IN ( + SELECT `username` FROM `admin` + WHERE `superadmin`!='1' + )"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $domainadmins[] = $row['username']; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $domainadmins; +} +function get_domain_admin_details($domain_admin) { + global $pdo; + global $lang; + $domainadmindata = array(); + if (isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + (!isset($domain_admin)) ? $domain_admin = $_SESSION['mailcow_cc_username'] : null; + + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $domain_admin))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT + `tfa`.`active` AS `tfa_active_int`, + `domain_admins`.`username`, + `domain_admins`.`created`, + `domain_admins`.`active` AS `active_int`, + CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `domain_admins` + LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username` + WHERE `domain_admins`.`username`= :domain_admin"); + $stmt->execute(array( + ':domain_admin' => $domain_admin + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $domainadmindata['username'] = $row['username']; + $domainadmindata['active'] = $row['active']; + $domainadmindata['active_int'] = $row['active_int']; + $domainadmindata['tfa_active_int'] = $row['tfa_active_int']; + $domainadmindata['created'] = $row['created']; + // GET SELECTED + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` IN ( + SELECT `domain` FROM `domain_admins` + WHERE `username`= :domain_admin)"); + $stmt->execute(array(':domain_admin' => $domain_admin)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domainadmindata['selected_domains'][] = $row['domain']; + } + // GET UNSELECTED + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` NOT IN ( + SELECT `domain` FROM `domain_admins` + WHERE `username`= :domain_admin)"); + $stmt->execute(array(':domain_admin' => $domain_admin)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domainadmindata['unselected_domains'][] = $row['domain']; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $domainadmindata; +} +function set_tfa($postarray) { + global $lang; + global $pdo; + global $yubi; + global $u2f; + + if ($_SESSION['mailcow_cc_role'] != "domainadmin" && + $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $username = $_SESSION['mailcow_cc_username']; + + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :user"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!verify_ssha256($row['password'], $postarray["confirm_password"])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + switch ($postarray["tfa_method"]) { + case "yubi_otp": + (!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"]; + $yubico_id = $postarray['yubico_id']; + $yubico_key = $postarray['yubico_key']; + $yubi = new Auth_Yubico($yubico_id, $yubico_key); + if (!$yubi) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!ctype_alnum($postarray["otp_token"]) || strlen($postarray["otp_token"]) != 44) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['tfa_token_invalid']) + ); + return false; + } + $yauth = $yubi->verify($postarray["otp_token"]); + if (PEAR::isError($yauth)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Yubico API: ' . $yauth->getMessage() + ); + return false; + } + try { + // We could also do a modhex translation here + $yubico_modhex_id = substr($postarray["otp_token"], 0, 12); + $stmt = $pdo->prepare("DELETE FROM `tfa` + WHERE `username` = :username + AND (`authmech` != 'yubi_otp') + OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES + (:key_id, :username, 'yubi_otp', '1', :secret)"); + $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username)) + ); + break; + + case "u2f": + try { + (!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"]; + $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token'])); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')"); + $stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], $username) + ); + $_SESSION['regReq'] = null; + } + catch (Exception $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => "U2F: " . $e->getMessage() + ); + $_SESSION['regReq'] = null; + } + break; + + case "none": + try { + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username)) + ); + break; + } +} +function unset_tfa_key($postarray) { + // Can only unset own keys + // Needs at least one key left + global $pdo; + global $lang; + $id = intval($postarray['unset_tfa_key']); + if ($_SESSION['mailcow_cc_role'] != "domainadmin" && + $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $username = $_SESSION['mailcow_cc_username']; + try { + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['keys'] == "1") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['last_key']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], $username) + ); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function get_tfa($username = null) { + global $pdo; + if (isset($_SESSION['mailcow_cc_username'])) { + $username = $_SESSION['mailcow_cc_username']; + } + elseif (empty($username)) { + return false; + } + + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + switch ($row["authmech"]) { + case "yubi_otp": + $data['name'] = "yubi_otp"; + $data['pretty'] = "Yubico OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "u2f": + $data['name'] = "u2f"; + $data['pretty'] = "Fido U2F"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "hotp": + $data['name'] = "hotp"; + $data['pretty'] = "HMAC-based OTP"; + return $data; + break; + case "totp": + $data['name'] = "totp"; + $data['pretty'] = "Time-based OTP"; + return $data; + break; + default: + $data['name'] = 'none'; + $data['pretty'] = "-"; + return $data; + break; + } +} +function verify_tfa_login($username, $token) { + global $pdo; + global $lang; + global $yubi; + + $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + switch ($row["authmech"]) { + case "yubi_otp": + if (!ctype_alnum($token) || strlen($token) != 44) { + return false; + } + $yubico_modhex_id = substr($token, 0, 12); + $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'yubi_otp' + AND `active`='1' + AND `secret` LIKE :modhex"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $yubico_auth = explode(':', $row['secret']); + $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); + $yauth = $yubi->verify($token); + if (PEAR::isError($yauth)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Yubico Authentication error: ' . $yauth->getMessage() + ); + return false; + } + else { + $_SESSION['tfa_id'] = $row['id']; + return true; + } + return false; + break; + case "u2f": + try { + global $u2f; + $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token)); + $stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?"); + $stmt->execute(array($reg->counter, $reg->id)); + $_SESSION['tfa_id'] = $reg->id; + $_SESSION['authReq'] = null; + return true; + } + catch (Exception $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => "U2F: " . $e->getMessage() + ); + $_SESSION['regReq'] = null; + return false; + } + return false; + break; + case "hotp": + return false; + break; + case "totp": + return false; + break; + default: + return false; + break; + } + return false; +} +function edit_domain_admin($postarray) { + global $lang; + global $pdo; + + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + // Administrator + if ($_SESSION['mailcow_cc_role'] == "admin") { + $username = $postarray['username']; + $username_now = $postarray['username_now']; + $password = $postarray['password']; + $password2 = $postarray['password2']; + isset($postarray['active']) ? $active = '1' : $active = '0'; + + if(isset($postarray['domain'])) { + foreach ($postarray['domain'] as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + } + } + + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + if ($username != $username_now) { + if (empty(get_domain_admin_details($username_now)['username']) || !empty(get_domain_admin_details($username)['username'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + } + try { + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username_now, + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if(isset($postarray['domain'])) { + foreach ($postarray['domain'] as $domain) { + try { + $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`) + VALUES (:username, :domain, :created, :active)"); + $stmt->execute(array( + ':username' => $username, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + } + + if (!empty($password) && !empty($password2)) { + if ($password != $password2) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + $password_hashed = hash_password($password); + try { + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username1, `modified` = :modified, `active` = :active, `password` = :password_hashed WHERE `username` = :username2"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username1' => $username, + ':username2' => $username_now, + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + if (isset($postarray['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username_now)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username WHERE `username` = :username_now"); + $stmt->execute(array(':username' => $username, ':username_now' => $username_now)); + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + try { + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username1, `modified` = :modified, `active` = :active WHERE `username` = :username2"); + $stmt->execute(array( + ':username1' => $username, + ':username2' => $username_now, + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + if (isset($postarray['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username WHERE `username` = :username_now"); + $stmt->execute(array(':username' => $username, ':username_now' => $username_now)); + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username)) + ); + } + // Domain administrator + // Can only edit itself + elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") { + $username = $_SESSION['mailcow_cc_username']; + $password_old = $postarray['user_old_pass']; + $password_new = $postarray['user_new_pass']; + $password_new2 = $postarray['user_new_pass2']; + + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :user"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!verify_ssha256($row['password'], $password_old)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if (!empty($password_new2) && !empty($password_new)) { + if ($password_new2 != $password_new) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + if (strlen($password_new) < "6" || + !preg_match('/[A-Za-z]/', $password_new) || + !preg_match('/[0-9]/', $password_new)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_complexity']) + ); + return false; + } + $password_hashed = hash_password($password_new); + try { + $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':modified' => date('Y-m-d H:i:s'), + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username)) + ); + } +} +function get_admin_details() { + // No parameter to be given, only one admin should exist + global $pdo; + global $lang; + $data = array(); + if ($_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin`WHERE `superadmin`='1' AND active='1'"); + $stmt->execute(); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $data; +} +function dkim_add_key($postarray) { + global $lang; + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + // $_SESSION['return'] = array( + // 'type' => 'danger', + // 'msg' => sprintf($lang['danger']['access_denied']) + // ); + // return false; + // } + $key_length = intval($postarray['key_size']); + $domain = $postarray['domain']; + if (!is_valid_domain_name($domain) || !is_numeric($key_length)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid']) + ); + return false; + } + + if (!empty(glob($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid']) + ); + return false; + } + + $config = array( + "digest_alg" => "sha256", + "private_key_bits" => $key_length, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ); + if ($keypair_ressource = openssl_pkey_new($config)) { + $key_details = openssl_pkey_get_details($keypair_ressource); + $pubKey = implode(array_slice( + array_filter( + explode(PHP_EOL, $key_details['key']) + ), 1, -1) + ); + // Save public key to file + file_put_contents($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim', $pubKey); + // Save private key to file + openssl_pkey_export_to_file($keypair_ressource, $GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim'); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['dkim_added']) + ); + return true; + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid']) + ); + return false; + } +} +function dkim_get_key_details($domain) { + $data = array(); + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $dkim_pubkey_file = escapeshellarg($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim"); + if (file_exists(substr($dkim_pubkey_file, 1, -1))) { + $data['pubkey'] = file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim"); + $data['length'] = (strlen($data['pubkey']) < 391) ? 1024 : 2048; + $data['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim"); + } + } + return $data; +} +function dkim_get_blind_keys() { + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $domains = array(); + $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]); + $dnstxt_files = array_diff($dnstxt_folder, array('.', '..')); + foreach($dnstxt_files as $file) { + $domains[] = substr($file, 0, -5); + } + return array_diff($domains, array_merge(mailbox_get_domains(), mailbox_get_alias_domains())); +} +function dkim_delete_key($postarray) { + global $lang; + $domain = $postarray['domain']; + + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + // $_SESSION['return'] = array( + // 'type' => 'danger', + // 'msg' => sprintf($lang['danger']['access_denied']) + // ); + // return false; + // } + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid']) + ); + return false; + } + exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'), $out, $return); + if ($return != "0") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_remove_failed']) + ); + return false; + } + exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim'), $out, $return); + if ($return != "0") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['dkim_remove_failed']) + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['dkim_removed']) + ); + return true; +} +function mailbox_add_domain($postarray) { + // Array elements + // domain string + // description string + // aliases int + // mailboxes int + // maxquota int + // quota int + // active int + // relay_all_recipients int + // backupmx int + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $domain = idn_to_ascii(strtolower(trim($postarray['domain']))); + $description = $postarray['description']; + $aliases = $postarray['aliases']; + $mailboxes = $postarray['mailboxes']; + $maxquota = $postarray['maxquota']; + $quota = $postarray['quota']; + + if ($maxquota > $quota) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota']) + ); + return false; + } + + isset($postarray['active']) ? $active = '1' : $active = '0'; + isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0'; + isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0'; + isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true; + + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + + foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) { + if (!is_numeric($data)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['object_is_not_numeric'], htmlspecialchars($data)) + ); + return false; + } + } + + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_exists'], htmlspecialchars($domain)) + ); + return false; + } + + try { + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `created`, `modified`, `active`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, 'virtual', :backupmx, :created, :modified, :active, :relay_all_recipients)"); + $stmt->execute(array( + ':domain' => $domain, + ':description' => $description, + ':aliases' => $aliases, + ':mailboxes' => $mailboxes, + ':maxquota' => $maxquota, + ':quota' => $quota, + ':backupmx' => $backupmx, + ':active' => $active, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':relay_all_recipients' => $relay_all_recipients + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_added'], htmlspecialchars($domain)) + ); + } + catch (PDOException $e) { + mailbox_delete_domain(array('domain' => $domain)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_add_alias($postarray) { + // Array elements + // address string (separated by " ", "," ";" "\n") - email address or domain + // goto string (separated by " ", "," ";" "\n") + // active int + global $lang; + global $pdo; + $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['address'])); + $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto'])); + isset($postarray['active']) ? $active = '1' : $active = '0'; + if (empty($addresses[0])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_empty']) + ); + return false; + } + + if (empty($gotos[0])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['goto_empty']) + ); + return false; + } + + foreach ($addresses as $address) { + if (empty($address)) { + continue; + } + + $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $local_part = strstr($address, '@', true); + $address = $local_part.'@'.$domain; + + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); + $stmt->execute(array(':domain1' => $domain, ':domain2' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_not_found'], $domain) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address`= :address"); + $stmt->execute(array(':address' => $address)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` + WHERE `address`= :address"); + $stmt->execute(array(':address' => $address)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($address)) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_invalid']) + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + foreach ($gotos as &$goto) { + if (empty($goto)) { + continue; + } + + $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1)); + $goto_local_part = strstr($goto, '@', true); + $goto = $goto_local_part.'@'.$goto_domain; + + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `kind` REGEXP 'location|thing|group' + AND `username`= :goto"); + $stmt->execute(array(':goto' => $goto)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['goto_invalid']) + ); + return false; + } + + if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['goto_invalid']) + ); + return false; + } + if ($goto == $address) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_goto_identical']) + ); + return false; + } + } + + $gotos = array_filter($gotos); + $goto = implode(",", $gotos); + + try { + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`) + VALUES (:address, :goto, :domain, :created, :modified, :active)"); + + if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { + $stmt->execute(array( + ':address' => '@'.$domain, + ':goto' => $goto, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + else { + $stmt->execute(array( + ':address' => $address, + ':goto' => $goto, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['alias_added']) + ); + } + catch (PDOException $e) { + mailbox_delete_alias(array('address' => $address)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['alias_added']) + ); +} +function mailbox_add_alias_domain($postarray) { + // Array elements + // active int + // alias_domain string + // target_domain string + global $lang; + global $pdo; + isset($postarray['active']) ? $active = '1' : $active = '0'; + $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain']))); + $target_domain = idn_to_ascii(strtolower(trim($postarray['target_domain']))); + + if (!is_valid_domain_name($alias_domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_domain_invalid']) + ); + return false; + } + + if (!is_valid_domain_name($target_domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['target_domain_invalid']) + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if ($alias_domain == $target_domain) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['aliasd_targetd_identical']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :target_domain"); + $stmt->execute(array(':target_domain' => $target_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['targetd_not_found']) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain + UNION + SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain_in_domain"); + $stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['aliasd_exists']) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + try { + $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `created`, `modified`, `active`) + VALUES (:alias_domain, :target_domain, :created, :modified, :active)"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + ':target_domain' => $target_domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['aliasd_added'], htmlspecialchars($alias_domain)) + ); + } + catch (PDOException $e) { + mailbox_delete_alias_domain(array('alias_domain' => $alias_domain)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_add_mailbox($postarray) { + // Array elements + // active int + // local_part string + // domain string + // name string (username if empty) + // password string + // password2 string + // quota int (MiB) + // active int + + global $pdo; + global $lang; + $local_part = strtolower(trim($postarray['local_part'])); + $domain = idn_to_ascii(strtolower(trim($postarray['domain']))); + $username = $local_part . '@' . $domain; + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_invalid']) + ); + return false; + } + if (empty($postarray['local_part'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_invalid']) + ); + return false; + } + $password = $postarray['password']; + $password2 = $postarray['password2']; + $name = $postarray['name']; + $quota_m = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT); + + if (empty($name)) { + $name = $local_part; + } + + isset($postarray['active']) ? $active = '1' : $active = '0'; + + $quota_b = ($quota_m * 1048576); + $maildir = $domain."/".$local_part."/"; + + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT + COUNT(*) as count, + COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota` + FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT `local_part` FROM `mailbox` WHERE `local_part` = :local_part and `domain`= :domain"); + $stmt->execute(array(':local_part' => $local_part, ':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :username"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_alias'], htmlspecialchars($username)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :username"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($username)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_not_found'], $domain) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if (!is_numeric($quota_m) || $quota_m == "0") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric']) + ); + return false; + } + + if (!empty($password) && !empty($password2)) { + if ($password != $password2) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + $password_hashed = hash_password($password); + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_empty']) + ); + return false; + } + + if ($MailboxData['count'] >= $DomainData['mailboxes']) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['max_mailbox_exceeded'], $MailboxData['count'], $DomainData['mailboxes']) + ); + return false; + } + + if ($quota_m > $DomainData['maxquota']) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota']) + ); + return false; + } + + if (($MailboxData['quota'] + $quota_m) > $DomainData['quota']) { + $quota_left_m = ($DomainData['quota'] - $MailboxData['quota']); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m) + ); + return false; + } + + try { + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `created`, `modified`, `active`) + VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :created, :modified, :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':name' => $name, + ':maildir' => $maildir, + ':quota_b' => $quota_b, + ':local_part' => $local_part, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + + $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) + VALUES (:username, '0', '0')"); + $stmt->execute(array(':username' => $username)); + + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`) + VALUES (:username1, :username2, :domain, :created, :modified, :active)"); + $stmt->execute(array( + ':username1' => $username, + ':username2' => $username, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_added'], htmlspecialchars($username)) + ); + } + catch (PDOException $e) { + mailbox_delete_mailbox(array('username' => $username)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_add_resource($postarray) { + // Array elements + // active int + // domain string + // description string + // multiple_bookings int + // kind string + + global $pdo; + global $lang; + $domain = idn_to_ascii(strtolower(trim($postarray['domain']))); + $description = $postarray['description']; + $local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/')); + $name = $local_part . '@' . $domain; + $kind = $postarray['kind']; + isset($postarray['active']) ? $active = '1' : $active = '0'; + isset($postarray['multiple_bookings']) ? $multiple_bookings = '1' : $multiple_bookings = '0'; + + if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['resource_invalid']) + ); + return false; + } + + if (empty($description)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['description_invalid']) + ); + return false; + } + + if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['resource_invalid']) + ); + return false; + } + + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($name)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_alias'], htmlspecialchars($name)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($name)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_not_found'], $domain) + ); + return false; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + try { + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `created`, `modified`, `active`, `multiple_bookings`, `kind`) + VALUES (:name, 'RESOURCE', :description, 'RESOURCE', 0, :local_part, :domain, :created, :modified, :active, :multiple_bookings, :kind)"); + $stmt->execute(array( + ':name' => $name, + ':description' => $description, + ':local_part' => $local_part, + ':domain' => $domain, + ':created' => date('Y-m-d H:i:s'), + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active, + ':kind' => $kind, + ':multiple_bookings' => $multiple_bookings + )); + + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['resource_added'], htmlspecialchars($name)) + ); + } + catch (PDOException $e) { + mailbox_delete_resource(array('name' => $name)); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_edit_alias_domain($postarray) { + // Array elements + // active int + // alias_domain_now string + // alias_domain string + global $lang; + global $pdo; + isset($postarray['active']) ? $active = '1' : $active = '0'; + $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain']))); + $alias_domain_now = strtolower(trim($postarray['alias_domain_now'])); + if (!is_valid_domain_name($alias_domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_domain_invalid']) + ); + return false; + } + + if (!is_valid_domain_name($alias_domain_now)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_domain_invalid']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` + WHERE `alias_domain`= :alias_domain_now"); + $stmt->execute(array(':alias_domain_now' => $alias_domain_now)); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` + WHERE `target_domain`= :alias_domain"); + $stmt->execute(array(':alias_domain' => $alias_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['aliasd_targetd_identical']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("UPDATE `alias_domain` SET + `alias_domain` = :alias_domain, + `active` = :active, + `modified` = :modified, + WHERE `alias_domain` = :alias_domain_now"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + ':modified' => date('Y-m-d H:i:s'), + ':alias_domain_now' => $alias_domain_now, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['aliasd_modified'], htmlspecialchars($alias_domain)) + ); +} +function mailbox_edit_alias($postarray) { + // Array elements + // address string + // goto string (separated by " ", "," ";" "\n") - email address or domain + // active int + global $lang; + global $pdo; + $address = $postarray['address']; + $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $local_part = strstr($address, '@', true); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (empty($postarray['goto'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['goto_empty']) + ); + return false; + } + $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto'])); + foreach ($gotos as &$goto) { + if (empty($goto)) { + continue; + } + if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' =>sprintf($lang['danger']['goto_invalid']) + ); + return false; + } + if ($goto == $address) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_goto_identical']) + ); + return false; + } + } + $gotos = array_filter($gotos); + $goto = implode(",", $gotos); + isset($postarray['active']) ? $active = '1' : $active = '0'; + if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['alias_invalid']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("UPDATE `alias` SET + `goto` = :goto, + `active`= :active, + `modified` = :modified + WHERE `address` = :address"); + $stmt->execute(array( + ':goto' => $goto, + ':active' => $active, + ':address' => $address, + ':modified' => date('Y-m-d H:i:s'), + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['alias_modified'], htmlspecialchars($address)) + ); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_edit_domain($postarray) { + // Array elements + // domain string + // description string + // active int + // relay_all_recipients int + // backupmx int + // aliases float + // mailboxes float + // maxquota float + // quota float (Byte) + // active int + + global $lang; + global $pdo; + + $domain = idn_to_ascii($postarray['domain']); + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + + if ($_SESSION['mailcow_cc_role'] == "domainadmin" && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $description = $postarray['description']; + isset($postarray['active']) ? $active = '1' : $active = '0'; + try { + $stmt = $pdo->prepare("UPDATE `domain` SET + `modified`= :modified, + `description` = :description + WHERE `domain` = :domain"); + $stmt->execute(array( + ':modified' => date('Y-m-d H:i:s'), + ':description' => $description, + ':domain' => $domain + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain)) + ); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + elseif ($_SESSION['mailcow_cc_role'] == "admin") { + $description = $postarray['description']; + isset($postarray['active']) ? $active = '1' : $active = '0'; + $aliases = filter_var($postarray['aliases'], FILTER_SANITIZE_NUMBER_FLOAT); + $mailboxes = filter_var($postarray['mailboxes'], FILTER_SANITIZE_NUMBER_FLOAT); + $maxquota = filter_var($postarray['maxquota'], FILTER_SANITIZE_NUMBER_FLOAT); + $quota = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT); + isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0'; + isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0'; + isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true; + try { + // GET MAILBOX DATA + $stmt = $pdo->prepare("SELECT + COUNT(*) AS count, + MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `maxquota`, + COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota` + FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND domain = :domain"); + $stmt->execute(array(':domain' => $domain)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + // GET ALIAS DATA + $stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias` + WHERE domain = :domain + AND address NOT IN ( + SELECT `username` FROM `mailbox` + )"); + $stmt->execute(array(':domain' => $domain)); + $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if ($maxquota > $quota) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota']) + ); + return false; + } + + if ($MailboxData['maxquota'] > $maxquota) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['max_quota_in_use'], $MailboxData['maxquota']) + ); + return false; + } + + if ($MailboxData['quota'] > $quota) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_quota_m_in_use'], $MailboxData['quota']) + ); + return false; + } + + if ($MailboxData['count'] > $mailboxes) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailboxes_in_use'], $MailboxData['count']) + ); + return false; + } + + if ($AliasData['count'] > $aliases) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['aliases_in_use'], $AliasData['count']) + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `domain` SET + `modified`= :modified, + `relay_all_recipients` = :relay_all_recipients, + `backupmx` = :backupmx, + `active` = :active, + `quota` = :quota, + `maxquota` = :maxquota, + `mailboxes` = :mailboxes, + `aliases` = :aliases, + `description` = :description + WHERE `domain` = :domain"); + $stmt->execute(array( + ':relay_all_recipients' => $relay_all_recipients, + ':backupmx' => $backupmx, + ':active' => $active, + ':quota' => $quota, + ':maxquota' => $maxquota, + ':mailboxes' => $mailboxes, + ':aliases' => $aliases, + ':modified' => date('Y-m-d H:i:s'), + ':description' => $description, + ':domain' => $domain + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain)) + ); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } +} +function mailbox_edit_mailbox($postarray) { + global $lang; + global $pdo; + isset($postarray['active']) ? $active = '1' : $active = '0'; + if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['username_invalid']) + ); + return false; + } + $quota_m = $postarray['quota']; + $quota_b = $quota_m*1048576; + $username = $postarray['username']; + $name = $postarray['name']; + $password = $postarray['password']; + $password2 = $postarray['password2']; + + try { + $stmt = $pdo->prepare("SELECT `domain` + FROM `mailbox` + WHERE username = :username"); + $stmt->execute(array(':username' => $username)); + $MailboxData1 = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT + COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_now` + FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $MailboxData2 = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT + COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_in_use` + FROM `mailbox` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $MailboxData1['domain'])); + $MailboxData3 = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT `quota`, `maxquota` + FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $MailboxData1['domain'])); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $MailboxData1['domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!is_numeric($quota_m) || $quota_m == "0") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'], htmlspecialchars($quota_m)) + ); + return false; + } + if ($quota_m > $DomainData['maxquota']) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota']) + ); + return false; + } + if (($MailboxData3['quota_m_in_use'] - $MailboxData2['quota_m_now'] + $quota_m) > $DomainData['quota']) { + $quota_left_m = ($DomainData['quota'] - $MailboxData3['quota_m_in_use'] + $MailboxData2['quota_m_now']); + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m) + ); + return false; + } + + // Get sender_acl items set by admin + $sender_acl_admin = array_merge( + mailbox_get_sender_acl_handles($username)['sender_acl_domains']['ro'], + mailbox_get_sender_acl_handles($username)['sender_acl_addresses']['ro'] + ); + + // Get sender_acl items from POST array + (isset($postarray['sender_acl'])) ? $sender_acl_domain_admin = $postarray['sender_acl'] : $sender_acl_domain_admin = array(); + + if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) { + // Check items in POST array + foreach ($sender_acl_domain_admin as $sender_acl) { + if (!filter_var($sender_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($sender_acl, '@'))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['sender_acl_invalid']) + ); + return false; + } + if (is_valid_domain_name(ltrim($sender_acl, '@'))) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], ltrim($sender_acl, '@'))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['sender_acl_invalid']) + ); + return false; + } + } + if (filter_var($sender_acl, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $sender_acl)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['sender_acl_invalid']) + ); + return false; + } + } + } + + // Merge both arrays + $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin); + + try { + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + foreach ($sender_acl_merged as $sender_acl) { + $domain = ltrim($sender_acl, '@'); + if (is_valid_domain_name($domain)) { + $sender_acl = '@' . $domain; + } + try { + $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`) + VALUES (:sender_acl, :username)"); + $stmt->execute(array( + ':sender_acl' => $sender_acl, + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + } + else { + try { + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + if (!empty($password) && !empty($password2)) { + if ($password != $password2) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['password_mismatch']) + ); + return false; + } + $password_hashed = hash_password($password); + try { + $stmt = $pdo->prepare("UPDATE `alias` SET + `modified` = :modified, + `active` = :active + WHERE `address` = :address"); + $stmt->execute(array( + ':address' => $username, + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `modified` = :modified, + `active` = :active, + `password` = :password_hashed, + `name`= :name, + `quota` = :quota_b + WHERE `username` = :username"); + $stmt->execute(array( + ':modified' => date('Y-m-d H:i:s'), + ':password_hashed' => $password_hashed, + ':active' => $active, + ':name' => $name, + ':quota_b' => $quota_b, + ':username' => $username + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); + return true; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + try { + $stmt = $pdo->prepare("UPDATE `alias` SET + `modified` = :modified, + `active` = :active + WHERE `address` = :address"); + $stmt->execute(array( + ':address' => $username, + ':modified' => date('Y-m-d H:i:s'), + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `modified` = :modified, + `active` = :active, + `name`= :name, + `quota` = :quota_b + WHERE `username` = :username"); + $stmt->execute(array( + ':active' => $active, + ':modified' => date('Y-m-d H:i:s'), + ':name' => $name, + ':quota_b' => $quota_b, + ':username' => $username + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_modified'], $username) + ); + return true; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_edit_resource($postarray) { + global $lang; + global $pdo; + + isset($postarray['active']) ? $active = '1' : $active = '0'; + isset($postarray['multiple_bookings']) ? $multiple_bookings = '1' : $multiple_bookings = '0'; + $name = $postarray['name']; + $kind = $postarray['kind']; + $description = $postarray['description']; + + if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['resource_invalid']) + ); + return false; + } + + if (empty($description)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['description_invalid']) + ); + return false; + } + + if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['resource_invalid']) + ); + return false; + } + + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `modified` = :modified, + `active` = :active, + `name`= :description, + `kind`= :kind, + `multiple_bookings`= :multiple_bookings + WHERE `username` = :name"); + $stmt->execute(array( + ':active' => $active, + ':modified' => date('Y-m-d H:i:s'), + ':description' => $description, + ':multiple_bookings' => $multiple_bookings, + ':kind' => $kind, + ':name' => $name + )); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['resource_modified'], $name) + ); + return true; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} +function mailbox_get_mailboxes($domain = null) { + global $lang; + global $pdo; + $mailboxes = array(); + if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $mailboxes[] = $row['username']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $mailboxes[] = $row['username']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + return $mailboxes; +} +function mailbox_get_resources($domain = null) { + global $lang; + global $pdo; + $resources = array(); + if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` != 'ALL' AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $resources[] = $row['username']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $resources[] = $row['username']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + return $resources; +} +function mailbox_get_alias_domains($domain = null) { + // Get all domains assigned to mailcow_cc_username or domain, if set + // Domain admin needs to be active + // Domain does not need to be active + global $lang; + global $pdo; + $aliasdomains = array(); + if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + try { + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliasdomains[] = $row['alias_domain']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + else { + try { + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliasdomains[] = $row['alias_domain']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + return $aliasdomains; +} +function mailbox_get_aliases($domain) { + global $lang; + global $pdo; + $aliases = array(); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliases[] = $row['address']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $aliases; +} +function mailbox_get_alias_details($address) { + global $lang; + global $pdo; + $aliasdata = array(); + try { + $stmt = $pdo->prepare("SELECT + `domain`, + `goto`, + `address`, + `active` as `active_int`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `created`, + `modified` + FROM `alias` + WHERE `address` = :address AND `address` != `goto`"); + $stmt->execute(array( + ':address' => $address, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $aliasdata['domain'] = $row['domain']; + $aliasdata['goto'] = $row['goto']; + $aliasdata['address'] = $row['address']; + (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; + $aliasdata['active'] = $row['active']; + $aliasdata['active_int'] = $row['active_int']; + $aliasdata['created'] = $row['created']; + $aliasdata['modified'] = $row['modified']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $aliasdata; +} +function mailbox_get_alias_domain_details($aliasdomain) { + global $lang; + global $pdo; + $aliasdomaindata = array(); + try { + $stmt = $pdo->prepare("SELECT + `alias_domain`, + `target_domain`, + `active` AS `active_int`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `created`, + `modified` + FROM `alias_domain` + WHERE `alias_domain` = :aliasdomain"); + $stmt->execute(array( + ':aliasdomain' => $aliasdomain, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $aliasdomaindata['alias_domain'] = $row['alias_domain']; + $aliasdomaindata['target_domain'] = $row['target_domain']; + $aliasdomaindata['active'] = $row['active']; + $aliasdomaindata['active_int'] = $row['active_int']; + $aliasdomaindata['created'] = $row['created']; + $aliasdomaindata['modified'] = $row['modified']; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + return $aliasdomaindata; +} +function mailbox_get_domains() { + // Get all domains assigned to mailcow_cc_username + // Domain admin needs to be active + // Domain does not need to be active + global $lang; + global $pdo; + + try { + $domains = array(); + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE (`domain` IN ( + SELECT `domain` from `domain_admins` + WHERE (`active`='1' AND `username` = :username)) + ) + OR ('admin'= :role) + AND `domain` != 'ALL'"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domains[] = $row['domain']; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $domains; +} +function mailbox_get_domain_details($domain) { + global $lang; + global $pdo; + + $domaindata = array(); + $domain = idn_to_ascii(strtolower(trim($domain))); + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("SELECT + `domain`, + `description`, + `aliases`, + `mailboxes`, + `maxquota`, + `quota`, + `relay_all_recipients` as `relay_all_recipients_int`, + `backupmx` as `backupmx_int`, + `active` as `active_int`, + CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`, + CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain"); + $stmt->execute(array(':domain' => $row['domain'])); + $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + + $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; + if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { + $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); + } + $domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use']; + $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count']; + $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; + $domaindata['domain_name'] = $row['domain']; + $domaindata['description'] = $row['description']; + $domaindata['max_num_aliases_for_domain'] = $row['aliases']; + $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; + $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; + $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; + $domaindata['backupmx'] = $row['backupmx']; + $domaindata['backupmx_int'] = $row['backupmx_int']; + $domaindata['active'] = $row['active']; + $domaindata['active_int'] = $row['active_int']; + $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; + $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int']; + + $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias` + WHERE `domain`= :domain + AND `address` NOT IN ( + SELECT `username` FROM `mailbox` + )"); + $stmt->execute(array( + ':domain' => $domain, + )); + $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); + (isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0"; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + return $domaindata; +} +function mailbox_get_mailbox_details($mailbox) { + global $lang; + global $pdo; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailbox)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $mailboxdata = array(); + try { + $stmt = $pdo->prepare("SELECT + `domain`.`backupmx`, + `mailbox`.`username`, + `mailbox`.`name`, + `mailbox`.`active` AS `active_int`, + CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `mailbox`.`domain`, + `mailbox`.`quota`, + `quota2`.`bytes`, + `quota2`.`messages` + FROM `mailbox`, `quota2`, `domain` + WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox"); + $stmt->execute(array( + ':mailbox' => $mailbox, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $row['domain'])); + $DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username"); + $stmt->execute(array(':domain' => $row['domain'], ':username' => $row['username'])); + $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); + + $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; + if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) { + $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); + } + + $mailboxdata['username'] = $row['username']; + $mailboxdata['is_relayed'] = $row['backupmx']; + $mailboxdata['name'] = $row['name']; + $mailboxdata['active'] = $row['active']; + $mailboxdata['active_int'] = $row['active_int']; + $mailboxdata['domain'] = $row['domain']; + $mailboxdata['quota'] = $row['quota']; + $mailboxdata['quota_used'] = intval($row['bytes']); + $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); + $mailboxdata['messages'] = $row['messages']; + if ($mailboxdata['percent_in_use'] >= 90) { + $mailboxdata['percent_class'] = "danger"; + } + elseif ($mailboxdata['percent_in_use'] >= 75) { + $mailboxdata['percent_class'] = "warning"; + } + else { + $mailboxdata['percent_class'] = "success"; + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $mailboxdata; +} +function mailbox_get_resource_details($resource) { + global $lang; + global $pdo; + $resourcedata = array(); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resource)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT + `username`, + `name`, + `kind`, + `multiple_bookings` AS `multiple_bookings_int`, + `local_part`, + `active` AS `active_int`, + CASE `multiple_bookings` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `multiple_bookings`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `domain` + FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource"); + $stmt->execute(array( + ':resource' => $resource, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $resourcedata['name'] = $row['username']; + $resourcedata['kind'] = $row['kind']; + $resourcedata['multiple_bookings'] = $row['multiple_bookings']; + $resourcedata['multiple_bookings_int'] = $row['multiple_bookings']; + $resourcedata['description'] = $row['name']; + $resourcedata['active'] = $row['active']; + $resourcedata['active_int'] = $row['active_int']; + $resourcedata['domain'] = $row['domain']; + $resourcedata['local_part'] = $row['local_part']; + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (!isset($resourcedata['domain']) || + (isset($resourcedata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resourcedata['domain']))) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + return $resourcedata; +} +function mailbox_delete_domain($postarray) { + global $lang; + global $pdo; + $domain = $postarray['domain']; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + if (!is_valid_domain_name($domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + $domain = idn_to_ascii(strtolower(trim($domain))); + + try { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if ($num_results != 0 || !empty($num_results)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_not_empty']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `target_domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` = :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['domain_removed'], htmlspecialchars($domain)) + ); + return true; +} +function mailbox_delete_alias($postarray) { + global $lang; + global $pdo; + $address = $postarray['address']; + $local_part = strstr($address, '@', true); + $domain = mailbox_get_alias_details($address)['domain']; + try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address"); + $stmt->execute(array(':address' => $address)); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $goto_array = explode(',', $gotos['goto']); + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `address` = :address AND `address` NOT IN (SELECT `username` FROM `mailbox`)"); + $stmt->execute(array( + ':address' => $address + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['alias_removed'], htmlspecialchars($address)) + ); + +} +function mailbox_delete_alias_domain($postarray) { + global $lang; + global $pdo; + $alias_domain = $postarray['alias_domain']; + if (!is_valid_domain_name($postarray['alias_domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['domain_invalid']) + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` + WHERE `alias_domain`= :alias_domain"); + $stmt->execute(array(':alias_domain' => $alias_domain)); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `alias_domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['alias_domain_removed'], htmlspecialchars($alias_domain)) + ); +} +function mailbox_delete_mailbox($postarray) { + global $lang; + global $pdo; + $username = $postarray['username']; + + if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` + WHERE `goto` LIKE :username"); + $stmt->execute(array(':username' => '%'.$username.'%')); + $GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($GotoData as $gotos) { + $goto_exploded = explode(',', $gotos['goto']); + if (($key = array_search($username, $goto_exploded)) !== false) { + unset($goto_exploded[$key]); + } + $gotos_rebuild = implode(',', $goto_exploded); + $stmt = $pdo->prepare("UPDATE `alias` SET + `goto` = :goto, + `modified` = :modified, + WHERE `address` = :address"); + $stmt->execute(array( + ':goto' => $gotos_rebuild, + ':modified' => date('Y-m-d H:i:s'), + ':address' => $gotos['address'] + )); + } + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['mailbox_removed'], htmlspecialchars($username)) + ); +} +function mailbox_reset_eas($username) { + global $lang; + global $pdo; + + (isset($postarray['username'])) ? $username = $postarray['username'] : $username = $_SESSION['mailcow_cc_username']; + + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['eas_reset'], htmlspecialchars($username)) + ); +} +function mailbox_delete_resource($postarray) { + global $lang; + global $pdo; + $name = $postarray['name']; + if (!filter_var($postarray['name'], FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + try { + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $name + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['resource_removed'], htmlspecialchars($name)) + ); +} +function mailbox_get_sender_acl_handles($mailbox) { + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + + $data['sender_acl_domains']['ro'] = array(); + $data['sender_acl_domains']['rw'] = array(); + $data['sender_acl_domains']['selectable'] = array(); + $data['sender_acl_addresses']['ro'] = array(); + $data['sender_acl_addresses']['rw'] = array(); + $data['sender_acl_addresses']['selectable'] = array(); + $data['fixed_sender_aliases'] = array(); + + try { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :goto AND `address` NOT LIKE '@%'"); + $stmt->execute(array(':goto' => $mailbox)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $data['fixed_sender_aliases'][] = $row['address']; + } + + // Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects + // Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted) + $stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` LIKE '@%'"); + $stmt->execute(array(':logged_in_as' => $mailbox)); + $domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($domain_row = array_shift($domain_rows)) { + if (is_valid_domain_name($domain_row['send_as']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { + $data['sender_acl_domains']['ro'][] = $domain_row['send_as']; + continue; + } + if (is_valid_domain_name($domain_row['send_as']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { + $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; + continue; + } + } + + $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` NOT LIKE '@%'"); + $stmt->execute(array(':logged_in_as' => $mailbox)); + $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($address_row = array_shift($address_rows)) { + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + $data['sender_acl_addresses']['ro'][] = $address_row['send_as']; + continue; + } + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + $data['sender_acl_addresses']['rw'][] = $address_row['send_as']; + continue; + } + } + + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` NOT IN ( + SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` + WHERE `logged_in_as` = :logged_in_as + AND `send_as` LIKE '@%')"); + $stmt->execute(array( + ':logged_in_as' => $mailbox, + )); + $rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row_domain = array_shift($rows_domain)) { + if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) { + $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; + } + } + + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `goto` != :goto + AND `address` NOT IN ( + SELECT `send_as` FROM `sender_acl` + WHERE `logged_in_as` = :logged_in_as + AND `send_as` NOT LIKE '@%')"); + $stmt->execute(array( + ':logged_in_as' => $mailbox, + ':goto' => $mailbox + )); + $rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows_mbox)) { + if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { + $data['sender_acl_addresses']['selectable'][] = $row['address']; + } + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $data; +} +function get_u2f_registrations($username) { + global $pdo; + $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'"); + $sel->execute(array($username)); + return $sel->fetchAll(PDO::FETCH_OBJ); +} +?> diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php new file mode 100644 index 00000000..a128df88 --- /dev/null +++ b/data/web/inc/header.inc.php @@ -0,0 +1,104 @@ + + + + + + +mailcow UI + + + + + + + + + + + +' : null;?> + + + + + +
diff --git a/data/web/inc/init.sql b/data/web/inc/init.sql new file mode 100644 index 00000000..84e19f74 --- /dev/null +++ b/data/web/inc/init.sql @@ -0,0 +1,281 @@ +CREATE TABLE IF NOT EXISTS `admin` ( + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) NOT NULL, + `superadmin` TINYINT(1) NOT NULL DEFAULT '0', + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `modified` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `active` TINYINT(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `alias` ( + `address` VARCHAR(255) NOT NULL, + `goto` TEXT NOT NULL, + `domain` VARCHAR(255) NOT NULL, + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `modified` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `active` TINYINT(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`address`), + KEY `domain` (`domain`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `sender_acl` ( + `logged_in_as` VARCHAR(255) NOT NULL, + `send_as` VARCHAR(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `spamalias` ( + `address` VARCHAR(255) NOT NULL, + `goto` TEXT NOT NULL, + `validity` INT(11) NOT NULL, + PRIMARY KEY (`address`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `alias_domain` ( + `alias_domain` VARCHAR(255) NOT NULL, + `target_domain` VARCHAR(255) NOT NULL, + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `modified` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `active` TINYINT(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`alias_domain`), + KEY `active` (`active`), + KEY `target_domain` (`target_domain`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `domain` ( + `domain` VARCHAR(255) NOT NULL, + `description` VARCHAR(255), + `aliases` INT(10) NOT NULL DEFAULT '0', + `mailboxes` INT(10) NOT NULL DEFAULT '0', + `maxquota` BIGINT(20) NOT NULL DEFAULT '0', + `quota` BIGINT(20) NOT NULL DEFAULT '0', + `transport` VARCHAR(255) NOT NULL, + `backupmx` TINYINT(1) NOT NULL DEFAULT '0', + `relay_all_recipients` TINYINT(1) NOT NULL DEFAULT '0', + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `modified` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `active` TINYINT(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`domain`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `domain_admins` ( + `username` VARCHAR(255) NOT NULL, + `domain` VARCHAR(255) NOT NULL, + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `active` TINYINT(1) NOT NULL DEFAULT '1', + KEY `username` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `mailbox` ( + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) NOT NULL, + `name` VARCHAR(255), + `maildir` VARCHAR(255) NOT NULL, + `quota` BIGINT(20) NOT NULL DEFAULT '0', + `local_part` VARCHAR(255) NOT NULL, + `domain` VARCHAR(255) NOT NULL, + `created` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `modified` DATETIME NOT NULL DEFAULT '2016-01-01 00:00:00', + `tls_enforce_in` TINYINT(1) NOT NULL DEFAULT '0', + `tls_enforce_out` TINYINT(1) NOT NULL DEFAULT '0', + `kind` VARCHAR(100) NOT NULL DEFAULT '', + `multiple_bookings` TINYINT(1) NOT NULL DEFAULT '0', + `wants_tagged_subject` TINYINT(1) NOT NULL DEFAULT '0', + `active` TINYINT(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`username`), + KEY `domain` (`domain`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `quota2` ( + `username` VARCHAR(100) NOT NULL, + `bytes` BIGINT(20) NOT NULL DEFAULT '0', + `messages` INT(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `filterconf` ( + `object` VARCHAR(100) NOT NULL DEFAULT '', + `option` VARCHAR(50) NOT NULL DEFAULT '', + `value` VARCHAR(100) NOT NULL DEFAULT '', + `prefid` INT(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`prefid`), + KEY `object` (`object`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `imapsync` ( + `id` INT NOT NULL AUTO_INCREMENT, + `user2` VARCHAR(255) NOT NULL, + `host1` VARCHAR(255) NOT NULL, + `authmech1` ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN', + `regextrans2` VARCHAR(255) DEFAULT '', + `authmd51` TINYINT(1) NOT NULL DEFAULT 0, + `domain2` VARCHAR(255) NOT NULL DEFAULT '', + `subfolder2` VARCHAR(255) NOT NULL DEFAULT '', + `user1` VARCHAR(255) NOT NULL, + `password1` VARCHAR(255) NOT NULL, + `exclude` VARCHAR(500) NOT NULL DEFAULT '', + `maxage` SMALLINT NOT NULL DEFAULT '0', + `mins_interval` VARCHAR(50) NOT NULL, + `port1` SMALLINT NOT NULL, + `enc1` ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS', + `delete2duplicates` TINYINT(1) NOT NULL DEFAULT '1', + `returned_text` TEXT, + `last_run` TIMESTAMP NULL DEFAULT NULL, + `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `active` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `tfa` ( + `id` INT NOT NULL AUTO_INCREMENT, + `username` VARCHAR(255) NOT NULL, + `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp'), + `secret` VARCHAR(255) DEFAULT NULL, + `keyHandle` VARCHAR(255) DEFAULT NULL, + `publicKey` VARCHAR(255) DEFAULT NULL, + `counter` INT NOT NULL DEFAULT '0', + `certificate` TEXT, + `active` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +DROP VIEW IF EXISTS grouped_mail_aliases; +DROP VIEW IF EXISTS grouped_sender_acl; +DROP VIEW IF EXISTS grouped_domain_alias_address; + +CREATE VIEW grouped_mail_aliases (username, aliases) AS +SELECT goto, IFNULL(GROUP_CONCAT(address SEPARATOR ' '), '') AS address FROM alias +WHERE address!=goto +AND active = '1' +AND address NOT LIKE '@%' +GROUP BY goto; + +CREATE VIEW grouped_sender_acl (username, send_as) AS +SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as FROM sender_acl +WHERE send_as NOT LIKE '@%' +GROUP BY logged_in_as; + +CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS +SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox +LEFT OUTER JOIN alias_domain on target_domain=domain GROUP BY username; + +CREATE TABLE IF NOT EXISTS sogo_acl ( + c_folder_id INTEGER NOT NULL, + c_object character varying(255) NOT NULL, + c_uid character varying(255) NOT NULL, + c_role character varying(80) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_alarms_folder ( + c_path VARCHAR(255) NOT NULL, + c_name VARCHAR(255) NOT NULL, + c_uid VARCHAR(255) NOT NULL, + c_recurrence_id INT(11) DEFAULT NULL, + c_alarm_number INT(11) NOT NULL, + c_alarm_date INT(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_cache_folder ( + c_uid VARCHAR(255) NOT NULL, + c_path VARCHAR(255) NOT NULL, + c_parent_path VARCHAR(255) DEFAULT NULL, + c_type TINYINT(3) unsigned NOT NULL, + c_creationdate INT(11) NOT NULL, + c_lastmodified INT(11) NOT NULL, + c_version INT(11) NOT NULL DEFAULT '0', + c_deleted TINYINT(4) NOT NULL DEFAULT '0', + c_content longTEXT, + PRIMARY KEY (c_uid,c_path) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_folder_info ( + c_folder_id BIGINT(20) unsigned NOT NULL AUTO_INCREMENT, + c_path VARCHAR(255) NOT NULL, + c_path1 VARCHAR(255) NOT NULL, + c_path2 VARCHAR(255) DEFAULT NULL, + c_path3 VARCHAR(255) DEFAULT NULL, + c_path4 VARCHAR(255) DEFAULT NULL, + c_foldername VARCHAR(255) NOT NULL, + c_location INTeger NULL, + c_quick_location VARCHAR(2048) DEFAULT NULL, + c_acl_location VARCHAR(2048) DEFAULT NULL, + c_folder_type VARCHAR(255) NOT NULL, + PRIMARY KEY (c_path), + UNIQUE KEY c_folder_id (c_folder_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_quick_appointment ( + c_folder_id INTeger NOT NULL, + c_name character varying(255) NOT NULL, + c_uid character varying(255) NOT NULL, + c_startdate INTeger, + c_enddate INTeger, + c_cycleenddate INTeger, + c_title character varying(1000) NOT NULL, + c_participants TEXT, + c_isallday INTeger, + c_iscycle INTeger, + c_cycleinfo TEXT, + c_classification INTeger NOT NULL, + c_isopaque INTeger NOT NULL, + c_status INTeger NOT NULL, + c_priority INTeger, + c_location character varying(255), + c_orgmail character varying(255), + c_partmails TEXT, + c_partstates TEXT, + c_category character varying(255), + c_sequence INTeger, + c_component character varying(10) NOT NULL, + c_nextalarm INTeger, + c_description TEXT, + CONSTRAINT sogo_quick_appointment_pkey PRIMARY KEY (c_folder_id, c_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_quick_contact ( + c_folder_id INTeger NOT NULL, + c_name character varying(255) NOT NULL, + c_givenname character varying(255), + c_cn character varying(255), + c_sn character varying(255), + c_screenname character varying(255), + c_l character varying(255), + c_mail character varying(255), + c_o character varying(255), + c_ou character varying(255), + c_telephonenumber character varying(255), + c_categories character varying(255), + c_component character varying(10) NOT NULL, + CONSTRAINT sogo_quick_contact_pkey PRIMARY KEY (c_folder_id, c_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_sessions_folder ( + c_id VARCHAR(255) NOT NULL, + c_value VARCHAR(255) NOT NULL, + c_creationdate INT(11) NOT NULL, + c_lastseen INT(11) NOT NULL, + PRIMARY KEY (c_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_store ( + c_folder_id INTeger NOT NULL, + c_name character varying(255) NOT NULL, + c_content mediumTEXT NOT NULL, + c_creationdate INTeger NOT NULL, + c_lastmodified INTeger NOT NULL, + c_version INTeger NOT NULL, + c_deleted INTeger, + CONSTRAINT sogo_store_pkey PRIMARY KEY (c_folder_id, c_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS sogo_user_profile ( + c_uid VARCHAR(255) NOT NULL, + c_defaults TEXT, + c_settings TEXT, + PRIMARY KEY (c_uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +INSERT INTO `admin` (username, password, superadmin, created, modified, active) SELECT 'admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1 WHERE NOT EXISTS (SELECT * FROM `admin`); +DELETE FROM `domain_admins`; +INSERT INTO `domain_admins` (username, domain, created, active) SELECT `username`, 'ALL', NOW(), 1 FROM `admin` WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`); diff --git a/data/web/inc/languages.min.css b/data/web/inc/languages.min.css new file mode 100644 index 00000000..79b740c3 --- /dev/null +++ b/data/web/inc/languages.min.css @@ -0,0 +1 @@ +.lang-xs{background-position:0 -473px;min-width:14px;height:11px;min-height:11px;max-height:11px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-sm{background-position:0 -1172px;min-width:22px;height:16px;min-height:16px;max-height:16px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-sm[lang=ar]{background-position:0 -484px}.lang-sm[lang=be]{background-position:0 -500px}.lang-sm[lang=bg]{background-position:0 -516px}.lang-sm[lang=cs]{background-position:0 -532px}.lang-sm[lang=da]{background-position:0 -548px}.lang-sm[lang=de]{background-position:0 -564px}.lang-sm[lang=el]{background-position:0 -580px}.lang-sm[lang=en]{background-position:0 -596px}.lang-sm[lang=es]{background-position:0 -612px}.lang-sm[lang=et]{background-position:0 -628px}.lang-sm[lang=fi]{background-position:0 -644px}.lang-sm[lang=fr]{background-position:0 -660px}.lang-sm[lang=ga]{background-position:0 -676px}.lang-sm[lang=hi]{background-position:0 -692px}.lang-sm[lang=hr]{background-position:0 -708px}.lang-sm[lang=hu]{background-position:0 -724px}.lang-sm[lang=in]{background-position:0 -740px}.lang-sm[lang=is]{background-position:0 -756px}.lang-sm[lang=it]{background-position:0 -772px}.lang-sm[lang=iw]{background-position:0 -788px}.lang-sm[lang=ja]{background-position:0 -804px}.lang-sm[lang=ko]{background-position:0 -820px}.lang-sm[lang=lt]{background-position:0 -836px}.lang-sm[lang=lv]{background-position:0 -852px}.lang-sm[lang=mk]{background-position:0 -868px}.lang-sm[lang=ms]{background-position:0 -884px}.lang-sm[lang=mt]{background-position:0 -900px}.lang-sm[lang=nl]{background-position:0 -916px}.lang-sm[lang=no]{background-position:0 -932px}.lang-sm[lang=pl]{background-position:0 -948px}.lang-sm[lang=pt]{background-position:0 -964px}.lang-sm[lang=ro]{background-position:0 -980px}.lang-sm[lang=ru]{background-position:0 -996px}.lang-sm[lang=sk]{background-position:0 -1012px}.lang-sm[lang=sl]{background-position:0 -1028px}.lang-sm[lang=sq]{background-position:0 -1044px}.lang-sm[lang=sr]{background-position:0 -1060px}.lang-sm[lang=sv]{background-position:0 -1076px}.lang-sm[lang=th]{background-position:0 -1092px}.lang-sm[lang=tr]{background-position:0 -1108px}.lang-sm[lang=uk]{background-position:0 -1124px}.lang-sm[lang=vi]{background-position:0 -1140px}.lang-sm[lang=zh]{background-position:0 -1156px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px;position:relative}.lang-sm:after{top:-1px;position:relative}.lang-xs{top:4px;position:relative}.lang-xs:after{top:-4px;position:relative}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} \ No newline at end of file diff --git a/data/web/inc/languages.png b/data/web/inc/languages.png new file mode 100644 index 00000000..c88e039b Binary files /dev/null and b/data/web/inc/languages.png differ diff --git a/data/web/inc/lib/U2F.php b/data/web/inc/lib/U2F.php new file mode 100644 index 00000000..b79d7fac --- /dev/null +++ b/data/web/inc/lib/U2F.php @@ -0,0 +1,506 @@ +appId = $appId; + $this->attestDir = $attestDir; + } + + /** + * Called to get a registration request to send to a user. + * Returns an array of one registration request and a array of sign requests. + * + * @param array $registrations List of current registrations for this + * user, to prevent the user from registering the same authenticator several + * times. + * @return array An array of two elements, the first containing a + * RegisterRequest the second being an array of SignRequest + * @throws Error + */ + public function getRegisterData(array $registrations = array()) + { + $challenge = $this->createChallenge(); + $request = new RegisterRequest($challenge, $this->appId); + $signs = $this->getAuthenticateData($registrations); + return array($request, $signs); + } + + /** + * Called to verify and unpack a registration message. + * + * @param RegisterRequest $request this is a reply to + * @param object $response response from a user + * @param bool $includeCert set to true if the attestation certificate should be + * included in the returned Registration object + * @return Registration + * @throws Error + */ + public function doRegister($request, $response, $includeCert = true) + { + if( !is_object( $request ) ) { + throw new \InvalidArgumentException('$request of doRegister() method only accepts object.'); + } + + if( !is_object( $response ) ) { + throw new \InvalidArgumentException('$response of doRegister() method only accepts object.'); + } + + if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) { + throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING ); + } + + if( !is_bool( $includeCert ) ) { + throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.'); + } + + $rawReg = $this->base64u_decode($response->registrationData); + $regData = array_values(unpack('C*', $rawReg)); + $clientData = $this->base64u_decode($response->clientData); + $cli = json_decode($clientData); + + if($cli->challenge !== $request->challenge) { + throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE ); + } + + $registration = new Registration(); + $offs = 1; + $pubKey = substr($rawReg, $offs, PUBKEY_LEN); + $offs += PUBKEY_LEN; + // decode the pubKey to make sure it's good + $tmpKey = $this->pubkey_to_pem($pubKey); + if($tmpKey === null) { + throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); + } + $registration->publicKey = base64_encode($pubKey); + $khLen = $regData[$offs++]; + $kh = substr($rawReg, $offs, $khLen); + $offs += $khLen; + $registration->keyHandle = $this->base64u_encode($kh); + + // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes) + $certLen = 4; + $certLen += ($regData[$offs + 2] << 8); + $certLen += $regData[$offs + 3]; + + $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen)); + $offs += $certLen; + $pemCert = "-----BEGIN CERTIFICATE-----\r\n"; + $pemCert .= chunk_split(base64_encode($rawCert), 64); + $pemCert .= "-----END CERTIFICATE-----"; + if($includeCert) { + $registration->certificate = base64_encode($rawCert); + } + if($this->attestDir) { + if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) { + throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION ); + } + } + + if(!openssl_pkey_get_public($pemCert)) { + throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); + } + $signature = substr($rawReg, $offs); + + $dataToVerify = chr(0); + $dataToVerify .= hash('sha256', $request->appId, true); + $dataToVerify .= hash('sha256', $clientData, true); + $dataToVerify .= $kh; + $dataToVerify .= $pubKey; + + if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) { + return $registration; + } else { + throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE ); + } + } + + /** + * Called to get an authentication request. + * + * @param array $registrations An array of the registrations to create authentication requests for. + * @return array An array of SignRequest + * @throws Error + */ + public function getAuthenticateData(array $registrations) + { + $sigs = array(); + foreach ($registrations as $reg) { + if( !is_object( $reg ) ) { + throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.'); + } + + $sig = new SignRequest(); + $sig->appId = $this->appId; + $sig->keyHandle = $reg->keyHandle; + $sig->challenge = $this->createChallenge(); + $sigs[] = $sig; + } + return $sigs; + } + + /** + * Called to verify an authentication response + * + * @param array $requests An array of outstanding authentication requests + * @param array $registrations An array of current registrations + * @param object $response A response from the authenticator + * @return Registration + * @throws Error + * + * The Registration object returned on success contains an updated counter + * that should be saved for future authentications. + * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of + * token cloning or similar and appropriate action should be taken. + */ + public function doAuthenticate(array $requests, array $registrations, $response) + { + if( !is_object( $response ) ) { + throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.'); + } + + if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) { + throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING ); + } + + /** @var object|null $req */ + $req = null; + + /** @var object|null $reg */ + $reg = null; + + $clientData = $this->base64u_decode($response->clientData); + $decodedClient = json_decode($clientData); + foreach ($requests as $req) { + if( !is_object( $req ) ) { + throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.'); + } + + if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) { + break; + } + + $req = null; + } + if($req === null) { + throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST ); + } + foreach ($registrations as $reg) { + if( !is_object( $reg ) ) { + throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.'); + } + + if($reg->keyHandle === $response->keyHandle) { + break; + } + $reg = null; + } + if($reg === null) { + throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION ); + } + $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey)); + if($pemKey === null) { + throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE ); + } + + $signData = $this->base64u_decode($response->signatureData); + $dataToVerify = hash('sha256', $req->appId, true); + $dataToVerify .= substr($signData, 0, 5); + $dataToVerify .= hash('sha256', $clientData, true); + $signature = substr($signData, 5); + + if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) { + $ctr = unpack("Nctr", substr($signData, 1, 4)); + $counter = $ctr['ctr']; + /* TODO: wrap-around should be handled somehow.. */ + if($counter > $reg->counter) { + $reg->counter = $counter; + return $reg; + } else { + throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW ); + } + } else { + throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE ); + } + } + + /** + * @return array + */ + private function get_certs() + { + $files = array(); + $dir = $this->attestDir; + if($dir && $handle = opendir($dir)) { + while(false !== ($entry = readdir($handle))) { + if(is_file("$dir/$entry")) { + $files[] = "$dir/$entry"; + } + } + closedir($handle); + } + return $files; + } + + /** + * @param string $data + * @return string + */ + private function base64u_encode($data) + { + return trim(strtr(base64_encode($data), '+/', '-_'), '='); + } + + /** + * @param string $data + * @return string + */ + private function base64u_decode($data) + { + return base64_decode(strtr($data, '-_', '+/')); + } + + /** + * @param string $key + * @return null|string + */ + private function pubkey_to_pem($key) + { + if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") { + return null; + } + + /* + * Convert the public key to binary DER format first + * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480 + * + * SEQUENCE(2 elem) 30 59 + * SEQUENCE(2 elem) 30 13 + * OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01 + * OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07 + * BIT STRING(520 bit) 03 42 ..key.. + */ + $der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01"; + $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42"; + $der .= "\0".$key; + + $pem = "-----BEGIN PUBLIC KEY-----\r\n"; + $pem .= chunk_split(base64_encode($der), 64); + $pem .= "-----END PUBLIC KEY-----"; + + return $pem; + } + + /** + * @return string + * @throws Error + */ + private function createChallenge() + { + $challenge = openssl_random_pseudo_bytes(32, $crypto_strong ); + if( $crypto_strong !== true ) { + throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM); + } + + $challenge = $this->base64u_encode( $challenge ); + + return $challenge; + } + + /** + * Fixes a certificate where the signature contains unused bits. + * + * @param string $cert + * @return mixed + */ + private function fixSignatureUnusedBits($cert) + { + if(in_array(hash('sha256', $cert), $this->FIXCERTS)) { + $cert[strlen($cert) - 257] = "\0"; + } + return $cert; + } +} + +/** + * Class for building a registration request + * + * @package u2flib_server + */ +class RegisterRequest +{ + /** Protocol version */ + public $version = U2F_VERSION; + + /** Registration challenge */ + public $challenge; + + /** Application id */ + public $appId; + + /** + * @param string $challenge + * @param string $appId + * @internal + */ + public function __construct($challenge, $appId) + { + $this->challenge = $challenge; + $this->appId = $appId; + } +} + +/** + * Class for building up an authentication request + * + * @package u2flib_server + */ +class SignRequest +{ + /** Protocol version */ + public $version = U2F_VERSION; + + /** Authentication challenge */ + public $challenge; + + /** Key handle of a registered authenticator */ + public $keyHandle; + + /** Application id */ + public $appId; +} + +/** + * Class returned for successful registrations + * + * @package u2flib_server + */ +class Registration +{ + /** The key handle of the registered authenticator */ + public $keyHandle; + + /** The public key of the registered authenticator */ + public $publicKey; + + /** The attestation certificate of the registered authenticator */ + public $certificate; + + /** The counter associated with this registration */ + public $counter = -1; +} + +/** + * Error class, returned on errors + * + * @package u2flib_server + */ +class Error extends \Exception +{ + /** + * Override constructor and make message and code mandatory + * @param string $message + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($message, $code, \Exception $previous = null) { + parent::__construct($message, $code, $previous); + } +} diff --git a/data/web/inc/lib/Yubico.php b/data/web/inc/lib/Yubico.php new file mode 100644 index 00000000..68a3087b --- /dev/null +++ b/data/web/inc/lib/Yubico.php @@ -0,0 +1,475 @@ +, Olov Danielson + * @copyright 2007-2015 Yubico AB + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version 2.0 + * @link http://www.yubico.com/ + */ + +require_once 'PEAR.php'; + +/** + * Class for verifying Yubico One-Time-Passcodes + * + * Simple example: + * + * require_once 'Auth/Yubico.php'; + * $otp = "ccbbddeertkrctjkkcglfndnlihhnvekchkcctif"; + * + * # Generate a new id+key from https://api.yubico.com/get-api-key/ + * $yubi = new Auth_Yubico('42', 'FOOBAR='); + * $auth = $yubi->verify($otp); + * if (PEAR::isError($auth)) { + * print "

Authentication failed: " . $auth->getMessage(); + * print "

Debug output from server: " . $yubi->getLastResponse(); + * } else { + * print "

You are authenticated!"; + * } + * + */ +class Auth_Yubico +{ + /**#@+ + * @access private + */ + + /** + * Yubico client ID + * @var string + */ + var $_id; + + /** + * Yubico client key + * @var string + */ + var $_key; + + /** + * URL part of validation server + * @var string + */ + var $_url; + + /** + * List with URL part of validation servers + * @var array + */ + var $_url_list; + + /** + * index to _url_list + * @var int + */ + var $_url_index; + + /** + * Last query to server + * @var string + */ + var $_lastquery; + + /** + * Response from server + * @var string + */ + var $_response; + + /** + * Flag whether to use https or not. + * @var boolean + */ + var $_https; + + /** + * Flag whether to verify HTTPS server certificates or not. + * @var boolean + */ + var $_httpsverify; + + /** + * Constructor + * + * Sets up the object + * @param string $id The client identity + * @param string $key The client MAC key (optional) + * @param boolean $https Flag whether to use https (optional) + * @param boolean $httpsverify Flag whether to use verify HTTPS + * server certificates (optional, + * default true) + * @access public + */ + function __construct($id, $key = '', $https = 0, $httpsverify = 1) + { + $this->_id = $id; + $this->_key = base64_decode($key); + $this->_https = $https; + $this->_httpsverify = $httpsverify; + } + + function Auth_Yubico($id, $key = '', $https = 0, $httpsverify = 1) + { + self::__construct(); + } + + /** + * Specify to use a different URL part for verification. + * The default is "api.yubico.com/wsapi/verify". + * + * @param string $url New server URL part to use + * @access public + */ + function setURLpart($url) + { + $this->_url = $url; + } + + /** + * Get URL part to use for validation. + * + * @return string Server URL part + * @access public + */ + function getURLpart() + { + if ($this->_url) { + return $this->_url; + } else { + return "api.yubico.com/wsapi/verify"; + } + } + + + /** + * Get next URL part from list to use for validation. + * + * @return mixed string with URL part of false if no more URLs in list + * @access public + */ + function getNextURLpart() + { + if ($this->_url_list) $url_list=$this->_url_list; + else $url_list=array('api.yubico.com/wsapi/2.0/verify', + 'api2.yubico.com/wsapi/2.0/verify', + 'api3.yubico.com/wsapi/2.0/verify', + 'api4.yubico.com/wsapi/2.0/verify', + 'api5.yubico.com/wsapi/2.0/verify'); + + if ($this->_url_index>=count($url_list)) return false; + else return $url_list[$this->_url_index++]; + } + + /** + * Resets index to URL list + * + * @access public + */ + function URLreset() + { + $this->_url_index=0; + } + + /** + * Add another URLpart. + * + * @access public + */ + function addURLpart($URLpart) + { + $this->_url_list[]=$URLpart; + } + + /** + * Return the last query sent to the server, if any. + * + * @return string Request to server + * @access public + */ + function getLastQuery() + { + return $this->_lastquery; + } + + /** + * Return the last data received from the server, if any. + * + * @return string Output from server + * @access public + */ + function getLastResponse() + { + return $this->_response; + } + + /** + * Parse input string into password, yubikey prefix, + * ciphertext, and OTP. + * + * @param string Input string to parse + * @param string Optional delimiter re-class, default is '[:]' + * @return array Keyed array with fields + * @access public + */ + function parsePasswordOTP($str, $delim = '[:]') + { + if (!preg_match("/^((.*)" . $delim . ")?" . + "(([cbdefghijklnrtuv]{0,16})" . + "([cbdefghijklnrtuv]{32}))$/i", + $str, $matches)) { + /* Dvorak? */ + if (!preg_match("/^((.*)" . $delim . ")?" . + "(([jxe\.uidchtnbpygk]{0,16})" . + "([jxe\.uidchtnbpygk]{32}))$/i", + $str, $matches)) { + return false; + } else { + $ret['otp'] = strtr($matches[3], "jxe.uidchtnbpygk", "cbdefghijklnrtuv"); + } + } else { + $ret['otp'] = $matches[3]; + } + $ret['password'] = $matches[2]; + $ret['prefix'] = $matches[4]; + $ret['ciphertext'] = $matches[5]; + return $ret; + } + + /* TODO? Add functions to get parsed parts of server response? */ + + /** + * Parse parameters from last response + * + * example: getParameters("timestamp", "sessioncounter", "sessionuse"); + * + * @param array @parameters Array with strings representing + * parameters to parse + * @return array parameter array from last response + * @access public + */ + function getParameters($parameters) + { + if ($parameters == null) { + $parameters = array('timestamp', 'sessioncounter', 'sessionuse'); + } + $param_array = array(); + foreach ($parameters as $param) { + if(!preg_match("/" . $param . "=([0-9]+)/", $this->_response, $out)) { + return PEAR::raiseError('Could not parse parameter ' . $param . ' from response'); + } + $param_array[$param]=$out[1]; + } + return $param_array; + } + + /** + * Verify Yubico OTP against multiple URLs + * Protocol specification 2.0 is used to construct validation requests + * + * @param string $token Yubico OTP + * @param int $use_timestamp 1=>send request with ×tamp=1 to + * get timestamp and session information + * in the response + * @param boolean $wait_for_all If true, wait until all + * servers responds (for debugging) + * @param string $sl Sync level in percentage between 0 + * and 100 or "fast" or "secure". + * @param int $timeout Max number of seconds to wait + * for responses + * @return mixed PEAR error on error, true otherwise + * @access public + */ + function verify($token, $use_timestamp=null, $wait_for_all=False, + $sl=null, $timeout=null) + { + /* Construct parameters string */ + $ret = $this->parsePasswordOTP($token); + if (!$ret) { + return PEAR::raiseError('Could not parse Yubikey OTP'); + } + $params = array('id'=>$this->_id, + 'otp'=>$ret['otp'], + 'nonce'=>md5(uniqid(rand()))); + /* Take care of protocol version 2 parameters */ + if ($use_timestamp) $params['timestamp'] = 1; + if ($sl) $params['sl'] = $sl; + if ($timeout) $params['timeout'] = $timeout; + ksort($params); + $parameters = ''; + foreach($params as $p=>$v) $parameters .= "&" . $p . "=" . $v; + $parameters = ltrim($parameters, "&"); + + /* Generate signature. */ + if($this->_key <> "") { + $signature = base64_encode(hash_hmac('sha1', $parameters, + $this->_key, true)); + $signature = preg_replace('/\+/', '%2B', $signature); + $parameters .= '&h=' . $signature; + } + + /* Generate and prepare request. */ + $this->_lastquery=null; + $this->URLreset(); + $mh = curl_multi_init(); + $ch = array(); + while($URLpart=$this->getNextURLpart()) + { + /* Support https. */ + if ($this->_https) { + $query = "https://"; + } else { + $query = "http://"; + } + $query .= $URLpart . "?" . $parameters; + + if ($this->_lastquery) { $this->_lastquery .= " "; } + $this->_lastquery .= $query; + + $handle = curl_init($query); + curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico"); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1); + if (!$this->_httpsverify) { + curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0); + } + curl_setopt($handle, CURLOPT_FAILONERROR, true); + /* If timeout is set, we better apply it here as well + in case the validation server fails to follow it. + */ + if ($timeout) curl_setopt($handle, CURLOPT_TIMEOUT, $timeout); + curl_multi_add_handle($mh, $handle); + + $ch[(int)$handle] = $handle; + } + + /* Execute and read request. */ + $this->_response=null; + $replay=False; + $valid=False; + do { + /* Let curl do its work. */ + while (($mrc = curl_multi_exec($mh, $active)) + == CURLM_CALL_MULTI_PERFORM) + ; + + while ($info = curl_multi_info_read($mh)) { + if ($info['result'] == CURLE_OK) { + + /* We have a complete response from one server. */ + + $str = curl_multi_getcontent($info['handle']); + $cinfo = curl_getinfo ($info['handle']); + + if ($wait_for_all) { # Better debug info + $this->_response .= 'URL=' . $cinfo['url'] ."\n" + . $str . "\n"; + } + + if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) { + $status = $out[1]; + + /* + * There are 3 cases. + * + * 1. OTP or Nonce values doesn't match - ignore + * response. + * + * 2. We have a HMAC key. If signature is invalid - + * ignore response. Return if status=OK or + * status=REPLAYED_OTP. + * + * 3. Return if status=OK or status=REPLAYED_OTP. + */ + if (!preg_match("/otp=".$params['otp']."/", $str) || + !preg_match("/nonce=".$params['nonce']."/", $str)) { + /* Case 1. Ignore response. */ + } + elseif ($this->_key <> "") { + /* Case 2. Verify signature first */ + $rows = explode("\r\n", trim($str)); + $response=array(); + while (list($key, $val) = each($rows)) { + /* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */ + $val = preg_replace('/=/', '#', $val, 1); + $row = explode("#", $val); + $response[$row[0]] = $row[1]; + } + + $parameters=array('nonce','otp', 'sessioncounter', 'sessionuse', 'sl', 'status', 't', 'timeout', 'timestamp'); + sort($parameters); + $check=Null; + foreach ($parameters as $param) { + if (array_key_exists($param, $response)) { + if ($check) $check = $check . '&'; + $check = $check . $param . '=' . $response[$param]; + } + } + + $checksignature = + base64_encode(hash_hmac('sha1', utf8_encode($check), + $this->_key, true)); + + if($response['h'] == $checksignature) { + if ($status == 'REPLAYED_OTP') { + if (!$wait_for_all) { $this->_response = $str; } + $replay=True; + } + if ($status == 'OK') { + if (!$wait_for_all) { $this->_response = $str; } + $valid=True; + } + } + } else { + /* Case 3. We check the status directly */ + if ($status == 'REPLAYED_OTP') { + if (!$wait_for_all) { $this->_response = $str; } + $replay=True; + } + if ($status == 'OK') { + if (!$wait_for_all) { $this->_response = $str; } + $valid=True; + } + } + } + if (!$wait_for_all && ($valid || $replay)) + { + /* We have status=OK or status=REPLAYED_OTP, return. */ + foreach ($ch as $h) { + curl_multi_remove_handle($mh, $h); + curl_close($h); + } + curl_multi_close($mh); + if ($replay) return PEAR::raiseError('REPLAYED_OTP'); + if ($valid) return true; + return PEAR::raiseError($status); + } + + curl_multi_remove_handle($mh, $info['handle']); + curl_close($info['handle']); + unset ($ch[(int)$info['handle']]); + } + curl_multi_select($mh); + } + } while ($active); + + /* Typically this is only reached for wait_for_all=true or + * when the timeout is reached and there is no + * OK/REPLAYED_REQUEST answer (think firewall). + */ + + foreach ($ch as $h) { + curl_multi_remove_handle ($mh, $h); + curl_close ($h); + } + curl_multi_close ($mh); + + if ($replay) return PEAR::raiseError('REPLAYED_OTP'); + if ($valid) return true; + return PEAR::raiseError('NO_VALID_ANSWER'); + } +} +?> diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php new file mode 100644 index 00000000..ffa1eac6 --- /dev/null +++ b/data/web/inc/prerequisites.inc.php @@ -0,0 +1,103 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { +?> +

🐮 Connection failed, database may be in warm-up state, please try again later.

The following error was reported:
getMessage();?>
+ + + + + + + + + + + \ No newline at end of file diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php new file mode 100644 index 00000000..2895420d --- /dev/null +++ b/data/web/inc/triggers.inc.php @@ -0,0 +1,173 @@ + 'danger', + 'msg' => $lang['danger']['login_failed'] + ); + } +} + +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { + if (isset($_GET["duallogin"])) { + if (filter_var($_GET["duallogin"], FILTER_VALIDATE_EMAIL)) { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :duallogin"); + $stmt->execute(array(':duallogin' => $_GET["duallogin"])); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username']; + $_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role']; + $_SESSION['mailcow_cc_username'] = $_GET["duallogin"]; + $_SESSION['mailcow_cc_role'] = "user"; + header("Location: /user.php"); + } + } + } + + if (isset($_POST["edit_admin_account"])) { + edit_admin_account($_POST); + } + if (isset($_POST["dkim_delete_key"])) { + dkim_delete_key($_POST); + } + if (isset($_POST["dkim_add_key"])) { + dkim_add_key($_POST); + } + if (isset($_POST["add_domain_admin"])) { + add_domain_admin($_POST); + } + if (isset($_POST["delete_domain_admin"])) { + delete_domain_admin($_POST); + } +} +if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user") { + if (isset($_POST["edit_user_account"])) { + edit_user_account($_POST); + } + if (isset($_POST["mailbox_reset_eas"])) { + mailbox_reset_eas($_POST); + } + if (isset($_POST["edit_spam_score"])) { + edit_spam_score($_POST); + } + if (isset($_POST["edit_delimiter_action"])) { + edit_delimiter_action($_POST); + } + if (isset($_POST["add_policy_list_item"])) { + add_policy_list_item($_POST); + } + if (isset($_POST["delete_policy_list_item"])) { + delete_policy_list_item($_POST); + } + if (isset($_POST["edit_tls_policy"])) { + edit_tls_policy($_POST); + } + if (isset($_POST["add_syncjob"])) { + add_syncjob($_POST); + } + if (isset($_POST["edit_syncjob"])) { + edit_syncjob($_POST); + } + if (isset($_POST["delete_syncjob"])) { + delete_syncjob($_POST); + } + if (isset($_POST["set_time_limited_aliases"])) { + set_time_limited_aliases($_POST); + } +} +if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) { + if (isset($_POST["edit_domain_admin"])) { + edit_domain_admin($_POST); + } + if (isset($_POST["set_tfa"])) { + set_tfa($_POST); + } + if (isset($_POST["unset_tfa_key"])) { + unset_tfa_key($_POST); + } + if (isset($_POST["add_policy_list_item"])) { + add_policy_list_item($_POST); + } + if (isset($_POST["delete_policy_list_item"])) { + delete_policy_list_item($_POST); + } + if (isset($_POST["mailbox_add_domain"])) { + mailbox_add_domain($_POST); + } + if (isset($_POST["mailbox_add_alias"])) { + mailbox_add_alias($_POST); + } + if (isset($_POST["mailbox_add_alias_domain"])) { + mailbox_add_alias_domain($_POST); + } + if (isset($_POST["mailbox_add_mailbox"])) { + mailbox_add_mailbox($_POST); + } + if (isset($_POST["mailbox_add_resource"])) { + mailbox_add_resource($_POST); + } + if (isset($_POST["mailbox_edit_alias"])) { + mailbox_edit_alias($_POST); + } + if (isset($_POST["mailbox_edit_domain"])) { + mailbox_edit_domain($_POST); + } + if (isset($_POST["mailbox_edit_mailbox"])) { + mailbox_edit_mailbox($_POST); + } + if (isset($_POST["mailbox_edit_alias_domain"])) { + mailbox_edit_alias_domain($_POST); + } + if (isset($_POST["mailbox_edit_resource"])) { + mailbox_edit_resource($_POST); + } + if (isset($_POST["mailbox_delete_domain"])) { + mailbox_delete_domain($_POST); + } + if (isset($_POST["mailbox_delete_alias"])) { + mailbox_delete_alias($_POST); + } + if (isset($_POST["mailbox_delete_alias_domain"])) { + mailbox_delete_alias_domain($_POST); + } + if (isset($_POST["mailbox_delete_mailbox"])) { + mailbox_delete_mailbox($_POST); + } + if (isset($_POST["mailbox_delete_resource"])) { + mailbox_delete_resource($_POST); + } +} +?> diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php new file mode 100644 index 00000000..b8270ba7 --- /dev/null +++ b/data/web/inc/vars.inc.php @@ -0,0 +1,38 @@ + diff --git a/data/web/index.php b/data/web/index.php new file mode 100644 index 00000000..31bafda6 --- /dev/null +++ b/data/web/index.php @@ -0,0 +1,90 @@ + +
+
+
+
+
+
+
mailcow
+ mailcow UI +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+ +

+ + mailcow Apps + +
+
+
+
+
+
+ +
+
+
+

mailcow UI

+

+

mailcow Apps

+

+
+
+
+
+
+
+ + diff --git a/data/web/js/add.js b/data/web/js/add.js new file mode 100644 index 00000000..1ebcdbb2 --- /dev/null +++ b/data/web/js/add.js @@ -0,0 +1,18 @@ +$(document).ready(function() { + // add.php + // Get max. possible quota for a domain when domain field changes + $('#addSelectDomain').on('change', function() { + $.get("json_api.php", { action:"get_domain_details", object:this.value }, function(data){ + var result = jQuery.parseJSON( data ); + 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}); + } + else { + $("#quotaBadge").html('max. ' + max_new_mailbox_quota + ' MiB'); + $('#addInputQuota').attr({"disabled": true, "value": "", "type": "text", "value": "n/a"}); + } + }); + }); +}); diff --git a/data/web/js/admin.js b/data/web/js/admin.js new file mode 100644 index 00000000..a235a422 --- /dev/null +++ b/data/web/js/admin.js @@ -0,0 +1,31 @@ +$(document).ready(function() { + // Postfix restrictions, drag and drop functions + $( "[id*=srr-sortable]" ).sortable({ + items: "li:not(.list-heading)", + cancel: ".ui-state-disabled", + connectWith: "[id*=srr-sortable]", + dropOnEmpty: true, + placeholder: "ui-state-highlight" + }); + $( "[id*=ssr-sortable]" ).sortable({ + items: "li:not(.list-heading)", + cancel: ".ui-state-disabled", + connectWith: "[id*=ssr-sortable]", + dropOnEmpty: true, + placeholder: "ui-state-highlight" + }); + $('#srr_form').submit(function(){ + var srr_joined_vals = $("[id^=srr-sortable-active] li").map(function() { + return $(this).data("value"); + }).get().join(', '); + var input = $("").attr("type", "hidden").attr("name", "srr_value").val(srr_joined_vals); + $('#srr_form').append($(input)); + }); + $('#ssr_form').submit(function(){ + var ssr_joined_vals = $("[id^=ssr-sortable-active] li").map(function() { + return $(this).data("value"); + }).get().join(', '); + var input = $("").attr("type", "hidden").attr("name", "ssr_value").val(ssr_joined_vals); + $('#ssr_form').append($(input)); + }); +}); \ No newline at end of file diff --git a/data/web/js/bootstrap-select.min.js b/data/web/js/bootstrap-select.min.js new file mode 100644 index 00000000..d2a33149 --- /dev/null +++ b/data/web/js/bootstrap-select.min.js @@ -0,0 +1,9 @@ +/*! + * Bootstrap-select v1.12.2 (http://silviomoreto.github.io/bootstrap-select) + * + * Copyright 2013-2017 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) + */ +!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){"use strict";function b(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b?b.replace(this.re,this.ch):""}),b}function c(b){var c=arguments,d=b;[].shift.apply(c);var e,f=this.each(function(){var b=a(this);if(b.is("select")){var f=b.data("selectpicker"),g="object"==typeof d&&d;if(f){if(g)for(var h in g)g.hasOwnProperty(h)&&(f.options[h]=g[h])}else{var i=a.extend({},k.DEFAULTS,a.fn.selectpicker.defaults||{},b.data(),g);i.template=a.extend({},k.DEFAULTS.template,a.fn.selectpicker.defaults?a.fn.selectpicker.defaults.template:{},b.data().template,g.template),b.data("selectpicker",f=new k(this,i))}"string"==typeof d&&(e=f[d]instanceof Function?f[d].apply(f,c):f.options[d])}});return"undefined"!=typeof e?e:f}String.prototype.includes||!function(){var a={}.toString,b=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),c="".indexOf,d=function(b){if(null==this)throw new TypeError;var d=String(this);if(b&&"[object RegExp]"==a.call(b))throw new TypeError;var e=d.length,f=String(b),g=f.length,h=arguments.length>1?arguments[1]:void 0,i=h?Number(h):0;i!=i&&(i=0);var j=Math.min(Math.max(i,0),e);return!(g+j>e)&&c.call(d,f,i)!=-1};b?b(String.prototype,"includes",{value:d,configurable:!0,writable:!0}):String.prototype.includes=d}(),String.prototype.startsWith||!function(){var a=function(){try{var a={},b=Object.defineProperty,c=b(a,a,a)&&b}catch(a){}return c}(),b={}.toString,c=function(a){if(null==this)throw new TypeError;var c=String(this);if(a&&"[object RegExp]"==b.call(a))throw new TypeError;var d=c.length,e=String(a),f=e.length,g=arguments.length>1?arguments[1]:void 0,h=g?Number(g):0;h!=h&&(h=0);var i=Math.min(Math.max(h,0),d);if(f+i>d)return!1;for(var j=-1;++j":">",'"':""","'":"'","`":"`"},g={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},h=function(a){var b=function(b){return a[b]},c="(?:"+Object.keys(a).join("|")+")",d=RegExp(c),e=RegExp(c,"g");return function(a){return a=null==a?"":""+a,d.test(a)?a.replace(e,b):a}},i=h(f),j=h(g),k=function(b,c){d.useDefault||(a.valHooks.select.set=d._set,d.useDefault=!0),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title"));var e=this.options.windowPadding;"number"==typeof e&&(this.options.windowPadding=[e,e,e,e]),this.val=k.prototype.val,this.render=k.prototype.render,this.refresh=k.prototype.refresh,this.setStyle=k.prototype.setStyle,this.selectAll=k.prototype.selectAll,this.deselectAll=k.prototype.deselectAll,this.destroy=k.prototype.destroy,this.remove=k.prototype.remove,this.show=k.prototype.show,this.hide=k.prototype.hide,this.init()};k.VERSION="1.12.2",k.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(a,b){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){return[1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",showTick:!1,template:{caret:''},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0},k.prototype={constructor:k,init:function(){var b=this,c=this.$element.attr("id");this.$element.addClass("bs-select-hidden"),this.liObj={},this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement).appendTo(this.$newElement),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(".dropdown-menu"),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),this.$element.removeClass("bs-select-hidden"),this.options.dropdownAlignRight===!0&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!1),b.$element.trigger("hide.bs.select",a)},"hidden.bs.dropdown":function(a){b.$element.trigger("hidden.bs.select",a)},"show.bs.dropdown":function(a){b.$menuInner.attr("aria-expanded",!0),b.$element.trigger("show.bs.select",a)},"shown.bs.dropdown":function(a){b.$element.trigger("shown.bs.select",a)}}),b.$element[0].hasAttribute("required")&&this.$element.on("invalid",function(){b.$button.addClass("bs-invalid").focus(),b.$element.on({"focus.bs.select":function(){b.$button.focus(),b.$element.off("focus.bs.select")},"shown.bs.select":function(){b.$element.val(b.$element.val()).off("shown.bs.select")},"rendered.bs.select":function(){this.validity.valid&&b.$button.removeClass("bs-invalid"),b.$element.off("rendered.bs.select")}})}),setTimeout(function(){b.$element.trigger("loaded.bs.select")})},createDropdown:function(){var b=this.multiple||this.options.showTick?" show-tick":"",c=this.$element.parent().hasClass("input-group")?" input-group-btn":"",d=this.autofocus?" autofocus":"",e=this.options.header?'
'+this.options.header+"
":"",f=this.options.liveSearch?'':"",g=this.multiple&&this.options.actionsBox?'
":"",h=this.multiple&&this.options.doneButton?'
":"",j='
";return a(j)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul")[0].innerHTML=b,a},reloadLi:function(){var a=this.createLi();this.$menuInner[0].innerHTML=a},createLi:function(){var c=this,d=[],e=0,f=document.createElement("option"),g=-1,h=function(a,b,c,d){return""+a+""},j=function(d,e,f,g){return''+d+''};if(this.options.title&&!this.multiple&&(g--,!this.$element.find(".bs-title-option").length)){var k=this.$element[0];f.className="bs-title-option",f.innerHTML=this.options.title,f.value="",k.insertBefore(f,k.firstChild);var l=a(k.options[k.selectedIndex]);void 0===l.attr("selected")&&void 0===this.$element.data("selected")&&(f.selected=!0)}return this.$element.find("option").each(function(b){var f=a(this);if(g++,!f.hasClass("bs-title-option")){var k=this.className||"",l=this.style.cssText,m=f.data("content")?f.data("content"):f.html(),n=f.data("tokens")?f.data("tokens"):null,o="undefined"!=typeof f.data("subtext")?''+f.data("subtext")+"":"",p="undefined"!=typeof f.data("icon")?' ':"",q=f.parent(),r="OPTGROUP"===q[0].tagName,s=r&&q[0].disabled,t=this.disabled||s;if(""!==p&&t&&(p=""+p+""),c.options.hideDisabled&&(t&&!r||s))return void g--;if(f.data("content")||(m=p+''+m+o+""),r&&f.data("divider")!==!0){if(c.options.hideDisabled&&t){if(void 0===q.data("allOptionsDisabled")){var u=q.children();q.data("allOptionsDisabled",u.filter(":disabled").length===u.length)}if(q.data("allOptionsDisabled"))return void g--}var v=" "+q[0].className||"";if(0===f.index()){e+=1;var w=q[0].label,x="undefined"!=typeof q.data("subtext")?''+q.data("subtext")+"":"",y=q.data("icon")?' ':"";w=y+''+i(w)+x+"",0!==b&&d.length>0&&(g++,d.push(h("",null,"divider",e+"div"))),g++,d.push(h(w,null,"dropdown-header"+v,e))}if(c.options.hideDisabled&&t)return void g--;d.push(h(j(m,"opt "+k+v,l,n),b,"",e))}else if(f.data("divider")===!0)d.push(h("",b,"divider"));else if(f.data("hidden")===!0)d.push(h(j(m,k,l,n),b,"hidden is-hidden"));else{var z=this.previousElementSibling&&"OPTGROUP"===this.previousElementSibling.tagName;if(!z&&c.options.hideDisabled)for(var A=a(this).prevAll(),B=0;B ':"";return b=d.options.showSubtext&&c.data("subtext")&&!d.multiple?' '+c.data("subtext")+"":"","undefined"!=typeof c.attr("title")?c.attr("title"):c.data("content")&&d.options.showContent?c.data("content").toString():e+c.html()+b}}).toArray(),f=this.multiple?e.join(this.options.multipleSeparator):e[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var g=this.options.selectedTextFormat.split(">");if(g.length>1&&e.length>g[1]||1==g.length&&e.length>=2){c=this.options.hideDisabled?", [disabled]":"";var h=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+c).length,i="function"==typeof this.options.countSelectedText?this.options.countSelectedText(e.length,h):this.options.countSelectedText;f=i.replace("{0}",e.length.toString()).replace("{1}",h.toString())}}void 0==this.options.title&&(this.options.title=this.$element.attr("title")),"static"==this.options.selectedTextFormat&&(f=this.options.title),f||(f="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",j(a.trim(f.replace(/<[^>]*>?/g,"")))),this.$button.children(".filter-option").html(f),this.$element.trigger("rendered.bs.select")},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(b){if(b||this.options.size!==!1&&!this.sizeInfo){var c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("ul"),f=document.createElement("li"),g=document.createElement("li"),h=document.createElement("a"),i=document.createElement("span"),j=this.options.header&&this.$menu.find(".popover-title").length>0?this.$menu.find(".popover-title")[0].cloneNode(!0):null,k=this.options.liveSearch?document.createElement("div"):null,l=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,m=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null;if(i.className="text",c.className=this.$menu[0].parentNode.className+" open",d.className="dropdown-menu open",e.className="dropdown-menu inner",f.className="divider",i.appendChild(document.createTextNode("Inner text")),h.appendChild(i),g.appendChild(h),e.appendChild(g),e.appendChild(f),j&&d.appendChild(j),k){var n=document.createElement("input");k.className="bs-searchbox",n.className="form-control",k.appendChild(n),d.appendChild(k)}l&&d.appendChild(l),d.appendChild(e),m&&d.appendChild(m),c.appendChild(d),document.body.appendChild(c);var o=h.offsetHeight,p=j?j.offsetHeight:0,q=k?k.offsetHeight:0,r=l?l.offsetHeight:0,s=m?m.offsetHeight:0,t=a(f).outerHeight(!0),u="function"==typeof getComputedStyle&&getComputedStyle(d),v=u?null:a(d),w={vert:parseInt(u?u.paddingTop:v.css("paddingTop"))+parseInt(u?u.paddingBottom:v.css("paddingBottom"))+parseInt(u?u.borderTopWidth:v.css("borderTopWidth"))+parseInt(u?u.borderBottomWidth:v.css("borderBottomWidth")),horiz:parseInt(u?u.paddingLeft:v.css("paddingLeft"))+parseInt(u?u.paddingRight:v.css("paddingRight"))+parseInt(u?u.borderLeftWidth:v.css("borderLeftWidth"))+parseInt(u?u.borderRightWidth:v.css("borderRightWidth"))},x={vert:w.vert+parseInt(u?u.marginTop:v.css("marginTop"))+parseInt(u?u.marginBottom:v.css("marginBottom"))+2,horiz:w.horiz+parseInt(u?u.marginLeft:v.css("marginLeft"))+parseInt(u?u.marginRight:v.css("marginRight"))+2};document.body.removeChild(c),this.sizeInfo={liHeight:o,headerHeight:p,searchHeight:q,actionsHeight:r,doneButtonHeight:s,dividerHeight:t,menuPadding:w,menuExtras:x}}},setSize:function(){if(this.findLis(),this.liHeight(),this.options.header&&this.$menu.css("padding-top",0),this.options.size!==!1){var b,c,d,e,f,g,h,i,j=this,k=this.$menu,l=this.$menuInner,m=a(window),n=this.$newElement[0].offsetHeight,o=this.$newElement[0].offsetWidth,p=this.sizeInfo.liHeight,q=this.sizeInfo.headerHeight,r=this.sizeInfo.searchHeight,s=this.sizeInfo.actionsHeight,t=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,w=this.sizeInfo.menuExtras,x=this.options.hideDisabled?".disabled":"",y=function(){var b,c=j.$newElement.offset(),d=a(j.options.container);j.options.container&&!d.is("body")?(b=d.offset(),b.top+=parseInt(d.css("borderTopWidth")),b.left+=parseInt(d.css("borderLeftWidth"))):b={top:0,left:0};var e=j.options.windowPadding;f=c.top-b.top-m.scrollTop(),g=m.height()-f-n-b.top-e[2],h=c.left-b.left-m.scrollLeft(),i=m.width()-h-o-b.left-e[1],f-=e[0],h-=e[3]};if(y(),"auto"===this.options.size){var z=function(){var m,n=function(b,c){return function(d){return c?d.classList?d.classList.contains(b):a(d).hasClass(b):!(d.classList?d.classList.contains(b):a(d).hasClass(b))}},u=j.$menuInner[0].getElementsByTagName("li"),x=Array.prototype.filter?Array.prototype.filter.call(u,n("hidden",!1)):j.$lis.not(".hidden"),z=Array.prototype.filter?Array.prototype.filter.call(x,n("dropdown-header",!0)):x.filter(".dropdown-header");y(),b=g-w.vert,c=i-w.horiz,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height"),k.data("width")||k.data("width",k.width()),e=k.data("width")):(d=k.height(),e=k.width()),j.options.dropupAuto&&j.$newElement.toggleClass("dropup",f>g&&b-w.verti&&c-w.horiz3?3*p+w.vert-2:0,k.css({"max-height":b+"px",overflow:"hidden","min-height":m+q+r+s+t+"px"}),l.css({"max-height":b-q-r-s-t-v.vert+"px","overflow-y":"auto","min-height":Math.max(m-v.vert,0)+"px"})};z(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",z),m.off("resize.getSize scroll.getSize").on("resize.getSize scroll.getSize",z)}else if(this.options.size&&"auto"!=this.options.size&&this.$lis.not(x).length>this.options.size){var A=this.$lis.not(".divider").not(x).children().slice(0,this.options.size).last().parent().index(),B=this.$lis.slice(0,A+1).filter(".divider").length;b=p*this.options.size+B*u+v.vert,j.options.container?(k.data("height")||k.data("height",k.height()),d=k.data("height")):d=k.height(),j.options.dropupAuto&&this.$newElement.toggleClass("dropup",f>g&&b-w.vert');var b,c,d,e=this,f=a(this.options.container),g=function(a){e.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),f.is("body")?c={top:0,left:0}:(c=f.offset(),c.top+=parseInt(f.css("borderTopWidth"))-f.scrollTop(),c.left+=parseInt(f.css("borderLeftWidth"))-f.scrollLeft()),d=a.hasClass("dropup")?0:a[0].offsetHeight,e.$bsContainer.css({top:b.top-c.top+d,left:b.left-c.left,width:a[0].offsetWidth})};this.$button.on("click",function(){var b=a(this);e.isDisabled()||(g(e.$newElement),e.$bsContainer.appendTo(e.options.container).toggleClass("open",!b.hasClass("open")).append(e.$menu))}),a(window).on("resize scroll",function(){g(e.$newElement)}),this.$element.on("hide.bs.select",function(){e.$menu.data("height",e.$menu.height()),e.$bsContainer.detach()})},setSelected:function(a,b,c){c||(this.togglePlaceholder(),c=this.findLis().eq(this.liObj[a])),c.toggleClass("selected",b).find("a").attr("aria-selected",b)},setDisabled:function(a,b,c){c||(c=this.findLis().eq(this.liObj[a])),b?c.addClass("disabled").children("a").attr("href","#").attr("tabindex",-1).attr("aria-disabled",!0):c.removeClass("disabled").children("a").removeAttr("href").attr("tabindex",0).attr("aria-disabled",!1)},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){var a=this;this.isDisabled()?(this.$newElement.addClass("disabled"),this.$button.addClass("disabled").attr("tabindex",-1).attr("aria-disabled",!0)):(this.$button.hasClass("disabled")&&(this.$newElement.removeClass("disabled"),this.$button.removeClass("disabled").attr("aria-disabled",!1)),this.$button.attr("tabindex")!=-1||this.$element.data("tabindex")||this.$button.removeAttr("tabindex")),this.$button.click(function(){return!a.isDisabled()})},togglePlaceholder:function(){var a=this.$element.val();this.$button.toggleClass("bs-placeholder",null===a||""===a||a.constructor===Array&&0===a.length)},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&this.$element.attr("tabindex")!==-98&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var b=this,c=a(document);c.data("spaceSelect",!1),this.$button.on("keyup",function(a){/(32)/.test(a.keyCode.toString(10))&&c.data("spaceSelect")&&(a.preventDefault(),c.data("spaceSelect",!1))}),this.$button.on("click",function(){b.setSize()}),this.$element.on("shown.bs.select",function(){if(b.options.liveSearch||b.multiple){if(!b.multiple){var a=b.liObj[b.$element[0].selectedIndex];if("number"!=typeof a||b.options.size===!1)return;var c=b.$lis.eq(a)[0].offsetTop-b.$menuInner[0].offsetTop;c=c-b.$menuInner[0].offsetHeight/2+b.sizeInfo.liHeight/2,b.$menuInner[0].scrollTop=c}}else b.$menuInner.find(".selected a").focus()}),this.$menuInner.on("click","li a",function(c){var d=a(this),f=d.parent().data("originalIndex"),g=b.$element.val(),h=b.$element.prop("selectedIndex"),i=!0;if(b.multiple&&1!==b.options.maxOptions&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var j=b.$element.find("option"),k=j.eq(f),l=k.prop("selected"),m=k.parent("optgroup"),n=b.options.maxOptions,o=m.data("maxOptions")||!1;if(b.multiple){if(k.prop("selected",!l),b.setSelected(f,!l),d.blur(),n!==!1||o!==!1){var p=n');t[2]&&(u=u.replace("{var}",t[2][n>1?0:1]),v=v.replace("{var}",t[2][o>1?0:1])),k.prop("selected",!1),b.$menu.append(w),n&&p&&(w.append(a("
"+u+"
")),i=!1,b.$element.trigger("maxReached.bs.select")),o&&q&&(w.append(a("
"+v+"
")),i=!1,b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(f,!1)},10),w.delay(750).fadeOut(300,function(){a(this).remove()})}}}else j.prop("selected",!1),k.prop("selected",!0),b.$menuInner.find(".selected").removeClass("selected").find("a").attr("aria-selected",!1),b.setSelected(f,!0);!b.multiple||b.multiple&&1===b.options.maxOptions?b.$button.focus():b.options.liveSearch&&b.$searchbox.focus(),i&&(g!=b.$element.val()&&b.multiple||h!=b.$element.prop("selectedIndex")&&!b.multiple)&&(e=[f,k.prop("selected"),l],b.$element.triggerNative("change"))}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(c){c.currentTarget==this&&(c.preventDefault(),c.stopPropagation(),b.options.liveSearch&&!a(c.target).hasClass("close")?b.$searchbox.focus():b.$button.focus())}),this.$menuInner.on("click",".divider, .dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.click()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).hasClass("bs-select-all")?b.selectAll():b.deselectAll()}),this.$element.change(function(){b.render(!1),b.$element.trigger("changed.bs.select",e),e=null})},liveSearchListener:function(){var c=this,d=a('
  • ');this.$button.on("click.dropdown.data-api",function(){c.$menuInner.find(".active").removeClass("active"),c.$searchbox.val()&&(c.$searchbox.val(""),c.$lis.not(".is-hidden").removeClass("hidden"),d.parent().length&&d.remove()),c.multiple||c.$menuInner.find(".selected").addClass("active"),setTimeout(function(){c.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){if(c.$lis.not(".is-hidden").removeClass("hidden"),c.$lis.filter(".active").removeClass("active"),d.remove(),c.$searchbox.val()){var e,f=c.$lis.not(".is-hidden, .divider, .dropdown-header");if(e=c.options.liveSearchNormalize?f.find("a").not(":a"+c._searchStyle()+'("'+b(c.$searchbox.val())+'")'):f.find("a").not(":"+c._searchStyle()+'("'+c.$searchbox.val()+'")'),e.length===f.length)d.html(c.options.noneResultsText.replace("{0}",'"'+i(c.$searchbox.val())+'"')),c.$menuInner.append(d),c.$lis.addClass("hidden");else{e.parent().addClass("hidden");var g,h=c.$lis.not(".hidden");h.each(function(b){var c=a(this);c.hasClass("divider")?void 0===g?c.addClass("hidden"):(g&&g.addClass("hidden"),g=c):c.hasClass("dropdown-header")&&h.eq(b+1).data("optgroup")!==c.data("optgroup")?c.addClass("hidden"):g=null}),g&&g.addClass("hidden"),f.not(".hidden").first().addClass("active")}}})},_searchStyle:function(){var a={begins:"ibegins",startsWith:"ibegins"};return a[this.options.liveSearchStyle]||"icontains"},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},changeAll:function(b){if(this.multiple){"undefined"==typeof b&&(b=!0),this.findLis();var c=this.$element.find("option"),d=this.$lis.not(".divider, .dropdown-header, .disabled, .hidden"),e=d.length,f=[];if(b){if(d.filter(".selected").length===d.length)return}else if(0===d.filter(".selected").length)return;d.toggleClass("selected",b);for(var g=0;g=48&&c.keyCode<=57||c.keyCode>=96&&c.keyCode<=105||c.keyCode>=65&&c.keyCode<=90))return o.options.container?o.$button.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),void o.$searchbox.focus();if(o.options.liveSearch&&(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(c.preventDefault(),c.stopPropagation(),o.$menuInner.click(),o.$button.focus()),d=a('[role="listbox"] li'+p,n),m.val()||/(38|40)/.test(c.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$menuInner.find("li"),d=o.options.liveSearchNormalize?d.filter(":a"+o._searchStyle()+"("+b(q[c.keyCode])+")"):d.filter(":"+o._searchStyle()+"("+q[c.keyCode]+")"))),d.length){if(/(38|40)/.test(c.keyCode.toString(10)))e=d.index(d.find("a").filter(":focus").parent()),g=d.filter(p).first().index(),h=d.filter(p).last().index(),f=d.eq(e).nextAll(p).eq(0).index(),i=d.eq(e).prevAll(p).eq(0).index(),j=d.eq(f).prevAll(p).eq(0).index(),o.options.liveSearch&&(d.each(function(b){a(this).hasClass("disabled")||a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.first().data("index"),h=d.last().data("index"),f=d.eq(e).nextAll().eq(0).data("index"),i=d.eq(e).prevAll().eq(0).data("index"),j=d.eq(f).prevAll().eq(0).data("index")),k=m.data("prevIndex"),38==c.keyCode?(o.options.liveSearch&&e--,e!=j&&e>i&&(e=i),eh&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(c.preventDefault(),m.hasClass("dropdown-toggle")||(d.removeClass("active").eq(e).addClass("active").children("a").focus(),m.focus())):d.eq(e).children("a").focus();else if(!m.is("input")){var r,s,t=[];d.each(function(){a(this).hasClass("disabled")||a.trim(a(this).children("a").text().toLowerCase()).substring(0,1)==q[c.keyCode]&&t.push(a(this).index())}),r=a(document).data("keycount"),r++,a(document).data("keycount",r),s=a.trim(a(":focus").text().toLowerCase()).substring(0,1),s!=q[c.keyCode]?(r=1,a(document).data("keycount",r)):r>=t.length&&(a(document).data("keycount",0),r>t.length&&(r=1)),d.eq(t[r-1]).children("a").focus()}if((/(13|32)/.test(c.keyCode.toString(10))||/(^9$)/.test(c.keyCode.toString(10))&&o.options.selectOnTab)&&l){if(/(32)/.test(c.keyCode.toString(10))||c.preventDefault(),o.options.liveSearch)/(32)/.test(c.keyCode.toString(10))||(o.$menuInner.find(".active a").click(), +m.focus());else{var u=a(":focus");u.click(),u.focus(),c.preventDefault(),a(document).data("spaceSelect",!0)}a(document).data("keycount",0)}(/(^9$|27)/.test(c.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(c.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.options.container&&o.$newElement.removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device")},refresh:function(){this.$lis=null,this.liObj={},this.reloadLi(),this.render(),this.checkDisabled(),this.liHeight(!0),this.setStyle(),this.setWidth(),this.$lis&&this.$searchbox.trigger("propertychange"),this.$element.trigger("refreshed.bs.select")},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.$element.off(".bs.select").removeData("selectpicker").removeClass("bs-select-hidden selectpicker")}};var l=a.fn.selectpicker;a.fn.selectpicker=c,a.fn.selectpicker.Constructor=k,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=l,this},a(document).data("keycount",0).on("keydown.bs.select",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',k.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input',function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);c.call(b,b.data())})})}(a)}); +//# sourceMappingURL=bootstrap-select.js.map \ No newline at end of file diff --git a/data/web/js/bootstrap-slider.min.js b/data/web/js/bootstrap-slider.min.js new file mode 100644 index 00000000..72363a27 --- /dev/null +++ b/data/web/js/bootstrap-slider.min.js @@ -0,0 +1,5 @@ +/*! ======================================================= + VERSION 9.7.2 +========================================================= */ +"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},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=0;f0)for(var s=0;s0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),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=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||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=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){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=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};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,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._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._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.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];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&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(),this.$element.removeData("slider"))},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(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_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;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),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;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]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetWidth/2+"px")}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");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 p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.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=""):p.right>q.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;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._layout(),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._setDataVal(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="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),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,this._layout();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._state.value[1]/this.options.max*100a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;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 a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d (http://larentis.eu) + * @license Apache-2.0 + */ + +'use strict';var _createClass=function(){function a(b,c){for(var e,d=0;d',{class:function _class(){var h=[];return h.push(g.options.state?'on':'off'),g.options.size&&h.push(g.options.size),g.options.disabled&&h.push('disabled'),g.options.readonly&&h.push('readonly'),g.options.indeterminate&&h.push('indeterminate'),g.options.inverse&&h.push('inverse'),g.$element.attr('id')&&h.push('id-'+g.$element.attr('id')),h.map(g._getClass.bind(g)).concat([g.options.baseClass],g._getClasses(g.options.wrapperClass)).join(' ')}}),this.$container=a('
    ',{class:this._getClass('container')}),this.$on=a('',{html:this.options.onText,class:this._getClass('handle-on')+' '+this._getClass(this.options.onColor)}),this.$off=a('',{html:this.options.offText,class:this._getClass('handle-off')+' '+this._getClass(this.options.offColor)}),this.$label=a('',{html:this.options.labelText,class:this._getClass('label')}),this.$element.on('init.bootstrapSwitch',this.options.onInit.bind(this,e)),this.$element.on('switchChange.bootstrapSwitch',function(){for(var j=arguments.length,h=Array(j),k=0;k-(f._handleWidth/2);f._dragEnd=!1,f.state(f.options.inverse?!h:h)}else f.state(!f.options.state);f._dragStart=!1}},'mouseleave.bootstrapSwitch':function mouseleaveBootstrapSwitch(){f.$label.trigger('mouseup.bootstrapSwitch')}})}},{key:'_externalLabelHandler',value:function _externalLabelHandler(){var f=this,e=this.$element.closest('label');e.on('click',function(g){g.preventDefault(),g.stopImmediatePropagation(),g.target===e[0]&&f.toggleState()})}},{key:'_formHandler',value:function _formHandler(){var e=this.$element.closest('form');e.data('bootstrap-switch')||e.on('reset.bootstrapSwitch',function(){b.setTimeout(function(){e.find('input').filter(function(){return a(this).data('bootstrap-switch')}).each(function(){return a(this).bootstrapSwitch('state',this.checked)})},1)}).data('bootstrap-switch',!0)}},{key:'_getClass',value:function _getClass(e){return this.options.baseClass+'-'+e}},{key:'_getClasses',value:function _getClasses(e){return a.isArray(e)?e.map(this._getClass.bind(this)):[this._getClass(e)]}}]),d}();a.fn.bootstrapSwitch=function(d){for(var f=arguments.length,e=Array(1tbody >#data').length; + var rowCountDomain = $('#domaintable >tbody >#data').length; + var rowCountMailbox = $('#mailboxtable >tbody >#data').length; + var rowCountAlias = $('#aliastable >tbody >#data').length; + var rowCountResource = $('#resourcetable >tbody >#data').length; + $("#numRowsDomainAlias").text(rowCountDomainAlias); + $("#numRowsDomain").text(rowCountDomain); + $("#numRowsMailbox").text(rowCountMailbox); + $("#numRowsAlias").text(rowCountAlias); + $("#numRowsResource").text(rowCountResource); + + // Filter table function + $.fn.extend({ + filterTable: function(){ + return this.each(function(){ + $(this).on('keyup', function(e){ + var $this = $(this), + search = $this.val().toLowerCase(), + target = $this.attr('data-filters'), + $target = $(target), + $rows = $target.find('tbody #data'); + $target.find('tbody .filterTable_no_results').remove(); + if(search == '') { + $target.find('tbody #no-data').show(); + $rows.show(); + } else { + $target.find('tbody #no-data').hide(); + $rows.each(function(){ + var $this = $(this); + $this.text().toLowerCase().indexOf(search) === -1 ? $this.hide() : $this.show(); + }) + if($target.find('tbody #data:visible').size() === 0) { + var col_count = $target.find('#data').first().find('td').size(); + var no_results = $('-') + $target.find('tbody').prepend(no_results); + } + } + }); + }); + } + }); + $('[data-action="filter"]').filterTable(); + $('.container').on('click', '.panel-heading span.filter', function(e){ + var $this = $(this), + $panel = $this.parents('.panel'); + $panel.find('.panel-body').slideToggle("fast"); + if($this.css('display') != 'none') { + $panel.find('.panel-body input').focus(); + } + }); +}); diff --git a/data/web/js/sorttable.js b/data/web/js/sorttable.js new file mode 100644 index 00000000..cb3e293c --- /dev/null +++ b/data/web/js/sorttable.js @@ -0,0 +1,236 @@ +(function() { + var SELECTOR, addEventListener, clickEvents, numberRegExp, sortable, touchDevice, trimRegExp; + + SELECTOR = 'table[data-sortable]'; + + numberRegExp = /^-?[£$¤]?[\d,.]+%?$/; + + trimRegExp = /^\s+|\s+$/g; + + clickEvents = ['click']; + + touchDevice = 'ontouchstart' in document.documentElement; + + if (touchDevice) { + clickEvents.push('touchstart'); + } + + addEventListener = function(el, event, handler) { + if (el.addEventListener != null) { + return el.addEventListener(event, handler, false); + } else { + return el.attachEvent("on" + event, handler); + } + }; + + sortable = { + init: function(options) { + var table, tables, _i, _len, _results; + if (options == null) { + options = {}; + } + if (options.selector == null) { + options.selector = SELECTOR; + } + tables = document.querySelectorAll(options.selector); + _results = []; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; + _results.push(sortable.initTable(table)); + } + return _results; + }, + initTable: function(table) { + var i, th, ths, _i, _len, _ref; + if (((_ref = table.tHead) != null ? _ref.rows.length : void 0) !== 1) { + return; + } + if (table.getAttribute('data-sortable-initialized') === 'true') { + return; + } + table.setAttribute('data-sortable-initialized', 'true'); + ths = table.querySelectorAll('th'); + for (i = _i = 0, _len = ths.length; _i < _len; i = ++_i) { + th = ths[i]; + if (th.getAttribute('data-sortable') !== 'false') { + sortable.setupClickableTH(table, th, i); + } + } + return table; + }, + setupClickableTH: function(table, th, i) { + var eventName, onClick, type, _i, _len, _results; + type = sortable.getColumnType(table, i); + onClick = function(e) { + var compare, item, newSortedDirection, position, row, rowArray, sorted, sortedDirection, tBody, ths, value, _compare, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1; + if (e.handled !== true) { + e.handled = true; + } else { + return false; + } + sorted = this.getAttribute('data-sorted') === 'true'; + sortedDirection = this.getAttribute('data-sorted-direction'); + if (sorted) { + newSortedDirection = sortedDirection === 'ascending' ? 'descending' : 'ascending'; + } else { + newSortedDirection = type.defaultSortDirection; + } + ths = this.parentNode.querySelectorAll('th'); + for (_i = 0, _len = ths.length; _i < _len; _i++) { + th = ths[_i]; + th.setAttribute('data-sorted', 'false'); + th.removeAttribute('data-sorted-direction'); + } + this.setAttribute('data-sorted', 'true'); + this.setAttribute('data-sorted-direction', newSortedDirection); + tBody = table.tBodies[0]; + rowArray = []; + if (!sorted) { + if (type.compare != null) { + _compare = type.compare; + } else { + _compare = function(a, b) { + return b - a; + }; + } + compare = function(a, b) { + if (a[0] === b[0]) { + return a[2] - b[2]; + } + if (type.reverse) { + return _compare(b[0], a[0]); + } else { + return _compare(a[0], b[0]); + } + }; + _ref = tBody.rows; + for (position = _j = 0, _len1 = _ref.length; _j < _len1; position = ++_j) { + row = _ref[position]; + value = sortable.getNodeValue(row.cells[i]); + if (type.comparator != null) { + value = type.comparator(value); + } + rowArray.push([value, row, position]); + } + rowArray.sort(compare); + for (_k = 0, _len2 = rowArray.length; _k < _len2; _k++) { + row = rowArray[_k]; + tBody.appendChild(row[1]); + } + } else { + _ref1 = tBody.rows; + for (_l = 0, _len3 = _ref1.length; _l < _len3; _l++) { + item = _ref1[_l]; + rowArray.push(item); + } + rowArray.reverse(); + for (_m = 0, _len4 = rowArray.length; _m < _len4; _m++) { + row = rowArray[_m]; + tBody.appendChild(row); + } + } + if (typeof window['CustomEvent'] === 'function') { + return typeof table.dispatchEvent === "function" ? table.dispatchEvent(new CustomEvent('Sortable.sorted', { + bubbles: true + })) : void 0; + } + }; + _results = []; + for (_i = 0, _len = clickEvents.length; _i < _len; _i++) { + eventName = clickEvents[_i]; + _results.push(addEventListener(th, eventName, onClick)); + } + return _results; + }, + getColumnType: function(table, i) { + var row, specified, text, type, _i, _j, _len, _len1, _ref, _ref1, _ref2; + specified = (_ref = table.querySelectorAll('th')[i]) != null ? _ref.getAttribute('data-sortable-type') : void 0; + if (specified != null) { + return sortable.typesObject[specified]; + } + _ref1 = table.tBodies[0].rows; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + row = _ref1[_i]; + text = sortable.getNodeValue(row.cells[i]); + _ref2 = sortable.types; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + type = _ref2[_j]; + if (type.match(text)) { + return type; + } + } + } + return sortable.typesObject.alpha; + }, + getNodeValue: function(node) { + var dataValue; + if (!node) { + return ''; + } + dataValue = node.getAttribute('data-value'); + if (dataValue !== null) { + return dataValue; + } + if (typeof node.innerText !== 'undefined') { + return node.innerText.replace(trimRegExp, ''); + } + return node.textContent.replace(trimRegExp, ''); + }, + setupTypes: function(types) { + var type, _i, _len, _results; + sortable.types = types; + sortable.typesObject = {}; + _results = []; + for (_i = 0, _len = types.length; _i < _len; _i++) { + type = types[_i]; + _results.push(sortable.typesObject[type.name] = type); + } + return _results; + } + }; + + sortable.setupTypes([ + { + name: 'numeric', + defaultSortDirection: 'descending', + match: function(a) { + return a.match(numberRegExp); + }, + comparator: function(a) { + return parseFloat(a.replace(/[^0-9.-]/g, ''), 10) || 0; + } + }, { + name: 'date', + defaultSortDirection: 'ascending', + reverse: true, + match: function(a) { + return !isNaN(Date.parse(a)); + }, + comparator: function(a) { + return Date.parse(a) || 0; + } + }, { + name: 'alpha', + defaultSortDirection: 'ascending', + match: function() { + return true; + }, + compare: function(a, b) { + return a.localeCompare(b); + } + } + ]); + + setTimeout(sortable.init, 0); + + if (typeof define === 'function' && define.amd) { + define(function() { + return sortable; + }); + } else if (typeof exports !== 'undefined') { + module.exports = sortable; + } else { + window.Sortable = sortable; + } + +}).call(this); diff --git a/data/web/js/u2f-api.js b/data/web/js/u2f-api.js new file mode 100644 index 00000000..0f06f50d --- /dev/null +++ b/data/web/js/u2f-api.js @@ -0,0 +1,651 @@ +// Copyright 2014-2015 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ + +'use strict'; + +/** Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * The U2F extension id + * @type {string} + * @const + */ +u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response' +}; + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + +/** + * A message type for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * signRequests: Array, + * registerRequests: ?Array, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.Request; + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.Response; + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string, + * appId: string + * }} + */ +u2f.RegisterRequest; + +/** + * Data object for a registration response. + * @typedef {{ + * registrationData: string, + * clientData: string + * }} + */ +u2f.RegisterResponse; + + +// Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format a return a sign request. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ = + function(signRequests, timeoutSeconds, reqId) { + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + }; + +/** + * Format a return a register request. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ = + function(signRequests, registerRequests, timeoutSeconds, reqId) { + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + }; + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } + }; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentLocation = /** @type {string} */ (message); + document.location = intentLocation; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } + }; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + responseObject['requestId'] = this.requestId_; + } + + /* Sign responses from the authenticator do not conform to U2F, + * convert to U2F here. */ + responseObject = this.doResponseFixups_(responseObject); + callback({'data': responseObject}); + }; + +/** + * Fixup the response provided by the Authenticator to conform with + * the U2F spec. + * @param {Object} responseData + * @return {Object} the U2F compliant response object + */ +u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ = + function(responseObject) { + if (responseObject.hasOwnProperty('responseData')) { + return responseObject; + } else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) { + // Only sign responses require fixups. If this is not a response + // to a sign request, then an internal error has occurred. + return { + 'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE, + 'responseData': { + 'errorCode': u2f.ErrorCodes.OTHER_ERROR, + 'errorMessage': 'Internal error: invalid response from Authenticator' + } + }; + } + + /* Non-conformant sign response, do fixups. */ + var encodedChallengeObject = responseObject['challenge']; + if (typeof encodedChallengeObject !== 'undefined') { + var challengeObject = JSON.parse(atob(encodedChallengeObject)); + var serverChallenge = challengeObject['challenge']; + var challengesList = this.requestObject_['signData']; + var requestChallengeObject = null; + for (var i = 0; i < challengesList.length; i++) { + var challengeObject = challengesList[i]; + if (challengeObject['keyHandle'] == responseObject['keyHandle']) { + requestChallengeObject = challengeObject; + break; + } + } + } + var responseData = { + 'errorCode': responseObject['resultCode'], + 'keyHandle': responseObject['keyHandle'], + 'signatureData': responseObject['signature'], + 'clientData': encodedChallengeObject + }; + return { + 'type': u2f.MessageTypes.U2F_SIGN_RESPONSE, + 'responseData': responseData, + 'requestId': responseObject['requestId'] + } + }; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Format a return a sign request. + * @param {Array} signRequests + * @param {number} timeoutSeconds (ignored for now) + * @param {number} reqId + * @return {string} + */ +u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ = + function(signRequests, timeoutSeconds, reqId) { + if (!signRequests || signRequests.length == 0) { + return null; + } + /* TODO(fixme): stash away requestId, as the authenticator app does + * not return it for sign responses. */ + this.requestId_ = reqId; + /* TODO(fixme): stash away the signRequests, to deal with the legacy + * response format returned by the Authenticator app. */ + this.requestObject_ = { + 'type': u2f.MessageTypes.U2F_SIGN_REQUEST, + 'signData': signRequests, + 'requestId': reqId, + 'timeout': timeoutSeconds + }; + + var appId = signRequests[0]['appId']; + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.appId=' + encodeURIComponent(appId) + + ';S.eventId=' + reqId + + ';S.challenges=' + + encodeURIComponent( + JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end'; + return intentUrl; + }; + +/** + * Get the browser data objects from the challenge list + * @param {Array} challenges list of challenges + * @return {Array} list of browser data objects + * @private + */ +u2f.WrappedAuthenticatorPort_ + .prototype.getBrowserDataList_ = function(challenges) { + return challenges + .map(function(challenge) { + var browserData = { + 'typ': 'navigator.id.getAssertion', + 'challenge': challenge['challenge'] + }; + var challengeObject = { + 'challenge' : browserData, + 'keyHandle' : challenge['keyHandle'] + }; + return challengeObject; + }); +}; + +/** + * Format a return a register request. + * @param {Array} signRequests + * @param {Array} enrollChallenges + * @param {number} timeoutSeconds (ignored for now) + * @param {number} reqId + * @return {Object} + */ +u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ = + function(signRequests, enrollChallenges, timeoutSeconds, reqId) { + if (!enrollChallenges || enrollChallenges.length == 0) { + return null; + } + // Assume the appId is the same for all enroll challenges. + var appId = enrollChallenges[0]['appId']; + var registerRequests = []; + for (var i = 0; i < enrollChallenges.length; i++) { + var registerRequest = { + 'challenge': enrollChallenges[i]['challenge'], + 'version': enrollChallenges[i]['version'] + }; + if (enrollChallenges[i]['appId'] != appId) { + // Only include the appId when it differs from the first appId. + registerRequest['appId'] = enrollChallenges[i]['appId']; + } + registerRequests.push(registerRequest); + } + var registeredKeys = []; + if (signRequests) { + for (i = 0; i < signRequests.length; i++) { + var key = { + 'keyHandle': signRequests[i]['keyHandle'], + 'version': signRequests[i]['version'] + }; + // Only include the appId when it differs from the appId that's + // being registered now. + if (signRequests[i]['appId'] != appId) { + key['appId'] = signRequests[i]['appId']; + } + registeredKeys.push(key); + } + } + var request = { + 'type': u2f.MessageTypes.U2F_REGISTER_REQUEST, + 'appId': appId, + 'registerRequests': registerRequests, + 'registeredKeys': registeredKeys, + 'requestId': reqId, + 'timeoutSeconds': timeoutSeconds + }; + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(request)) + + ';end'; + /* TODO(fixme): stash away requestId, this is is not necessary for + * register requests, but here to keep parity with sign. + */ + this.requestId_ = reqId; + return intentUrl; + }; + + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +// High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {Array} signRequests + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(signRequests, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {Array} registerRequests + * @param {Array} signRequests + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(registerRequests, signRequests, + callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = port.formatRegisterRequest_( + signRequests, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; \ No newline at end of file diff --git a/data/web/js/user.js b/data/web/js/user.js new file mode 100644 index 00000000..8d9450be --- /dev/null +++ b/data/web/js/user.js @@ -0,0 +1,33 @@ +$(document).ready(function() { + // Show and activate password fields after box was checked + // Hidden by default + if ( !$("#togglePwNew").is(':checked') ) { + $(".passFields").hide(); + } + $('#togglePwNew').click(function() { + $("#user_new_pass").attr("disabled", !this.checked); + $("#user_new_pass2").attr("disabled", !this.checked); + var $this = $(this); + if ($this.is(':checked')) { + $(".passFields").slideDown(); + } else { + $(".passFields").slideUp(); + } + }); + // Show generate button after time selection + $('#generate_tla').hide(); + $('#validity').change(function(){ + $('#generate_tla').show(); + }); + + // Init Bootstrap Switch + $.fn.bootstrapSwitch.defaults.onColor = 'success'; + $("[name='tls_out']").bootstrapSwitch(); + $("[name='tls_in']").bootstrapSwitch(); + + // Log modal + $('#logModal').on('show.bs.modal', function(e) { + var logText = $(e.relatedTarget).data('log-text'); + $(e.currentTarget).find('#logText').html('
    ' + logText + '
    '); + }); +}); \ No newline at end of file diff --git a/data/web/json_api.php b/data/web/json_api.php new file mode 100644 index 00000000..d404b0bd --- /dev/null +++ b/data/web/json_api.php @@ -0,0 +1,57 @@ +getRegisterData(get_u2f_registrations($object)); + list($req, $sigs) = $data; + $_SESSION['regReq'] = json_encode($req); + echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';'; + } + else { + echo '{}'; + } + break; + case "get_u2f_auth_challenge": + if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) { + $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object))); + $_SESSION['authReq'] = $reqs; + echo 'var req = ' . $reqs . ';'; + } + else { + echo '{}'; + } + break; + default: + echo '{}'; + break; + } + } +} \ No newline at end of file diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php new file mode 100644 index 00000000..f63f1557 --- /dev/null +++ b/data/web/lang/lang.de.php @@ -0,0 +1,448 @@ +
    Wichtig: Ein korrekter Neustart SOGos kann eine Weile in Anspruch nehmen, bitte warten Sie, bis der Prozess vollständig beendet wurde.'; +$lang['dkim']['confirm'] = 'Sind Sie sicher?'; +$lang['danger']['dkim_not_found'] = 'DKIM-Key nicht gefunden'; +$lang['danger']['dkim_remove_failed'] = 'Kann DKIM-Key nicht entfernen'; +$lang['danger']['dkim_add_failed'] = 'Kann DKIM-Key nicht hinzufügen'; +$lang['danger']['dkim_domain_or_sel_invalid'] = 'DKIM-Domain oder -Selector nicht korrekt'; +$lang['danger']['dkim_key_length_invalid'] = 'DKIM Schlüssellänge ungültig'; +$lang['success']['dkim_removed'] = 'DKIM-Key wurde entfernt'; +$lang['success']['dkim_added'] = 'DKIM-Key wurde hinzugefügt'; +$lang['danger']['access_denied'] = 'Zugriff verweigert oder unvollständige/ungültige Daten'; +$lang['danger']['whitelist_from_invalid'] = 'Whitelist-Eintrag ist ungültig'; +$lang['danger']['domain_invalid'] = 'Domainname ist ungültig'; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = 'Maximale Größe für Mailboxen überschreitet das Domain Speicherlimit'; +$lang['danger']['object_is_not_numeric'] = 'Wert %s ist nicht numerisch'; +$lang['success']['domain_added'] = 'Domain %s wurde angelegt'; +$lang['danger']['alias_empty'] = 'Alias-Adresse darf nicht leer sein'; +$lang['danger']['goto_empty'] = 'Ziel-Adresse darf nicht leer sein'; +$lang['danger']['policy_list_from_exists'] = 'Ein Eintrag mit diesem Wert existiert bereits'; +$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ungültiges Format'; +$lang['danger']['alias_invalid'] = 'Alias-Adrese ist ungültig'; +$lang['danger']['goto_invalid'] = 'Ziel-Adrese ist ungültig'; +$lang['danger']['last_key'] = 'Letzter Key kann nicht gelöscht werden'; +$lang['danger']['alias_domain_invalid'] = 'Alias-Domain ist ungültig'; +$lang['danger']['target_domain_invalid'] = 'Ziel-Domain ist ungültig'; +$lang['danger']['object_exists'] = 'Objekt %s existiert bereits'; +$lang['danger']['domain_exists'] = 'Domain %s existiert bereits'; +$lang['danger']['alias_goto_identical'] = 'Alias- und Ziel-Adresse dürfen nicht identisch sein'; +$lang['danger']['aliasd_targetd_identical'] = 'Alias-Domain darf nicht gleich Ziel-Domain sein'; +$lang['success']['alias_added'] = 'Alias-Adresse(n) wurden angelegt'; +$lang['success']['alias_modified'] = 'Änderungen an Alias %s wurden gespeichert'; +$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert'; +$lang['success']['mailbox_modified'] = 'Änderungen an Mailbox %s wurden gespeichert'; +$lang['success']['resource_modified'] = "Änderungen an Ressource %s wurden gespeichert"; +$lang['success']['object_modified'] = "Änderungen an Objekt %s wurden gespeichert"; +$lang['success']['msg_size_saved'] = 'Limit wurde gesetzt'; +$lang['danger']['aliasd_not_found'] = 'Alias-Domain nicht gefunden'; +$lang['danger']['targetd_not_found'] = 'Ziel-Domain nicht gefunden'; +$lang['danger']['aliasd_exists'] = 'Alias-Domain existiert bereits'; +$lang['success']['aliasd_added'] = 'Alias-Domain %s wurde angelegt'; +$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert'; +$lang['success']['domain_modified'] = 'Änderungen an Domain %s wurden gespeichert'; +$lang['success']['domain_admin_modified'] = 'Änderungen an Domain-Administrator %s wurden gespeichert'; +$lang['success']['domain_admin_added'] = 'Domain-Administrator %s wurde angelegt'; +$lang['success']['changes_general'] = 'Änderungen wurden gespeichert'; +$lang['success']['admin_modified'] = 'Änderungen am Administrator wurden gespeichert'; +$lang['danger']['exit_code_not_null'] = 'Fehler: Exit-Code ist %d'; +$lang['danger']['mailbox_not_available'] = 'Mailbox nicht verfügbar'; +$lang['danger']['username_invalid'] = 'Benutzername kann nicht verwendet werden'; +$lang['danger']['password_mismatch'] = 'Passwort-Wiederholung stimmt nicht überein'; +$lang['danger']['password_complexity'] = 'Passwort entspricht nicht den Vorgaben (Klein- und Großschreibung und mindestens eine Ziffer, mindestens 6 Zeichen lang)'; +$lang['danger']['password_empty'] = 'Passwort darf nicht leer sein'; +$lang['danger']['login_failed'] = 'Anmeldung fehlgeschlagen'; +$lang['danger']['mailbox_invalid'] = 'Mailboxname ist ungültig'; +$lang['danger']['resource_invalid'] = 'Ressourcenname ist ungültig'; +$lang['danger']['description_invalid'] = 'Ressourcenbeschreibung ist ungültig'; +$lang['danger']['mailbox_invalid_suggest'] = 'Mailboxname ist ungültig, meinten Sie vielleicht "%s"?'; +$lang['danger']['is_alias'] = '%s lautet bereits eine Alias-Adresse'; +$lang['danger']['is_alias_or_mailbox'] = "Eine Mailbox oder ein Alias mit der Adresse %s ist bereits vorhanden"; +$lang['danger']['is_spam_alias'] = '%s lautet bereits eine Spam-Alias-Adresse'; +$lang['danger']['quota_not_0_not_numeric'] = 'Speicherplatz muss numerisch und >= 0 sein'; +$lang['danger']['domain_not_found'] = 'Domain "%s" nicht gefunden.'; +$lang['danger']['max_mailbox_exceeded'] = 'Anzahl an Mailboxen überschritten (%d von %d)'; +$lang['danger']['mailbox_quota_exceeded'] = 'Speicherplatz überschreitet das Limit (max. %d MiB)'; +$lang['danger']['mailbox_quota_left_exceeded'] = 'Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)'; +$lang['success']['mailbox_added'] = 'Mailbox %s wurde angelegt'; +$lang['success']['resource_added'] = 'Ressource %s wurde angelegt'; +$lang['success']['domain_removed'] = 'Domain %s wurde entfernt'; +$lang['success']['alias_removed'] = 'Alias-Adresse %s wurde entfernt'; +$lang['success']['alias_domain_removed'] = 'Alias-Domain %s wurde entfernt'; +$lang['success']['domain_admin_removed'] = 'Domain-Administrator %s wurde entfernt'; +$lang['success']['mailbox_removed'] = 'Mailbox %s wurde entfernt'; +$lang['success']['eas_reset'] = "ActiveSync Gerät des Benutzers %s wurden zurückgesetzt"; +$lang['success']['resource_removed'] = 'Ressource %s wurde entfernt'; +$lang['danger']['max_quota_in_use'] = 'Mailbox Speicherplatzlimit muss größer oder gleich %d MiB sein'; +$lang['danger']['domain_quota_m_in_use'] = 'Domain Speicherplatzlimit muss größer oder gleich %d MiB sein'; +$lang['danger']['mailboxes_in_use'] = 'Maximale Anzahl an Mailboxen muss größer oder gleich %d sein'; +$lang['danger']['aliases_in_use'] = 'Maximale Anzahl an Aliassen muss größer oder gleich %d sein'; +$lang['danger']['sender_acl_invalid'] = 'Sender ACL Wert muss eine Adresse oder Domain sein'; +$lang['danger']['domain_not_empty'] = 'Kann nur leere Domains entfernen'; +$lang['warning']['spam_alias_temp_error'] = 'Kann zur Zeit keinen Spam-Alias erstellen, bitte versuchen Sie es später noch einmal.'; +$lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adressen erreicht'; +$lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an'; +$lang['user']['on'] = 'Ein'; +$lang['user']['off'] = 'Aus'; +$lang['user']['user_change_fn'] = ''; +$lang['user']['user_settings'] = 'Benutzereinstellungen'; +$lang['user']['mailbox_settings'] = 'Mailbox-Einstellungen'; +$lang['user']['mailbox_details'] = 'Mailbox-Details'; +$lang['user']['change_password'] = 'Passwort ändern'; +$lang['user']['new_password'] = 'Neues Passwort'; +$lang['user']['save_changes'] = 'Änderungen speichern'; +$lang['user']['password_now'] = 'Aktuelles Passwort (Änderungen bestätigen)'; +$lang['user']['new_password_repeat'] = 'Neues Passwort (Wiederholung)'; +$lang['user']['new_password_description'] = 'Mindestanforderung: 6 Zeichen lang, Buchstaben und Zahlen.'; +$lang['user']['did_you_know'] = 'Wussten Sie schon? Sie können Ihre E-Mail-Adresse mit Tags versehen, etwa "ich+Privat@example.com", um Nachrichten automatisch in einem Unterordner (Beispiel: "Privat") abzulegen.'; +$lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['aliases'] = 'Aliasse'; +$lang['user']['domain_aliases'] = 'Domain-Alias Adressen'; +$lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)'; +$lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als'; +$lang['user']['aliases_send_as_all'] = 'Absender für folgende Domains nicht prüfen'; +$lang['user']['alias_create_random'] = 'Zufälligen Alias generieren'; +$lang['user']['alias_extend_all'] = 'Gültigkeit +1h'; +$lang['user']['alias_valid_until'] = 'Gültig bis'; +$lang['user']['alias_remove_all'] = 'Alle entfernen'; +$lang['user']['alias_time_left'] = 'Zeit verbleibend'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['syncjob_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Bitte Gültigkeit auswählen'; +$lang['user']['sync_jobs'] = 'Sync Jobs'; +$lang['user']['hour'] = 'Stunde'; +$lang['user']['hours'] = 'Stunden'; +$lang['user']['day'] = 'Tag'; +$lang['user']['week'] = 'Woche'; +$lang['user']['weeks'] = 'Wochen'; +$lang['user']['spamfilter'] = 'Spamfilter'; +$lang['user']['spamfilter_wl'] = 'Whitelist'; +$lang['user']['spamfilter_wl_desc'] = 'Für E-Mail-Adressen, die vom Spamfilter nicht erfasst werden sollen. Die Verwendung von Wildcards ist gestattet.'; +$lang['user']['spamfilter_bl'] = 'Blacklist'; +$lang['user']['spamfilter_bl_desc'] = 'Für E-Mail-Adressen, die vom Spamfilter immer als Spam erfasst und abgelehnt werden. Die Verwendung von Wildcards ist gestattet.'; +$lang['user']['spamfilter_table_rule'] = 'Regel'; +$lang['user']['spamfilter_table_action'] = 'Aktion'; +$lang['user']['spamfilter_table_empty'] = 'Keine Einträge vorhanden'; +$lang['user']['spamfilter_table_remove'] = 'entfernen'; +$lang['user']['spamfilter_table_add'] = 'Eintrag hinzufügen'; +$lang['user']['spamfilter_behavior'] = 'Bewertung'; +$lang['user']['spamfilter_default_score'] = 'Spam-Score:'; +$lang['user']['spamfilter_green'] = 'Grün: Die Nachricht ist kein Spam'; +$lang['user']['spamfilter_yellow'] = 'Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben'; +$lang['user']['spamfilter_red'] = 'Rot: Die Nachricht ist eindeutig Spam und wird vom Server abgelehnt'; +$lang['user']['spamfilter_default_score'] = 'Standardwert:'; +$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".'; +$lang['user']['spamfilter_table_domain_policy'] = "n.v. (Domainrichtlinie)"; + +$lang['user']['tls_policy_warning'] = 'Vorsicht: Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.
    Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.'; +$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie'; +$lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen'; +$lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen'; +$lang['user']['no_record'] = 'Kein Eintrag'; + +$lang['user']['misc_settings'] = 'Sonstige Kontoeinstellungen'; +$lang['user']['misc_delete_profile'] = 'Sonstige Kontoeinstellungen'; + +$lang['user']['tag_handling'] = 'Umgang mit getaggten E-Mails steuern'; +$lang['user']['tag_in_subfolder'] = 'In Unterordner'; +$lang['user']['tag_in_subject'] = 'In Betreff'; +$lang['user']['tag_help_explain'] = 'Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt ("INBOX/Facebook").
    +In Betreff: Der Name des Tags wird dem Betreff angefügt, etwa "[Facebook] Meine Neuigkeiten".'; +$lang['user']['tag_help_example'] = 'Beispiel für eine getaggte E-Mail-Adresse: ich+Facebook@example.org'; +$lang['user']['eas_reset'] = 'ActiveSync Geräte-Cache zurücksetzen'; +$lang['user']['eas_reset_now'] = 'Jetzt zurücksetzen'; +$lang['user']['eas_reset_help'] = 'In vielen Fällen kann ein ActiveSync Profil durch das Zurücksetzen des Caches repariert werden.
    Vorsicht: Alle Elemente werden erneut heruntergeladen!'; + +$lang['user']['encryption'] = 'Verschlüsselung'; +$lang['user']['username'] = 'Benutzername'; +$lang['user']['password'] = 'Password'; +$lang['user']['last_run'] = 'Letzte Ausführung'; +$lang['user']['excludes'] = 'Ausschlüsse'; +$lang['user']['interval'] = 'Intervall'; +$lang['user']['active'] = 'Aktiv'; +$lang['user']['action'] = 'Aktion'; +$lang['user']['edit'] = 'Bearbeiten'; +$lang['user']['remove'] = 'Entfernen'; +$lang['user']['delete_now'] = 'Sofort löschen'; +$lang['user']['create_syncjob'] = 'Neuen Sync-Job erstellen'; + +$lang['start']['dashboard'] = '%s - Dashboard'; +$lang['start']['start_rc'] = 'Roundcube öffnen'; +$lang['start']['start_sogo'] = 'SOGo öffnen'; +$lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender- und Kontakte zu verwalten und vieles mehr.'; +$lang['start']['mailcow_panel'] = 'mailcow UI starten'; +$lang['start']['mailcow_panel_description'] = 'Die mailcow Steuerung steht sowohl für Administratoren als auch Mailbox-Benutzer zur Verfügung.'; +$lang['start']['mailcow_panel_detail'] = 'Domain-Administratoren erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.
    + Als Mailbox-Benutzer erstellen Sie hier zeitlich limitierte Aliasse, ändern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.'; +$lang['start']['recommended_config'] = 'Empfohlene Software-Konfiguration (ohne ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- und SMTP-Server'; +$lang['start']['imap_smtp_server_description'] = 'Für eine optimale Verbindung empfehlen wir die Verwendung des Mozilla Thunderbirds.'; +$lang['start']['imap_smtp_server_badge'] = 'E-Mail lesen und schreiben'; +$lang['start']['imap_smtp_server_auth_info'] = 'Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.
    +Ihre Anmeldedaten werden durch die obligatorische Verschlüsselung entgegen des Begriffes "PLAIN" nicht unverschlüsselt übertragen.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'E-Mail-Filter'; +$lang['start']['managesieve_description'] = 'Bitte verwenden Sie Mozilla Thunderbirds zusammen mit der Sieve Erweiterung.
    Nach dem Herunterladen der Erweiterung starten Sie Thunderbird, öffnen das Fenster für Erweiterungen und ziehen die heruntergeladene Datei in das offene Fenster.
    Der Servername lautet %s, als Port konfigurieren Sie bitte 4190. Die Anmeldedaten entsprechen dem E-Mail Login.'; +$lang['start']['service'] = 'Dienstname'; +$lang['start']['encryption'] = 'Verschlüsselungstyp'; +$lang['start']['help'] = 'Hilfe ein-/ausblenden'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Port'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Konfiguration'; +$lang['header']['administration'] = 'Administration'; +$lang['header']['mailboxes'] = 'Mailboxen'; +$lang['header']['user_settings'] = 'Benutzereinstellungen'; +$lang['header']['login'] = 'Anmeldung'; +$lang['header']['logged_in_as_logout'] = 'Eingeloggt als %s (abmelden)'; +$lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als %s [%s]'; +$lang['header']['locale'] = 'Sprache'; +$lang['mailbox']['domain'] = 'Domain'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['aliases'] = 'Aliasse'; +$lang['mailbox']['multiple_bookings'] = 'Mehrfachbuchen'; +$lang['mailbox']['kind'] = 'Art'; +$lang['mailbox']['description'] = 'Beschreibung'; +$lang['mailbox']['resources'] = 'Ressourcen'; +$lang['mailbox']['resource_name'] = 'Ressourcenname'; +$lang['mailbox']['domains'] = 'Domains'; +$lang['mailbox']['mailboxes'] = 'Mailboxen'; +$lang['mailbox']['mailbox_quota'] = 'Max. Größe einer Mailbox'; +$lang['mailbox']['domain_quota'] = 'Gesamtspeicher'; +$lang['mailbox']['ratelimit'] = 'Limit ausgehend/Stunde'; +$lang['mailbox']['active'] = 'Aktiv'; +$lang['mailbox']['action'] = 'Aktion'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Domain-Aliasse'; +$lang['mailbox']['target_domain'] = 'Ziel-Domain'; +$lang['mailbox']['target_address'] = 'Ziel-Adresse'; +$lang['mailbox']['username'] = 'Benutzername'; +$lang['mailbox']['fname'] = 'Name'; +$lang['mailbox']['filter_table'] = 'Tabelle filtern'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Speicherplatz'; +$lang['mailbox']['in_use'] = 'Prozentualer Gebrauch'; +$lang['mailbox']['msg_num'] = 'Anzahl Nachrichten'; +$lang['mailbox']['remove'] = 'Entfernen'; +$lang['mailbox']['edit'] = 'Bearbeiten'; +$lang['mailbox']['archive'] = 'Archiv-Zugriff'; +$lang['mailbox']['no_record'] = 'Kein Eintrag für Objekt %s'; +$lang['mailbox']['no_record_single'] = 'Kein Eintrag'; +$lang['mailbox']['add_domain'] = 'Domain hinzufügen'; +$lang['mailbox']['add_domain_alias'] = 'Domain-Alias hinzufügen'; +$lang['mailbox']['add_mailbox'] = 'Mailbox hinzufügen'; +$lang['mailbox']['add_resource'] = 'Ressource hinzufügen'; +$lang['mailbox']['add_alias'] = 'Alias hinzufügen'; + +$lang['info']['no_action'] = 'Keine Aktion anwendbar'; + +$lang['delete']['title'] = 'Objekt entfernen'; +$lang['delete']['remove_domain_warning'] = 'Warnung: Sie entfernen die Domain %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Warnung: Sie entfernen die Alias-Domain %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Warnung: Sie entfernen den Domain-Administrator %s!'; +$lang['delete']['remove_alias_warning'] = 'Warnung: Sie entfernen die Alias-Adresse %s!'; +$lang['delete']['remove_syncjob_warning'] = 'Warnung: Sie entfernen einen Sync-Job des Benutzers %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Warnung: Sie entfernen die Mailbox %s!'; +$lang['delete']['remove_mailbox_details'] = 'Die Mailbox wird vollständig und permanent entfernt!'; +$lang['delete']['remove_resource_warning'] = 'Warnung: Sie entfernen die Ressource %s!'; +$lang['delete']['remove_resource_details'] = 'Die Ressource wird vollständig und permanent entfernt!'; +$lang['delete']['remove_domain_details'] = 'Diese Aktion entfernt ebenfalls Domain-Aliasse.

    Eine Domain muss leer sein, um entfernt zu werden.'; +$lang['delete']['remove_syncjob_details'] = 'Objekte dieses Sync-Jobs werden nicht mehr vom entfernten Server abgeholt.'; +$lang['delete']['remove_alias_details'] = 'Benutzer werden keine Nachrichten mehr von dieser Adresse erhalten und versenden koennen!'; +$lang['delete']['remove_button'] = 'Entfernen'; +$lang['delete']['previous'] = 'Vorherige Seite'; + +$lang['edit']['syncjob'] = 'Sync-Job bearbeiten'; +$lang['edit']['save'] = 'Änderungen speichern'; +$lang['edit']['username'] = 'Benutzername'; +$lang['edit']['hostname'] = 'Servername'; +$lang['edit']['encryption'] = 'Verschlüsselungsmethode'; +$lang['edit']['maxage'] = 'Maximales Alter in Tagen einer Nachricht, die kopiert werden soll
    (0 = alle Nachrichten kopieren)'; +$lang['edit']['subfolder2'] = 'Ziel-Ordner
    (leer = kein Unterordner)'; +$lang['edit']['mins_interval'] = 'Intervall (min)'; +$lang['edit']['exclude'] = 'Elemente ausschließen (Regex)'; +$lang['edit']['archive'] = 'Archiv-Zugriff'; +$lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:'; +$lang['edit']['title'] = 'Objekt bearbeiten'; +$lang['edit']['target_address'] = 'Ziel-Adresse(n) (getrennt durch Komma):'; +$lang['edit']['active'] = 'Aktiv'; +$lang['edit']['target_domain'] = 'Ziel-Domain:'; +$lang['edit']['password'] = 'Passwort:'; +$lang['edit']['ratelimit'] = 'Limit ausgehender Nachrichten/Stunde:'; +$lang['danger']['ratelimt_less_one'] = 'Limit ausgehender Nachrichten/Stunde darf nicht kleiner als 1 sein'; +$lang['edit']['password_repeat'] = 'Passwort (Wiederholung):'; +$lang['edit']['domain_admin'] = 'Domain-Administrator bearbeiten'; +$lang['edit']['domain'] = 'Domain bearbeiten'; +$lang['edit']['edit_alias_domain'] = 'Alias-Domain bearbeiten'; +$lang['edit']['alias_domain'] = 'Alias-Domain'; +$lang['edit']['domains'] = 'Domains'; +$lang['edit']['destroy'] = 'Manuelle Eingabe des Wertes'; +$lang['edit']['alias'] = 'Alias bearbeiten'; +$lang['edit']['mailbox'] = 'Mailbox bearbeiten'; +$lang['edit']['description'] = 'Beschreibung:'; +$lang['edit']['max_aliases'] = 'Max. Aliasse:'; +$lang['edit']['max_quota'] = 'Max. Größe per Mailbox (MiB):'; +$lang['edit']['domain_quota'] = 'Domain Speicherplatz gesamt (MiB):'; +$lang['edit']['backup_mx_options'] = 'Backup MX Optionen:'; +$lang['edit']['relay_domain'] = 'Relay Domain'; +$lang['edit']['relay_all'] = 'Alle Empfänger-Adressen relayen'; +$lang['edit']['dkim_signature'] = 'DKIM-Signatur:'; +$lang['edit']['dkim_record_info'] = 'Bitte hinterlegen Sie einen TXT-Record mit obigem Wert in den DNS-Einstellungen Ihrer Domainverwaltung.'; +$lang['edit']['relay_all_info'] = 'Wenn Sie nicht alle Empfänger-Adressen relayen möchten, müssen Sie eine ("blinde") Mailbox für jede Adresse, die relayt werden soll, erstellen.'; +$lang['edit']['full_name'] = 'Voller Name'; +$lang['edit']['quota_mb'] = 'Speicherplatz (MiB)'; +$lang['edit']['sender_acl'] = 'Darf Nachrichten versenden als'; +$lang['edit']['sender_acl_info'] = 'Aliasse sind nicht abwählbar und vorausgewählt.'; +$lang['edit']['dkim_txt_name'] = 'TXT-Record Name:'; +$lang['edit']['dkim_txt_value'] = 'TXT-Record Wert:'; +$lang['edit']['previous'] = 'Vorherige Seite'; +$lang['edit']['unchanged_if_empty'] = 'Unverändert, wenn leer'; +$lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s nicht prüfen'; +$lang['edit']['multiple_bookings'] = 'Mehrfaches Buchen'; +$lang['edit']['kind'] = 'Art'; +$lang['edit']['resource'] = 'Ressource'; + +$lang['add']['syncjob'] = 'Sync-Job erstellen'; +$lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!'; +$lang['add']['hostname'] = 'Servername'; +$lang['add']['username'] = 'Benutzername'; +$lang['add']['enc_method'] = 'Verschlüsselungsmethode'; +$lang['add']['maxage'] = 'Maximum age of messages that will be polled from remote (0 = ignore age)'; +$lang['add']['subfolder2'] = 'Sync into subfolder on destination'; +$lang['add']['mins_interval'] = 'Abrufintervall (Minuten)'; +$lang['add']['exclude'] = 'Elemente ausschließen (Regex)'; +$lang['add']['delete2duplicates'] = 'Lösche Duplikate im Ziel'; + +$lang['add']['title'] = 'Objekt anlegen'; +$lang['add']['domain'] = 'Domain'; +$lang['add']['active'] = 'Aktiv'; +$lang['add']['multiple_bookings'] = 'Mehrfaches Buchen möglich'; +$lang['add']['save'] = 'Änderungen speichern'; +$lang['add']['description'] = 'Beschreibung:'; +$lang['add']['max_aliases'] = 'Max. mögliche Aliasse:'; +$lang['add']['max_mailboxes'] = 'Max. mögliche Mailboxen:'; +$lang['add']['mailbox_quota_m'] = 'Max. Speicherplatz pro Mailbox (MiB):'; +$lang['add']['domain_quota_m'] = 'Domain Speicherplatz gesamt (MiB):'; +$lang['add']['backup_mx_options'] = 'Backup MX Optionen:'; +$lang['add']['relay_all'] = 'Alle Empfänger-Adressen relayen'; +$lang['add']['relay_domain'] = 'Relay Domain'; +$lang['add']['relay_all_info'] = 'Wenn Sie nicht alle Empfänger-Adressen relayen möchten, müssen Sie eine Mailbox für jede Adresse, die relayt werden soll, erstellen.'; +$lang['add']['alias'] = 'Alias(se)'; +$lang['add']['alias_spf_fail'] = 'Hinweis: Wählen Sie ein externes Postfach als Ziel-Adresse, kann es unter Umständen zu fehlerhaften Spam-Erkennungen beim Empfänger kommen. Weitere Informationen zu diesem Thema finden Sie hier.'; +$lang['add']['alias_address'] = 'Alias-Adresse(n):'; +$lang['add']['alias_address_info'] = 'Vollständige E-Mail-Adresse(n) oder @example.com, um alle Nachrichten einer Domain weiterzuleiten. Getrennt durch Komma. Nur eigene Domains.'; +$lang['add']['alias_domain_info'] = 'Nur gültige Domains. Getrennt durch Komma.'; +$lang['add']['target_address'] = 'Ziel-Adresse(n):'; +$lang['add']['target_address_info'] = 'Vollständige E-Mail-Adresse(n). Getrennt durch Komma.'; +$lang['add']['alias_domain'] = 'Alias-Domain'; +$lang['add']['select'] = 'Bitte auswählen'; +$lang['add']['target_domain'] = 'Ziel-Domain:'; +$lang['add']['mailbox'] = 'Mailbox'; +$lang['add']['resource'] = 'Ressource'; +$lang['add']['kind'] = 'Art'; +$lang['add']['mailbox_username'] = 'Benutzername (linker Teil der E-Mail-Adresse):'; +$lang['add']['resource_name'] = 'Ressourcenname:'; +$lang['add']['full_name'] = 'Vor- und Zuname:'; +$lang['add']['quota_mb'] = 'Speicherplatz (MiB):'; +$lang['add']['select_domain'] = 'Bitte zuerst eine Domain auswählen'; +$lang['add']['password'] = 'Passwort:'; +$lang['add']['password_repeat'] = 'Passwort (Wiederholung):'; +$lang['add']['previous'] = 'Vorherige Seite'; +$lang['add']['restart_sogo_hint'] = 'Der SOGo Container muss nach dem Hinzufügen einer neuen Domain neugestartet werden!'; + +$lang['login']['title'] = 'Anmeldung'; +$lang['login']['administration'] = 'Administration'; +$lang['login']['administration_details'] = 'Bitte verwenden Sie Ihre Administrator Anmeldedaten, um administrative Aufgaben wie das Anlegen einer Mailbox zu starten.'; +$lang['login']['user_settings'] = 'Benutzereinstellungen'; +$lang['login']['user_settings_details'] = 'Als E-Mail Benutzer vewenden Sie bitte Ihre E-Mail Anmeldedaten, um Passwörter zu verändern, temporäre (Spam-)Aliasse zu erstellen, den Spamfilter einzustellen oder auch um E-Mails zu importieren.'; +$lang['login']['username'] = 'Benutzername'; +$lang['login']['password'] = 'Passwort'; +$lang['login']['reset_password'] = 'Mein Passwort zurücksetzen'; +$lang['login']['login'] = 'Anmelden'; +$lang['login']['previous'] = 'Vorherige Seite'; +$lang['login']['delayed'] = 'Login wurde zur Sicherheit um %s Sekunde/n verzögert.'; + +$lang['tfa']['tfa'] = "Two-Factor Authentication"; +$lang['tfa']['set_tfa'] = "Konfiguriere Two-Factor Authentication Methode"; +$lang['tfa']['yubi_otp'] = "Yubico OTP Authentifizierung"; +$lang['tfa']['key_id'] = "Ein Name für diesen YubiKey"; +$lang['tfa']['api_register'] = 'mailcow verwendet die Yubico Cloud API. Ein API-Key für den Yubico Stick kann hier bezogen werden.'; +$lang['tfa']['u2f'] = "U2F Authentifizierung"; +$lang['tfa']['hotp'] = "HOTP Authentifizierung"; +$lang['tfa']['totp'] = "TOTP Authentifizierung"; +$lang['tfa']['none'] = "Deaktiviert"; +$lang['tfa']['delete_tfa'] = "Deaktiviere TFA"; +$lang['tfa']['disable_tfa'] = "Deaktiviere TFA bis zur nächsten erfolgreichen Anmeldung"; +$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field"; +$lang['tfa']['confirm'] = "Bestätigen"; +$lang['tfa']['otp'] = "Einmalpasswort"; +$lang['tfa']['trash_login'] = "Login verwerfen"; +$lang['tfa']['select'] = "Bitte auswählen"; +$lang['tfa']['waiting_usb_auth'] = "Warte auf USB-Gerät...

    Bitte jetzt den vorgesehenen Taster des U2F USB-Gerätes berühren."; +$lang['tfa']['waiting_usb_register'] = "Warte auf USB-Gerät...

    Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des U2F USB-Gerätes berühren."; + +$lang['admin']['search_domain_da'] = 'Domains durchsuchen'; +$lang['admin']['restrictions'] = 'Postifx Restriktionen'; +$lang['admin']['rr'] = 'Postifx Recipient Restriktionen'; +$lang['admin']['sr'] = 'Postifx Sender Restriktionen'; +$lang['admin']['reset_defaults'] = 'Standard wiederherstellen'; +$lang['admin']['r_inactive'] = 'Inaktive Restriktionen'; +$lang['admin']['r_active'] = 'Aktive Restriktionen'; +$lang['admin']['r_info'] = 'Ausgegraute/deaktivierte Elemente sind mailcow nicht bekannt und können nicht in die Liste inaktiver Elemente verschoben werden. Unbekannte Restriktionen werden trotzdem in Reihenfolge der Erscheinung gesetzt.
    Sie können ein Element in der Datei inc/vars.local.inc.php als bekannt hinzufügen, um es zu bewegen.'; +$lang['admin']['public_folders'] = 'Öffentliche Ordner'; +$lang['admin']['public_folders_text'] = 'Ein Namespace "Public" wird erstellt. Der untenstehende Ordnername betrifft den Namen der automatisch erstellten Mailbox in diesem Namespace.'; +$lang['admin']['public_folder_name'] = 'Ordnername (alphanumerisch)'; +$lang['admin']['public_folder_enable'] = 'Öffentliche Ordner aktivieren'; +$lang['admin']['public_folder_enable_text'] = 'Das Umschalten dieser Option entfernt keine Nachrichten aus den öffentlichen Ordnern.'; +$lang['admin']['public_folder_pusf'] = 'Aktiviere "per-user seen flag"'; +$lang['admin']['public_folder_pusf_text'] = 'Ein "per-user seen flag"-aktiviertes System markiert Nachrichten nicht als gelesen, wenn nur ein Benutzer sie gelesen hat. Jeder Benutzer verwaltet seine eigenen "seen flags".'; +$lang['admin']['privacy'] = 'Datenschutz'; +$lang['admin']['privacy_text'] = 'Diese Option aktiviert eine PCRE-Prüfung, die die Werte der Kopfzeilen "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" sowie "Received: from" durch "localhost" bzw. "127.0.0.1" ersetzt.'; +$lang['admin']['privacy_anon_mail'] = 'Anonymisiere ausgehende Kopfzeilen'; +$lang['admin']['msg_size'] = 'Aktuelles Limit der Nachrichtengröße'; +$lang['admin']['msg_size_limit'] = 'Aktuelles Limit der Nachrichtengröße'; +$lang['admin']['msg_size_limit_details'] = 'Diese Einstellung wird Postfix und den Webserver neuladen.'; +$lang['admin']['save'] = 'Änderungen speichern'; +$lang['admin']['maintenance'] = 'Wartung und Information'; +$lang['admin']['sys_info'] = 'Systeminformation'; +$lang['admin']['dkim_add_key'] = 'DKIM-Key hinzufügen'; +$lang['admin']['dkim_keys'] = 'DKIM-Keys'; +$lang['admin']['dkim_key_valid'] = 'Key gültig'; +$lang['admin']['dkim_key_unused'] = 'Key ohne Zuweisung'; +$lang['admin']['dkim_key_missing'] = 'Key fehlt'; +$lang['admin']['dkim_key_hint'] = 'Der Selector für DKIM-Keys lautet immer dkim.'; +$lang['admin']['add'] = 'Hinzufügen'; +$lang['admin']['configuration'] = 'Konfiguration'; +$lang['admin']['password'] = 'Passwort'; +$lang['admin']['password_repeat'] = 'Passwort (Wiederholung)'; +$lang['admin']['active'] = 'Aktiv'; +$lang['admin']['action'] = 'Aktion'; +$lang['admin']['add_domain_admin'] = 'Domain-Administrator hinzufügen'; +$lang['admin']['admin_domains'] = 'Domain-Zuweisungen'; +$lang['admin']['domain_admins'] = 'Domain-Administratoren'; +$lang['admin']['username'] = 'Benutzername'; +$lang['admin']['edit'] = 'Bearbeiten'; +$lang['admin']['remove'] = 'Entfernen'; +$lang['admin']['save'] = 'Änderungen speichern'; +$lang['admin']['admin'] = 'Administrator'; +$lang['admin']['admin_details'] = 'Administrator bearbeiten'; +$lang['admin']['unchanged_if_empty'] = 'Unverändert, wenn leer'; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Zugang'; +$lang['admin']['invalid_max_msg_size'] = 'Invalid max. message size'; // NEEDS TRANSLATION +$lang['admin']['site_not_found'] = 'Kann mailcow Site-Konfiguration nicht finden'; +$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; // NEEDS TRANSLATION +$lang['admin']['set_rr_failed'] = 'Kann Postfix Restriktionen nicht setzen'; +$lang['admin']['no_record'] = 'Kein Eintrag'; +?> diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php new file mode 100644 index 00000000..7abafe13 --- /dev/null +++ b/data/web/lang/lang.en.php @@ -0,0 +1,458 @@ +
    Important: A graceful restart may take a while to complete, please wait for it to finish.'; +$lang['dkim']['confirm'] = "Are you sure?"; +$lang['danger']['dkim_not_found'] = "DKIM key not found"; +$lang['danger']['dkim_remove_failed'] = "Cannot remove selected DKIM key"; +$lang['danger']['dkim_add_failed'] = "Cannot add given DKIM key"; +$lang['danger']['dkim_domain_or_sel_invalid'] = "DKIM domain or selector invalid"; +$lang['danger']['dkim_key_length_invalid'] = "DKIM key length invalid"; +$lang['success']['dkim_removed'] = "DKIM key has been removed"; +$lang['success']['dkim_added'] = "DKIM key has been saved"; +$lang['danger']['access_denied'] = "Access denied or invalid form data"; +$lang['danger']['whitelist_from_invalid'] = "Whitelist entry invalid"; +$lang['danger']['domain_invalid'] = "Domain name is invalid"; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. quota exceeds domain quota limit"; +$lang['danger']['object_is_not_numeric'] = "Value %s is not numeric"; +$lang['success']['domain_added'] = "Added domain %s"; +$lang['danger']['alias_empty'] = "Alias address must not be empty"; +$lang['danger']['last_key'] = 'Last key cannot be deleted'; +$lang['danger']['goto_empty'] = "Goto address must not be empty"; +$lang['danger']['policy_list_from_exists'] = "A record with given name exists"; +$lang['danger']['policy_list_from_invalid'] = "Record has invalid format"; +$lang['danger']['whitelist_exists'] = "A whitelist record with that name exists"; +$lang['danger']['whitelist_from_invalid'] = "Whitelist record has invalid format"; +$lang['danger']['alias_invalid'] = "Alias address is invalid"; +$lang['danger']['goto_invalid'] = "Goto address is invalid"; +$lang['danger']['alias_domain_invalid'] = "Alias domain is invalid"; +$lang['danger']['target_domain_invalid'] = "Goto domain is invalid"; +$lang['danger']['object_exists'] = "Object %s already exists"; +$lang['danger']['domain_exists'] = "Domain %s already exists"; +$lang['danger']['alias_goto_identical'] = "Alias and goto address must not be identical"; +$lang['danger']['aliasd_targetd_identical'] = "Alias domain must not be equal to target domain"; +$lang['success']['alias_added'] = "Alias address/es has/have been added"; +$lang['success']['alias_modified'] = "Changes to alias have been saved"; +$lang['success']['aliasd_modified'] = "Changes to alias domain have been saved"; +$lang['success']['mailbox_modified'] = "Changes to mailbox %s have been saved"; +$lang['success']['resource_modified'] = "Changes to mailbox %s have been saved"; +$lang['success']['object_modified'] = "Changes to object %s have been saved"; +$lang['success']['msg_size_saved'] = "Message size limit has been set"; +$lang['danger']['aliasd_not_found'] = "Alias domain not found"; +$lang['danger']['targetd_not_found'] = "Target domain not found"; +$lang['danger']['aliasd_exists'] = "Alias domain already exists"; +$lang['success']['aliasd_added'] = "Added alias domain %s"; +$lang['success']['aliasd_modified'] = "Changes to alias domain %s have been saved"; +$lang['success']['domain_modified'] = "Changes to domain %s have been saved"; +$lang['success']['domain_admin_modified'] = "Changes to domain administrator %s have been saved"; +$lang['success']['domain_admin_added'] = "Domain administrator %s has been added"; +$lang['success']['changes_general'] = 'Changes have been saved'; +$lang['success']['admin_modified'] = "Changes to administrator have been saved"; +$lang['danger']['exit_code_not_null'] = "Error: Exit code was %d"; +$lang['danger']['mailbox_not_available'] = "Mailbox not available"; +$lang['danger']['username_invalid'] = "Username cannot be used"; +$lang['danger']['password_mismatch'] = "Confirmation password is not identical"; +$lang['danger']['password_complexity'] = "Password does not meet requirements (upper and lowercase letters and at least one number, min. 6 characters long)"; +$lang['danger']['password_empty'] = "Password must not be empty"; +$lang['danger']['login_failed'] = "Login failed"; +$lang['danger']['mailbox_invalid'] = "Mailbox name is invalid"; +$lang['danger']['description_invalid'] = 'Resource description is invalid'; +$lang['danger']['resource_invalid'] = "Resource name is invalid"; +$lang['danger']['mailbox_invalid_suggest'] = 'Mailbox name is invalid, did you mean to type "%s"?'; +$lang['danger']['is_alias'] = "%s is already known as an alias address"; +$lang['danger']['is_alias_or_mailbox'] = "%s is already known as an alias or a mailbox"; +$lang['danger']['is_spam_alias'] = "%s is already known as a spam alias address"; +$lang['danger']['quota_not_0_not_numeric'] = "Quota must be numeric and >= 0"; +$lang['danger']['domain_not_found'] = "Domain not found."; +$lang['danger']['max_mailbox_exceeded'] = "Max. mailboxes exceeded (%d of %d)"; +$lang['danger']['mailbox_quota_exceeded'] = "Quota exceeds the domain limit (max. %d MiB)"; +$lang['danger']['mailbox_quota_left_exceeded'] = "Not enough space left (space left: %d MiB)"; +$lang['success']['mailbox_added'] = "Mailbox %s has been added"; +$lang['success']['resource_added'] = "Resource %s has been added"; +$lang['success']['domain_removed'] = "Domain %s has been removed"; +$lang['success']['alias_removed'] = "Alias-Adresse %s has been removed"; +$lang['success']['alias_domain_removed'] = "Alias domain %s has been removed"; +$lang['success']['domain_admin_removed'] = "Domain administrator %s has been removed"; +$lang['success']['mailbox_removed'] = "Mailbox %s has been removed"; +$lang['success']['eas_reset'] = "ActiveSync devices for user %s were reset"; +$lang['success']['resource_removed'] = "Resource %s has been removed"; +$lang['danger']['max_quota_in_use'] = "Mailbox quota must be greater or equal to %d MiB"; +$lang['danger']['domain_quota_m_in_use'] = "Domain quota must be greater or equal to %s MiB"; +$lang['danger']['mailboxes_in_use'] = "Max. mailboxes must be greater or equal to %d"; +$lang['danger']['aliases_in_use'] = "Max. aliases must be greater or equal to %d"; +$lang['danger']['sender_acl_invalid'] = "Sender ACL value is invalid"; +$lang['danger']['domain_not_empty'] = "Cannot remove non-empty domain"; +$lang['warning']['spam_alias_temp_error'] = "Temporary error: Cannot add spam alias, please try again later."; +$lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses exceeded"; +$lang['danger']['validity_missing'] = 'Please assign a period of validity'; +$lang['user']['on'] = "On"; +$lang['user']['off'] = "Off"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = 'User settings'; +$lang['user']['mailbox_settings'] = 'Mailbox settings'; +$lang['user']['mailbox_details'] = 'Mailbox details'; +$lang['user']['change_password'] = 'Change password'; +$lang['user']['new_password'] = 'New password'; +$lang['user']['save_changes'] = 'Save changes'; +$lang['user']['password_now'] = 'Current password (confirm changes)'; +$lang['user']['new_password_repeat'] = 'Confirmation password (repeat)'; +$lang['user']['new_password_description'] = 'Requirement: 6 characters long, letters and numbers.'; +$lang['user']['did_you_know'] = 'Did you know? You can use tags in your email address ("me+privat@example.com") to move messages to a folder automatically (example: "privat").'; +$lang['user']['spam_aliases'] = 'Temporary email aliases'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['aliases'] = 'Aliases'; +$lang['user']['domain_aliases'] = 'Domain alias addresses'; +$lang['user']['is_catch_all'] = 'Catch-all for domain/s'; +$lang['user']['aliases_also_send_as'] = 'Also allowed to send as'; +$lang['user']['aliases_send_as_all'] = 'Do not check sender access for following domain/s'; +$lang['user']['alias_create_random'] = 'Generate random alias'; +$lang['user']['alias_extend_all'] = 'Extend aliases by 1 hour'; +$lang['user']['alias_valid_until'] = 'Valid until'; +$lang['user']['alias_remove_all'] = 'Remove all aliases'; +$lang['user']['alias_time_left'] = 'Time left'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['syncjob_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Period of validity'; +$lang['user']['sync_jobs'] = 'Sync jobs'; +$lang['user']['hour'] = 'Hour'; +$lang['user']['hours'] = 'Hours'; +$lang['user']['day'] = 'Day'; +$lang['user']['week'] = 'Week'; +$lang['user']['weeks'] = 'Weeks'; +$lang['user']['spamfilter'] = 'Spam filter'; +$lang['user']['spamfilter_wl'] = 'Whitelist'; +$lang['user']['spamfilter_wl_desc'] = 'Whitelisted email addresses to never classify as spam. Wildcards maybe used.'; +$lang['user']['spamfilter_bl'] = 'Blacklist'; +$lang['user']['spamfilter_bl_desc'] = 'Blacklisted email addresses to always classify as spam and reject. Wildcards maybe used.'; +$lang['user']['spamfilter_behavior'] = 'Rating'; +$lang['user']['spamfilter_table_rule'] = 'Rule'; +$lang['user']['spamfilter_table_action'] = 'Action'; +$lang['user']['spamfilter_table_empty'] = 'No data to display'; +$lang['user']['spamfilter_table_remove'] = 'remove'; +$lang['user']['spamfilter_table_add'] = 'Add item'; +$lang['user']['spamfilter_default_score'] = 'Spam score:'; +$lang['user']['spamfilter_green'] = 'Green: this message is not spam'; +$lang['user']['spamfilter_yellow'] = 'Yellow: this message may be spam, will be tagged as spam and moved to your junk folder'; +$lang['user']['spamfilter_red'] = 'Red: This message is spam and will be rejected by the server'; +$lang['user']['spamfilter_default_score'] = 'Default values:'; +$lang['user']['spamfilter_hint'] = 'The first value describes the "low spam score", the second represents the "high spam score".'; +$lang['user']['spamfilter_table_domain_policy'] = "n/a (domain policy)"; + +$lang['user']['tls_policy_warning'] = 'Warning: If you decide to enforce encrypted mail transfer, you may lose emails.
    Messages to not satisfy the policy will be bounced with a hard fail by the mail system.'; +$lang['user']['tls_policy'] = 'Encryption policy'; +$lang['user']['tls_enforce_in'] = 'Enforce TLS incoming'; +$lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing'; +$lang['user']['no_record'] = 'No record'; + +$lang['user']['misc_settings'] = 'Other profile settings'; +$lang['user']['misc_delete_profile'] = 'Other profile settings'; + +$lang['user']['tag_handling'] = 'Set handling for tagged mail'; +$lang['user']['tag_in_subfolder'] = 'In subfolder'; +$lang['user']['tag_in_subject'] = 'In subject'; +$lang['user']['tag_help_explain'] = 'In subfolder: a new subfolder named after the tag will be created below INBOX ("INBOX/Facebook").
    +In subject: the tags name will be prepended to the mails subject, example: "[Facebook] Meine Neuigkeiten".'; +$lang['user']['tag_help_example'] = 'Example for a tagged email address: ich+Facebook@example.org'; +$lang['user']['eas_reset'] = 'Reset ActiveSync device cache'; +$lang['user']['eas_reset_now'] = 'Reset now'; +$lang['user']['eas_reset_help'] = 'In many cases a device cache reset will help to recover a broken ActiveSync profile.
    Attention: All elements will be redownloaded!'; + +$lang['user']['encryption'] = 'Encyrption'; +$lang['user']['username'] = 'Username'; +$lang['user']['password'] = 'Password'; +$lang['user']['last_run'] = 'Last run'; +$lang['user']['excludes'] = 'Excludes'; +$lang['user']['interval'] = 'Interval'; +$lang['user']['active'] = 'Active'; +$lang['user']['action'] = 'Action'; +$lang['user']['edit'] = 'Edit'; +$lang['user']['remove'] = 'Remove'; +$lang['user']['delete_now'] = 'Remove now'; +$lang['user']['create_syncjob'] = 'Create new sync job'; + +$lang['start']['dashboard'] = '%s - dashboard'; +$lang['start']['start_rc'] = 'Open Roundcube'; +$lang['start']['start_sogo'] = 'Open SOGo'; +$lang['start']['mailcow_apps_detail'] = 'Use a mailcow app to access your mails, calendar, contacts and more.'; +$lang['start']['mailcow_panel'] = 'Start mailcow UI'; +$lang['start']['mailcow_panel_description'] = 'The mailcow UI is available for administrators and mailbox users.'; +$lang['start']['mailcow_panel_detail'] = 'Domain administrators create, modify or delete mailboxes and aliases, change domains and read further information about their assigned domains.
    + Mailbox users are able to create time-limited aliases (spam aliases), change their password and spam filter settings.'; +$lang['start']['recommended_config'] = 'Recommended configuration (without ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- and SMTP server data'; +$lang['start']['imap_smtp_server_description'] = 'For the best experience we recommend to use Mozilla Thunderbird.'; +$lang['start']['imap_smtp_server_badge'] = 'Read/Write emails'; +$lang['start']['imap_smtp_server_auth_info'] = 'Please use your full email address and the PLAIN authentication mechanism.
    +Your login data will be encrypted by the server-side mandatory encryption.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Email filter'; +$lang['start']['managesieve_description'] = 'Please use Mozilla Thunderbird with the nightly sieve extension.
    Start Thunderbird, open the add-on settings and drop the newly downloaded xpi file into the opened window.
    The server name is %s, use port 4190 if you are asked for. The login data match your email login.'; +$lang['start']['service'] = 'Service'; +$lang['start']['encryption'] = 'Encryption method'; +$lang['start']['help'] = 'Show/Hide help panel'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Port'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Configuration'; +$lang['header']['administration'] = 'Administration'; +$lang['header']['mailboxes'] = 'Mailboxes'; +$lang['header']['user_settings'] = 'User settings'; +$lang['header']['login'] = 'Login'; +$lang['header']['logged_in_as_logout'] = 'Logged in as %s (logout)'; +$lang['header']['logged_in_as_logout_dual'] = 'Logged in as %s [%s]'; +$lang['header']['locale'] = 'Language'; +$lang['mailbox']['domain'] = 'Domain'; +$lang['mailbox']['multiple_bookings'] = 'Multiple bookings'; +$lang['mailbox']['kind'] = 'Kind'; +$lang['mailbox']['description'] = 'Description'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['resource_name'] = 'Resource name'; +$lang['mailbox']['aliases'] = 'Aliases'; +$lang['mailbox']['domains'] = 'Domains'; +$lang['mailbox']['mailboxes'] = 'Mailboxes'; +$lang['mailbox']['resources'] = 'Resources'; +$lang['mailbox']['mailbox_quota'] = 'Max. size of a mailbox'; +$lang['mailbox']['domain_quota'] = 'Quota'; +$lang['mailbox']['active'] = 'Active'; +$lang['mailbox']['action'] = 'Action'; +$lang['mailbox']['ratelimit'] = 'Outgoing rate limit/h'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Domain aliases'; +$lang['mailbox']['target_domain'] = 'Target domain'; +$lang['mailbox']['target_address'] = 'Goto address'; +$lang['mailbox']['username'] = 'Username'; +$lang['mailbox']['fname'] = 'Full name'; +$lang['mailbox']['filter_table'] = 'Filter table'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Quota'; +$lang['mailbox']['in_use'] = 'In use (%)'; +$lang['mailbox']['msg_num'] = 'Message #'; +$lang['mailbox']['remove'] = 'Remove'; +$lang['mailbox']['edit'] = 'Edit'; +$lang['mailbox']['archive'] = 'Archive'; +$lang['mailbox']['no_record'] = 'No record for object %s'; +$lang['mailbox']['no_record_single'] = 'No record'; +$lang['mailbox']['add_domain'] = 'Add domain'; +$lang['mailbox']['add_domain_alias'] = 'Add domain alias'; +$lang['mailbox']['add_mailbox'] = 'Add mailbox'; +$lang['mailbox']['add_resource'] = 'Add resource'; +$lang['mailbox']['add_alias'] = 'Add alias'; + +$lang['info']['no_action'] = 'No action applicable'; + +$lang['delete']['title'] = 'Remove object'; +$lang['delete']['remove_domain_warning'] = 'Warning: You are about to remove the domain %s!'; +$lang['delete']['remove_syncjob_warning'] = 'Warning: You are about to remove a sync job for user %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Warning: You are about to remove the domain alias %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Warning: You are about to remove the domain administrator %s!'; +$lang['delete']['remove_alias_warning'] = 'Warning: You are about to remove the alias address %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Warning: You are about to remove the mailbox %s!'; +$lang['delete']['remove_mailbox_details'] = 'The mailbox will be purged permanently!'; +$lang['delete']['remove_resource_warning'] = 'Warning: You are about to remove the resource %s!'; +$lang['delete']['remove_resource_details'] = 'The resource will be purged permanently!'; +$lang['delete']['remove_domain_details'] = 'This also removes domain aliases.

    A domain must be empty to be removed.'; +$lang['delete']['remove_syncjob_details'] = 'Objects from this sync job will not be pulled from the remote server anymore.'; +$lang['delete']['remove_alias_details'] = 'Users will no longer be able to receive mail for or send mail from this address.'; +$lang['delete']['remove_button'] = 'Remove'; +$lang['delete']['previous'] = 'Previous page'; + +$lang['edit']['syncjob'] = 'Edit sync job'; +$lang['edit']['save'] = 'Save changes'; +$lang['edit']['username'] = 'Username'; +$lang['edit']['hostname'] = 'Hostname'; +$lang['edit']['encryption'] = 'Encryption'; +$lang['edit']['maxage'] = 'Maximum age of messages in days that will be polled from remote
    (0 = ignore age)'; +$lang['edit']['subfolder2'] = 'Sync into subfolder on destination
    (empty = do not use subfolder)'; +$lang['edit']['mins_interval'] = 'Interval (min)'; +$lang['edit']['exclude'] = 'Exclude objects (regex)'; +$lang['edit']['save'] = 'Save changes'; +$lang['edit']['archive'] = 'Archive access'; +$lang['edit']['max_mailboxes'] = 'Max. possible mailboxes'; +$lang['edit']['title'] = 'Edit object'; +$lang['edit']['target_address'] = 'Goto address/es (comma-separated)'; +$lang['edit']['active'] = 'Active'; +$lang['edit']['target_domain'] = 'Target domain'; +$lang['edit']['password'] = 'Password'; +$lang['edit']['ratelimit'] = 'Outgoing rate limit/h'; +$lang['danger']['ratelimt_less_one'] = 'Outgoing rate limit/h must not be less than 1'; +$lang['edit']['password_repeat'] = 'Confirmation password (repeat)'; +$lang['edit']['domain_admin'] = 'Edit domain administrator'; +$lang['edit']['domain'] = 'Edit domain'; +$lang['edit']['alias_domain'] = 'Alias domain'; +$lang['edit']['edit_alias_domain'] = 'Edit Alias domain'; +$lang['edit']['domains'] = 'Domains'; +$lang['edit']['destroy'] = 'Manual data input'; +$lang['edit']['alias'] = 'Edit alias'; +$lang['edit']['mailbox'] = 'Edit mailbox'; +$lang['edit']['description'] = 'Description'; +$lang['edit']['max_aliases'] = 'Max. aliases'; +$lang['edit']['max_quota'] = 'Max. quota per mailbox (MiB)'; +$lang['edit']['domain_quota'] = 'Domain quota'; +$lang['edit']['backup_mx_options'] = 'Backup MX options'; +$lang['edit']['relay_domain'] = 'Relay domain'; +$lang['edit']['relay_all'] = 'Relay all recipients'; +$lang['edit']['dkim_signature'] = 'DKIM signature'; +$lang['edit']['dkim_record_info'] = 'Please add a TXT record with the given value to your DNS settings.'; +$lang['edit']['relay_all_info'] = 'If you choose not to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.'; +$lang['edit']['full_name'] = 'Full name'; +$lang['edit']['quota_mb'] = 'Quota (MiB)'; +$lang['edit']['sender_acl'] = 'Allow to send as'; +$lang['edit']['sender_acl_info'] = 'Aliases cannot be deselected.'; +$lang['edit']['dkim_txt_name'] = 'TXT record name:'; +$lang['edit']['dkim_txt_value'] = 'TXT record value:'; +$lang['edit']['previous'] = 'Previous page'; +$lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank'; +$lang['edit']['dont_check_sender_acl'] = 'Do not check sender for domain %s'; +$lang['edit']['multiple_bookings'] = 'Multiple bookings'; +$lang['edit']['kind'] = 'Kind'; +$lang['edit']['resource'] = 'Resource'; + +$lang['add']['syncjob'] = 'Add sync job'; +$lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!'; +$lang['add']['hostname'] = 'Hostname'; +$lang['add']['username'] = 'Username'; +$lang['add']['enc_method'] = 'Encryption method'; +$lang['add']['mins_interval'] = 'Polling interval (minutes)'; +$lang['add']['maxage'] = 'Maximum age of messages that will be polled from remote (0 = ignore age)'; +$lang['add']['subfolder2'] = 'Sync into subfolder on destination'; +$lang['add']['exclude'] = 'Exclude objects (regex)'; +$lang['add']['delete2duplicates'] = 'Delete duplicates on destination'; + +$lang['add']['title'] = 'Add object'; +$lang['add']['domain'] = 'Domain'; +$lang['add']['active'] = 'Active'; +$lang['add']['multiple_bookings'] = 'Multiple bookings'; +$lang['add']['save'] = 'Save changes'; +$lang['add']['description'] = 'Description:'; +$lang['add']['max_aliases'] = 'Max. possible aliases:'; +$lang['add']['resource_name'] = 'Resource name'; +$lang['add']['max_mailboxes'] = 'Max. possible mailboxes:'; +$lang['add']['mailbox_quota_m'] = 'Max. quota per mailbox (MiB):'; +$lang['add']['domain_quota_m'] = 'Total domain quota (MiB):'; +$lang['add']['backup_mx_options'] = 'Backup MX options:'; +$lang['add']['relay_all'] = 'Relay all recipients'; +$lang['add']['relay_domain'] = 'Relay this domain'; +$lang['add']['relay_all_info'] = 'If you choose not to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.'; +$lang['add']['alias'] = 'Alias(es)'; +$lang['add']['alias_spf_fail'] = 'Note: If your chosen destination address is an external mailbox, the receiving mailserver may reject your message due to an SPF failure.'; +$lang['add']['alias_address'] = 'Alias address/es:'; +$lang['add']['alias_address_info'] = 'Full email address/es or @example.com, to catch all messages for a domain (comma-separated). mailcow domains only.'; +$lang['add']['alias_domain_info'] = 'Valid domain names only (comma-separated).'; +$lang['add']['target_address'] = 'Goto addresses:'; +$lang['add']['target_address_info'] = 'Full email address/es (comma-separated).'; +$lang['add']['alias_domain'] = 'Alias domain'; +$lang['add']['select'] = 'Please select...'; +$lang['add']['target_domain'] = 'Target domain:'; +$lang['add']['mailbox'] = 'Mailbox'; +$lang['add']['resource'] = 'Resource'; +$lang['add']['kind'] = 'Kind'; +$lang['add']['mailbox_username'] = 'Username (left part of an email address):'; +$lang['add']['full_name'] = 'Full name:'; +$lang['add']['quota_mb'] = 'Quota (MiB):'; +$lang['add']['select_domain'] = 'Please select a domain first'; +$lang['add']['password'] = 'Password:'; +$lang['add']['password_repeat'] = 'Confirmation password (repeat):'; +$lang['add']['previous'] = 'Previous page'; +$lang['add']['restart_sogo_hint'] = 'You will need to restart the SOGo service container after adding a new domain!'; + +$lang['login']['title'] = 'Login'; +$lang['login']['administration'] = 'Administration'; +$lang['login']['administration_details'] = 'Please use your Administrator login to perform administrative tasks.'; +$lang['login']['user_settings'] = 'User settings'; +$lang['login']['user_settings_details'] = 'Mailbox users can use mailcow UI to change their passwords, create temporary aliases (spam aliases), adjust the spam filter behaviour or import messages from a remote IMAP server.'; +$lang['login']['username'] = 'Username'; +$lang['login']['password'] = 'Password'; +$lang['login']['reset_password'] = 'Reset my password'; +$lang['login']['login'] = 'Login'; +$lang['login']['previous'] = "Previous page"; +$lang['login']['delayed'] = 'Login was delayed by %s seconds.'; + +$lang['tfa']['tfa'] = "Two-factor authentication"; +$lang['tfa']['set_tfa'] = "Set two-factor authentication method"; +$lang['tfa']['yubi_otp'] = "Yubico OTP authentication"; +$lang['tfa']['key_id'] = "An identifier for your YubiKey"; +$lang['tfa']['api_register'] = 'mailcow uses the Yubico Cloud API. Please get an API key for your key here'; +$lang['tfa']['u2f'] = "U2F authentication"; +$lang['tfa']['hotp'] = "HOTP authentication"; +$lang['tfa']['totp'] = "TOTP authentication"; +$lang['tfa']['none'] = "Deaktiviert"; +$lang['tfa']['delete_tfa'] = "Disable TFA"; +$lang['tfa']['disable_tfa'] = "Disable TFA until next successful login"; +$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field"; +$lang['tfa']['confirm'] = "Confirm"; +$lang['tfa']['otp'] = "One-time password"; +$lang['tfa']['trash_login'] = "Trash login"; +$lang['tfa']['select'] = "Please select"; +$lang['tfa']['waiting_usb_auth'] = "Waiting for USB device...

    Please tap the button on your U2F USB device now."; +$lang['tfa']['waiting_usb_register'] = "Waiting for USB device...

    Please enter your password above and confirm your U2F registration by tapping the button on your U2F USB device."; + +$lang['admin']['search_domain_da'] = 'Search domains'; +$lang['admin']['restrictions'] = 'Postifx Restrictions'; +$lang['admin']['rr'] = 'Postifx Recipient Restrictions'; +$lang['admin']['sr'] = 'Postifx Sender Restrictions'; +$lang['admin']['reset_defaults'] = 'Reset to defaults'; +$lang['admin']['sr'] = 'Postifx Sender Restrictions'; +$lang['admin']['r_inactive'] = 'Inactive restrictions'; +$lang['admin']['r_active'] = 'Active restrictions'; +$lang['admin']['r_info'] = 'Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway.
    You can add new elements in inc/vars.local.inc.php to be able to toggle them.'; +$lang['admin']['public_folders'] = 'Public Folders'; +$lang['admin']['public_folders_text'] = 'A namespace "Public" is created. Below\'s public folder name indicates the name of the first auto-created mailbox within this namespace.'; +$lang['admin']['public_folder_name'] = 'Folder name (alphanumeric)'; +$lang['admin']['public_folder_enable'] = 'Enable public folder'; +$lang['admin']['public_folder_enable_text'] = 'Toggling this option does not delete mail in any public folder.'; +$lang['admin']['public_folder_pusf'] = 'Enable per-user seen flag'; +$lang['admin']['public_folder_pusf_text'] = 'A "per-user seen flag"-enabled system will not mark a mail as read for User B, when User A has seen it, but User B did not.'; +$lang['admin']['privacy'] = 'Privacy'; +$lang['admin']['privacy_text'] = 'This option enables a PCRE table to remove "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" and replaces "Received: from" headers with localhost/127.0.0.1.'; +$lang['admin']['privacy_anon_mail'] = 'Anonymize outgoing mail'; +$lang['admin']['dkim_txt_name'] = 'TXT record name:'; +$lang['admin']['dkim_txt_value'] = 'TXT record value:'; +$lang['admin']['dkim_key_length'] = 'DKIM key length (bits)'; +$lang['admin']['dkim_key_valid'] = 'Key valid'; +$lang['admin']['dkim_key_unused'] = 'Key unused'; +$lang['admin']['dkim_key_missing'] = 'Key missing'; +$lang['admin']['dkim_key_hint'] = 'Selector for DKIM keys is always dkim.'; +$lang['admin']['previous'] = 'Previous page'; +$lang['admin']['quota_mb'] = 'Quota (MiB):'; +$lang['admin']['sender_acl'] = 'Allow to send as:'; +$lang['admin']['msg_size'] = 'Message size'; +$lang['admin']['msg_size_limit'] = 'Message size limit now'; +$lang['admin']['msg_size_limit_details'] = 'Applying a new limit will reload Postfix and the webserver.'; +$lang['admin']['save'] = 'Save changes'; +$lang['admin']['maintenance'] = 'Maintenance and Information'; +$lang['admin']['sys_info'] = 'System information'; +$lang['admin']['dkim_add_key'] = 'Add DKIM key'; +$lang['admin']['dkim_keys'] = 'DKIM keys'; +$lang['admin']['add'] = 'Add'; +$lang['admin']['configuration'] = 'Configuration'; +$lang['admin']['password'] = 'Password'; +$lang['admin']['password_repeat'] = 'Confirmation password (repeat)'; +$lang['admin']['active'] = 'Active'; +$lang['admin']['action'] = 'Action'; +$lang['admin']['add_domain_admin'] = 'Add Domain administrator'; +$lang['admin']['admin_domains'] = 'Domain assignments'; +$lang['admin']['domain_admins'] = 'Domain administrators'; +$lang['admin']['username'] = 'Username'; +$lang['admin']['edit'] = 'Edit'; +$lang['admin']['remove'] = 'Remove'; +$lang['admin']['save'] = 'Save changes'; +$lang['admin']['admin'] = 'Administrator'; +$lang['admin']['admin_details'] = 'Edit administrator details'; +$lang['admin']['unchanged_if_empty'] = 'If unchanged leave blank'; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Access'; +$lang['admin']['invalid_max_msg_size'] = 'Invalid max. message size'; +$lang['admin']['site_not_found'] = 'Cannot locate mailcow site configuration'; +$lang['admin']['public_folder_empty'] = 'Public folder name must not be empty'; +$lang['admin']['set_rr_failed'] = 'Cannot set Postfix restrictions'; +$lang['admin']['no_record'] = 'No record'; +?> diff --git a/data/web/lang/lang.es.php b/data/web/lang/lang.es.php new file mode 100644 index 00000000..dbe3f3d0 --- /dev/null +++ b/data/web/lang/lang.es.php @@ -0,0 +1,378 @@ +
    Importante: Un reinicio sencillo puede tardar un poco en completarse, por favor espere a que termine.'; +$lang['dkim']['confirm'] = "¿Estás Seguro?"; +$lang['danger']['dkim_not_found'] = "Registro DKIM no encontrado"; +$lang['danger']['dkim_remove_failed'] = "No se puede eliminar el registro DKIM seleccionado"; +$lang['danger']['dkim_add_failed'] = "No se puede agregar el registro DKIM dado"; +$lang['danger']['dkim_domain_or_sel_invalid'] = "Dominio DKIM ó selector inválido"; +$lang['danger']['dkim_key_length_invalid'] = "Longitud de la llave DKIM inválida"; +$lang['success']['dkim_removed'] = "Registro DKIM removido"; +$lang['success']['dkim_added'] = "Registro DKIM guardado"; +$lang['danger']['access_denied'] = "Acceso denegado o datos del formulario inválidos"; +$lang['danger']['whitelist_from_invalid'] = "Entrada de la lista blanca inválida"; +$lang['danger']['domain_invalid'] = "Nombre de dominio inválido"; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Cuota máx. excede el limite de cuota del dominio"; +$lang['danger']['object_is_not_numeric'] = "El valor %s no es numérico"; +$lang['success']['domain_added'] = "Dominio agregado %s"; +$lang['danger']['alias_empty'] = "Dirección alias no debe estar vacía"; +$lang['danger']['goto_empty'] = "Dirección \"goto\" no debe estar vacía"; +$lang['danger']['policy_list_from_exists'] = "Un registro con ese nombre ya existe"; +$lang['danger']['policy_list_from_invalid'] = "El registro tiene formato inválido"; +$lang['danger']['whitelist_exists'] = "Ya existe un registro con ese nombre en la lista blanca"; +$lang['danger']['whitelist_from_invalid'] = "Formato inválido para el registro de lista blanca"; +$lang['danger']['alias_invalid'] = "Dirección alias inválida"; +$lang['danger']['goto_invalid'] = "Dirección \"goto\" inválida"; +$lang['danger']['alias_domain_invalid'] = "El dominio alias es inválido"; +$lang['danger']['target_domain_invalid'] = "El dominio \"goto\" es inválido"; +$lang['danger']['object_exists'] = "El objeto %s ya existe"; +$lang['danger']['domain_exists'] = "El dominio %s ya existe"; +$lang['danger']['alias_goto_identical'] = "Las direcciones alias y \"goto\" no deben ser idénticas"; +$lang['danger']['aliasd_targetd_identical'] = "El dominio alias no debe ser igual al dominio destino"; +$lang['success']['alias_added'] = "Dirección/es alias ha/han sidgo agregada"; +$lang['success']['alias_modified'] = "Cambios al alias guardados"; +$lang['success']['aliasd_modified'] = "Cambios al dominio alias guardados"; +$lang['success']['mailbox_modified'] = "Cambios al buzón %s guardados"; +$lang['success']['msg_size_saved'] = "Limite del mensaje establecido"; +$lang['danger']['aliasd_not_found'] = "Dominio alias no encontrado"; +$lang['danger']['targetd_not_found'] = "Dominio destino no encontrado"; +$lang['danger']['aliasd_exists'] = "Dominio alias ya existe"; +$lang['success']['aliasd_added'] = "Agregado dominio alias %s"; +$lang['success']['aliasd_modified'] = "Cambios al dominio alias %s guardados"; +$lang['success']['domain_modified'] = "Cambios al dominio %s guardados"; +$lang['success']['domain_admin_modified'] = "Cambios al administrador del dominio %s guardados"; +$lang['success']['domain_admin_added'] = "Administrador del dominio %s agregado"; +$lang['success']['changes_general'] = 'Cambios guardados'; +$lang['success']['admin_modified'] = "Cambios al administrador guardados"; +$lang['danger']['exit_code_not_null'] = "Error: Código de salida es %d"; +$lang['danger']['mailbox_not_available'] = "Buzón no disponible"; +$lang['danger']['username_invalid'] = "Nombre de usuario no se puede utilizar"; +$lang['danger']['password_mismatch'] = "Confirmación de contraseña no es identica"; +$lang['danger']['password_complexity'] = "La contraseña no cumple con los requisitos"; +$lang['danger']['password_empty'] = "El campo de la contraseña no debe estar vacío"; +$lang['danger']['login_failed'] = "Inicio de sesión fallido"; +$lang['danger']['mailbox_invalid'] = "Nombre de buzón inválido"; +$lang['danger']['mailbox_invalid_suggest'] = 'El nombre del buzón es inválido, ¿pretendías escribir "%s"?'; +$lang['info']['fetchmail_planned'] = "La tarea para buscar correos se ha planeado. Por favor verifica el proceso más tarde."; +$lang['danger']['fetchmail_source_empty'] = "Por favor define una carpeta fuente"; +$lang['danger']['fetchmail_dest_empty'] = "Por favor define una carpeta destino"; +$lang['danger']['is_alias'] = "%s ya está definida como una dirección alias"; +$lang['danger']['is_alias_or_mailbox'] = "%s ya está definido como un alias ó como un buzón"; +$lang['danger']['is_spam_alias'] = "%s ya está definida como una dirección alias de correo no deseado"; +$lang['danger']['quota_not_0_not_numeric'] = "Cuota debe ser numérica y >= 0"; +$lang['danger']['domain_not_found'] = "Dominio no encontrado."; +$lang['danger']['max_mailbox_exceeded'] = "Máx. de buzones superado (%d de %d)"; +$lang['danger']['mailbox_quota_exceeded'] = "Cuota excede el límite de dominio (máx. %d MiB)"; +$lang['danger']['mailbox_quota_left_exceeded'] = "No queda espacio suficiente (espacio libre: %d MiB)"; +$lang['success']['mailbox_added'] = "Buzón %s agregado"; +$lang['success']['domain_removed'] = "Dominio %s removido"; +$lang['success']['alias_removed'] = "Dirección alias %s removida"; +$lang['success']['alias_domain_removed'] = "Dominio alias %s removido"; +$lang['success']['domain_admin_removed'] = "Administrador del dominio %s removido"; +$lang['success']['mailbox_removed'] = "Buzón %s removido"; +$lang['danger']['max_quota_in_use'] = "Cuota del buzón debe ser mayor o igual a %d MiB"; +$lang['danger']['domain_quota_m_in_use'] = "Cuota del dominio debe ser mayor o igual a %d MiB"; +$lang['danger']['mailboxes_in_use'] = "Máx. de buzones debe ser mayor o igual a %d"; +$lang['danger']['aliases_in_use'] = "Máx. de alias debe ser mayor o igual a %d"; +$lang['danger']['sender_acl_invalid'] = "Valor del remitente ACL inválido"; +$lang['danger']['domain_not_empty'] = "No se puede eliminar un dominio que no esté vacío"; +$lang['warning']['spam_alias_temp_error'] = "Error temporal: No se puede agregar ese \"spam alias\", inténtelo más tarde."; +$lang['danger']['spam_alias_max_exceeded'] = "Máx. direcciones \"spam alias\" permitidas excedido"; +$lang['danger']['fetchmail_active'] = "Un proceso ya se está ejecutando, por favor espera a que termine."; +$lang['danger']['validity_missing'] = 'Por favor asigna un periodo de validez'; +$lang['user']['off'] = "Apagado"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = 'Configuración del usuario'; +$lang['user']['mailbox_settings'] = 'Configuración del buzón'; +$lang['user']['mailbox_details'] = 'Detalles del buzón'; +$lang['user']['change_password'] = 'Cambiar contraseña'; +$lang['user']['new_password'] = 'Nueva contraseña:'; +$lang['user']['save_changes'] = 'Guardar cambios'; +$lang['user']['password_now'] = 'Contraseña actual (confirmar cambios):'; +$lang['user']['new_password_repeat'] = 'Confirmación de contraseña (repetir):'; +$lang['user']['new_password_description'] = 'Requisitos: longitud de 6 caracteres, letras y números.'; +$lang['user']['did_you_know'] = '¿Sabías qué? Puedes utilizar etiquetas en tu dirección email ("me+privat@example.com") para mover mensajes a una carpeta automáticamente (ejemplo: "privat").'; +$lang['user']['spam_aliases'] = 'Alias de email temporales'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['aliases'] = 'Alias'; +$lang['user']['is_catch_all'] = 'Atrapa-Todo para el/los dominio/s'; +$lang['user']['aliases_also_send_as'] = 'También permitido para mandarse como'; +$lang['user']['aliases_send_as_all'] = 'No verifiques el acceso del remitente para los siguientes dominios'; +$lang['user']['alias_create_random'] = 'Generar alias aleatorio'; +$lang['user']['alias_extend_all'] = 'Extender alias por 1 hora'; +$lang['user']['alias_valid_until'] = 'Válido hasta'; +$lang['user']['alias_remove_all'] = 'Eliminar todos los alias'; +$lang['user']['alias_time_left'] = 'Tiempo restante'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Periodo de validez'; +$lang['user']['hour'] = 'Hora'; +$lang['user']['hours'] = 'Horas'; +$lang['user']['day'] = 'Día'; +$lang['user']['week'] = 'Semana'; +$lang['user']['weeks'] = 'Semanas'; +$lang['user']['spamfilter'] = 'Filtro de spam'; +$lang['user']['spamfilter_wl'] = 'Lista blanca'; +$lang['user']['spamfilter_wl_desc'] = 'Direcciones en la lista blanca nunca clasificarán como spam. Probablemente se usará un comodín.'; +$lang['user']['spamfilter_bl'] = 'Lista negra'; +$lang['user']['spamfilter_bl_desc'] = 'Direcciones en la lista negra siempre clasificarán como spam. Probablemente se usará un comodín.'; +$lang['user']['spamfilter_behavior'] = 'Clasificación'; +$lang['user']['spamfilter_table_rule'] = 'Regla'; +$lang['user']['spamfilter_table_action'] = 'Acción'; +$lang['user']['spamfilter_table_empty'] = 'No hay datos para mostrar'; +$lang['user']['spamfilter_table_remove'] = 'eliminar'; +$lang['user']['spamfilter_table_add'] = 'Agregar elemento'; +$lang['user']['spamfilter_default_score'] = 'Calificación de spam:'; +$lang['user']['spamfilter_green'] = 'Verde: éste mensaje no es spam'; +$lang['user']['spamfilter_yellow'] = 'Amarillo: éste mensaje puede ser spam, será etiquetado como spam y trasladado a tu carpeta basura'; +$lang['user']['spamfilter_red'] = 'Rojo: Este mensaje es spam y sera rechazado por el servidor'; +$lang['user']['spamfilter_default_score'] = 'Valores por defecto:'; +$lang['user']['spamfilter_hint'] = 'El primer valor representa la "calificación baja de spam", el segundo representa la "calificación alta de spam".'; + +$lang['user']['tls_policy_warning'] = 'Advertencia: Si decides forzar la transmisión de correo encriptado, puedes perder correos.
    Mensajes que no satisfagan la política serán rebotados con una falla grave en el sistema de correos .'; +$lang['user']['tls_policy'] = 'Política de encriptación'; +$lang['user']['tls_enforce_in'] = 'Aplicar TLS entrante'; +$lang['user']['tls_enforce_out'] = 'Aplicar TLS saliente'; +$lang['user']['no_record'] = 'Sin registro'; + +$lang['user']['misc_settings'] = 'Otras configuraciones de usuario'; +$lang['user']['misc_delete_profile'] = 'Otras configuraciones de usuario'; + +$lang['user']['tag_handling'] = 'Establecer manejo para el correo etiquetado'; +$lang['user']['tag_in_subfolder'] = 'En subcarpeta'; +$lang['user']['tag_in_subject'] = 'En asunto'; +$lang['user']['tag_help_explain'] = 'En subcarpeta: una nueva subcarpeta llamada como la etiqueta será creada debajo de INBOX ("INBOX/Facebook").
    +En asunto: los nombres de las etiquetas serán añadidos al asunto de los correos, ejemplo: "[Facebook] Mis Noticias".'; +$lang['user']['tag_help_example'] = 'Ejemplo de una dirección email etiquetada: mi+Facebook@ejemplo.org'; + +$lang['start']['dashboard'] = '%s - panel'; +$lang['start']['start_rc'] = 'Abrir Roundcube'; +$lang['start']['start_sogo'] = 'Abrir SOGo'; +$lang['start']['mailcow_apps_detail'] = 'Utiliza una aplicación de mailcow para acceder a tus correos, calendario, contactos y más.'; +$lang['start']['mailcow_panel'] = 'Iniciar mailcow UI'; +$lang['start']['mailcow_panel_description'] = 'Mailcow UI está disponible para administradores y usuarios de buzón.'; +$lang['start']['mailcow_panel_detail'] = 'Administradores del dominio crean, modifican o eliminan buzones y alias, cambia dominios y lee información más detallada sobre sus dominios asignados
    + Usuarios de buzón son capaces de crear alias de tiempo limitado (spam alias), cambiar su contraseña y la configuración del filtro de spam.'; +$lang['start']['recommended_config'] = 'Configuración recomendada (sin ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- y SMTP datos del servidor'; +$lang['start']['imap_smtp_server_description'] = 'Para la mejor experiencia recomendamos utilizar Mozilla Thunderbird.'; +$lang['start']['imap_smtp_server_badge'] = 'Leer/Escribir correos'; +$lang['start']['imap_smtp_server_auth_info'] = 'Por favor utiliza tu dirección de correo completa y el mecanismo de autenticación PLANO.
    +Tus datos para iniciar sesión serán encriptados por la encriptación obligatoria del servidor'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Filtro de correos'; +$lang['start']['managesieve_description'] = 'Por favor utiliza Mozilla Thunderbird con la extensión nightly sieve.
    Inicia Thunderbird, abre la configuración de complementos y suelta el archivo xpi descargado en la ventana abierta.
    El servidor es %s, utiliza el puerto 4190 si se te pregunta. Los datos para iniciar sesión coinciden con los datos de tu correo.'; +$lang['start']['service'] = 'Servicio'; +$lang['start']['encryption'] = 'Método de encriptación'; +$lang['start']['help'] = 'Mostrar/Ocultar panel de ayuda'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Port'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Configuracion'; +$lang['header']['administration'] = 'Administración'; +$lang['header']['mailboxes'] = 'Buzones'; +$lang['header']['user_settings'] = 'Configuraciones de usuario'; +$lang['header']['login'] = 'Inicio de sesión'; +$lang['header']['logged_in_as_logout'] = 'Sesión iniciada como %s (cerrar sesión)'; +$lang['header']['locale'] = 'Idioma'; +$lang['mailbox']['domain'] = 'Dominio'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['aliases'] = 'Alias'; +$lang['mailbox']['domains'] = 'Dominios'; +$lang['mailbox']['mailboxes'] = 'Buzones'; +$lang['mailbox']['mailbox_quota'] = 'Tamaño máx. de cuota'; +$lang['mailbox']['domain_quota'] = 'Cuota'; +$lang['mailbox']['active'] = 'Activo'; +$lang['mailbox']['action'] = 'Acción'; +$lang['mailbox']['ratelimit'] = 'Límite de la tarifa saliente/h'; +$lang['mailbox']['backup_mx'] = 'Respaldar MX'; +$lang['mailbox']['domain_aliases'] = 'Alias de dominio'; +$lang['mailbox']['target_domain'] = 'Dominio destino'; +$lang['mailbox']['target_address'] = 'Dirección Goto'; +$lang['mailbox']['username'] = 'Nombre de usuario'; +$lang['mailbox']['fname'] = 'Nombre completo'; +$lang['mailbox']['filter_table'] = 'Filtrar tabla'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Cuota'; +$lang['mailbox']['in_use'] = 'En uso (%)'; +$lang['mailbox']['msg_num'] = 'Mensaje #'; +$lang['mailbox']['remove'] = 'Eliminar'; +$lang['mailbox']['edit'] = 'Editar'; +$lang['mailbox']['archive'] = 'Archivar'; +$lang['mailbox']['no_record'] = 'Sin registro'; +$lang['mailbox']['add_domain'] = 'Agregar dominio'; +$lang['mailbox']['add_domain_alias'] = 'Agregar alias de dominio'; +$lang['mailbox']['add_mailbox'] = 'Agregar buzón'; +$lang['mailbox']['add_alias'] = 'Agregar alias'; + +$lang['info']['no_action'] = 'No hay acción aplicable'; + +$lang['delete']['title'] = 'Eliminar objeto'; +$lang['delete']['remove_domain_warning'] = 'Advertencia: ¡Estás a punto de eliminar el dominio %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Advertencia: ¡Estás a punto de eliminar el alias de dominio %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Advertencia: ¡Estás a punto de eliminar el administrador de dominio %s!'; +$lang['delete']['remove_alias_warning'] = 'Advertencia: ¡Estás a punto de eliminar la dirección alias %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Advertencia: ¡Estás a punto de eliminar el buzón %s!'; +$lang['delete']['remove_mailbox_details'] = 'El buzón será purgado permanentemente!'; +$lang['delete']['remove_domain_details'] = 'Esto también eliminará alias de dominio.

    Un dominio debe estar vacío para poder ser eliminado.'; +$lang['delete']['remove_alias_details'] = 'Los usuarios ya no serán capaces de recibir correos o enviar correos desde esta dirección.'; +$lang['delete']['remove_button'] = 'Eliminar'; +$lang['delete']['previous'] = 'Página anterior'; + +$lang['edit']['save'] = 'Guardar cambios'; +$lang['edit']['archive'] = 'Acceso a archivos'; +$lang['edit']['max_mailboxes'] = 'Máx. buzones posibles:'; +$lang['edit']['title'] = 'Editas objeto'; +$lang['edit']['target_address'] = 'Dirección/es goto (separadas por coma):'; +$lang['edit']['active'] = 'Activo'; +$lang['edit']['target_domain'] = 'Dominio destino:'; +$lang['edit']['password'] = 'Contraseña:'; +$lang['edit']['ratelimit'] = 'Límite de la tarifa saliente/h:'; +$lang['danger']['ratelimt_less_one'] = 'El límite de la tarifa saliente/h no puede ser menos que 1'; +$lang['edit']['password_repeat'] = 'Confirmación de contraseña (repetir):'; +$lang['edit']['domain_admin'] = 'Editar administrador del dominio'; +$lang['edit']['domain'] = 'Editar dominio'; +$lang['edit']['alias_domain'] = 'Alias de dominio'; +$lang['edit']['edit_alias_domain'] = 'Editar alias de dominio'; +$lang['edit']['domains'] = 'Dominios'; +$lang['edit']['destroy'] = 'Entrada manual de datos'; +$lang['edit']['alias'] = 'Editar alias'; +$lang['edit']['mailbox'] = 'Editar buzón'; +$lang['edit']['description'] = 'Descripción:'; +$lang['edit']['max_aliases'] = 'Máx. alias:'; +$lang['edit']['max_quota'] = 'Máx. cuota por buzón (MiB):'; +$lang['edit']['domain_quota'] = 'Cuota de dominio:'; +$lang['edit']['backup_mx_options'] = 'Opciones del respaldo MX:'; +$lang['edit']['relay_domain'] = 'Dominio de retransmisión'; +$lang['edit']['relay_all'] = 'Retransmitir todos los recipientes'; +$lang['edit']['dkim_signature'] = 'Firma DKIM:'; +$lang['edit']['dkim_record_info'] = 'Por favor agrega un registro TXT con el siguiente valor a tu configuración DNS.'; +$lang['edit']['relay_all_info'] = 'Si eliges no retransmitir a todos los recipientes, necesitas agregar un buzón "blind"("ciego") por cada recipiente que debe ser retransmitido.'; +$lang['edit']['full_name'] = 'Nombre completo'; +$lang['edit']['full_name'] = 'Nombre completo'; +$lang['edit']['quota_mb'] = 'Cuota (MiB)'; +$lang['edit']['sender_acl'] = 'Permitir envío como:'; +$lang['edit']['sender_acl_info'] = 'Los alias no pueden deseleccionarse.'; +$lang['edit']['dkim_txt_name'] = 'Nombre del registro TXT:'; +$lang['edit']['dkim_txt_value'] = 'Valor del registro TXT:'; +$lang['edit']['previous'] = 'Página anterior'; +$lang['edit']['unchanged_if_empty'] = 'Si no hay cambios dejalo en blanco'; +$lang['edit']['dont_check_sender_acl'] = 'No verifiques remitente para el dominio %s'; + +$lang['add']['title'] = 'Agregar objeto'; +$lang['add']['domain'] = 'Dominio'; +$lang['add']['active'] = 'Activo'; +$lang['add']['save'] = 'Guardar cambios'; +$lang['add']['description'] = 'Descripción:'; +$lang['add']['max_aliases'] = 'Máx. alias posibles:'; +$lang['add']['max_mailboxes'] = 'Máx. buzones posibles:'; +$lang['add']['mailbox_quota_m'] = 'Máx. cuota por buzón (MiB):'; +$lang['add']['domain_quota_m'] = 'Cuota total del dominio (MiB):'; +$lang['add']['backup_mx_options'] = 'Opciones del respaldo MX:'; +$lang['add']['relay_all'] = 'Retransmitir todos los recipientes'; +$lang['add']['relay_domain'] = 'Retransmitir este dominio'; +$lang['add']['relay_all_info'] = 'Si eliges no retransmitir a todos los recipientes, necesitas agregar un buzón "blind"("ciego") por cada recipiente que debe ser retransmitido.'; +$lang['add']['alias'] = 'Alias'; +$lang['add']['alias_spf_fail'] = 'Nota: Si tu dirección destino está en un buzón externo, el servidor de correo que recibe puede rechazar tu mensaje por una falla SPF.'; +$lang['add']['alias_address'] = 'Dirección/es alias:'; +$lang['add']['alias_address_info'] = 'Dirección/es de correo completa/s ó @ejemplo.com, para atrapar todos los mensajes para un dominio (separado por coma). Dominios mailcow solamente.'; +$lang['add']['alias_domain_info'] = 'Nombres de dominio válidos solamente (separado por coma).'; +$lang['add']['target_address'] = 'Direcciones goto:'; +$lang['add']['target_address_info'] = 'Dirección/es de correo completa/s (separado por coma).'; +$lang['add']['alias_domain'] = 'Dominio alias'; +$lang['add']['select'] = 'Por favor selecciona...'; +$lang['add']['target_domain'] = 'Dominio destino:'; +$lang['add']['mailbox'] = 'Buzón'; +$lang['add']['mailbox_username'] = 'Nombre de usuario (parte izquierda de una dirección de correo):'; +$lang['add']['full_name'] = 'Nombre completo:'; +$lang['add']['quota_mb'] = 'Cuota (MiB):'; +$lang['add']['select_domain'] = 'Por favor elige un dominio primero'; +$lang['add']['password'] = 'Constraseña:'; +$lang['add']['password_repeat'] = 'Confirmación de contraseña (repetir):'; +$lang['add']['previous'] = 'Página anterior'; +$lang['add']['restart_sogo_hint'] = '¡Necesitas reiniciar el contenedor del servicio SOGo antes de agregar un nuevo dominio!'; + +$lang['login']['title'] = 'Inicio de sesión'; +$lang['login']['administration'] = 'Administración'; +$lang['login']['administration_details'] = 'Por favor utiliza tu inicio de sesión de Administrador para realizar tareas administrativas.'; +$lang['login']['user_settings'] = 'Configuración de usuario'; +$lang['login']['user_settings_details'] = 'Usuarios de buzón pueden utilizar mailcow UI para cambiar sus contraseñas, crear alias temporales (alias de spam), ajustar el comportamiento del filtro de spam ó importar mensajes desde un servidor IMAP remoto.'; +$lang['login']['username'] = 'Nombre de usuario'; +$lang['login']['password'] = 'Contraseña'; +$lang['login']['reset_password'] = 'Reiniciar mi contraseña'; +$lang['login']['login'] = 'Inicio de sesión'; +$lang['login']['previous'] = "Página anterior"; +$lang['login']['delayed'] = 'El inicio de sesión ha sido retrasado %s segundos.'; + +$lang['login']['tfa'] = "Autenticación de dos factores"; +$lang['login']['tfa_details'] = "Por favor confirma tu contraseña de un solo uso en el campo de abajo"; +$lang['login']['confirm'] = "Confirmar"; +$lang['login']['otp'] = "Contraseña de un solo uso"; +$lang['login']['trash_login'] = "Inicio de sesión basura"; + +$lang['admin']['search_domain_da'] = 'Buscar dominios'; +$lang['admin']['restrictions'] = 'Restricciones Postfix'; +$lang['admin']['rr'] = 'Restricciones Postfix para recipientes'; +$lang['admin']['sr'] = 'Restricciones Postfix para remitentes'; +$lang['admin']['reset_defaults'] = 'Restablecer los valores predeterminados'; +$lang['admin']['sr'] = 'Restricciones Postfix para remitentes'; +$lang['admin']['r_inactive'] = 'Restricciones inactivas'; +$lang['admin']['r_active'] = 'Restricciones activas'; +$lang['admin']['r_info'] = 'Elementos en gris/deshabilitados en la lista de restricciones activas no son reconocidas como restricciones válidas para mailcow y no pueden ser movidas. Restricciones desconocidas serán establecidas en el orden de aparicion de todas maneras.
    Puedes agregar nuevos elementos en inc/vars.local.inc.php para ser capaz de habilitarlas.'; +$lang['admin']['public_folders'] = 'Carpetas Públicas'; +$lang['admin']['public_folders_text'] = 'Un espacio de nombres "Public" (Público) será creado. Debajo del nombre de la carpeta pública se indica el nombre del primer buzón creado automáticamente dentro de este espacio de nombres'; +$lang['admin']['public_folder_name'] = 'Nombre de la carpeta (alfanumérico)'; +$lang['admin']['public_folder_enable'] = 'Habilitar carpeta pública'; +$lang['admin']['public_folder_enable_text'] = 'Activar ésta opción no elimina correos en cualquier otra carpeta pública.'; +$lang['admin']['public_folder_pusf'] = 'Habilitar el indicador visto por usuario'; +$lang['admin']['public_folder_pusf_text'] = 'Un sistema habilitado por indicador "por usuario visto" no marcará un correo como leído para el Usuario B, cuando el Usuario A lo haya visto, pero el Usuario B no.'; +$lang['admin']['privacy'] = 'Privacidad'; +$lang['admin']['privacy_text'] = 'Ésta opción activa una tabla PCRE para remover "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" y remplaza las cabezeras "Received: from" con localhost/127.0.0.1.'; +$lang['admin']['privacy_anon_mail'] = 'Anonimizar correo saliente'; +$lang['admin']['dkim_txt_name'] = 'Nombre del registro TXT:'; +$lang['admin']['dkim_txt_value'] = 'Valor del registro TXT:'; +$lang['admin']['dkim_key_length'] = 'Longitud de la llave DKIM (bits)'; +$lang['admin']['previous'] = 'Página anterior'; +$lang['admin']['quota_mb'] = 'Cuota (MiB):'; +$lang['admin']['sender_acl'] = 'Permitir envío como:'; +$lang['admin']['msg_size'] = 'Tamaño del mensaje'; +$lang['admin']['msg_size_limit'] = 'Límite del tamaño del mensaje ahora'; +$lang['admin']['msg_size_limit_details'] = 'Aplicando un nuebo límite reiniciará Postfix y el servidor web.'; +$lang['admin']['save'] = 'Guardar cambios'; +$lang['admin']['maintenance'] = 'Mantenimiento e Información'; +$lang['admin']['sys_info'] = 'información del sistema'; +$lang['admin']['dkim_add_key'] = 'Agregar registro DKIM'; +$lang['admin']['dkim_keys'] = 'Registros DKIM'; +$lang['admin']['add'] = 'Agregar'; +$lang['admin']['configuration'] = 'Configuración'; +$lang['admin']['password'] = 'Contraseña'; +$lang['admin']['password_repeat'] = 'Confirmación de contraseña (repetir)'; +$lang['admin']['active'] = 'Activo'; +$lang['admin']['action'] = 'Acción'; +$lang['admin']['add_domain_admin'] = 'Agregar Administrador del dominio'; +$lang['admin']['admin_domains'] = 'Asignaciones de dominio'; +$lang['admin']['domain_admins'] = 'Administradores de dominio'; +$lang['admin']['username'] = 'Nombre de usuario'; +$lang['admin']['edit'] = 'Editar'; +$lang['admin']['remove'] = 'Eliminar'; +$lang['admin']['save'] = 'Guardar cambios'; +$lang['admin']['admin'] = 'Administrador'; +$lang['admin']['admin_details'] = 'Editar detalles del administrador'; +$lang['admin']['unchanged_if_empty'] = 'Si no hay cambios dejalo en blanco'; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Acceso'; +$lang['admin']['invalid_max_msg_size'] = 'Tamaño máx. del mensaje no válido'; +$lang['admin']['site_not_found'] = 'No se puede localizar la configuración del sitio de mailcow'; +$lang['admin']['public_folder_empty'] = 'El nombre de la carpeta pública no debe estar vacío'; +$lang['admin']['set_rr_failed'] = 'No se pueden establecer las restricciones de Postfix'; +$lang['admin']['no_record'] = 'Sin registro'; +?> diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php new file mode 100644 index 00000000..35192ddb --- /dev/null +++ b/data/web/lang/lang.nl.php @@ -0,0 +1,369 @@ +
    Belangrijk: Het opnieuw opstarten kan een poos duren, wacht a.u.b. totdat dit volledig voltooid is.'; +$lang['dkim']['confirm'] = "Weet u het zeker?"; +$lang['danger']['dkim_not_found'] = "DKIM record niet gevonden."; +$lang['danger']['dkim_remove_failed'] = "Kan geselecteerde DKIM record niet verwijderen."; +$lang['danger']['dkim_add_failed'] = "Kan DKIM record niet toevoegen."; +$lang['danger']['dkim_domain_or_sel_invalid'] = "DKIM domein of selector zijn ongeldig."; +$lang['danger']['dkim_key_length_invalid'] = "Lengte DKIM sleutel ongeldig."; +$lang['success']['dkim_removed'] = "DKIM record is verwijderd."; +$lang['success']['dkim_added'] = "DKIM record is opgeslagen."; +$lang['danger']['access_denied'] = "Toegang geweigerd of ongeldige gegevens."; +$lang['danger']['whitelist_from_invalid'] = "Witte lijst invoer ongeldig."; +$lang['danger']['domain_invalid'] = "Domeinnaam is ongeldig."; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. quotum > Domeinquotum."; +$lang['danger']['object_is_not_numeric'] = "%s is niet numeriek."; +$lang['success']['domain_added'] = "Domein toegevoegd: %s."; +$lang['danger']['alias_empty'] = "Aliasadres mag niet leeg blijven."; +$lang['danger']['goto_empty'] = "Doeladres mag niet leeg blijven."; +$lang['danger']['policy_list_from_exists'] = "Deze invoer bestaat al."; +$lang['danger']['policy_list_from_invalid'] = "Deze invoer heeft een ongeldig format."; +$lang['danger']['whitelist_exists'] = "Deze invoer staat op de witte lijst."; +$lang['danger']['whitelist_from_invalid'] = "Witte lijst invoer heeft een ongeldig format."; +$lang['danger']['alias_invalid'] = "Aliasadres is ongeldig."; +$lang['danger']['goto_invalid'] = "Doeladres is ongeldig."; +$lang['danger']['alias_domain_invalid'] = "Aliasdomein is ongeldig."; +$lang['danger']['target_domain_invalid'] = "Doeldomein is ongeldig."; +$lang['danger']['object_exists'] = "Object %s bestaat reeds."; +$lang['danger']['domain_exists'] = "Domein %s bestaat reeds."; +$lang['danger']['alias_goto_identical'] = "Het Aliasadres en het Doeladres moeten van elkaar verschillen."; +$lang['danger']['aliasd_targetd_identical'] = "Het Aliasdomein kan niet gelijk zijn aan het doel."; +$lang['success']['alias_added'] = "Aliasadres(sen) toegevoegd."; +$lang['success']['alias_modified'] = "Wijzigingen aan Alias zijn opgeslagen."; +$lang['success']['mailbox_modified'] = "Wijzigingen aan postvak %s zijn opgeslagen."; +$lang['success']['msg_size_saved'] = "Maximale berichtgrootte opgeslagen."; +$lang['danger']['aliasd_not_found'] = "Aliasdomein werd niet gevonden."; +$lang['danger']['targetd_not_found'] = "Doeldomein werd niet gevonden."; +$lang['danger']['aliasd_exists'] = "Aliasdomein bestaat al."; +$lang['success']['aliasd_added'] = "Aliasdomein %s toegevoegd."; +$lang['success']['aliasd_modified'] = "Wijzigingen aan aliasdomein %s zijn opgeslagen."; +$lang['success']['domain_modified'] = "Wijzigingen aan domein %s zijn opgeslagen."; +$lang['success']['domain_admin_modified'] = "Wijzigingen aan domeinbeheerder %s zijn opgeslagen."; +$lang['success']['domain_admin_added'] = "Domeinbeheerder %s is toegevoegd."; +$lang['success']['changes_general'] = 'Wijzigingen zijn opgeslagen.'; +$lang['success']['admin_modified'] = "Wijzigingen aan de beheerder zijn opgeslagen."; +$lang['danger']['exit_code_not_null'] = "Fout: Exitcode was %d."; +$lang['danger']['mailbox_not_available'] = "Postvak niet beschikbaar."; +$lang['danger']['username_invalid'] = "Gebruikersnaam kan niet worden gebruikt."; +$lang['danger']['password_mismatch'] = "Bevestigingswachtwoord verschilt."; +$lang['danger']['password_complexity'] = "Het wachtwoord voldoet niet aan de vereisten."; +$lang['danger']['password_empty'] = "Er moet een wachtwoord worden ingesteld."; +$lang['danger']['login_failed'] = "Aanmelding mislukt."; +$lang['danger']['mailbox_invalid'] = "De naam van het postvak is ongeldig."; +$lang['danger']['mailbox_invalid_suggest'] = "De naam van het postvak is ongeldig, bedoelde u \"%s\"?"; +$lang['info']['fetchmail_planned'] = "Taak voor het ophalen van e-mails is gepland. Controleer dit proces later."; +$lang['danger']['fetchmail_source_empty'] = "Geef een bron-map op."; +$lang['danger']['fetchmail_dest_empty'] = "Geef een doel-map op."; +$lang['danger']['is_alias'] = "%s is reeds een Aliasadres."; +$lang['danger']['is_alias_or_mailbox'] = "%s is reeds een Alias of een postvak."; +$lang['danger']['is_spam_alias'] = "%s is reeds bekend als spam-alias adres."; +$lang['danger']['quota_not_0_not_numeric'] = "Quotum moet numeriek zijn en >= 0."; +$lang['danger']['domain_not_found'] = "Domein werd niet gevonden."; +$lang['danger']['max_mailbox_exceeded'] = "Max. aantal postvakken overschreden (%d van %d)."; +$lang['danger']['mailbox_quota_exceeded'] = "Quotum heeft het domeinlimiet overschreven (max. %d MiB)."; +$lang['danger']['mailbox_quota_left_exceeded'] = "Onvoldoende ruimte beschikbaar (%d MiB)."; +$lang['success']['mailbox_added'] = "Postvak %s is toegevoegd."; +$lang['success']['domain_removed'] = "Domein %s is verwijderd."; +$lang['success']['alias_removed'] = "Aliasadres %s is verwijderd."; +$lang['success']['alias_domain_removed'] = "Aliasdomein %s is verwijderd."; +$lang['success']['domain_admin_removed'] = "Domeinbeheerder %s is verwijderd."; +$lang['success']['mailbox_removed'] = "Postvak %s is verwijderd."; +$lang['danger']['max_quota_in_use'] = "Postvakquotum moet >= %d MiB."; +$lang['danger']['domain_quota_m_in_use'] = "Domeinquotum moet >= %s MiB."; +$lang['danger']['mailboxes_in_use'] = "Maximaal aantal postvakken moet >= %d."; +$lang['danger']['aliases_in_use'] = "Maximaal aantal aliassen moet >= %d."; +$lang['danger']['sender_acl_invalid'] = "Verzender ACL-waarde is ongeldig."; +$lang['danger']['domain_not_empty'] = "Kan domein in gebruik niet verwijderen."; +$lang['warning']['spam_alias_temp_error'] = "Tijdelijke fout: Kan geen spam-alias toevoegen. Probeer het later nogmaals."; +$lang['danger']['spam_alias_max_exceeded'] = "Maximaal aantal spam-aliassen bereikt."; +$lang['danger']['fetchmail_active'] = "Er draait reeds een proces, wacht tot deze klaar is."; +$lang['danger']['validity_missing'] = 'Voer een geldigheidstermijn in.'; +$lang['user']['on'] = "Aan"; +$lang['user']['off'] = "Uit"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = 'Gebruikersinstellingen'; +$lang['user']['mailbox_settings'] = 'Postvakinstellingen'; +$lang['user']['mailbox_details'] = 'Postvakdetails'; +$lang['user']['change_password'] = 'Verander wachtwoord'; +$lang['user']['new_password'] = 'Nieuw wachtwoord'; +$lang['user']['save_changes'] = 'Wijzigingen opslaan'; +$lang['user']['password_now'] = 'Huidig wachtwoord (bevestig wijzigingen)'; +$lang['user']['new_password_repeat'] = 'Bevestig wachtwoord (herhalen)'; +$lang['user']['new_password_description'] = 'Vereisten: 6 karakters lang, letters en nummers.'; +$lang['user']['did_you_know'] = 'Wist u dat? U kunt tags in het e-mailadres gebruiken ("me+prive@voorbeeld.nl") om berichten automatisch naar een bijbehorende map te sturen (voorbeeld: "prive").'; +$lang['user']['spam_aliases'] = 'Tijdelijk e-mailadres'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['alias_create_random'] = 'Creëer willekeurige alias'; +$lang['user']['alias_extend_all'] = 'Verleng alias met 1 uur'; +$lang['user']['alias_valid_until'] = 'Geldig tot'; +$lang['user']['alias_remove_all'] = 'Verwijder alle aliassen'; +$lang['user']['alias_time_left'] = 'Tijd over'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Geldigheid'; +$lang['user']['hour'] = 'Uur'; +$lang['user']['hours'] = 'Uren'; +$lang['user']['day'] = 'Dag'; +$lang['user']['week'] = 'Week'; +$lang['user']['weeks'] = 'Weken'; +$lang['user']['spamfilter'] = 'Spam filter'; +$lang['user']['spamfilter_wl'] = 'Witte lijst'; +$lang['user']['spamfilter_wl_desc'] = 'Zet e-mailadressen op de witte lijst om ze nooit als spam te classificeren. Wildcards (*) zijn toegestaan.'; +$lang['user']['spamfilter_bl'] = 'Zwarte lijst'; +$lang['user']['spamfilter_bl_desc'] = 'E-mailadressen op de zwarte lijst worden altijd als spam geclassificeerd en geweigerd. Wildcards (*) zijn toegestaan.'; +$lang['user']['spamfilter_behavior'] = 'Beoordeling'; +$lang['user']['spamfilter_table_rule'] = 'Regel'; +$lang['user']['spamfilter_table_action'] = 'Handeling'; +$lang['user']['spamfilter_table_empty'] = 'Geen gegevens om weer te geven.'; +$lang['user']['spamfilter_table_remove'] = 'verwijder'; +$lang['user']['spamfilter_table_add'] = 'Voeg item toe'; +$lang['user']['spamfilter_default_score'] = 'Spamscore:'; +$lang['user']['spamfilter_green'] = 'Groen: Dit bericht is geen spam.'; +$lang['user']['spamfilter_yellow'] = 'Geel: Dit bericht is mogelijk spam, zal worden gelabeled en verplaatst worden naar de Junk-map.'; +$lang['user']['spamfilter_red'] = 'Rood: Dit bericht is spam en zal worden geweigerd.'; +$lang['user']['spamfilter_default_score'] = 'Standaardwaarden:'; +$lang['user']['spamfilter_hint'] = 'De eerste waarde omschrijft een "lage spam score", de tweede waarde een "hoge spam score".'; + +$lang['user']['tls_policy_warning'] = 'Attentie: Door versleutelde e-mails te forceren, worden mogelijk niet alle e-mails afgeleverd.
    Berichten die niet aan het ingestelde beleid voldoen worden resoluut geweigerd (bounced met hard-fail).'; +$lang['user']['tls_policy'] = 'Versleutelbeleid'; +$lang['user']['tls_enforce_in'] = 'Forceer TLS-gebruik inkomend'; +$lang['user']['tls_enforce_out'] = 'Forceer TLS-gebruik uitgaand'; +$lang['user']['no_record'] = 'Geen vermelding.'; + +$lang['user']['misc_settings'] = 'Andere profielinstellingen'; +$lang['user']['misc_delete_profile'] = 'Andere profielinstellingen'; +$lang['user']['tag_handling'] = 'Omgaan met e-mail tags'; +$lang['user']['tag_in_subfolder'] = 'In onderliggende map'; +$lang['user']['tag_in_subject'] = 'In onderwerp'; +$lang['user']['tag_help_explain'] = 'In onderliggende map: maakt onder INBOX een nieuwe map aan met de naam van de tag (bijv.: "INBOX/Facebook").
    +In onderwerp: de tag wordt vóór het oorspronkelijke e-mail onderwerp geplaatst (bijv.: "[Facebook] Mijn nieuws").'; +$lang['user']['tag_help_example'] = 'Voorbeeld van een e-mailadres met tag: ik+Facebook@voorbeeld.org'; +$lang['start']['dashboard'] = '%s - startpagina'; +$lang['start']['start_rc'] = 'Open Roundcube'; +$lang['start']['start_sogo'] = 'Open SOGo'; +$lang['start']['mailcow_apps_detail'] = 'Gebruik een mailcow app om toegang te hebben tot uw e-mails, kalender, contactpersonen en meer.'; +$lang['start']['mailcow_panel'] = 'Start mailcow UI'; +$lang['start']['mailcow_panel_description'] = 'De mailcow UI is beschikbaar voor zowel beheerders als gebruikers.'; +$lang['start']['mailcow_panel_detail'] = 'Domeinbeheerders kunnen postvakken en aliassen aanmaken, wijzigen of verwijderen, domeinen veranderen of informatie krijgen over hun domein.
    + Gebruikers kunnen tijdsgelimiteerde aliassen (spam-aliasses) aanmaken, hun wachtwoord wijzigen en spamfilterinstellingen wijzigen.'; +$lang['start']['recommended_config'] = 'Aanbevoen instellingen (zonder ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- en SMTP-server gegevens'; +$lang['start']['imap_smtp_server_description'] = 'Voor de best mogelijke ervaring bevelen wij Mozilla Thunderbird aan.'; +$lang['start']['imap_smtp_server_badge'] = 'Lees/schrijf e-mails'; +$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik uw volledige e-mailadres en de onversleutelde (PLAIN) verificatiemechanisme.
    +De aanmeldgegevens zullen door de server worden versleuteld.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Emailfilter'; +$lang['start']['managesieve_description'] = 'Gebruik Mozilla Thunderbird met een nightly sieve addon.
    Start Thunderbird, open de add-on instellingen en sleep het gedownloadde xpi-bestand naar dit venster.
    Servernaam %s, Poort 4190. De aanmeldgegevens zijn gelijk aan de gegevens voor uw e-mail.'; +$lang['start']['service'] = 'Service'; +$lang['start']['encryption'] = 'Versleutelmethode'; +$lang['start']['help'] = 'Toon/Verberg Hulppaneel'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Poort'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Instellingen'; +$lang['header']['administration'] = 'Beheer'; +$lang['header']['mailboxes'] = 'Postvakken'; +$lang['header']['user_settings'] = 'Gebruikersinstellingen'; +$lang['header']['login'] = 'Aanmelden'; +$lang['header']['logged_in_as_logout'] = 'Aangemeld als %s (Afmelden)'; +$lang['header']['locale'] = 'Taal'; +$lang['mailbox']['domain'] = 'Domein'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['aliases'] = 'Aliassen'; +$lang['mailbox']['domains'] = 'Domeinen'; +$lang['mailbox']['mailboxes'] = 'Mailboxen'; +$lang['mailbox']['mailbox_quota'] = 'Max. grootte van een postvak'; +$lang['mailbox']['domain_quota'] = 'Quotum'; +$lang['mailbox']['active'] = 'Actief'; +$lang['mailbox']['action'] = 'Handeling'; +$lang['mailbox']['ratelimit'] = 'Maximale snelheid uitgaand/uur'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Domein-aliassen'; +$lang['mailbox']['target_domain'] = 'Doeldomein'; +$lang['mailbox']['target_address'] = 'Doeladres'; +$lang['mailbox']['username'] = 'Gebruikersnaam'; +$lang['mailbox']['fname'] = 'Volledige naam'; +$lang['mailbox']['filter_table'] = 'Filter tabel'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Quotum'; +$lang['mailbox']['in_use'] = 'In gebruik (%)'; +$lang['mailbox']['msg_num'] = 'Berichten #'; +$lang['mailbox']['remove'] = 'Verwijder'; +$lang['mailbox']['edit'] = 'Wijzig'; +$lang['mailbox']['archive'] = 'Archief'; +$lang['mailbox']['no_record'] = 'Geen vermelding'; +$lang['mailbox']['add_domain'] = 'Toevoegen domein'; +$lang['mailbox']['add_domain_alias'] = 'Toevoegen domein-alias'; +$lang['mailbox']['add_mailbox'] = 'Toevoegen postvak'; +$lang['mailbox']['add_alias'] = 'Toevoegen alias'; + +$lang['info']['no_action'] = 'Geen handelingen uitvoerbaar'; + +$lang['delete']['title'] = 'Verwijder object'; +$lang['delete']['remove_domain_warning'] = 'Let op: U staat op het punt domein %s te verwijderen!'; +$lang['delete']['remove_domainalias_warning'] = 'Let op: U staat op het punt domeinalias %s te verwijderen!'; +$lang['delete']['remove_domainadmin_warning'] = 'Let op: U staat op het punt domeinbeheerder %s te verwijderen!'; +$lang['delete']['remove_alias_warning'] = 'Let op: U staat op het punt alias %s te verwijderen!'; +$lang['delete']['remove_mailbox_warning'] = 'Let op:: U staat op het punt postvak %s te verwijderen!'; +$lang['delete']['remove_mailbox_details'] = 'Het postvak zal permanent worden verwijderd!'; +$lang['delete']['remove_domain_details'] = 'Dit verwijdert ook de domeinaliassen.

    Een domein moet leeg zijn alvorens deze verwijderd kan worden.'; +$lang['delete']['remove_alias_details'] = 'Gebruikers zullen niet meer in staat zijn e-mails te ontvangen op -of te versturen vanaf- dit adres.'; +$lang['delete']['remove_button'] = 'Verwijder'; +$lang['delete']['previous'] = 'Vorige pagina'; + +$lang['edit']['save'] = 'Wijzigingen opslaan'; +$lang['edit']['archive'] = 'Toegang tot archief'; +$lang['edit']['max_mailboxes'] = 'Max. aantal postvakken:'; +$lang['edit']['title'] = 'Wijzig object'; +$lang['edit']['target_address'] = 'Doeladres(sen) (scheiden met komma):'; +$lang['edit']['active'] = 'Actief'; +$lang['edit']['target_domain'] = 'Doeldomein:'; +$lang['edit']['password'] = 'Wachtwoord:'; +$lang['edit']['ratelimit'] = 'Uitgaande e-mail beperking (aantal/uur):'; +$lang['danger']['ratelimt_less_one'] = 'De uitgaande e-mail beperking moet >= 1'; +$lang['edit']['password_repeat'] = 'Bevestig wachtwoord (herhalen):'; +$lang['edit']['domain_admin'] = 'Wijzig domeinbeheerder'; +$lang['edit']['domain'] = 'Wijzig domein'; +$lang['edit']['alias_domain'] = 'Aliasdomein'; +$lang['edit']['edit_alias_domain'] = 'Wijzig aliasdomein'; +$lang['edit']['domains'] = 'Domeinen'; +$lang['edit']['destroy'] = 'Handmatige invoer'; +$lang['edit']['alias'] = 'Wijzig alias'; +$lang['edit']['mailbox'] = 'Wijzig postvak'; +$lang['edit']['description'] = 'Beschrijving:'; +$lang['edit']['max_aliases'] = 'Max. aliassen:'; +$lang['edit']['max_quota'] = 'Max. grootte per postvak (MiB):'; +$lang['edit']['domain_quota'] = 'Domeinquotum'; +$lang['edit']['backup_mx_options'] = 'Backup MX opties:'; +$lang['edit']['relay_domain'] = 'Doorschakeldomein'; +$lang['edit']['relay_all'] = 'Schakel alle ontvangers door'; +$lang['edit']['dkim_signature'] = 'DKIM handtekening:'; +$lang['edit']['dkim_record_info'] = 'Voeg de volgende TXT-record toe aan de DNS-instellingen van uw domein.'; +$lang['edit']['relay_all_info'] = 'Indien u ervoor kiest om niet alle ontvangers door te schakelen, dient u per ontvanger die u wenst door te schakelen een (lege) postvak aan te maken.'; +$lang['edit']['full_name'] = 'Volledige naam'; +$lang['edit']['quota_mb'] = 'Quotum (MiB)'; +$lang['edit']['sender_acl'] = 'Toestaan te verzenden als:'; +$lang['edit']['sender_acl_info'] = 'Aliassen kunnen niet worden deselecteerd.'; +$lang['edit']['dkim_txt_name'] = 'Naam TXT-record:'; +$lang['edit']['dkim_txt_value'] = 'Waarde TXT-record:'; +$lang['edit']['previous'] = 'Vorige pagina'; +$lang['edit']['unchanged_if_empty'] = 'Leeg laten indien niet veranderd.'; +$lang['edit']['dont_check_sender_acl'] = 'Geen zenderverificatie uitvoeren voor domein %s.'; + +$lang['add']['title'] = 'Object toevoegen'; +$lang['add']['domain'] = 'Domein'; +$lang['add']['active'] = 'Actief'; +$lang['add']['save'] = 'Wijzigingen opslaan'; +$lang['add']['description'] = 'Beschrijving:'; +$lang['add']['max_aliases'] = 'Max. aantal aliassen:'; +$lang['add']['max_mailboxes'] = 'Max. aantal postvakken:'; +$lang['add']['mailbox_quota_m'] = 'Max. grootte postvak (MiB):'; +$lang['add']['domain_quota_m'] = 'Totale grootte domein (MiB):'; +$lang['add']['backup_mx_options'] = 'Backup MX opties:'; +$lang['add']['relay_all'] = 'Doorschakelen van alle ontvangers'; +$lang['add']['relay_domain'] = 'Schakel dit domein door'; +$lang['add']['relay_all_info'] = 'Indien u ervoor kiest om niet alle ontvangers door te schakelen, moet u voor iedere ontvanger die u wenst door te schakelen een een (lege) mailbox aanmaken.'; +$lang['add']['alias'] = 'Alias(sen)'; +$lang['add']['alias_spf_fail'] = 'Opmerking: Als het gekozen doeladres een extern postvak is, kan de ontvangende mailservver mogelijk het bericht weigeren vanwege niet kloppende SPF-gegevens.'; +$lang['add']['alias_address'] = 'Aliasadres(sen):'; +$lang['add']['alias_address_info'] = 'Volledig(e) emailadres(sen) of @voorbeeld.nl om alle berichten op te vangen van een domein (scheiden met komma). Alleen mailcow-domeinen!'; +$lang['add']['alias_domain_info'] = 'Alleen geldige domeinen (scheiden met komma).'; +$lang['add']['target_address'] = 'Doeladressen:'; +$lang['add']['target_address_info'] = 'Volledig(e) e-mailadres(sen) (scheiden met komma).'; +$lang['add']['alias_domain'] = 'Aliasdomein'; +$lang['add']['select'] = 'Kies uit...'; +$lang['add']['target_domain'] = 'Doeldomein:'; +$lang['add']['mailbox'] = 'Postvak'; +$lang['add']['mailbox_username'] = 'Gebruikersnaam (linker deel van het e-mailadres):'; +$lang['add']['full_name'] = 'Volledige naam:'; +$lang['add']['quota_mb'] = 'Quotum (MiB):'; +$lang['add']['select_domain'] = 'Selecteer eerst een domein'; +$lang['add']['password'] = 'Wachtwoord:'; +$lang['add']['password_repeat'] = 'Bevestig wachtwoord (herhalen):'; +$lang['add']['previous'] = 'Vorige pagina'; +$lang['add']['restart_sogo_hint'] = 'SOGo dient opnieuw te worden gestart nadat een domein is toegevoegd!'; + +$lang['login']['title'] = 'Aanmelden'; +$lang['login']['administration'] = 'Beheer'; +$lang['login']['administration_details'] = 'Gebruik uw beheerders-login om administratieve taken uit te voeren.'; +$lang['login']['user_settings'] = 'Gebruikersinstellingen'; +$lang['login']['user_settings_details'] = 'Mailbox-gebruikers kunnen in het mailcow-gebruikersbeheer hun wachtwoorden wijzigen, tijdelijke aliassen aanmaken (tegengaan van spam), de spamfilter aanpassen of berichten van externe IMAP-servers importeren.'; +$lang['login']['username'] = 'Gebruikersnaam'; +$lang['login']['password'] = 'Wachtwoord'; +$lang['login']['reset_password'] = 'Wijzig mijn wachtwoord'; +$lang['login']['login'] = 'Aanmelden'; +$lang['login']['previous'] = "Vorige pagina"; +$lang['login']['delayed'] = 'Aanmelding met %s sec. vertraagd.'; + +$lang['login']['tfa'] = "Two-factor authentication"; +$lang['login']['tfa_details'] = "Voer uw eenmalige wachtwoord hieronder in."; +$lang['login']['confirm'] = "Bevestigen"; +$lang['login']['otp'] = "Eenmalig wachtwoord"; +$lang['login']['trash_login'] = "Verwijder login"; + +$lang['admin']['search_domain_da'] = 'Doorzoek domeinen'; +$lang['admin']['restrictions'] = 'Postfix beperkingen'; +$lang['admin']['rr'] = 'Postfix ontvangersbeperkingen'; +$lang['admin']['sr'] = 'Postifx verzendersbeperkingen'; +$lang['admin']['reset_defaults'] = 'Herstel standaardwaarden'; +$lang['admin']['r_inactive'] = 'Inactieve beperkingen'; +$lang['admin']['r_active'] = 'Actieve beperkignen'; +$lang['admin']['r_info'] = 'Grijze, uitgeschakelde, elementen in de lijst met actieve beperkingen zijn voor mailcow niet bekend als valide en kunnen daarom niet verplaatst worden.
    U kunt nieuwe elementen toevoegen in inc/vars.inc.php om ze te (de)activeren.'; +$lang['admin']['public_folders'] = 'Gemeenschappelijke mappen'; +$lang['admin']['public_folders_text'] = 'Een namespace "Public" wordt aangemaakt. Onder deze map worden de automatisch aangemaakte postvakken in deze namespace weergegeven.'; +$lang['admin']['public_folder_name'] = 'Mapnaam (alphanumeriek)'; +$lang['admin']['public_folder_enable'] = 'Inschakelen gemeenschappelijke map'; +$lang['admin']['public_folder_enable_text'] = 'Deze optie uitschakelen verwijderd géén e-mails uit de gemeenschappelijke mappen.'; +$lang['admin']['public_folder_pusf'] = 'De \'gelezen\'-markering per gebruiker'; +$lang['admin']['public_folder_pusf_text'] = 'Deze "\'gelezen\'-markering per gebruiker"-optie zorgt ervoor dat er per gebruiker afzonderlijk wordt bepaald of deze het bericht heeft gelezen.'; +$lang['admin']['privacy'] = 'Privacy'; +$lang['admin']['privacy_text'] = 'Deze optie activeert een PCRE-tabel die de "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP"-headers verwijderd en de "Received: from"-headers vervangt met localhost/127.0.0.1.'; +$lang['admin']['privacy_anon_mail'] = 'Anonimiseer uitgaande e-mails'; +$lang['admin']['dkim_txt_name'] = 'Naam TXT-record:'; +$lang['admin']['dkim_txt_value'] = 'Waarde TXT-record:'; +$lang['admin']['dkim_key_length'] = 'DKIM sleutel lengte (bits)'; +$lang['admin']['previous'] = 'Vorige pagina'; +$lang['admin']['quota_mb'] = 'Quotum (MiB):'; +$lang['admin']['sender_acl'] = 'Toestaan te verzenden als:'; +$lang['admin']['msg_size'] = 'Berichtgrootte'; +$lang['admin']['msg_size_limit'] = 'Huidige limiet berichtgroote'; +$lang['admin']['msg_size_limit_details'] = 'Een nieuw limiet doorvoeren zal Postfix en de webserver herladen.'; +$lang['admin']['save'] = 'Wijzigingen opslaan'; +$lang['admin']['maintenance'] = 'Onderhoud en informatie'; +$lang['admin']['sys_info'] = 'Systeeminformatie'; +$lang['admin']['dkim_add_key'] = 'DKIM-record toevoegen'; +$lang['admin']['dkim_keys'] = 'DKIM records'; +$lang['admin']['add'] = 'Toevoegen'; +$lang['admin']['configuration'] = 'Instellingen'; +$lang['admin']['password'] = 'Wachtwoord'; +$lang['admin']['password_repeat'] = 'Bevestig wachtwoord (herhalen)'; +$lang['admin']['active'] = 'Actief'; +$lang['admin']['action'] = 'Handeling'; +$lang['admin']['add_domain_admin'] = 'Voeg domeinbeheerder toe'; +$lang['admin']['admin_domains'] = 'Toegewezen domein'; +$lang['admin']['domain_admins'] = 'Domeinbeheerders'; +$lang['admin']['username'] = 'Gebruikersnaam'; +$lang['admin']['edit'] = 'Wijzig'; +$lang['admin']['remove'] = 'Verwijder'; +$lang['admin']['save'] = 'Wijzigingen opslaan'; +$lang['admin']['admin'] = 'Beheerder'; +$lang['admin']['admin_details'] = 'Wijzig details beheerder'; +$lang['admin']['unchanged_if_empty'] = 'Leeg laten indien onveranderd'; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Toegang'; +$lang['admin']['invalid_max_msg_size'] = 'Ongeldige max. berichtgrootte'; +$lang['admin']['site_not_found'] = 'Kan mailcow instellingenbeheer niet vinden'; +$lang['admin']['public_folder_empty'] = 'Namen van gemeenschappelijke mappen mogen niet leeg blijven.'; +$lang['admin']['set_rr_failed'] = 'Kan Postfix beperkingen niet opleggen.'; +$lang['admin']['no_record'] = 'Geen vermelding'; +?> diff --git a/data/web/lang/lang.pt.php b/data/web/lang/lang.pt.php new file mode 100644 index 00000000..f8c0e330 --- /dev/null +++ b/data/web/lang/lang.pt.php @@ -0,0 +1,355 @@ += 0"; +$lang['danger']['domain_not_found'] = "Domínio não encontrado."; +$lang['danger']['max_mailbox_exceeded'] = "Número máximo de contas exedido (%d of %d)"; +$lang['danger']['mailbox_quota_exceeded'] = "Espaço excede o limite do domínio (max. %d MiB)"; +$lang['danger']['mailbox_quota_left_exceeded'] = "Não existe espaço suficiente (espaço disponível: %d MiB)"; +$lang['success']['mailbox_added'] = "Conta %s adicionada com sucesso"; +$lang['success']['domain_removed'] = "Domínio %s removido com sucesso"; +$lang['success']['alias_removed'] = "Apelido %s removido com sucesso"; +$lang['success']['alias_domain_removed'] = "Encaminhamento de Domínio %s removido com sucesso"; +$lang['success']['domain_admin_removed'] = "Administrator do domínio %s removido com sucesso"; +$lang['success']['mailbox_removed'] = "Conta %s removida com sucesso"; +$lang['danger']['max_quota_in_use'] = "Espaço da Conta deve ser maior ou igual a %d MiB"; +$lang['danger']['domain_quota_m_in_use'] = "Espaço do Domínio deve ser maior ou igual a %s MiB"; +$lang['danger']['mailboxes_in_use'] = "O máximo de Contas deve ser maior ou igual a %d"; +$lang['danger']['aliases_in_use'] = "O máximo de Apelidos deve ser maior ou igual a %d"; +$lang['danger']['sender_acl_invalid'] = "Campo Sender ACL é inválido"; +$lang['danger']['domain_not_empty'] = "Não é possível remover um domínio com Contas/Apelidos/Direcionamentos"; +$lang['warning']['spam_alias_temp_error'] = "Falha Temporária: Não foi possível adicionar Apelido para Spam."; +$lang['danger']['spam_alias_max_exceeded'] = "O número máximo de Apelidos para Spam foi excedido"; +$lang['danger']['fetchmail_active'] = "O processo esta em andamento, aguarde o seu término."; +$lang['danger']['validity_missing'] = 'Você deve definir um período de validade'; +$lang['user']['on'] = "On"; +$lang['user']['off'] = "Off"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = 'Configurações do usuário'; +$lang['user']['mailbox_settings'] = 'Configrações da conta'; +$lang['user']['mailbox_details'] = 'Detalhes da conta'; +$lang['user']['change_password'] = 'Alterar senha'; +$lang['user']['new_password'] = 'Nova senha'; +$lang['user']['save_changes'] = 'Salvar'; +$lang['user']['password_now'] = 'Senha atual (confirme a alteração)'; +$lang['user']['new_password_repeat'] = 'Confirmar senha (repetir)'; +$lang['user']['new_password_description'] = 'Requerido: mínimo de 6 characteres com letras e números.'; +$lang['user']['did_you_know'] = 'Você sabia? Você pode usar tags no endereço de email ("conta+privado@example.com") para classificar as mensagens automaticamente para uma determinada pasta (exemplo: "privado").'; +$lang['user']['spam_aliases'] = 'Apelidos temporários'; +$lang['user']['alias'] = 'Apelido'; +$lang['user']['aliases'] = 'Apelidos'; +$lang['user']['aliases_send_as_all'] = 'Não verificar remetente para os domínios'; +$lang['user']['alias_create_random'] = 'Gerar um apelido automaticamente'; +$lang['user']['alias_extend_all'] = 'Extender apelido por 1 hora'; +$lang['user']['alias_valid_until'] = 'Válido até'; +$lang['user']['alias_remove_all'] = 'Remover todos os apelidos'; +$lang['user']['alias_time_left'] = 'Tempo restante'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Período de validade'; +$lang['user']['hour'] = 'Hora'; +$lang['user']['hours'] = 'Horas'; +$lang['user']['day'] = 'Dia'; +$lang['user']['week'] = 'Semana'; +$lang['user']['weeks'] = 'Semanas'; +$lang['user']['spamfilter'] = 'Filtro de Spam'; +$lang['user']['spamfilter_wl'] = 'WhiteList'; +$lang['user']['spamfilter_wl_desc'] = 'Endereços em WhiteList nunca classificar como spam. Pode ser usado coringa *@example.com.'; +$lang['user']['spamfilter_bl'] = 'BlackList'; +$lang['user']['spamfilter_bl_desc'] = 'Endereços em BlackList sempre classificar como spam e rejeitar. Pode ser usado coringa *@example.com.'; +$lang['user']['spamfilter_behavior'] = 'Classificação'; +$lang['user']['spamfilter_table_rule'] = 'Regra'; +$lang['user']['spamfilter_table_action'] = 'Ação'; +$lang['user']['spamfilter_table_empty'] = 'Nenhum registro'; +$lang['user']['spamfilter_table_remove'] = 'remover'; +$lang['user']['spamfilter_table_add'] = "Adicionar registro"; +$lang['user']['spamfilter_behavior'] = 'Verificar'; +$lang['user']['spamfilter_default_score'] = 'Nivel de Spam:'; +$lang['user']['spamfilter_green'] = 'Verde: essa mensagem não é spam'; +$lang['user']['spamfilter_yellow'] = 'Amarelo: essa mensagem pode ser spam, será marcada como spam e classificada na pasta Spam'; +$lang['user']['spamfilter_red'] = 'Vermelho: essa mensagem é mesmo spam e será rejeitada definitivamente pelo servidor'; +$lang['user']['spamfilter_default_score'] = 'Valores padrão:'; +$lang['user']['spamfilter_hint'] = 'O primeiro espaço indica "baixo nível de spam", a segunda representa "alto nível de spam".'; +$lang['user']['tls_policy_warning'] = 'Aviso: Se você selecionar para forçar o envio encryptado , alguns emails poderão ser rejeitados.
    Mensages que não satisfizerem as politicas dos outros servidores serão rejeitadas definitivamente.'; +$lang['user']['tls_policy'] = 'Regras de Encryptação'; +$lang['user']['tls_enforce_in'] = 'Forçar TLS na entrada'; +$lang['user']['tls_enforce_out'] = 'Forçar TLS na saída'; +$lang['user']['misc_settings'] = 'Outras configurações'; +$lang['user']['misc_delete_profile'] = 'Outras configurações'; +$lang['user']['no_record'] = 'Nenhum registro'; +$lang['start']['dashboard'] = '%s - Painel'; +$lang['start']['start_rc'] = 'Webmail Roundcube'; +$lang['start']['start_sogo'] = 'Abrir SOGo'; +$lang['start']['mailcow_apps_detail'] = 'Use um mailcow app para acessar seus emails, calendário, contatos e outras informações.'; +$lang['start']['mailcow_panel'] = 'Iniciar mailcow UI'; +$lang['start']['mailcow_panel_description'] = 'O mailcow UI está disponível para Administradores e Usuários.'; +$lang['start']['mailcow_panel_detail'] = 'Administradores: podem criar, alterar ou apagar contas e apelidos , alterar domínios e outras informações de seus domínios atribuídos.
    + Usuários: podem criar apelidos por tempo determinado , alterar senha e configuração do nível do filtro de spam.'; +$lang['start']['recommended_config'] = 'Configuração recomendada (sem o ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP e SMTP server data'; +$lang['start']['imap_smtp_server_description'] = 'Para uma melhor utilização use o Mozilla Thunderbird.'; +$lang['start']['imap_smtp_server_badge'] = 'Ler/Criar emails'; +$lang['start']['imap_smtp_server_auth_info'] = 'Utilize o endereço de email completo com o método de autentucação PLAIN.
    +Os dados de login serão encryptados pelo servidor.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Filtro de email'; +$lang['start']['managesieve_description'] = 'Utilize o Mozilla Thunderbird com a extensão para sieve.
    Inicie o Thunderbird, acesse os Complementos e solte o arquivo xpi que foi baixado, na janela aberta.
    Preencha com o servidor %s, porta 4190 se for solicitado. Os dados de acesso são os mesmos da sua conta de email.'; +$lang['start']['service'] = 'Serviço'; +$lang['start']['encryption'] = 'Método de criptografia'; +$lang['start']['help'] = 'Mostrar/Ocultar painel de ajuda'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Porta'; +$lang['start']['footer'] = 'Rodapé'; +$lang['header']['mailcow_settings'] = 'Configuração'; +$lang['header']['administration'] = 'Administração'; +$lang['header']['mailboxes'] = 'Contas'; +$lang['header']['user_settings'] = 'Configurações do usuário'; +$lang['header']['login'] = 'Entrar'; +$lang['header']['logged_in_as_logout'] = 'Olá %s (Clique para Sair)'; +$lang['header']['locale'] = 'Idioma'; +$lang['mailbox']['domain'] = 'Domínio'; +$lang['mailbox']['alias'] = 'Apelido'; +$lang['mailbox']['aliases'] = 'Apelidos'; +$lang['mailbox']['domains'] = 'Domínios'; +$lang['mailbox']['mailboxes'] = 'Contas'; +$lang['mailbox']['mailbox_quota'] = 'Espaço máximo da Conta'; +$lang['mailbox']['domain_quota'] = 'Espaço'; +$lang['mailbox']['active'] = 'Ativo'; +$lang['mailbox']['action'] = 'Ação'; +$lang['mailbox']['ratelimit'] = 'Limite de envios por hora'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Encaminhamento de Domínio'; +$lang['mailbox']['target_domain'] = 'Domínio Destino'; +$lang['mailbox']['target_address'] = 'Encaminhar para'; +$lang['mailbox']['username'] = 'Usuário'; +$lang['mailbox']['fname'] = 'Nome'; +$lang['mailbox']['filter_table'] = 'Procurar'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Espaço'; +$lang['mailbox']['in_use'] = 'Em uso (%)'; +$lang['mailbox']['msg_num'] = 'Mensagens'; +$lang['mailbox']['remove'] = 'Remover'; +$lang['mailbox']['edit'] = 'Alterar'; +$lang['mailbox']['archive'] = 'Arquivo'; +$lang['mailbox']['no_record'] = 'Nenhum registro'; +$lang['mailbox']['add_domain'] = 'Adicionar Domínio'; +$lang['mailbox']['add_domain_alias'] = 'Adicionar Apelido de Domínio'; +$lang['mailbox']['add_mailbox'] = 'Adicionar Conta de Email'; +$lang['mailbox']['add_alias'] = 'Adicionar Apelido'; +$lang['info']['no_action'] = 'Nenhuma ação foi definida'; +$lang['delete']['title'] = 'Remover objeto'; +$lang['delete']['remove_domain_warning'] = 'Aviso: Você está prestes a remover o Domínio %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Aviso: Você está prestes a remover o Encaminhamento de Domínio %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Aviso: Você está prestes a remover o Administrador %s!'; +$lang['delete']['remove_alias_warning'] = 'Aviso: Você está prestes a remover o Apelido %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Aviso: Você está prestes a remover a Conta %s!'; +$lang['delete']['remove_mailbox_details'] = 'A Conta será excluída permanentemente!'; +$lang['delete']['remove_domain_details'] = 'Esse procedimento removerá o Encaminhamento de Domínio.

    O Domínio deve estar sem nenhuma configuração para ser removido.'; +$lang['delete']['remove_alias_details'] = 'Os usuários não poderão mais enviar ou receber emails através deste endereço.'; +$lang['delete']['remove_button'] = 'Remover'; +$lang['delete']['previous'] = 'Voltar'; +$lang['edit']['save'] = 'Salvar'; +$lang['edit']['archive'] = 'Acesso ao arquivo'; +$lang['edit']['max_mailboxes'] = 'Máximo de contas:'; +$lang['edit']['title'] = 'Editar dos Objetos'; +$lang['edit']['target_address'] = 'Enviar para os emails (separar por vírgula):'; +$lang['edit']['active'] = 'Ativo'; +$lang['edit']['target_domain'] = 'Domínio de Destino:'; +$lang['edit']['password'] = 'Senha:'; +$lang['edit']['ratelimit'] = 'Volume de envios por hora:'; +$lang['danger']['ratelimt_less_one'] = 'Limite da taxa de saída por hora não pode ser inferior a 1'; +$lang['edit']['password_repeat'] = 'Confirmar senha (repetir):'; +$lang['edit']['domain_admin'] = 'Editar administrador de domínio'; +$lang['edit']['domain'] = 'Editar domínio'; +$lang['edit']['alias_domain'] = 'Encaminhar domínio'; +$lang['edit']['edit_alias_domain'] = 'Editar encaminhamento de domínio'; +$lang['edit']['domains'] = 'Domínios'; +$lang['edit']['destroy'] = 'Inserir manualmente'; +$lang['edit']['alias'] = 'Editar apelido'; +$lang['edit']['mailbox'] = 'Editar conta'; +$lang['edit']['description'] = 'Descrição:'; +$lang['edit']['max_aliases'] = 'Máximo apelidos:'; +$lang['edit']['max_quota'] = 'Máximo espaço por conta (MiB):'; +$lang['edit']['domain_quota'] = 'Espaço do domínio:'; +$lang['edit']['backup_mx_options'] = 'Opções de Backup MX:'; +$lang['edit']['relay_domain'] = 'Relay de domínio'; +$lang['edit']['relay_all'] = 'Relay para todas as contas'; +$lang['edit']['dkim_signature'] = 'Assinatura DKIM:'; +$lang['edit']['dkim_record_info'] = 'Adicione um registro TXT com o mesmo a mesma configuração dos registros DNS.'; +$lang['edit']['relay_all_info'] = 'Se você escolher não direcionar todas as contas de email, você deve adicionar um ("blind") para cada uma das contas.'; +$lang['edit']['full_name'] = 'Nome completo'; +$lang['edit']['quota_mb'] = 'Espaço (MiB)'; +$lang['edit']['sender_acl'] = 'Permitir Enviar como'; +$lang['edit']['sender_acl_info'] = 'Apelidos não podem ser removidos.'; +$lang['edit']['dkim_txt_name'] = 'Nome do registro TXT:'; +$lang['edit']['dkim_txt_value'] = 'Valor do registro TXT:'; +$lang['edit']['previous'] = 'Voltar'; +$lang['edit']['unchanged_if_empty'] = 'Deixar em branco para não modificar'; +$lang['edit']['dont_check_sender_acl'] = 'Não verificar o remetente para o domínio %s'; +$lang['add']['title'] = 'Adicionar objeto'; +$lang['add']['domain'] = 'Domínio'; +$lang['add']['active'] = 'Ativo'; +$lang['add']['save'] = 'Salvar'; +$lang['add']['description'] = 'Descrição:'; +$lang['add']['max_aliases'] = 'Máximo de apelidos:'; +$lang['add']['max_mailboxes'] = 'Máximo de contas:'; +$lang['add']['mailbox_quota_m'] = 'Máximo espaço por conta (MiB):'; +$lang['add']['domain_quota_m'] = 'Total de espaço por domínio(MiB):'; +$lang['add']['backup_mx_options'] = 'Opções Backup MX:'; +$lang['add']['relay_all'] = 'Relay para todas as contas'; +$lang['add']['relay_domain'] = 'Relay para todo domínio'; +$lang['add']['relay_all_info'] = 'Se não selecionar para retransmitir todas as contas, você deve adicionar uma ("blind") para cada conta que será direcionada.'; +$lang['add']['alias'] = 'Apelido(s)'; +$lang['add']['alias_spf_fail'] = 'Aviso: Se você escolher uma conta externa, o servidor externo poderá rejeitar algumas mensagens por erro de SPF.'; +$lang['add']['alias_address'] = 'Apelidos:'; +$lang['add']['alias_address_info'] = 'Endereço de email completo ou @example.com, para uma conta coringa -catch all. (separado por vírgula). apenas domínios cadastrados.'; +$lang['add']['alias_domain_info'] = 'Domínios válidos apenas (separado por vírgulas).'; +$lang['add']['target_address'] = 'Encaminhar para:'; +$lang['add']['target_address_info'] = 'Endereço de email completo (separado por vírgulas).'; +$lang['add']['alias_domain'] = 'Encaminhamento de Domínio'; +$lang['add']['select'] = 'Selecione...'; +$lang['add']['target_domain'] = 'Domínio de Destino:'; +$lang['add']['mailbox'] = 'Conta'; +$lang['add']['mailbox_username'] = 'Usuário (primeira parte do endereço de email):'; +$lang['add']['full_name'] = 'Nome:'; +$lang['add']['quota_mb'] = 'Espaço (MiB):'; +$lang['add']['select_domain'] = 'Selecione um domínio antes'; +$lang['add']['password'] = 'Senha:'; +$lang['add']['password_repeat'] = 'Confirmar a senha (repetir):'; +$lang['add']['previous'] = 'Voltar'; +$lang['login']['title'] = 'Entrar'; +$lang['login']['administration'] = 'Administração'; +$lang['login']['administration_details'] = 'Utilize o login de Administrador para efetuar tarefas de administração.'; +$lang['login']['user_settings'] = 'Configuração do usuário'; +$lang['login']['user_settings_details'] = 'Usuários podem utilizar o mailcow UI para alterar suas senhas, criar apelidos temporários (Apelido Anti-Spam), adjustar a sensibilidade do filtro the spam ou importar mensagens de um servidor IMAP.'; +$lang['login']['username'] = 'Usuário'; +$lang['login']['password'] = 'Senha'; +$lang['login']['reset_password'] = 'Esqueci minha senha'; +$lang['login']['login'] = 'Entrar'; +$lang['login']['previous'] = "Voltar"; +$lang['login']['delayed'] = 'Sua entrada será atrasada por %s segundos.'; +$lang['login']['tfa'] = "Autenticação em duas etapas"; +$lang['login']['tfa_details'] = "Confirme sua senha no campo abaixo"; +$lang['login']['confirm'] = "Confirmar"; +$lang['login']['otp'] = "Senha única"; +$lang['login']['trash_login'] = "Tentativas de entrada"; +$lang['admin']['search_domain_da'] = 'Selecione o(s) domínio(s)'; +$lang['admin']['restrictions'] = 'Postfix Restrictions'; +$lang['admin']['rr'] = 'Postfix Recipient Restrictions'; +$lang['admin']['sr'] = 'Postfix Sender Restrictions'; +$lang['admin']['reset_defaults'] = 'Voltar configuração padrão'; +$lang['admin']['sr'] = 'Postfix Sender Restrictions'; +$lang['admin']['r_inactive'] = 'Restrictions Inativos'; +$lang['admin']['r_active'] = 'Restrictions Ativos'; +$lang['admin']['r_info'] = 'Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway.
    You can add new elements in inc/vars.local.inc.php to be able to toggle them.'; +$lang['admin']['public_folders'] = 'Pastas públicas'; +$lang['admin']['public_folders_text'] = 'A pasta "Public" esta criada. Abaixo a pasta pública indica o nome da primeira pasta criada automaticamente na conta, com este nome.'; +$lang['admin']['public_folder_name'] = 'Nome da Pasta (alfa numérico)'; +$lang['admin']['public_folder_enable'] = 'Habilitar Pasta Pública'; +$lang['admin']['public_folder_enable_text'] = 'Ao alterar esta configuração os emails das pastas públicas não serão apagados.'; +$lang['admin']['public_folder_pusf'] = 'Habilitar visualização por usuário'; +$lang['admin']['public_folder_pusf_text'] = 'A "per-user seen flag"-enabled system will not mark a mail as read for User B, when User A has seen it, but User B did not.'; +$lang['admin']['privacy'] = 'Privacidade'; +$lang['admin']['privacy_text'] = 'Esta opção habilita a tabela PCRE para remover "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" e substitui pelo Header "Received: from" localhost/127.0.0.1.'; +$lang['admin']['privacy_anon_mail'] = 'Limpar o Cabeçalho dos emails de saída'; +$lang['admin']['dkim_txt_name'] = 'Registro TXT:'; +$lang['admin']['dkim_txt_value'] = 'Valor do TXT:'; +$lang['admin']['dkim_key_length'] = 'Tamanho do registro DKIM (bits)'; +$lang['admin']['previous'] = 'Voltar'; +$lang['admin']['quota_mb'] = 'Espaço (MiB):'; +$lang['admin']['sender_acl'] = 'Permitir Enviar como:'; +$lang['admin']['msg_size'] = 'Tamanho da mensagem'; +$lang['admin']['msg_size_limit'] = 'Tamanho limite de mensagem atual'; +$lang['admin']['msg_size_limit_details'] = 'Ao aplicar um novo limite os Serviços de Email e Web serão reiniciados.'; +$lang['admin']['save'] = 'Salvar'; +$lang['admin']['maintenance'] = 'Manutenção e Informação'; +$lang['admin']['sys_info'] = 'Informações de Sistema'; +$lang['admin']['dkim_add_key'] = 'Adicionar registro DKIM'; +$lang['admin']['dkim_keys'] = 'Registro DKIM'; +$lang['admin']['add'] = 'Salvar'; +$lang['admin']['configuration'] = 'Configuração'; +$lang['admin']['password'] = 'Senha'; +$lang['admin']['password_repeat'] = 'Confirmar senha (repetir)'; +$lang['admin']['active'] = 'Ativo'; +$lang['admin']['action'] = 'Ação'; +$lang['admin']['add_domain_admin'] = 'Adicionar administrador de domínio(s)'; +$lang['admin']['admin_domains'] = 'Acesso aos Domínios'; +$lang['admin']['domain_admins'] = 'Administradores de domínio'; +$lang['admin']['username'] = 'Administrador'; +$lang['admin']['edit'] = 'Editar'; +$lang['admin']['remove'] = 'Remover'; +$lang['admin']['save'] = 'Salvar'; +$lang['admin']['admin'] = 'Administrador'; +$lang['admin']['admin_details'] = 'Editar informações do administrator'; +$lang['admin']['unchanged_if_empty'] = 'Deixar em branco para não alterar'; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Acessos'; +$lang['admin']['invalid_max_msg_size'] = 'Tamanho máximo da mensagem inválido'; +$lang['admin']['site_not_found'] = 'Não foi possível localizar as configuração do painel mailcow'; +$lang['admin']['public_folder_empty'] = 'O nome da Pasta Pública deve ser preenchido'; +$lang['admin']['set_rr_failed'] = 'Não foi possível alterar Postfix Restrictions'; +$lang['admin']['no_record'] = 'Nenhum registro'; +?> diff --git a/data/web/mailbox.php b/data/web/mailbox.php new file mode 100644 index 00000000..08798ffe --- /dev/null +++ b/data/web/mailbox.php @@ -0,0 +1,426 @@ + +
    +
    +
    +
    +
    +

    +
    + + + + + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    / / / +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ' . htmlspecialchars($mailboxdata['username']) . '';?> / +
    +
    + % +
    +
    +
    +
    + + + + Login + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +

    +
    + + + + +
    +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Catch-all ' . htmlspecialchars($aliasdata['address']) : htmlspecialchars($aliasdata['address']); ?> + + + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + diff --git a/data/web/robots.txt b/data/web/robots.txt new file mode 100644 index 00000000..1f53798b --- /dev/null +++ b/data/web/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/data/web/u2f_api.php b/data/web/u2f_api.php new file mode 100644 index 00000000..634757ce --- /dev/null +++ b/data/web/u2f_api.php @@ -0,0 +1,157 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + +$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://"; +$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']); + +function getRegs($username) { + global $pdo; + $sel = $pdo->prepare("select * from tfa where username = ?"); + $sel->execute(array($username)); + return $sel->fetchAll(); +} +function addReg($username, $reg) { + global $pdo; + $ins = $pdo->prepare("INSERT INTO `tfa` (`username`, `keyHandle`, `publicKey`, `certificate`, `counter`) values (?, ?, ?, ?, ?)"); + $ins->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); +} +function updateReg($reg) { + global $pdo; + $upd = $pdo->prepare("update tfa set counter = ? where id = ?"); + $upd->execute(array($reg->counter, $reg->id)); +} +?> + + + +getRegisterData(getRegs($username)); + list($req, $sigs) = $data; + $_SESSION['regReq'] = json_encode($req); +?> + +getMessage(); + } + break; + + case 'authenticate': + try { + $reqs = json_encode($u2f->getAuthenticateData(getRegs($username))); + $_SESSION['authReq'] = $reqs; +?> + +getMessage(); + } + break; + } + } + if (!empty($_POST['u2f_register_data'])) { + try { + $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_POST['u2f_register_data'])); + addReg($username, $reg); + } + catch (Exception $e) { + echo "U2F error: " . $e->getMessage(); + } + finally { + echo "Success"; + $_SESSION['regReq'] = null; + } + } + if (!empty($_POST['u2f_auth_data'])) { + try { + $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), getRegs($username), json_decode($_POST['u2f_auth_data'])); + updateReg($reg); + } + catch (Exception $e) { + echo "U2F error: " . $e->getMessage(); + } + finally { + echo "Success"; + $_SESSION['authReq'] = null; + } + } + } +?> + + +
    +
    + + +
    +
    +
    + +
    +Username:

    +Action:
    + Register
    + Authenticate
    + +
    + + + \ No newline at end of file diff --git a/data/web/user.php b/data/web/user.php new file mode 100644 index 00000000..253715b6 --- /dev/null +++ b/data/web/user.php @@ -0,0 +1,529 @@ + +
    +

    +
    +
    +
    +
    +
    +

    []

    +
    +
    +
    +
    +
    +
    +

    +
    + +
    + +
    🔑 []
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +

    + +
    +
    +
    +
    +
    +

    []

    +
    +
    +
    + +
    +
    :
    +
    +

    +
    +
    +
    +
    :
    +
    +

    +
    +
    +
    +
    :
    +
    +

    +
    +
    +
    +
    :
    +
    +

    +
    +
    +
    +
    :
    +
    +

    +
    +
    +
    + +
    + +
    +
    :
    +
    + + +

    +

    +
    +
    +
    + +
    +
    +
    :
    +
    + +

    +
    +
    +
    +
    +
    + + + +
    + +
    +
    +
    +
    +

    +
    +
    +

    +
    +
    +

    +
    +
    + +
    +
    +

    +
    +
    +

    +
    +
    +
    + + + +
    +
    +
    +
    + + 1h + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +

    +
    +
    +
    + +

    +
      +
    • +
    • +
    • +
    +

    5:15

    +

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + + + + + + + +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + + + + + + + +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +

    +
    +
    +
    +

    + data-on-text="" data-off-text=""> +
    +
    +
    +
    +

    + data-on-text="" data-off-text=""> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Server:PortLog
    ' . $row['exclude'] . '';?> min + + Open logs + + +
    + + +
    +
    + +
    +
    +
    +
    +
    + + + + +
    + +
    + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..34b917f6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,241 @@ +version: '2.1' + +services: + pdns-mailcow: + image: andryyy/mailcow-dockerized:pdns + depends_on: + mysql-mailcow: + condition: service_healthy + volumes: + - ./data/conf/pdns/:/etc/powerdns/ + restart: always + networks: + mailcow-network: + ipv4_address: 172.22.1.254 + aliases: + - pdns + + mysql-mailcow: + image: mariadb:10.1 + healthcheck: + test: ["CMD", "mysqladmin", "ping", "--host", "localhost", "--silent"] + interval: 10s + timeout: 30s + retries: 5 + volumes: + - mysql-vol-1:/var/lib/mysql/ + - ./data/conf/mysql/:/etc/mysql/conf.d/:ro + dns: + - 172.22.1.254 + dns_search: mailcow-network + environment: + - MYSQL_ROOT_PASSWORD=${DBROOT} + - MYSQL_DATABASE=${DBNAME} + - MYSQL_USER=${DBUSER} + - MYSQL_PASSWORD=${DBPASS} + restart: always + networks: + mailcow-network: + aliases: + - mysql + + redis-mailcow: + image: redis + depends_on: + - pdns-mailcow + volumes: + - redis-vol-1:/data/ + restart: always + dns: + - 172.22.1.254 + dns_search: mailcow-network + networks: + mailcow-network: + aliases: + - redis + + rspamd-mailcow: + image: andryyy/mailcow-dockerized:rspamd + depends_on: + - nginx-mailcow + volumes: + - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro + - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro + - ./data/conf/rspamd/lua/:/etc/rspamd/lua/:ro + - dkim-vol-1:/data/dkim + - rspamd-vol-1:/var/lib/rspamd + restart: always + dns: + - 172.22.1.254 + dns_search: mailcow-network + networks: + mailcow-network: + ipv4_address: 172.22.1.253 + aliases: + - rspamd + + php-fpm-mailcow: + image: andryyy/mailcow-dockerized:phpfpm + command: "php-fpm -d date.timezone=${TZ}" + depends_on: + - pdns-mailcow + volumes: + - ./data/web:/web:ro + - ./data/conf/rspamd/dynmaps:/dynmaps:ro + - dkim-vol-1:/data/dkim + dns: + - 172.22.1.254 + dns_search: mailcow-network + environment: + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + restart: always + networks: + mailcow-network: + aliases: + - phpfpm + + sogo-mailcow: + image: andryyy/mailcow-dockerized:sogo + depends_on: + - pdns-mailcow + environment: + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + - TZ=${TZ} + dns: + - 172.22.1.254 + dns_search: mailcow-network + volumes: + - ./data/conf/sogo/:/etc/sogo/ + - /usr/lib/GNUstep/SOGo/WebServerResources/ + restart: always + networks: + mailcow-network: + ipv4_address: 172.22.1.252 + aliases: + - sogo + + rmilter-mailcow: + image: andryyy/mailcow-dockerized:rmilter + depends_on: + - pdns-mailcow + volumes: + - ./data/conf/rmilter/:/etc/rmilter.conf.d/:ro + restart: always + dns: + - 172.22.1.254 + dns_search: mailcow-network + networks: + mailcow-network: + aliases: + - rmilter + + dovecot-mailcow: + image: andryyy/mailcow-dockerized:dovecot + depends_on: + - pdns-mailcow + volumes: + - ./data/conf/dovecot:/etc/dovecot + - ./data/assets/ssl:/etc/ssl/mail/:ro + - ./data/conf/sogo/:/etc/sogo/ + - vmail-vol-1:/var/vmail + environment: + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + ports: + - "${IMAP_PORT:-143}:143" + - "${IMAPS_PORT:-993}:993" + - "${POP_PORT-110}:110" + - "${POPS_PORT:-995}:995" + - "${SIEVE_PORT:-4190}:4190" + dns: + - 172.22.1.254 + dns_search: mailcow-network + restart: always + hostname: ${MAILCOW_HOSTNAME} + networks: + mailcow-network: + aliases: + - dovecot + + postfix-mailcow: + image: andryyy/mailcow-dockerized:postfix + depends_on: + - pdns-mailcow + volumes: + - ./data/conf/postfix:/opt/postfix/conf + - ./data/assets/ssl:/etc/ssl/mail/:ro + environment: + - DBNAME=${DBNAME} + - DBUSER=${DBUSER} + - DBPASS=${DBPASS} + ports: + - "${SMTP_PORT:-25}:25" + - "${SMTPS_PORT:-465}:465" + - "${SUBMISSION_PORT:-587}:587" + restart: always + hostname: ${MAILCOW_HOSTNAME} + dns: + - 172.22.1.254 + dns_search: mailcow-network + networks: + mailcow-network: + aliases: + - postfix + + memcached-mailcow: + image: memcached + depends_on: + - pdns-mailcow + restart: always + dns: + - 172.22.1.254 + dns_search: mailcow-network + networks: + mailcow-network: + aliases: + - memcached + + nginx-mailcow: + depends_on: + - sogo-mailcow + - php-fpm-mailcow + image: nginx:mainline + command: /bin/bash -c "envsubst < /etc/nginx/conf.d/listen.template > /etc/nginx/conf.d/listen.active && nginx -g 'daemon off;'" + environment: + - HTTPS_PORT=${HTTPS_PORT:-443} + volumes: + - ./data/web:/web:ro + - ./data/conf/rspamd/dynmaps:/dynmaps:ro + - ./data/assets/ssl/:/etc/ssl/mail/:ro + - ./data/conf/nginx/:/etc/nginx/conf.d/:rw + dns: + - 172.22.1.254 + dns_search: mailcow-network + ports: + - "${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" + restart: always + networks: + mailcow-network: + aliases: + - nginx + +networks: + mailcow-network: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.22.1.0/24 + +volumes: + vmail-vol-1: + mysql-vol-1: + dkim-vol-1: + redis-vol-1: + rspamd-vol-1: diff --git a/docs/first_steps.md b/docs/first_steps.md new file mode 100644 index 00000000..85a1d09f --- /dev/null +++ b/docs/first_steps.md @@ -0,0 +1,76 @@ +# Change default language + +Change `data/conf/sogo/sogo.conf` and replace English by your language. + +Create a file `data/web/inc/vars.local.inc.php` and add "DEFAULT_LANG" with either "en", "pt", "de" or "nl": +``` + array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) +); +$config['enable_installer'] = false; +$config['smtp_conn_options'] = array( +'ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true) +); +``` + +Point your browser to `https://myserver/rc/installer` and follow the instructions. +Initialize the database and leave the installer. +**Delete the directory `data/web/rc/installer` after a successful installation!** + +## Enable password changing + +Open `data/web/rc/config.inc.php` and enable the password plugin: + +``` +... +$config['plugins'] = array( + 'archive', + 'password', +); +... +``` + +Open `data/web/rc/plugins/password/password.php`, search for `case 'ssha':` and add above: + +``` + case 'ssha256': + $salt = rcube_utils::random_bytes(8); + $crypted = base64_encode( hash('sha256', $password . $salt, TRUE ) . $salt ); + $prefix = '{SSHA256}'; + break; +``` + +Open `data/web/rc/plugins/password/config.inc.php` and change the following parameters (or add them at the bottom of that file): + +``` +$config['password_driver'] = 'sql'; +$config['password_algorithm'] = 'ssha256'; +$config['password_algorithm_prefix'] = '{SSHA256}'; +$config['password_query'] = "UPDATE mailbox SET password = %P WHERE username = %u"; +``` + +# Learn spam and ham + +Rspamd learns mail as spam or ham when you move a message in or out of the junk folder to any mailbox besides trash. +This is archived by using the Dovecot plugin "antispam" and a simple parser script. + +Rspamd also auto-learns mail when a high or low score is detected (see https://rspamd.com/doc/configuration/statistic.html#autolearning) + +The bayes statistics are written to Redis as keys `BAYES_HAM` and `BAYES_SPAM`. + +You can also use Rspamds web ui to learn ham and/or spam. + +# MySQL + +### Connect to the MySQL database: +``` +source mailcow.conf +docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} +``` + +### Backup the database: +``` +cd /path/to/mailcow-dockerized +source mailcow.conf +DATE=$(date +"%Y%m%d_%H%M%S") +docker-compose exec mysql-mailcow mysqldump --default-character-set=utf8mb4 -u${DBUSER} -p${DBPASS} ${DBNAME} > backup_${DBNAME}_${DATE}.sql +``` + +### Restore the database: +``` +cd /path/to/mailcow-dockerized +source mailcow.conf +docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < backup_file.sql +``` + +# Read logs +You can use `docker-compose logs $service-name` for all containers. + +Run `docker-compose logs` for all logs at once. + +Follow the log output by running docker-compose with `logs -f`. + +# Redirect port 80 to 443 + +Since February the 28th 2017 mailcow does come with port 80 and 443 enabled. + +Open `mailcow.conf` and set `HTTP_BIND=0.0.0.0`. + +Open `data/conf/nginx/site.conf` and add a new "catch-all" site at the top of that file: + +``` +server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; +} +``` + +Restart the stack, changed containers will be updated: + +`docker-compose up -d` + +# Redis + +## Connect to redis key store: + +``` +docker-compose exec redis-mailcow redis-cli +``` + +# Remove persistent data + +- Remove volume `mysql-vol-1` to remove all MySQL data. +- Remove volume `redis-vol-1` to remove all Redis data. +- Remove volume `vmail-vol-1` to remove all contents of `/var/vmail` mounted to `dovecot-mailcow`. +- Remove volume `dkim-vol-1` to remove all DKIM keys. +- Remove volume `rspamd-vol-1` to remove all Rspamd data. + +Running `docker-compose down -v` will **destroy all mailcow: dockerized volumes** and delete any related containers.Reset mailcow admin to `admin:moohoo`: + +1. Drop admin table + +``` +source mailcow.conf +docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TABLE admin;" +``` + +2. Open mailcow UI to auto-init the db + +# Rspamd + +## Rspamd CLI tools + +``` +docker-compose exec rspamd-mailcow rspamc --help +docker-compose exec rspamd-mailcow rspamadm --help +``` + +See [Rspamd documentation](https://rspamd.com/doc/index.html) + +# Adjust service configurations +The most important configuration files are mounted from the host into the related containers: +``` +data/conf +├── bind9 +│   └── named.conf +├── dovecot +│   ├── dovecot.conf +│   ├── dovecot-master.passwd +│   ├── sieve_after +│   └── sql +│   ├── dovecot-dict-sql.conf +│   └── dovecot-mysql.conf +├── mysql +│   └── my.cnf +├── nginx +│   ├── dynmaps.conf +│   ├── site.conf +│   └── templates +│   ├── listen_plain.template +│   ├── listen_ssl.template +│   └── server_name.template +├── pdns +│   ├── pdns_custom.lua +│   └── recursor.conf +├── postfix +│   ├── main.cf +│   ├── master.cf +│   ├── postscreen_access.cidr +│   ├── smtp_dsn_filter +│   └── sql +│   ├── mysql_relay_recipient_maps.cf +│   ├── mysql_tls_enforce_in_policy.cf +│   ├── mysql_tls_enforce_out_policy.cf +│   ├── mysql_virtual_alias_domain_catchall_maps.cf +│   ├── mysql_virtual_alias_domain_maps.cf +│   ├── mysql_virtual_alias_maps.cf +│   ├── mysql_virtual_domains_maps.cf +│   ├── mysql_virtual_mailbox_maps.cf +│   ├── mysql_virtual_relay_domain_maps.cf +│   ├── mysql_virtual_sender_acl.cf +│   └── mysql_virtual_spamalias_maps.cf +├── rmilter +│   └── rmilter.conf +├── rspamd +│   ├── dynmaps +│   │   ├── authoritative.php +│   │   ├── settings.php +│   │   ├── tags.php +│   │   └── vars.inc.php -> ../../../web/inc/vars.inc.php +│   ├── local.d +│   │   ├── dkim.conf +│   │   ├── metrics.conf +│   │   ├── options.inc +│   │   ├── redis.conf +│   │   ├── rspamd.conf.local +│   │   └── statistic.conf +│   ├── lua +│   │   └── rspamd.local.lua +│   └── override.d +│   ├── logging.inc +│   ├── worker-controller.inc +│   └── worker-normal.inc +└── sogo + ├── sieve.creds + └── sogo.conf + +``` +Just change the according configuration file on the host and restart the related service: `docker-compose restart service-mailcow` +# Tagging + +Mailbox users can tag their mail address like in `me+facebook@example.org` and choose between to setups to handle this tag: + +1. Move this message to a subfolder "facebook" (will be created lower case if not existing) +2. Prepend the tag to the subject: "[facebook] Subject" +# Two-factor authentication + +So far two methods for TFA are impelemented. Both work with the fantastic [Yubikey](https://www.yubico.com). + +While Yubi OTP needs an active internet connection and an API ID/key, U2F will work with any FIDO U2F USB key out of the box. + +Both methods support mulitple YubiKeys. + +As administrator you are able to temporary disable a domain adminsitrators TFA login until they successfully logged in. + +The key used to login will be displayed in green, while other keys remain grey. + +## Yubi OTP + +The Yubi API ID and Key will be checked against the Yubico Cloud API. When setting up TFA you will be asked for your personal API account for this key. +The API ID, API key and the first 12 characters (your YubiKeys ID in modhex) are stored in the MySQL table as secret. + +## U2F + +Only Google Chrome (+derivates) and Opera support U2F authentication to this day natively. +For Firefox you will need to install the "U2F Support Add-on" as provided on [mozilla.org](https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/). +U2F works without an internet connection.# Why does mailcow come with a DNS resolver? + +For DNS blacklist lookups and DNSSEC. + +Most systems use either a public or a local caching DNS resolver. +That's a very bad idea when it comes to filter spam using DNS-based blackhole lists (DNSBL) or similar technics. +Most if not all providers apply a rate limit based on the DNS resolver that is used to query their service. +Using a public resolver like Googles 4x8, OpenDNS or any other shared DNS resolver like your ISPs will hit that limit very soon. + + diff --git a/first_steps/index.html b/first_steps/index.html deleted file mode 100644 index 5df22073..00000000 --- a/first_steps/index.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - - - First Steps - mailcow: dockerized - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - -
    -
    -
    - -
    -
    -
    -
    - -

    Change default language

    -

    Change data/conf/sogo/sogo.conf and replace English by your language.

    -

    Create a file data/web/inc/vars.local.inc.php and add "DEFAULT_LANG" with either "en", "pt", "de" or "nl":

    -
    <?php
    -$DEFAULT_LANG = "de";
    -
    - -

    SSL (and: How to use Let's Encrypt)

    -

    mailcow dockerized comes with a snakeoil CA "mailcow" and a server certificate in data/assets/ssl. Please use your own trusted certificates.

    -

    mailcow uses 3 domain names that should be covered by your new certificate:

    -
      -
    • ${MAILCOW_HOSTNAME}
    • -
    • autodiscover.example.org
    • -
    • autoconfig.example.org
    • -
    -

    Obtain multi-SAN certificate by Let's Encrypt

    -

    This is just an example of how to obtain certificates with certbot. There are several methods!

    -
      -
    1. Get the certbot client:
    2. -
    -
    wget https://dl.eff.org/certbot-auto -O /usr/local/sbin/certbot && chmod +x /usr/local/sbin/certbot
    -
    - -
      -
    1. -

      Make sure you set HTTP_BIND=0.0.0.0 in mailcow.conf or setup a reverse proxy to enable connections to port 80. If you changed HTTP_BIND, then restart Nginx: docker-compose restart nginx-mailcow.

      -
    2. -
    3. -

      Request the certificate with the webroot method:

      -
    4. -
    -
    cd /path/to/git/clone/mailcow-dockerized
    -source mailcow.conf
    -certbot certonly \
    -        --webroot \
    -        -w ${PWD}/data/web \
    -        -d ${MAILCOW_HOSTNAME} \
    -        -d autodiscover.example.org \
    -        -d autoconfig.example.org \
    -        --email you@example.org \
    -        --agree-tos
    -
    - -
      -
    1. Create hard links to the full path of the new certificates. Assuming you are still in the mailcow root folder:
    2. -
    -
    mv data/assets/ssl/cert.{pem,pem.backup}
    -mv data/assets/ssl/key.{pem,pem.backup}
    -ln $(readlink -f /etc/letsencrypt/live/${MAILCOW_HOSTNAME}/fullchain.pem) data/assets/ssl/cert.pem
    -ln $(readlink -f /etc/letsencrypt/live/${MAILCOW_HOSTNAME}/privkey.pem) data/assets/ssl/key.pem
    -
    - -
      -
    1. Restart containers which use the certificate:
    2. -
    -
    docker-compose restart postfix-mailcow dovecot-mailcow nginx-mailcow
    -
    - -

    When renewing certificates, run the last two steps (link + restart) as post-hook in a script.

    -

    Rspamd UI access

    -

    At first you may want to setup Rspamds web interface which provides some useful features and information.

    -
      -
    1. Generate a Rspamd controller password hash:
    2. -
    -
    docker-compose exec rspamd-mailcow rspamadm pw
    -
    - -
      -
    1. Replace the default hash in data/conf/rspamd/override.d/worker-controller.inc by your newly generated:
    2. -
    -
    enable_password = "myhash";
    -
    - -
      -
    1. Restart rspamd:
    2. -
    -
    docker-compose restart rspamd-mailcow
    -
    - -

    Open https://${MAILCOW_HOSTNAME}/rspamd in a browser and login!

    - -
    -
    - - -
    -
    - -
    - -
    - -
    - - - GitHub - - - « Previous - - - Next » - - -
    - - - - diff --git a/fonts/fontawesome-webfont.eot b/fonts/fontawesome-webfont.eot deleted file mode 100755 index 0662cb96..00000000 Binary files a/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/fonts/fontawesome-webfont.svg b/fonts/fontawesome-webfont.svg deleted file mode 100755 index 2edb4ec3..00000000 --- a/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/fontawesome-webfont.ttf b/fonts/fontawesome-webfont.ttf deleted file mode 100755 index d3659246..00000000 Binary files a/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/fonts/fontawesome-webfont.woff b/fonts/fontawesome-webfont.woff deleted file mode 100755 index b9bd17e1..00000000 Binary files a/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/generate_config.sh b/generate_config.sh new file mode 100755 index 00000000..213bdcdb --- /dev/null +++ b/generate_config.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +if [[ -f mailcow.conf ]]; then + read -r -p "A config file exists and will be overwritten, are you sure you want to contine? [y/N] " response + case $response in + [yY][eE][sS]|[yY]) + mv mailcow.conf mailcow.conf_backup + ;; + *) + exit 1 + ;; + esac +fi + +if [ -z "$MAILCOW_HOSTNAME" ]; then + read -p "Hostname (FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME +fi + +if [ -z "$TZ" ]; then + read -p "Timezone: " -ei "Europe/Berlin" TZ +fi + +cat << EOF > mailcow.conf +# ------------------------------ +# mailcow web ui configuration +# ------------------------------ +# example.org is _not_ a valid hostname, use a fqdn here. +# Default admin user is "admin" +# Default password is "moohoo" +MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + +# ------------------------------ +# SQL database configuration +# ------------------------------ +DBNAME=mailcow +DBUSER=mailcow + +# Please use long, random alphanumeric strings (A-Za-z0-9) +DBPASS=$( - - - - - - - - - - Overview - mailcow: dockerized - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - -
    -
    -
    - -
    -
    -
    -
    - -

    mailcow: dockerized - 🐮 + 🐋 = 💕

    -

    Donate

    -

    mailcow dockerized comes with 11 containers linked in one bridged network.

    -
      -
    • Dovecot
    • -
    • Memcached
    • -
    • Redis
    • -
    • MySQL
    • -
    • Bind9 (Resolver) (formerly PDNS Recursor)
    • -
    • PHP-FPM
    • -
    • Postfix
    • -
    • Nginx
    • -
    • Rmilter
    • -
    • Rspamd
    • -
    • SOGo
    • -
    -

    6 volumes to keep dynamic data - take care of them!

    -
      -
    • vmail-vol-1
    • -
    • dkim-vol-1
    • -
    • redis-vol-1
    • -
    • mysql-vol-1
    • -
    • rspamd-vol-1
    • -
    • postfix-vol-1
    • -
    -

    The integrated mailcow UI allows administrative work on your mail server instance as well as separated domain administrator and mailbox user access:

    -
      -
    • DKIM key management
    • -
    • Black- and whitelists per domain and per user
    • -
    • Spam score managment per-user (reject spam, mark spam, greylist)
    • -
    • Allow mailbox users to create temporary spam aliases
    • -
    • Prepend mail tags to subject or move mail to subfolder (per-user)
    • -
    • Allow mailbox users to toggle incoming and outgoing TLS enforcement
    • -
    • Allow users to reset SOGo ActiveSync device caches
    • -
    • imapsync to migrate or pull remote mailboxes regularly
    • -
    • TFA: Yubi OTP and U2F USB (Google Chrome and derivates only)
    • -
    • Add domains, mailboxes, aliases, domain aliases and SOGo resources
    • -
    -

    Looking for a farm to host your cow?

    - -
    -
    - - -
    -
    - -
    - -
    - -
    - - - GitHub - - - - Next » - - -
    - - - - - - diff --git a/install/index.html b/install/index.html deleted file mode 100644 index c44371cf..00000000 --- a/install/index.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - Installation - mailcow: dockerized - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - -
    -
    -
    - -
    -
    -
    -
    - -

    Install mailcow

    -
      -
    1. -

      You need Docker.

      -
        -
      • Most systems can install Docker by running wget -qO- https://get.docker.com/ | sh
      • -
      -
    2. -
    3. -

      You need Docker Compose.

      -
        -
      • Learn how to install Docker Compose - or:
      • -
      • curl -L "https://github.com/docker/compose/releases/download/composer_version/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
      • -
      -
    4. -
    5. -

      Clone the master branch of the repository and run ./generate_config.sh to generate a file "mailcow.conf". You will be asked for a hostname and a timezone:

      -
        -
      • git clone https://github.com/andryyy/mailcow-dockerized && cd mailcow-dockerized
      • -
      • ./generate_config.sh
      • -
      • Open and check "mailcow.conf" if you need or want to make changes to ports (for example changing the default HTTPS port)
      • -
      -
    6. -
    7. -

      Run the composer file.

      -
        -
      • docker-compose up -d
      • -
      -
    8. -
    -

    Done.

    -

    You can now access https://${MAILCOW_HOSTNAME} with the default credentials admin + password moohoo.

    -

    It may take a while for MySQL to warm up, so please wait half a minute.

    -

    The database will be initialized right after a connection to MySQL can be established.

    -

    Update mailcow

    -

    There is no update routine.

    -

    You need to refresh your pulled repository clone by running git pull - this will likely fail due to changes to your local configuration. But that's why we use git! :-)

    -

    Whatever file has local changes, add and commit it to your repository clone. For example:

    -
    git add data/conf/postfix/main.cf data/conf/dovecot/dovecot.conf
    -git commit -m "My changes to main.cf and dovecot.conf
    -
    - -

    Try running git pull again and resolve conflicts, if any.

    -

    Now update all images, apply changes to containers and restart all services:

    -
    docker-compose pull
    -docker-compose up -d --remove-orphans
    -docker-compose restart
    -
    - - -

    When you checkout the dev branch, you will most likely end up using the "master" images with code base of "dev". -If there were critical changes to the images in dev, mailcow will not work.

    -

    But you can still build the images by yourself:

    -
    docker-compose up -d --build
    -
    - -
    -
    - - -
    -
    - -
    - -
    - -
    - - - GitHub - - - « Previous - - - Next » - - -
    - - - - diff --git a/js/highlight.pack.js b/js/highlight.pack.js deleted file mode 100644 index a5818dfb..00000000 --- a/js/highlight.pack.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,"")}),n.filter(function(e){return N(e)||/no(-?)highlight|plain|text/.test(e)})[0]}function i(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function o(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function u(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function c(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,o){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),o&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&o.tE&&(a.tE+=(a.e?"|":"")+o.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(i(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,o);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function d(){if(!L.k)return n(y);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(y)}return e+n(y.substr(t))}function h(){if(L.sL&&!w[L.sL])return n(y);var e=L.sL?s(L.sL,y,!0,M[L.sL]):l(y);return L.r>0&&(B+=e.r),"continuous"==L.subLanguageMode&&(M[L.sL]=e.top),p(e.language,e.value,!1,!0)}function b(){return void 0!==L.sL?h():d()}function v(e,t){var r=e.cN?p(e.cN,"",!0):"";e.rB?(k+=r,y=""):e.eB?(k+=n(t)+r,y=""):(k+=r,y=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(y+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(y+=t),k+=b();do L.cN&&(k+="
    "),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),y="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(f(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return y+=t,t.length||1}var E=N(e);if(!E)throw new Error('Unknown language: "'+e+'"');c(E);var R,L=i||E,M={},k="";for(R=L;R!=E;R=R.parent)R.cN&&(k=p(R.cN,"",!0)+k);var y="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="
    ");return{r:B,value:k,language:e,top:L}}catch(S){if(-1!=S.message.indexOf("Illegal"))return{r:0,value:n(t)};throw S}}function l(e,t){t=t||x.languages||Object.keys(w);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return x.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,x.tabReplace)})),x.useBR&&(e=e.replace(/\n/g,"
    ")),e}function g(e,n,t){var r=n?E[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=a(e);if(!/no(-?)highlight|plain|text/.test(n)){var t;x.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,i=n?s(n,r,!0):l(r),c=o(t);if(c.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=i.value,i.value=u(c,o(p),r)}i.value=f(i.value),e.innerHTML=i.value,e.className=g(e.className,n,i.language),e.result={language:i.language,re:i.r},i.second_best&&(e.second_best={language:i.second_best.language,re:i.second_best.r})}}function d(e){x=i(x,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function b(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)}function v(n,t){var r=w[n]=t(e);r.aliases&&r.aliases.forEach(function(e){E[e]=n})}function m(){return Object.keys(w)}function N(e){return w[e]||w[E[e]]}var x={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},w={},E={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=b,e.registerLanguage=v,e.listLanguages=m,e.getLanguage=N,e.inherit=i,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["m","mm","objc","obj-c"],k:i,l:o,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate savepoint release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke",e:/;/,eW:!0,k:{keyword:"abs absolute acos action add adddate addtime aes_decrypt aes_encrypt after aggregate all allocate alter analyze and any are as asc ascii asin assertion at atan atan2 atn2 authorization authors avg backup before begin benchmark between bin binlog bit_and bit_count bit_length bit_or bit_xor both by cache call cascade cascaded case cast catalog ceil ceiling chain change changed char_length character_length charindex charset check checksum checksum_agg choose close coalesce coercibility collate collation collationproperty column columns columns_updated commit compress concat concat_ws concurrent connect connection connection_id consistent constraint constraints continue contributors conv convert convert_tz corresponding cos cot count count_big crc32 create cross cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime data database databases datalength date_add date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts datetimeoffsetfromparts day dayname dayofmonth dayofweek dayofyear deallocate declare decode default deferrable deferred degrees delayed delete des_decrypt des_encrypt des_key_file desc describe descriptor diagnostics difference disconnect distinct distinctrow div do domain double drop dumpfile each else elt enclosed encode encrypt end end-exec engine engines eomonth errors escape escaped event eventdata events except exception exec execute exists exp explain export_set extended external extract fast fetch field fields find_in_set first first_value floor flush for force foreign format found found_rows from from_base64 from_days from_unixtime full function get get_format get_lock getdate getutcdate global go goto grant grants greatest group group_concat grouping grouping_id gtid_subset gtid_subtract handler having help hex high_priority hosts hour ident_current ident_incr ident_seed identified identity if ifnull ignore iif ilike immediate in index indicator inet6_aton inet6_ntoa inet_aton inet_ntoa infile initially inner innodb input insert install instr intersect into is is_free_lock is_ipv4 is_ipv4_compat is_ipv4_mapped is_not is_not_null is_used_lock isdate isnull isolation join key kill language last last_day last_insert_id last_value lcase lead leading least leaves left len lenght level like limit lines ln load load_file local localtime localtimestamp locate lock log log10 log2 logfile logs low_priority lower lpad ltrim make_set makedate maketime master master_pos_wait match matched max md5 medium merge microsecond mid min minute mod mode module month monthname mutex name_const names national natural nchar next no no_write_to_binlog not now nullif nvarchar oct octet_length of old_password on only open optimize option optionally or ord order outer outfile output pad parse partial partition password patindex percent_rank percentile_cont percentile_disc period_add period_diff pi plugin position pow power pragma precision prepare preserve primary prior privileges procedure procedure_analyze processlist profile profiles public publishingservername purge quarter query quick quote quotename radians rand read references regexp relative relaylog release release_lock rename repair repeat replace replicate reset restore restrict return returns reverse revoke right rlike rollback rollup round row row_count rows rpad rtrim savepoint schema scroll sec_to_time second section select serializable server session session_user set sha sha1 sha2 share show sign sin size slave sleep smalldatetimefromparts snapshot some soname soundex sounds_like space sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sql_variant_property sqlstate sqrt square start starting status std stddev stddev_pop stddev_samp stdev stdevp stop str str_to_date straight_join strcmp string stuff subdate substr substring subtime subtring_index sum switchoffset sysdate sysdatetime sysdatetimeoffset system_user sysutcdatetime table tables tablespace tan temporary terminated tertiary_weights then time time_format time_to_sec timediff timefromparts timestamp timestampadd timestampdiff timezone_hour timezone_minute to to_base64 to_days to_seconds todatetimeoffset trailing transaction translation trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse ucase uncompress uncompressed_length unhex unicode uninstall union unique unix_timestamp unknown unlock update upgrade upped upper usage use user user_resources using utc_date utc_time utc_timestamp uuid uuid_short validate_password_strength value values var var_pop var_samp variables variance varp version view warnings week weekday weekofyear weight_string when whenever where with work write xml xor year yearweek zon",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int integer interval number numeric real serial smallint varchar varying int8 serial8 text"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,v:[{b:/^\s*('|")use strict('|")/},{b:/^\s*('|")use asm('|")/}]},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",b:"\\b(0[xXbBoO][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}]}});hljs.registerLanguage("scss",function(e){{var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"function",b:t+"\\(",rB:!0,eE:!0,e:"\\("},o={cN:"hexcolor",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{cN:"value",eW:!0,eE:!0,c:[r,o,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"important",b:"!important"}]}})}return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,r,{cN:"id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{cN:"pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{cN:"value",b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{cN:"value",b:":",e:";",c:[r,i,o,e.CSSNM,e.QSM,e.ASM,{cN:"important",b:"!important"}]},{cN:"at_rule",b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[r,i,e.QSM,e.ASM,o,e.CSSNM,{cN:"preprocessor",b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("mel",function(e){return{k:"int float string vector matrix if else switch case default while do for in break continue global proc return about abs addAttr addAttributeEditorNodeHelp addDynamic addNewShelfTab addPP addPanelCategory addPrefixToName advanceToNextDrivenKey affectedNet affects aimConstraint air alias aliasAttr align alignCtx alignCurve alignSurface allViewFit ambientLight angle angleBetween animCone animCurveEditor animDisplay animView annotate appendStringArray applicationName applyAttrPreset applyTake arcLenDimContext arcLengthDimension arclen arrayMapper art3dPaintCtx artAttrCtx artAttrPaintVertexCtx artAttrSkinPaintCtx artAttrTool artBuildPaintMenu artFluidAttrCtx artPuttyCtx artSelectCtx artSetPaintCtx artUserPaintCtx assignCommand assignInputDevice assignViewportFactories attachCurve attachDeviceAttr attachSurface attrColorSliderGrp attrCompatibility attrControlGrp attrEnumOptionMenu attrEnumOptionMenuGrp attrFieldGrp attrFieldSliderGrp attrNavigationControlGrp attrPresetEditWin attributeExists attributeInfo attributeMenu attributeQuery autoKeyframe autoPlace bakeClip bakeFluidShading bakePartialHistory bakeResults bakeSimulation basename basenameEx batchRender bessel bevel bevelPlus binMembership bindSkin blend2 blendShape blendShapeEditor blendShapePanel blendTwoAttr blindDataType boneLattice boundary boxDollyCtx boxZoomCtx bufferCurve buildBookmarkMenu buildKeyframeMenu button buttonManip CBG cacheFile cacheFileCombine cacheFileMerge cacheFileTrack camera cameraView canCreateManip canvas capitalizeString catch catchQuiet ceil changeSubdivComponentDisplayLevel changeSubdivRegion channelBox character characterMap characterOutlineEditor characterize chdir checkBox checkBoxGrp checkDefaultRenderGlobals choice circle circularFillet clamp clear clearCache clip clipEditor clipEditorCurrentTimeCtx clipSchedule clipSchedulerOutliner clipTrimBefore closeCurve closeSurface cluster cmdFileOutput cmdScrollFieldExecuter cmdScrollFieldReporter cmdShell coarsenSubdivSelectionList collision color colorAtPoint colorEditor colorIndex colorIndexSliderGrp colorSliderButtonGrp colorSliderGrp columnLayout commandEcho commandLine commandPort compactHairSystem componentEditor compositingInterop computePolysetVolume condition cone confirmDialog connectAttr connectControl connectDynamic connectJoint connectionInfo constrain constrainValue constructionHistory container containsMultibyte contextInfo control convertFromOldLayers convertIffToPsd convertLightmap convertSolidTx convertTessellation convertUnit copyArray copyFlexor copyKey copySkinWeights cos cpButton cpCache cpClothSet cpCollision cpConstraint cpConvClothToMesh cpForces cpGetSolverAttr cpPanel cpProperty cpRigidCollisionFilter cpSeam cpSetEdit cpSetSolverAttr cpSolver cpSolverTypes cpTool cpUpdateClothUVs createDisplayLayer createDrawCtx createEditor createLayeredPsdFile createMotionField createNewShelf createNode createRenderLayer createSubdivRegion cross crossProduct ctxAbort ctxCompletion ctxEditMode ctxTraverse currentCtx currentTime currentTimeCtx currentUnit curve curveAddPtCtx curveCVCtx curveEPCtx curveEditorCtx curveIntersect curveMoveEPCtx curveOnSurface curveSketchCtx cutKey cycleCheck cylinder dagPose date defaultLightListCheckBox defaultNavigation defineDataServer defineVirtualDevice deformer deg_to_rad delete deleteAttr deleteShadingGroupsAndMaterials deleteShelfTab deleteUI deleteUnusedBrushes delrandstr detachCurve detachDeviceAttr detachSurface deviceEditor devicePanel dgInfo dgdirty dgeval dgtimer dimWhen directKeyCtx directionalLight dirmap dirname disable disconnectAttr disconnectJoint diskCache displacementToPoly displayAffected displayColor displayCull displayLevelOfDetail displayPref displayRGBColor displaySmoothness displayStats displayString displaySurface distanceDimContext distanceDimension doBlur dolly dollyCtx dopeSheetEditor dot dotProduct doubleProfileBirailSurface drag dragAttrContext draggerContext dropoffLocator duplicate duplicateCurve duplicateSurface dynCache dynControl dynExport dynExpression dynGlobals dynPaintEditor dynParticleCtx dynPref dynRelEdPanel dynRelEditor dynamicLoad editAttrLimits editDisplayLayerGlobals editDisplayLayerMembers editRenderLayerAdjustment editRenderLayerGlobals editRenderLayerMembers editor editorTemplate effector emit emitter enableDevice encodeString endString endsWith env equivalent equivalentTol erf error eval evalDeferred evalEcho event exactWorldBoundingBox exclusiveLightCheckBox exec executeForEachObject exists exp expression expressionEditorListen extendCurve extendSurface extrude fcheck fclose feof fflush fgetline fgetword file fileBrowserDialog fileDialog fileExtension fileInfo filetest filletCurve filter filterCurve filterExpand filterStudioImport findAllIntersections findAnimCurves findKeyframe findMenuItem findRelatedSkinCluster finder firstParentOf fitBspline flexor floatEq floatField floatFieldGrp floatScrollBar floatSlider floatSlider2 floatSliderButtonGrp floatSliderGrp floor flow fluidCacheInfo fluidEmitter fluidVoxelInfo flushUndo fmod fontDialog fopen formLayout format fprint frameLayout fread freeFormFillet frewind fromNativePath fwrite gamma gauss geometryConstraint getApplicationVersionAsFloat getAttr getClassification getDefaultBrush getFileList getFluidAttr getInputDeviceRange getMayaPanelTypes getModifiers getPanel getParticleAttr getPluginResource getenv getpid glRender glRenderEditor globalStitch gmatch goal gotoBindPose grabColor gradientControl gradientControlNoAttr graphDollyCtx graphSelectContext graphTrackCtx gravity grid gridLayout group groupObjectsByName HfAddAttractorToAS HfAssignAS HfBuildEqualMap HfBuildFurFiles HfBuildFurImages HfCancelAFR HfConnectASToHF HfCreateAttractor HfDeleteAS HfEditAS HfPerformCreateAS HfRemoveAttractorFromAS HfSelectAttached HfSelectAttractors HfUnAssignAS hardenPointCurve hardware hardwareRenderPanel headsUpDisplay headsUpMessage help helpLine hermite hide hilite hitTest hotBox hotkey hotkeyCheck hsv_to_rgb hudButton hudSlider hudSliderButton hwReflectionMap hwRender hwRenderLoad hyperGraph hyperPanel hyperShade hypot iconTextButton iconTextCheckBox iconTextRadioButton iconTextRadioCollection iconTextScrollList iconTextStaticLabel ikHandle ikHandleCtx ikHandleDisplayScale ikSolver ikSplineHandleCtx ikSystem ikSystemInfo ikfkDisplayMethod illustratorCurves image imfPlugins inheritTransform insertJoint insertJointCtx insertKeyCtx insertKnotCurve insertKnotSurface instance instanceable instancer intField intFieldGrp intScrollBar intSlider intSliderGrp interToUI internalVar intersect iprEngine isAnimCurve isConnected isDirty isParentOf isSameObject isTrue isValidObjectName isValidString isValidUiName isolateSelect itemFilter itemFilterAttr itemFilterRender itemFilterType joint jointCluster jointCtx jointDisplayScale jointLattice keyTangent keyframe keyframeOutliner keyframeRegionCurrentTimeCtx keyframeRegionDirectKeyCtx keyframeRegionDollyCtx keyframeRegionInsertKeyCtx keyframeRegionMoveKeyCtx keyframeRegionScaleKeyCtx keyframeRegionSelectKeyCtx keyframeRegionSetKeyCtx keyframeRegionTrackCtx keyframeStats lassoContext lattice latticeDeformKeyCtx launch launchImageEditor layerButton layeredShaderPort layeredTexturePort layout layoutDialog lightList lightListEditor lightListPanel lightlink lineIntersection linearPrecision linstep listAnimatable listAttr listCameras listConnections listDeviceAttachments listHistory listInputDeviceAxes listInputDeviceButtons listInputDevices listMenuAnnotation listNodeTypes listPanelCategories listRelatives listSets listTransforms listUnselected listerEditor loadFluid loadNewShelf loadPlugin loadPluginLanguageResources loadPrefObjects localizedPanelLabel lockNode loft log longNameOf lookThru ls lsThroughFilter lsType lsUI Mayatomr mag makeIdentity makeLive makePaintable makeRoll makeSingleSurface makeTubeOn makebot manipMoveContext manipMoveLimitsCtx manipOptions manipRotateContext manipRotateLimitsCtx manipScaleContext manipScaleLimitsCtx marker match max memory menu menuBarLayout menuEditor menuItem menuItemToShelf menuSet menuSetPref messageLine min minimizeApp mirrorJoint modelCurrentTimeCtx modelEditor modelPanel mouse movIn movOut move moveIKtoFK moveKeyCtx moveVertexAlongDirection multiProfileBirailSurface mute nParticle nameCommand nameField namespace namespaceInfo newPanelItems newton nodeCast nodeIconButton nodeOutliner nodePreset nodeType noise nonLinear normalConstraint normalize nurbsBoolean nurbsCopyUVSet nurbsCube nurbsEditUV nurbsPlane nurbsSelect nurbsSquare nurbsToPoly nurbsToPolygonsPref nurbsToSubdiv nurbsToSubdivPref nurbsUVSet nurbsViewDirectionVector objExists objectCenter objectLayer objectType objectTypeUI obsoleteProc oceanNurbsPreviewPlane offsetCurve offsetCurveOnSurface offsetSurface openGLExtension openMayaPref optionMenu optionMenuGrp optionVar orbit orbitCtx orientConstraint outlinerEditor outlinerPanel overrideModifier paintEffectsDisplay pairBlend palettePort paneLayout panel panelConfiguration panelHistory paramDimContext paramDimension paramLocator parent parentConstraint particle particleExists particleInstancer particleRenderInfo partition pasteKey pathAnimation pause pclose percent performanceOptions pfxstrokes pickWalk picture pixelMove planarSrf plane play playbackOptions playblast plugAttr plugNode pluginInfo pluginResourceUtil pointConstraint pointCurveConstraint pointLight pointMatrixMult pointOnCurve pointOnSurface pointPosition poleVectorConstraint polyAppend polyAppendFacetCtx polyAppendVertex polyAutoProjection polyAverageNormal polyAverageVertex polyBevel polyBlendColor polyBlindData polyBoolOp polyBridgeEdge polyCacheMonitor polyCheck polyChipOff polyClipboard polyCloseBorder polyCollapseEdge polyCollapseFacet polyColorBlindData polyColorDel polyColorPerVertex polyColorSet polyCompare polyCone polyCopyUV polyCrease polyCreaseCtx polyCreateFacet polyCreateFacetCtx polyCube polyCut polyCutCtx polyCylinder polyCylindricalProjection polyDelEdge polyDelFacet polyDelVertex polyDuplicateAndConnect polyDuplicateEdge polyEditUV polyEditUVShell polyEvaluate polyExtrudeEdge polyExtrudeFacet polyExtrudeVertex polyFlipEdge polyFlipUV polyForceUV polyGeoSampler polyHelix polyInfo polyInstallAction polyLayoutUV polyListComponentConversion polyMapCut polyMapDel polyMapSew polyMapSewMove polyMergeEdge polyMergeEdgeCtx polyMergeFacet polyMergeFacetCtx polyMergeUV polyMergeVertex polyMirrorFace polyMoveEdge polyMoveFacet polyMoveFacetUV polyMoveUV polyMoveVertex polyNormal polyNormalPerVertex polyNormalizeUV polyOptUvs polyOptions polyOutput polyPipe polyPlanarProjection polyPlane polyPlatonicSolid polyPoke polyPrimitive polyPrism polyProjection polyPyramid polyQuad polyQueryBlindData polyReduce polySelect polySelectConstraint polySelectConstraintMonitor polySelectCtx polySelectEditCtx polySeparate polySetToFaceNormal polySewEdge polyShortestPathCtx polySmooth polySoftEdge polySphere polySphericalProjection polySplit polySplitCtx polySplitEdge polySplitRing polySplitVertex polyStraightenUVBorder polySubdivideEdge polySubdivideFacet polyToSubdiv polyTorus polyTransfer polyTriangulate polyUVSet polyUnite polyWedgeFace popen popupMenu pose pow preloadRefEd print progressBar progressWindow projFileViewer projectCurve projectTangent projectionContext projectionManip promptDialog propModCtx propMove psdChannelOutliner psdEditTextureFile psdExport psdTextureFile putenv pwd python querySubdiv quit rad_to_deg radial radioButton radioButtonGrp radioCollection radioMenuItemCollection rampColorPort rand randomizeFollicles randstate rangeControl readTake rebuildCurve rebuildSurface recordAttr recordDevice redo reference referenceEdit referenceQuery refineSubdivSelectionList refresh refreshAE registerPluginResource rehash reloadImage removeJoint removeMultiInstance removePanelCategory rename renameAttr renameSelectionList renameUI render renderGlobalsNode renderInfo renderLayerButton renderLayerParent renderLayerPostProcess renderLayerUnparent renderManip renderPartition renderQualityNode renderSettings renderThumbnailUpdate renderWindowEditor renderWindowSelectContext renderer reorder reorderDeformers requires reroot resampleFluid resetAE resetPfxToPolyCamera resetTool resolutionNode retarget reverseCurve reverseSurface revolve rgb_to_hsv rigidBody rigidSolver roll rollCtx rootOf rot rotate rotationInterpolation roundConstantRadius rowColumnLayout rowLayout runTimeCommand runup sampleImage saveAllShelves saveAttrPreset saveFluid saveImage saveInitialState saveMenu savePrefObjects savePrefs saveShelf saveToolSettings scale scaleBrushBrightness scaleComponents scaleConstraint scaleKey scaleKeyCtx sceneEditor sceneUIReplacement scmh scriptCtx scriptEditorInfo scriptJob scriptNode scriptTable scriptToShelf scriptedPanel scriptedPanelType scrollField scrollLayout sculpt searchPathArray seed selLoadSettings select selectContext selectCurveCV selectKey selectKeyCtx selectKeyframeRegionCtx selectMode selectPref selectPriority selectType selectedNodes selectionConnection separator setAttr setAttrEnumResource setAttrMapping setAttrNiceNameResource setConstraintRestPosition setDefaultShadingGroup setDrivenKeyframe setDynamic setEditCtx setEditor setFluidAttr setFocus setInfinity setInputDeviceMapping setKeyCtx setKeyPath setKeyframe setKeyframeBlendshapeTargetWts setMenuMode setNodeNiceNameResource setNodeTypeFlag setParent setParticleAttr setPfxToPolyCamera setPluginResource setProject setStampDensity setStartupMessage setState setToolTo setUITemplate setXformManip sets shadingConnection shadingGeometryRelCtx shadingLightRelCtx shadingNetworkCompare shadingNode shapeCompare shelfButton shelfLayout shelfTabLayout shellField shortNameOf showHelp showHidden showManipCtx showSelectionInTitle showShadingGroupAttrEditor showWindow sign simplify sin singleProfileBirailSurface size sizeBytes skinCluster skinPercent smoothCurve smoothTangentSurface smoothstep snap2to2 snapKey snapMode snapTogetherCtx snapshot soft softMod softModCtx sort sound soundControl source spaceLocator sphere sphrand spotLight spotLightPreviewPort spreadSheetEditor spring sqrt squareSurface srtContext stackTrace startString startsWith stitchAndExplodeShell stitchSurface stitchSurfacePoints strcmp stringArrayCatenate stringArrayContains stringArrayCount stringArrayInsertAtIndex stringArrayIntersector stringArrayRemove stringArrayRemoveAtIndex stringArrayRemoveDuplicates stringArrayRemoveExact stringArrayToString stringToStringArray strip stripPrefixFromName stroke subdAutoProjection subdCleanTopology subdCollapse subdDuplicateAndConnect subdEditUV subdListComponentConversion subdMapCut subdMapSewMove subdMatchTopology subdMirror subdToBlind subdToPoly subdTransferUVsToCache subdiv subdivCrease subdivDisplaySmoothness substitute substituteAllString substituteGeometry substring surface surfaceSampler surfaceShaderList swatchDisplayPort switchTable symbolButton symbolCheckBox sysFile system tabLayout tan tangentConstraint texLatticeDeformContext texManipContext texMoveContext texMoveUVShellContext texRotateContext texScaleContext texSelectContext texSelectShortestPathCtx texSmudgeUVContext texWinToolCtx text textCurves textField textFieldButtonGrp textFieldGrp textManip textScrollList textToShelf textureDisplacePlane textureHairColor texturePlacementContext textureWindow threadCount threePointArcCtx timeControl timePort timerX toNativePath toggle toggleAxis toggleWindowVisibility tokenize tokenizeList tolerance tolower toolButton toolCollection toolDropped toolHasOptions toolPropertyWindow torus toupper trace track trackCtx transferAttributes transformCompare transformLimits translator trim trunc truncateFluidCache truncateHairCache tumble tumbleCtx turbulence twoPointArcCtx uiRes uiTemplate unassignInputDevice undo undoInfo ungroup uniform unit unloadPlugin untangleUV untitledFileName untrim upAxis updateAE userCtx uvLink uvSnapshot validateShelfName vectorize view2dToolCtx viewCamera viewClipPlane viewFit viewHeadOn viewLookAt viewManip viewPlace viewSet visor volumeAxis vortex waitCursor warning webBrowser webBrowserPrefs whatIs window windowPref wire wireContext workspace wrinkle wrinkleContext writeTake xbmLangPathList xform",i:"",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(r)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:r.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+s,e:"[-=]>",rB:!0,c:[i,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:s,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{cN:"attribute",b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("tex",function(c){var e={cN:"command",b:"\\\\[a-zA-Zа-яА-я]+[\\*]?"},m={cN:"command",b:"\\\\[^a-zA-Zа-яА-я0-9]"},r={cN:"special",b:"[{}\\[\\]\\&#~]",r:0};return{c:[{b:"\\\\[a-zA-Zа-яА-я]+[\\*]? *= *-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",rB:!0,c:[e,m,{cN:"number",b:" *=",e:"-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",eB:!0}],r:10},e,m,r,{cN:"formula",b:"\\$\\$",e:"\\$\\$",c:[e,m,r],r:0},{cN:"formula",b:"\\$",e:"\\$",c:[e,m,r],r:0},c.C("%","$",{r:0})]}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer",constant:"true false iota nil",typename:"bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"",sL:"vbscript"}]}});hljs.registerLanguage("haskell",function(e){var c=[e.C("--","$"),e.C("{-","-}",{c:["self"]})],a={cN:"pragma",b:"{-#",e:"#-}"},i={cN:"preprocessor",b:"^#",e:"$"},n={cN:"type",b:"\\b[A-Z][\\w']*",r:0},t={cN:"container",b:"\\(",e:"\\)",i:'"',c:[a,i,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"})].concat(c)},l={cN:"container",b:"{",e:"}",c:t.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{cN:"module",b:"\\bmodule\\b",e:"where",k:"module where",c:[t].concat(c),i:"\\W\\.|;"},{cN:"import",b:"\\bimport\\b",e:"$",k:"import|0 qualified as hiding",c:[t].concat(c),i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[n,t].concat(c)},{cN:"typedef",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,n,t,l].concat(c)},{cN:"default",bK:"default",e:"$",c:[n,t].concat(c)},{cN:"infix",bK:"infix infixl infixr",e:"$",c:[e.CNM].concat(c)},{cN:"foreign",b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[n,e.QSM].concat(c)},{cN:"shebang",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,i,e.QSM,e.CNM,n,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),{b:"->|<-"}].concat(c)}});hljs.registerLanguage("scilab",function(e){var n=[e.CNM,{cN:"string",b:"'|\"",e:"'|\"",c:[e.BE,{b:"''"}]}];return{aliases:["sci"],k:{keyword:"abort break case clear catch continue do elseif else endfunction end for functionglobal if pause return resume select try then while%f %F %t %T %pi %eps %inf %nan %e %i %z %s",built_in:"abs and acos asin atan ceil cd chdir clearglobal cosh cos cumprod deff disp errorexec execstr exists exp eye gettext floor fprintf fread fsolve imag isdef isemptyisinfisnan isvector lasterror length load linspace list listfiles log10 log2 logmax min msprintf mclose mopen ones or pathconvert poly printf prod pwd rand realround sinh sin size gsort sprintf sqrt strcat strcmps tring sum system tanh tantype typename warning zeros matrix"},i:'("|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function endfunction",e:"$",k:"function endfunction|10",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"}]},{cN:"transposed_variable",b:"[a-zA-Z_][a-zA-Z_0-9]*('+[\\.']*|[\\.']+)",e:"",r:0},{cN:"matrix",b:"\\[",e:"\\]'*[\\.']*",r:0,c:n},e.C("//","$")].concat(n)}});hljs.registerLanguage("profile",function(e){return{c:[e.CNM,{cN:"built_in",b:"{",e:"}$",eB:!0,eE:!0,c:[e.ASM,e.QSM],r:0},{cN:"filename",b:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",e:":",eE:!0},{cN:"header",b:"(ncalls|tottime|cumtime)",e:"$",k:"ncalls tottime|10 cumtime|10 filename",r:10},{cN:"summary",b:"function calls",e:"$",c:[e.CNM],r:10},e.ASM,e.QSM,{cN:"function",b:"\\(",e:"\\)$",c:[e.UTM],r:0}]}});hljs.registerLanguage("thrift",function(e){var t="bool byte i16 i32 i64 double string binary";return{k:{keyword:"namespace const typedef struct enum service exception void oneway set list map required optional",built_in:t,literal:"true false"},c:[e.QSM,e.NM,e.CLCM,e.CBCM,{cN:"class",bK:"struct enum service exception",e:/\{/,i:/\n/,c:[e.inherit(e.TM,{starts:{eW:!0,eE:!0}})]},{b:"\\b(set|list|map)\\s*<",e:">",k:t,c:["self"]}]}});hljs.registerLanguage("matlab",function(e){var a=[e.CNM,{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]}],s={r:0,c:[{cN:"operator",b:/'['\.]*/}]};return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson"},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"},{cN:"params",b:"\\[",e:"\\]"}]},{b:/[a-zA-Z_][a-zA-Z_0-9]*'['\.]*/,rB:!0,r:0,c:[{b:/[a-zA-Z_][a-zA-Z_0-9]*/,r:0},s.c[0]]},{cN:"matrix",b:"\\[",e:"\\]",c:a,r:0,starts:s},{cN:"cell",b:"\\{",e:/}/,c:a,r:0,starts:s},{b:/\)/,r:0,starts:s},e.C("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.C("\\%","$")].concat(a)}});hljs.registerLanguage("vbscript",function(e){return{aliases:["vbs"],cI:!0,k:{keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",built_in:"lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion scriptengine split scriptengineminorversion cint sin datepart ltrim sqr scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw chrw regexp server response request cstr err",literal:"true false null nothing empty"},i:"//",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C(/'/,/$/,{r:0}),e.CNM]}});hljs.registerLanguage("capnproto",function(t){return{aliases:["capnp"],k:{keyword:"struct enum interface union group import using const annotation extends in of on as with from fixed",built_in:"Void Bool Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64 Text Data AnyPointer AnyStruct Capability List",literal:"true false"},c:[t.QSM,t.NM,t.HCM,{cN:"shebang",b:/@0x[\w\d]{16};/,i:/\n/},{cN:"number",b:/@\d+\b/},{cN:"class",bK:"struct enum",e:/\{/,i:/\n/,c:[t.inherit(t.TM,{starts:{eW:!0,eE:!0}})]},{cN:"class",bK:"interface",e:/\{/,i:/\n/,c:[t.inherit(t.TM,{starts:{eW:!0,eE:!0}})]}]}});hljs.registerLanguage("xl",function(e){var t="ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts",o={keyword:"if then else do while until for loop import with is as where when by data constant",literal:"true false nil",type:"integer real text name boolean symbol infix prefix postfix block tree",built_in:"in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin acos atan exp expm1 log log2 log10 log1p pi at",module:t,id:"text_length text_range text_find text_replace contains page slide basic_slide title_slide title subtitle fade_in fade_out fade_at clear_color color line_color line_width texture_wrap texture_transform texture scale_?x scale_?y scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y rotate_?z? rectangle circle ellipse sphere path line_to move_to quad_to curve_to theme background contents locally time mouse_?x mouse_?y mouse_buttons"},a={cN:"constant",b:"[A-Z][A-Z_0-9]+",r:0},r={cN:"variable",b:"([A-Z][a-z_0-9]+)+",r:0},i={cN:"id",b:"[a-z][a-z_0-9]+",r:0},l={cN:"string",b:'"',e:'"',i:"\\n"},n={cN:"string",b:"'",e:"'",i:"\\n"},s={cN:"string",b:"<<",e:">>"},c={cN:"number",b:"[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?",r:10},_={cN:"import",bK:"import",e:"$",k:{keyword:"import",module:t},r:0,c:[l]},d={cN:"function",b:"[a-z].*->"};return{aliases:["tao"],l:/[a-zA-Z][a-zA-Z0-9_?]*/,k:o,c:[e.CLCM,e.CBCM,l,n,s,d,_,a,r,i,c,e.NM]}});hljs.registerLanguage("scala",function(e){var t={cN:"annotation",b:"@[A-Za-z]+"},a={cN:"string",b:'u?r?"""',e:'"""',r:10},r={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},c={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},i={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},l={cN:"class",bK:"class object trait type",e:/[:={\[(\n;]/,c:[{cN:"keyword",bK:"extends with",r:10},i]},n={cN:"function",bK:"def val",e:/[:={\[(\n;]/,c:[i]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,a,e.QSM,r,c,n,l,e.CNM,t]}});hljs.registerLanguage("elixir",function(e){var n="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?",r="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b="and false then defined module in return redo retry end for true self when next until do begin unless nil break not case cond alias while ensure or include use alias fn quote",c={cN:"subst",b:"#\\{",e:"}",l:n,k:b},a={cN:"string",c:[e.BE,c],v:[{b:/'/,e:/'/},{b:/"/,e:/"/}]},i={cN:"function",bK:"def defp defmacro",e:/\B\b/,c:[e.inherit(e.TM,{b:n,endsParent:!0})]},s=e.inherit(i,{cN:"class",bK:"defmodule defrecord",e:/\bdo\b|$|;/}),l=[a,e.HCM,s,i,{cN:"constant",b:"(\\b[A-Z_]\\w*(.)?)+",r:0},{cN:"symbol",b:":",c:[a,{b:r}],r:0},{cN:"symbol",b:n+":",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"->"},{b:"("+e.RSR+")\\s*",c:[e.HCM,{cN:"regexp",i:"\\n",c:[e.BE,c],v:[{b:"/",e:"/[a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];return c.c=l,{l:n,k:b,c:l}});hljs.registerLanguage("sml",function(e){return{aliases:["ml"],k:{keyword:"abstype and andalso as case datatype do else end eqtype exception fn fun functor handle if in include infix infixr let local nonfix of op open orelse raise rec sharing sig signature struct structure then type val with withtype where while",built_in:"array bool char exn int list option order real ref string substring vector unit word",literal:"true false NONE SOME LESS EQUAL GREATER nil"},i:/\/\/|>>/,l:"[a-z_]\\w*!?",c:[{cN:"literal",b:"\\[(\\|\\|)?\\]|\\(\\)"},e.C("\\(\\*","\\*\\)",{c:["self"]}),{cN:"symbol",b:"'[A-Za-z_](?!')[\\w']*"},{cN:"tag",b:"`[A-Z][\\w']*"},{cN:"type",b:"\\b[A-Z][\\w']*",r:0},{b:"[a-z_]\\w*'[\\w']*"},e.inherit(e.ASM,{cN:"char",r:0}),e.inherit(e.QSM,{i:null}),{cN:"number",b:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",r:0},{b:/[-=]>/}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"tag",b:""},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("dockerfile",function(n){return{aliases:["docker"],cI:!0,k:{built_ins:"from maintainer cmd expose add copy entrypoint volume user workdir onbuild run env"},c:[n.HCM,{k:{built_in:"run cmd entrypoint volume add copy workdir onbuild"},b:/^ *(onbuild +)?(run|cmd|entrypoint|volume|add|copy|workdir) +/,starts:{e:/[^\\]\n/,sL:"bash",subLanguageMode:"continuous"}},{k:{built_in:"from maintainer expose env user onbuild"},b:/^ *(onbuild +)?(from|maintainer|expose|env|user|onbuild) +/,e:/[^\\]\n/,c:[n.ASM,n.QSM,n.NM,n.HCM]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("haml",function(s){return{cI:!0,c:[{cN:"doctype",b:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",r:10},s.C("^\\s*(!=#|=#|-#|/).*$",!1,{r:0}),{b:"^\\s*(-|=|!=)(?!#)",starts:{e:"\\n",sL:"ruby"}},{cN:"tag",b:"^\\s*%",c:[{cN:"title",b:"\\w+"},{cN:"value",b:"[#\\.]\\w+"},{b:"{\\s*",e:"\\s*}",eE:!0,c:[{b:":\\w+\\s*=>",e:",\\s+",rB:!0,eW:!0,c:[{cN:"symbol",b:":\\w+"},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:!0,c:[{b:"\\w+\\s*=",e:"\\s+",rB:!0,eW:!0,c:[{cN:"attribute",b:"\\w+",r:0},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]}]},{cN:"bullet",b:"^\\s*[=~]\\s*",r:0},{b:"#{",starts:{e:"}",sL:"ruby"}}]}});hljs.registerLanguage("fortran",function(e){var t={cN:"params",b:"\\(",e:"\\)"},n={constant:".False. .True.",type:"integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_ofacosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image"};return{cI:!0,aliases:["f90","f95"],k:n,c:[e.inherit(e.ASM,{cN:"string",r:0}),e.inherit(e.QSM,{cN:"string",r:0}),{cN:"function",bK:"subroutine function program",i:"[${=\\n]",c:[e.UTM,t]},e.C("!","$",{r:0}),{cN:"number",b:"(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?",r:0}]}});hljs.registerLanguage("smali",function(r){var t=["add","and","cmp","cmpg","cmpl","const","div","double","float","goto","if","int","long","move","mul","neg","new","nop","not","or","rem","return","shl","shr","sput","sub","throw","ushr","xor"],n=["aget","aput","array","check","execute","fill","filled","goto/16","goto/32","iget","instance","invoke","iput","monitor","packed","sget","sparse"],s=["transient","constructor","abstract","final","synthetic","public","private","protected","static","bridge","system"];return{aliases:["smali"],c:[{cN:"string",b:'"',e:'"',r:0},r.C("#","$",{r:0}),{cN:"keyword",b:"\\s*\\.end\\s[a-zA-Z0-9]*",r:1},{cN:"keyword",b:"^[ ]*\\.[a-zA-Z]*",r:0},{cN:"keyword",b:"\\s:[a-zA-Z_0-9]*",r:0},{cN:"keyword",b:"\\s("+s.join("|")+")",r:1},{cN:"keyword",b:"\\[",r:0},{cN:"instruction",b:"\\s("+t.join("|")+")\\s",r:1},{cN:"instruction",b:"\\s("+t.join("|")+")((\\-|/)[a-zA-Z0-9]+)+\\s",r:10},{cN:"instruction",b:"\\s("+n.join("|")+")((\\-|/)[a-zA-Z0-9]+)*\\s",r:10},{cN:"class",b:"L[^(;:\n]*;",r:0},{cN:"function",b:'( |->)[^(\n ;"]*\\(',r:0},{cN:"function",b:"\\)",r:0},{cN:"variable",b:"[vp][0-9]+",r:0}]}});hljs.registerLanguage("julia",function(r){var e={keyword:"in abstract baremodule begin bitstype break catch ccall const continue do else elseif end export finally for function global if immutable import importall let local macro module quote return try type typealias using while",literal:"true false ANY ARGS CPU_CORES C_NULL DL_LOAD_PATH DevNull ENDIAN_BOM ENV I|0 Inf Inf16 Inf32 InsertionSort JULIA_HOME LOAD_PATH MS_ASYNC MS_INVALIDATE MS_SYNC MergeSort NaN NaN16 NaN32 OS_NAME QuickSort RTLD_DEEPBIND RTLD_FIRST RTLD_GLOBAL RTLD_LAZY RTLD_LOCAL RTLD_NODELETE RTLD_NOLOAD RTLD_NOW RoundDown RoundFromZero RoundNearest RoundToZero RoundUp STDERR STDIN STDOUT VERSION WORD_SIZE catalan cglobal e eu eulergamma golden im nothing pi γ π φ",built_in:"ASCIIString AbstractArray AbstractRNG AbstractSparseArray Any ArgumentError Array Associative Base64Pipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError Box CFILE Cchar Cdouble Cfloat Char CharString Cint Clong Clonglong ClusterManager Cmd Coff_t Colon Complex Complex128 Complex32 Complex64 Condition Cptrdiff_t Cshort Csize_t Cssize_t Cuchar Cuint Culong Culonglong Cushort Cwchar_t DArray DataType DenseArray Diagonal Dict DimensionMismatch DirectIndexString Display DivideError DomainError EOFError EachLine Enumerate ErrorException Exception Expr Factorization FileMonitor FileOffset Filter Float16 Float32 Float64 FloatRange FloatingPoint Function GetfieldNode GotoNode Hermitian IO IOBuffer IOStream IPv4 IPv6 InexactError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException IntrinsicFunction KeyError LabelNode LambdaStaticData LineNumberNode LoadError LocalProcess MIME MathConst MemoryError MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode Nothing Number ObjectIdDict OrdinalRange OverflowError ParseError PollingFileWatcher ProcessExitedException ProcessGroup Ptr QuoteNode Range Range1 Ranges Rational RawFD Real Regex RegexMatch RemoteRef RepString RevString RopeString RoundingMode Set SharedArray Signed SparseMatrixCSC StackOverflowError Stat StatStruct StepRange String SubArray SubString SymTridiagonal Symbol SymbolNode Symmetric SystemError Task TextDisplay Timer TmStruct TopNode Triangular Tridiagonal Type TypeConstructor TypeError TypeName TypeVar UTF16String UTF32String UTF8String UdpSocket Uint Uint128 Uint16 Uint32 Uint64 Uint8 UndefRefError UndefVarError UniformScaling UnionType UnitRange Unsigned Vararg VersionNumber WString WeakKeyDict WeakRef Woodbury Zip"},t="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",o={l:t,k:e},n={cN:"type-annotation",b:/::/},a={cN:"subtype",b:/<:/},i={cN:"number",b:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,r:0},l={cN:"char",b:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},c={cN:"subst",b:/\$\(/,e:/\)/,k:e},u={cN:"variable",b:"\\$"+t},d={cN:"string",c:[r.BE,c,u],v:[{b:/\w*"/,e:/"\w*/},{b:/\w*"""/,e:/"""\w*/}]},g={cN:"string",c:[r.BE,c,u],b:"`",e:"`"},s={cN:"macrocall",b:"@"+t},S={cN:"comment",v:[{b:"#=",e:"=#",r:10},{b:"#",e:"$"}]};return o.c=[i,l,n,a,d,g,s,S,r.HCM],c.c=o.c,o});hljs.registerLanguage("delphi",function(e){var r="exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure",t=[e.CLCM,e.C(/\{/,/\}/,{r:0}),e.C(/\(\*/,/\*\)/,{r:10})],i={cN:"string",b:/'/,e:/'/,c:[{b:/''/}]},c={cN:"string",b:/(#\d+)+/},o={b:e.IR+"\\s*=\\s*class\\s*\\(",rB:!0,c:[e.TM]},n={cN:"function",bK:"function constructor destructor procedure",e:/[:;]/,k:"function constructor|10 destructor|10 procedure|10",c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:r,c:[i,c]}].concat(t)};return{cI:!0,k:r,i:/"|\$[G-Zg-z]|\/\*|<\/|\|/,c:[i,c,e.NM,o,n].concat(t)}});hljs.registerLanguage("brainfuck",function(r){var n={cN:"literal",b:"[\\+\\-]",r:0};return{aliases:["bf"],c:[r.C("[^\\[\\]\\.,\\+\\-<> \r\n]","[\\[\\]\\.,\\+\\-<> \r\n]",{rE:!0,r:0}),{cN:"title",b:"[\\[\\]]",r:0},{cN:"string",b:"[\\.,]",r:0},{b:/\+\+|\-\-/,rB:!0,c:[n]},n]}});hljs.registerLanguage("ini",function(e){return{cI:!0,i:/\S/,c:[e.C(";","$"),{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:!0,k:"on off true false yes no",c:[e.QSM,e.NM],r:0}]}]}});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("powershell",function(e){var t={b:"`[\\s\\S]",r:0},r={cN:"variable",v:[{b:/\$[\w\d][\w\d_:]*/}]},o={cN:"string",b:/"/,e:/"/,c:[t,r,{cN:"variable",b:/\$[A-z]/,e:/[^A-z]/}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["ps"],l:/-?[A-z\.\-]+/,cI:!0,k:{keyword:"if else foreach return function do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch",literal:"$null $true $false",built_in:"Add-Content Add-History Add-Member Add-PSSnapin Clear-Content Clear-Item Clear-Item Property Clear-Variable Compare-Object ConvertFrom-SecureString Convert-Path ConvertTo-Html ConvertTo-SecureString Copy-Item Copy-ItemProperty Export-Alias Export-Clixml Export-Console Export-Csv ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command Get-Content Get-Credential Get-Culture Get-Date Get-EventLog Get-ExecutionPolicy Get-Help Get-History Get-Host Get-Item Get-ItemProperty Get-Location Get-Member Get-PfxCertificate Get-Process Get-PSDrive Get-PSProvider Get-PSSnapin Get-Service Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object Import-Alias Import-Clixml Import-Csv Invoke-Expression Invoke-History Invoke-Item Join-Path Measure-Command Measure-Object Move-Item Move-ItemProperty New-Alias New-Item New-ItemProperty New-Object New-PSDrive New-Service New-TimeSpan New-Variable Out-Default Out-File Out-Host Out-Null Out-Printer Out-String Pop-Location Push-Location Read-Host Remove-Item Remove-ItemProperty Remove-PSDrive Remove-PSSnapin Remove-Variable Rename-Item Rename-ItemProperty Resolve-Path Restart-Service Resume-Service Select-Object Select-String Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug Set-Service Set-TraceSource Set-Variable Sort-Object Split-Path Start-Service Start-Sleep Start-Transcript Stop-Process Stop-Service Stop-Transcript Suspend-Service Tee-Object Test-Path Trace-Command Update-FormatData Update-TypeData Where-Object Write-Debug Write-Error Write-Host Write-Output Write-Progress Write-Verbose Write-Warning",operator:"-ne -eq -lt -gt -ge -le -not -like -notlike -match -notmatch -contains -notcontains -in -notin -replace"},c:[e.HCM,e.NM,o,a,r]}});hljs.registerLanguage("gradle",function(e){return{cI:!0,k:{keyword:"task project allprojects subprojects artifacts buildscript configurations dependencies repositories sourceSets description delete from into include exclude source classpath destinationDir includes options sourceCompatibility targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant def abstract break case catch continue default do else extends final finally for if implements instanceof native new private protected public return static switch synchronized throw throws transient try volatile while strictfp package import false null super this true antlrtask checkstyle codenarc copy boolean byte char class double float int interface long short void compile runTime file fileTree abs any append asList asWritable call collect compareTo count div dump each eachByte eachFile eachLine every find findAll flatten getAt getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter newReader newWriter next plus pop power previous print println push putAt read readBytes readLines reverse reverseEach round size sort splitEachLine step subMap times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader withStream withWriter withWriterAppend write writeLine"},c:[e.CLCM,e.CBCM,e.ASM,e.QSM,e.NM,e.RM]}});hljs.registerLanguage("erb",function(e){return{sL:"xml",subLanguageMode:"continuous",c:[e.C("<%#","%>"),{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"class deinit enum extension func import init let protocol static struct subscript typealias var break case continue default do else fallthrough if in for return switch where while as dynamicType is new super self Self Type __COLUMN__ __FILE__ __FUNCTION__ __LINE__ associativity didSet get infix inout left mutating none nonmutating operator override postfix precedence prefix right set unowned unowned safe unsafe weak willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue assert bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal false filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced join lexicographicalCompare map max maxElement min minElement nil numericCast partition posix print println quickSort reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith strideof strideofValue swap swift toString transcode true underestimateCount unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafePointers withVaList"},t={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},s={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[s],{k:i,c:[o,e.CLCM,n,t,s,{cN:"func",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b://,i:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",s,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@assignment|@class_protocol|@exported|@final|@lazy|@noreturn|@NSCopying|@NSManaged|@objc|@optional|@required|@auto_closure|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix)"}]}});hljs.registerLanguage("lisp",function(b){var e="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",c="\\|[^]*?\\|",r="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",a={cN:"shebang",b:"^#!",e:"$"},i={cN:"literal",b:"\\b(t{1}|nil)\\b"},l={cN:"number",v:[{b:r,r:0},{b:"#(b|B)[0-1]+(/[0-1]+)?"},{b:"#(o|O)[0-7]+(/[0-7]+)?"},{b:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{b:"#(c|C)\\("+r+" +"+r,e:"\\)"}]},t=b.inherit(b.QSM,{i:null}),d=b.C(";","$",{r:0}),n={cN:"variable",b:"\\*",e:"\\*"},u={cN:"keyword",b:"[:&]"+e},N={b:e,r:0},o={b:c},s={b:"\\(",e:"\\)",c:["self",i,t,l,N]},v={cN:"quoted",c:[l,t,n,u,s,N],v:[{b:"['`]\\(",e:"\\)"},{b:"\\(quote ",e:"\\)",k:"quote"},{b:"'"+c}]},f={cN:"quoted",v:[{b:"'"+e},{b:"#'"+e+"(::"+e+")*"}]},g={cN:"list",b:"\\(\\s*",e:"\\)"},q={eW:!0,r:0};return g.c=[{cN:"keyword",v:[{b:e},{b:c}]},q],q.c=[v,f,g,i,l,t,d,n,u,o,N],{i:/\S/,c:[l,a,i,t,d,v,f,g,N]}});hljs.registerLanguage("rsl",function(e){return{k:{keyword:"float color point normal vector matrix while for if do return else break extern continue",built_in:"abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp faceforward filterstep floor format fresnel incident length lightsource log match max min mod noise normalize ntransform opposite option phong pnoise pow printf ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan texture textureinfo trace transform vtransform xcomp ycomp zcomp"},i:" > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"},n={cN:"shebang",b:"^#!",e:"$"},c={cN:"literal",b:"(#t|#f|#\\\\"+t+"|#\\\\.)"},l={cN:"number",v:[{b:r,r:0},{b:i,r:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},s=e.QSM,o=[e.C(";","$",{r:0}),e.C("#\\|","\\|#")],u={b:t,r:0},p={cN:"variable",b:"'"+t},d={eW:!0,r:0},g={cN:"list",v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}],c:[{cN:"keyword",b:t,l:t,k:a},d]};return d.c=[c,l,s,u,p,g].concat(o),{i:/\S/,c:[n,l,s,p,g].concat(o)}});hljs.registerLanguage("stata",function(e){return{aliases:["do","ado"],cI:!0,k:"if else in foreach for forv forva forval forvalu forvalue forvalues by bys bysort xi quietly qui capture about ac ac_7 acprplot acprplot_7 adjust ado adopath adoupdate alpha ameans an ano anov anova anova_estat anova_terms anovadef aorder ap app appe appen append arch arch_dr arch_estat arch_p archlm areg areg_p args arima arima_dr arima_estat arima_p as asmprobit asmprobit_estat asmprobit_lf asmprobit_mfx__dlg asmprobit_p ass asse asser assert avplot avplot_7 avplots avplots_7 bcskew0 bgodfrey binreg bip0_lf biplot bipp_lf bipr_lf bipr_p biprobit bitest bitesti bitowt blogit bmemsize boot bootsamp bootstrap bootstrap_8 boxco_l boxco_p boxcox boxcox_6 boxcox_p bprobit br break brier bro brow brows browse brr brrstat bs bs_7 bsampl_w bsample bsample_7 bsqreg bstat bstat_7 bstat_8 bstrap bstrap_7 ca ca_estat ca_p cabiplot camat canon canon_8 canon_8_p canon_estat canon_p cap caprojection capt captu captur capture cat cc cchart cchart_7 cci cd censobs_table centile cf char chdir checkdlgfiles checkestimationsample checkhlpfiles checksum chelp ci cii cl class classutil clear cli clis clist clo clog clog_lf clog_p clogi clogi_sw clogit clogit_lf clogit_p clogitp clogl_sw cloglog clonevar clslistarray cluster cluster_measures cluster_stop cluster_tree cluster_tree_8 clustermat cmdlog cnr cnre cnreg cnreg_p cnreg_sw cnsreg codebook collaps4 collapse colormult_nb colormult_nw compare compress conf confi confir confirm conren cons const constr constra constrai constrain constraint continue contract copy copyright copysource cor corc corr corr2data corr_anti corr_kmo corr_smc corre correl correla correlat correlate corrgram cou coun count cox cox_p cox_sw coxbase coxhaz coxvar cprplot cprplot_7 crc cret cretu cretur creturn cross cs cscript cscript_log csi ct ct_is ctset ctst_5 ctst_st cttost cumsp cumsp_7 cumul cusum cusum_7 cutil d datasig datasign datasigna datasignat datasignatu datasignatur datasignature datetof db dbeta de dec deco decod decode deff des desc descr descri describ describe destring dfbeta dfgls dfuller di di_g dir dirstats dis discard disp disp_res disp_s displ displa display distinct do doe doed doedi doedit dotplot dotplot_7 dprobit drawnorm drop ds ds_util dstdize duplicates durbina dwstat dydx e ed edi edit egen eivreg emdef en enc enco encod encode eq erase ereg ereg_lf ereg_p ereg_sw ereghet ereghet_glf ereghet_glf_sh ereghet_gp ereghet_ilf ereghet_ilf_sh ereghet_ip eret eretu eretur ereturn err erro error est est_cfexist est_cfname est_clickable est_expand est_hold est_table est_unhold est_unholdok estat estat_default estat_summ estat_vce_only esti estimates etodow etof etomdy ex exi exit expand expandcl fac fact facto factor factor_estat factor_p factor_pca_rotated factor_rotate factormat fcast fcast_compute fcast_graph fdades fdadesc fdadescr fdadescri fdadescrib fdadescribe fdasav fdasave fdause fh_st file open file read file close file filefilter fillin find_hlp_file findfile findit findit_7 fit fl fli flis flist for5_0 form forma format fpredict frac_154 frac_adj frac_chk frac_cox frac_ddp frac_dis frac_dv frac_in frac_mun frac_pp frac_pq frac_pv frac_wgt frac_xo fracgen fracplot fracplot_7 fracpoly fracpred fron_ex fron_hn fron_p fron_tn fron_tn2 frontier ftodate ftoe ftomdy ftowdate g gamhet_glf gamhet_gp gamhet_ilf gamhet_ip gamma gamma_d2 gamma_p gamma_sw gammahet gdi_hexagon gdi_spokes ge gen gene gener genera generat generate genrank genstd genvmean gettoken gl gladder gladder_7 glim_l01 glim_l02 glim_l03 glim_l04 glim_l05 glim_l06 glim_l07 glim_l08 glim_l09 glim_l10 glim_l11 glim_l12 glim_lf glim_mu glim_nw1 glim_nw2 glim_nw3 glim_p glim_v1 glim_v2 glim_v3 glim_v4 glim_v5 glim_v6 glim_v7 glm glm_6 glm_p glm_sw glmpred glo glob globa global glogit glogit_8 glogit_p gmeans gnbre_lf gnbreg gnbreg_5 gnbreg_p gomp_lf gompe_sw gomper_p gompertz gompertzhet gomphet_glf gomphet_glf_sh gomphet_gp gomphet_ilf gomphet_ilf_sh gomphet_ip gphdot gphpen gphprint gprefs gprobi_p gprobit gprobit_8 gr gr7 gr_copy gr_current gr_db gr_describe gr_dir gr_draw gr_draw_replay gr_drop gr_edit gr_editviewopts gr_example gr_example2 gr_export gr_print gr_qscheme gr_query gr_read gr_rename gr_replay gr_save gr_set gr_setscheme gr_table gr_undo gr_use graph graph7 grebar greigen greigen_7 greigen_8 grmeanby grmeanby_7 gs_fileinfo gs_filetype gs_graphinfo gs_stat gsort gwood h hadimvo hareg hausman haver he heck_d2 heckma_p heckman heckp_lf heckpr_p heckprob hel help hereg hetpr_lf hetpr_p hetprob hettest hexdump hilite hist hist_7 histogram hlogit hlu hmeans hotel hotelling hprobit hreg hsearch icd9 icd9_ff icd9p iis impute imtest inbase include inf infi infil infile infix inp inpu input ins insheet insp inspe inspec inspect integ inten intreg intreg_7 intreg_p intrg2_ll intrg_ll intrg_ll2 ipolate iqreg ir irf irf_create irfm iri is_svy is_svysum isid istdize ivprob_1_lf ivprob_lf ivprobit ivprobit_p ivreg ivreg_footnote ivtob_1_lf ivtob_lf ivtobit ivtobit_p jackknife jacknife jknife jknife_6 jknife_8 jkstat joinby kalarma1 kap kap_3 kapmeier kappa kapwgt kdensity kdensity_7 keep ksm ksmirnov ktau kwallis l la lab labe label labelbook ladder levels levelsof leverage lfit lfit_p li lincom line linktest lis list lloghet_glf lloghet_glf_sh lloghet_gp lloghet_ilf lloghet_ilf_sh lloghet_ip llogi_sw llogis_p llogist llogistic llogistichet lnorm_lf lnorm_sw lnorma_p lnormal lnormalhet lnormhet_glf lnormhet_glf_sh lnormhet_gp lnormhet_ilf lnormhet_ilf_sh lnormhet_ip lnskew0 loadingplot loc loca local log logi logis_lf logistic logistic_p logit logit_estat logit_p loglogs logrank loneway lookfor lookup lowess lowess_7 lpredict lrecomp lroc lroc_7 lrtest ls lsens lsens_7 lsens_x lstat ltable ltable_7 ltriang lv lvr2plot lvr2plot_7 m ma mac macr macro makecns man manova manova_estat manova_p manovatest mantel mark markin markout marksample mat mat_capp mat_order mat_put_rr mat_rapp mata mata_clear mata_describe mata_drop mata_matdescribe mata_matsave mata_matuse mata_memory mata_mlib mata_mosave mata_rename mata_which matalabel matcproc matlist matname matr matri matrix matrix_input__dlg matstrik mcc mcci md0_ md1_ md1debug_ md2_ md2debug_ mds mds_estat mds_p mdsconfig mdslong mdsmat mdsshepard mdytoe mdytof me_derd mean means median memory memsize meqparse mer merg merge mfp mfx mhelp mhodds minbound mixed_ll mixed_ll_reparm mkassert mkdir mkmat mkspline ml ml_5 ml_adjs ml_bhhhs ml_c_d ml_check ml_clear ml_cnt ml_debug ml_defd ml_e0 ml_e0_bfgs ml_e0_cycle ml_e0_dfp ml_e0i ml_e1 ml_e1_bfgs ml_e1_bhhh ml_e1_cycle ml_e1_dfp ml_e2 ml_e2_cycle ml_ebfg0 ml_ebfr0 ml_ebfr1 ml_ebh0q ml_ebhh0 ml_ebhr0 ml_ebr0i ml_ecr0i ml_edfp0 ml_edfr0 ml_edfr1 ml_edr0i ml_eds ml_eer0i ml_egr0i ml_elf ml_elf_bfgs ml_elf_bhhh ml_elf_cycle ml_elf_dfp ml_elfi ml_elfs ml_enr0i ml_enrr0 ml_erdu0 ml_erdu0_bfgs ml_erdu0_bhhh ml_erdu0_bhhhq ml_erdu0_cycle ml_erdu0_dfp ml_erdu0_nrbfgs ml_exde ml_footnote ml_geqnr ml_grad0 ml_graph ml_hbhhh ml_hd0 ml_hold ml_init ml_inv ml_log ml_max ml_mlout ml_mlout_8 ml_model ml_nb0 ml_opt ml_p ml_plot ml_query ml_rdgrd ml_repor ml_s_e ml_score ml_searc ml_technique ml_unhold mleval mlf_ mlmatbysum mlmatsum mlog mlogi mlogit mlogit_footnote mlogit_p mlopts mlsum mlvecsum mnl0_ mor more mov move mprobit mprobit_lf mprobit_p mrdu0_ mrdu1_ mvdecode mvencode mvreg mvreg_estat n nbreg nbreg_al nbreg_lf nbreg_p nbreg_sw nestreg net newey newey_7 newey_p news nl nl_7 nl_9 nl_9_p nl_p nl_p_7 nlcom nlcom_p nlexp2 nlexp2_7 nlexp2a nlexp2a_7 nlexp3 nlexp3_7 nlgom3 nlgom3_7 nlgom4 nlgom4_7 nlinit nllog3 nllog3_7 nllog4 nllog4_7 nlog_rd nlogit nlogit_p nlogitgen nlogittree nlpred no nobreak noi nois noisi noisil noisily note notes notes_dlg nptrend numlabel numlist odbc old_ver olo olog ologi ologi_sw ologit ologit_p ologitp on one onew onewa oneway op_colnm op_comp op_diff op_inv op_str opr opro oprob oprob_sw oprobi oprobi_p oprobit oprobitp opts_exclusive order orthog orthpoly ou out outf outfi outfil outfile outs outsh outshe outshee outsheet ovtest pac pac_7 palette parse parse_dissim pause pca pca_8 pca_display pca_estat pca_p pca_rotate pcamat pchart pchart_7 pchi pchi_7 pcorr pctile pentium pergram pergram_7 permute permute_8 personal peto_st pkcollapse pkcross pkequiv pkexamine pkexamine_7 pkshape pksumm pksumm_7 pl plo plot plugin pnorm pnorm_7 poisgof poiss_lf poiss_sw poisso_p poisson poisson_estat post postclose postfile postutil pperron pr prais prais_e prais_e2 prais_p predict predictnl preserve print pro prob probi probit probit_estat probit_p proc_time procoverlay procrustes procrustes_estat procrustes_p profiler prog progr progra program prop proportion prtest prtesti pwcorr pwd q\\s qby qbys qchi qchi_7 qladder qladder_7 qnorm qnorm_7 qqplot qqplot_7 qreg qreg_c qreg_p qreg_sw qu quadchk quantile quantile_7 que quer query range ranksum ratio rchart rchart_7 rcof recast reclink recode reg reg3 reg3_p regdw regr regre regre_p2 regres regres_p regress regress_estat regriv_p remap ren rena renam rename renpfix repeat replace report reshape restore ret retu retur return rm rmdir robvar roccomp roccomp_7 roccomp_8 rocf_lf rocfit rocfit_8 rocgold rocplot rocplot_7 roctab roctab_7 rolling rologit rologit_p rot rota rotat rotate rotatemat rreg rreg_p ru run runtest rvfplot rvfplot_7 rvpplot rvpplot_7 sa safesum sample sampsi sav save savedresults saveold sc sca scal scala scalar scatter scm_mine sco scob_lf scob_p scobi_sw scobit scor score scoreplot scoreplot_help scree screeplot screeplot_help sdtest sdtesti se search separate seperate serrbar serrbar_7 serset set set_defaults sfrancia sh she shel shell shewhart shewhart_7 signestimationsample signrank signtest simul simul_7 simulate simulate_8 sktest sleep slogit slogit_d2 slogit_p smooth snapspan so sor sort spearman spikeplot spikeplot_7 spikeplt spline_x split sqreg sqreg_p sret sretu sretur sreturn ssc st st_ct st_hc st_hcd st_hcd_sh st_is st_issys st_note st_promo st_set st_show st_smpl st_subid stack statsby statsby_8 stbase stci stci_7 stcox stcox_estat stcox_fr stcox_fr_ll stcox_p stcox_sw stcoxkm stcoxkm_7 stcstat stcurv stcurve stcurve_7 stdes stem stepwise stereg stfill stgen stir stjoin stmc stmh stphplot stphplot_7 stphtest stphtest_7 stptime strate strate_7 streg streg_sw streset sts sts_7 stset stsplit stsum sttocc sttoct stvary stweib su suest suest_8 sum summ summa summar summari summariz summarize sunflower sureg survcurv survsum svar svar_p svmat svy svy_disp svy_dreg svy_est svy_est_7 svy_estat svy_get svy_gnbreg_p svy_head svy_header svy_heckman_p svy_heckprob_p svy_intreg_p svy_ivreg_p svy_logistic_p svy_logit_p svy_mlogit_p svy_nbreg_p svy_ologit_p svy_oprobit_p svy_poisson_p svy_probit_p svy_regress_p svy_sub svy_sub_7 svy_x svy_x_7 svy_x_p svydes svydes_8 svygen svygnbreg svyheckman svyheckprob svyintreg svyintreg_7 svyintrg svyivreg svylc svylog_p svylogit svymarkout svymarkout_8 svymean svymlog svymlogit svynbreg svyolog svyologit svyoprob svyoprobit svyopts svypois svypois_7 svypoisson svyprobit svyprobt svyprop svyprop_7 svyratio svyreg svyreg_p svyregress svyset svyset_7 svyset_8 svytab svytab_7 svytest svytotal sw sw_8 swcnreg swcox swereg swilk swlogis swlogit swologit swoprbt swpois swprobit swqreg swtobit swweib symmetry symmi symplot symplot_7 syntax sysdescribe sysdir sysuse szroeter ta tab tab1 tab2 tab_or tabd tabdi tabdis tabdisp tabi table tabodds tabodds_7 tabstat tabu tabul tabula tabulat tabulate te tempfile tempname tempvar tes test testnl testparm teststd tetrachoric time_it timer tis tob tobi tobit tobit_p tobit_sw token tokeni tokeniz tokenize tostring total translate translator transmap treat_ll treatr_p treatreg trim trnb_cons trnb_mean trpoiss_d2 trunc_ll truncr_p truncreg tsappend tset tsfill tsline tsline_ex tsreport tsrevar tsrline tsset tssmooth tsunab ttest ttesti tut_chk tut_wait tutorial tw tware_st two twoway twoway__fpfit_serset twoway__function_gen twoway__histogram_gen twoway__ipoint_serset twoway__ipoints_serset twoway__kdensity_gen twoway__lfit_serset twoway__normgen_gen twoway__pci_serset twoway__qfit_serset twoway__scatteri_serset twoway__sunflower_gen twoway_ksm_serset ty typ type typeof u unab unabbrev unabcmd update us use uselabel var var_mkcompanion var_p varbasic varfcast vargranger varirf varirf_add varirf_cgraph varirf_create varirf_ctable varirf_describe varirf_dir varirf_drop varirf_erase varirf_graph varirf_ograph varirf_rename varirf_set varirf_table varlist varlmar varnorm varsoc varstable varstable_w varstable_w2 varwle vce vec vec_fevd vec_mkphi vec_p vec_p_w vecirf_create veclmar veclmar_w vecnorm vecnorm_w vecrank vecstable verinst vers versi versio version view viewsource vif vwls wdatetof webdescribe webseek webuse weib1_lf weib2_lf weib_lf weib_lf0 weibhet_glf weibhet_glf_sh weibhet_glfa weibhet_glfa_sh weibhet_gp weibhet_ilf weibhet_ilf_sh weibhet_ilfa weibhet_ilfa_sh weibhet_ip weibu_sw weibul_p weibull weibull_c weibull_s weibullhet wh whelp whi which whil while wilc_st wilcoxon win wind windo window winexec wntestb wntestb_7 wntestq xchart xchart_7 xcorr xcorr_7 xi xi_6 xmlsav xmlsave xmluse xpose xsh xshe xshel xshell xt_iis xt_tis xtab_p xtabond xtbin_p xtclog xtcloglog xtcloglog_8 xtcloglog_d2 xtcloglog_pa_p xtcloglog_re_p xtcnt_p xtcorr xtdata xtdes xtfront_p xtfrontier xtgee xtgee_elink xtgee_estat xtgee_makeivar xtgee_p xtgee_plink xtgls xtgls_p xthaus xthausman xtht_p xthtaylor xtile xtint_p xtintreg xtintreg_8 xtintreg_d2 xtintreg_p xtivp_1 xtivp_2 xtivreg xtline xtline_ex xtlogit xtlogit_8 xtlogit_d2 xtlogit_fe_p xtlogit_pa_p xtlogit_re_p xtmixed xtmixed_estat xtmixed_p xtnb_fe xtnb_lf xtnbreg xtnbreg_pa_p xtnbreg_refe_p xtpcse xtpcse_p xtpois xtpoisson xtpoisson_d2 xtpoisson_pa_p xtpoisson_refe_p xtpred xtprobit xtprobit_8 xtprobit_d2 xtprobit_re_p xtps_fe xtps_lf xtps_ren xtps_ren_8 xtrar_p xtrc xtrc_p xtrchh xtrefe_p xtreg xtreg_be xtreg_fe xtreg_ml xtreg_pa_p xtreg_re xtregar xtrere_p xtset xtsf_ll xtsf_llti xtsum xttab xttest0 xttobit xttobit_8 xttobit_p xttrans yx yxview__barlike_draw yxview_area_draw yxview_bar_draw yxview_dot_draw yxview_dropline_draw yxview_function_draw yxview_iarrow_draw yxview_ilabels_draw yxview_normal_draw yxview_pcarrow_draw yxview_pcbarrow_draw yxview_pccapsym_draw yxview_pcscatter_draw yxview_pcspike_draw yxview_rarea_draw yxview_rbar_draw yxview_rbarm_draw yxview_rcap_draw yxview_rcapsym_draw yxview_rconnected_draw yxview_rline_draw yxview_rscatter_draw yxview_rspike_draw yxview_spike_draw yxview_sunflower_draw zap_s zinb zinb_llf zinb_plf zip zip_llf zip_p zip_plf zt_ct_5 zt_hc_5 zt_hcd_5 zt_is_5 zt_iss_5 zt_sho_5 zt_smp_5 ztbase_5 ztcox_5 ztdes_5 ztereg_5 ztfill_5 ztgen_5 ztir_5 ztjoin_5 ztnb ztnb_p ztp ztp_p zts_5 ztset_5 ztspli_5 ztsum_5 zttoct_5 ztvary_5 ztweib_5",c:[{cN:"label",v:[{b:"\\$\\{?[a-zA-Z0-9_]+\\}?"},{b:"`[a-zA-Z0-9_]+'"}]},{cN:"string",v:[{b:'`"[^\r\n]*?"\''},{b:'"[^\r\n"]*"'}]},{cN:"literal",v:[{b:"\\b(abs|acos|asin|atan|atan2|atanh|ceil|cloglog|comb|cos|digamma|exp|floor|invcloglog|invlogit|ln|lnfact|lnfactorial|lngamma|log|log10|max|min|mod|reldif|round|sign|sin|sqrt|sum|tan|tanh|trigamma|trunc|betaden|Binomial|binorm|binormal|chi2|chi2tail|dgammapda|dgammapdada|dgammapdadx|dgammapdx|dgammapdxdx|F|Fden|Ftail|gammaden|gammap|ibeta|invbinomial|invchi2|invchi2tail|invF|invFtail|invgammap|invibeta|invnchi2|invnFtail|invnibeta|invnorm|invnormal|invttail|nbetaden|nchi2|nFden|nFtail|nibeta|norm|normal|normalden|normd|npnchi2|tden|ttail|uniform|abbrev|char|index|indexnot|length|lower|ltrim|match|plural|proper|real|regexm|regexr|regexs|reverse|rtrim|string|strlen|strlower|strltrim|strmatch|strofreal|strpos|strproper|strreverse|strrtrim|strtrim|strupper|subinstr|subinword|substr|trim|upper|word|wordcount|_caller|autocode|byteorder|chop|clip|cond|e|epsdouble|epsfloat|group|inlist|inrange|irecode|matrix|maxbyte|maxdouble|maxfloat|maxint|maxlong|mi|minbyte|mindouble|minfloat|minint|minlong|missing|r|recode|replay|return|s|scalar|d|date|day|dow|doy|halfyear|mdy|month|quarter|week|year|d|daily|dofd|dofh|dofm|dofq|dofw|dofy|h|halfyearly|hofd|m|mofd|monthly|q|qofd|quarterly|tin|twithin|w|weekly|wofd|y|yearly|yh|ym|yofd|yq|yw|cholesky|colnumb|colsof|corr|det|diag|diag0cnt|el|get|hadamard|I|inv|invsym|issym|issymmetric|J|matmissing|matuniform|mreldif|nullmat|rownumb|rowsof|sweep|syminv|trace|vec|vecdiag)(?=\\(|$)"}]},e.C("^[ ]*\\*.*$",!1),e.CLCM,e.CBCM]}});hljs.registerLanguage("asciidoc",function(e){return{aliases:["adoc"],c:[e.C("^/{4,}\\n","\\n/{4,}$",{r:10}),e.C("^//","$",{r:0}),{cN:"title",b:"^\\.\\w.*$"},{b:"^[=\\*]{4,}\\n",e:"\\n^[=\\*]{4,}$",r:10},{cN:"header",b:"^(={1,5}) .+?( \\1)?$",r:10},{cN:"header",b:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$",r:10},{cN:"attribute",b:"^:.+?:",e:"\\s",eE:!0,r:10},{cN:"attribute",b:"^\\[.+?\\]$",r:0},{cN:"blockquote",b:"^_{4,}\\n",e:"\\n_{4,}$",r:10},{cN:"code",b:"^[\\-\\.]{4,}\\n",e:"\\n[\\-\\.]{4,}$",r:10},{b:"^\\+{4,}\\n",e:"\\n\\+{4,}$",c:[{b:"<",e:">",sL:"xml",r:0}],r:10},{cN:"bullet",b:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{cN:"label",b:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",r:10},{cN:"strong",b:"\\B\\*(?![\\*\\s])",e:"(\\n{2}|\\*)",c:[{b:"\\\\*\\w",r:0}]},{cN:"emphasis",b:"\\B'(?!['\\s])",e:"(\\n{2}|')",c:[{b:"\\\\'\\w",r:0}],r:0},{cN:"emphasis",b:"_(?![_\\s])",e:"(\\n{2}|_)",r:0},{cN:"smartquote",v:[{b:"``.+?''"},{b:"`.+?'"}]},{cN:"code",b:"(`.+?`|\\+.+?\\+)",r:0},{cN:"code",b:"^[ \\t]",e:"$",r:0},{cN:"horizontal_rule",b:"^'{3,}[ \\t]*$",r:10},{b:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",rB:!0,c:[{b:"(link|image:?):",r:0},{cN:"link_url",b:"\\w",e:"[^\\[]+",r:0},{cN:"link_label",b:"\\[",e:"\\]",eB:!0,eE:!0,r:0}],r:10}]}});hljs.registerLanguage("php",function(e){var c={cN:"variable",b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"preprocessor",b:/<\?(php)?|\?>/},a={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},i]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[e.BE]},i,c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,a,n]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},a,n]}});hljs.registerLanguage("java",function(e){var a=e.UIR+"(<"+e.UIR+">)?",t="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",c="(\\b(0b[01_]+)|\\b0[xX][a-fA-F0-9_]+|(\\b[\\d_]+(\\.[\\d_]*)?|\\.[\\d_]+)([eE][-+]?\\d+)?)[lLfF]?",r={cN:"number",b:c,r:0};return{aliases:["jsp"],k:t,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",r:0,c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}]},e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return",r:0},{cN:"function",b:"("+a+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("glsl",function(e){return{k:{keyword:"atomic_uint attribute bool break bvec2 bvec3 bvec4 case centroid coherent const continue default discard dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 do double dvec2 dvec3 dvec4 else flat float for highp if iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray in inout int invariant isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 layout lowp mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 mediump noperspective out patch precision readonly restrict return sample sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow smooth struct subroutine switch uimage1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint uniform usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D usamplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 varying vec2 vec3 vec4 void volatile while writeonly",built_in:"gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial gl_FrontSecondaryColor gl_InstanceID gl_InvocationID gl_Layer gl_LightModel gl_LightSource gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms gl_MaxVertexOutputComponents gl_MaxVertexTextureImageUnits gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffsetgl_ModelViewMatrix gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_Normal gl_NormalMatrix gl_NormalScale gl_ObjectPlaneQ gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_PerVertex gl_Point gl_PointCoord gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter gl_TexCoord gl_TextureEnvColor gl_TextureMatrixInverseTranspose gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_in gl_out EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin asinh atan atanh atomicCounter atomicCounterDecrement atomicCounterIncrement barrier bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan greaterThanEqual imageAtomicAdd imageAtomicAnd imageAtomicCompSwap imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log log2 matrixCompMult max memoryBarrier min mix mod modf noise1 noise2 noise3 noise4 normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset textureProjLod textureProjLodOffset textureProjOffset textureQueryLod textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow gl_TextureMatrix gl_TextureMatrixInverse",literal:"true false"},i:'"',c:[e.CLCM,e.CBCM,e.CNM,{cN:"preprocessor",b:"#",e:"$"}]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",r={b:t,e:a,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[r],r:10})];return{l:e.UIR,k:{keyword:"and break do else elseif end false for if in local nil not or repeat return then true until while",built_in:"_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug io math os package string table"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[r],r:5}])}});hljs.registerLanguage("protobuf",function(e){return{k:{keyword:"package import option optional required repeated group",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},c:[e.QSM,e.NM,e.CLCM,{cN:"class",bK:"message enum service",e:/\{/,i:/\n/,c:[e.inherit(e.TM,{starts:{eW:!0,eE:!0}})]},{cN:"function",bK:"rpc",e:/;/,eE:!0,k:"rpc returns"},{cN:"constant",b:/^\s*[A-Z_]+/,e:/\s*=/,eE:!0}]}});hljs.registerLanguage("gcode",function(e){var N="[A-Z_][A-Z0-9_.]*",i="\\%",c={literal:"",built_in:"",keyword:"IF DO WHILE ENDWHILE CALL ENDIF SUB ENDSUB GOTO REPEAT ENDREPEAT EQ LT GT NE GE LE OR XOR"},r={cN:"preprocessor",b:"([O])([0-9]+)"},l=[e.CLCM,e.CBCM,e.C(/\(/,/\)/),e.inherit(e.CNM,{b:"([-+]?([0-9]*\\.?[0-9]+\\.?))|"+e.CNR}),e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null}),{cN:"keyword",b:"([G])([0-9]+\\.?[0-9]?)"},{cN:"title",b:"([M])([0-9]+\\.?[0-9]?)"},{cN:"title",b:"(VC|VS|#)",e:"(\\d+)"},{cN:"title",b:"(VZOFX|VZOFY|VZOFZ)"},{cN:"built_in",b:"(ATAN|ABS|ACOS|ASIN|SIN|COS|EXP|FIX|FUP|ROUND|LN|TAN)(\\[)",e:"([-+]?([0-9]*\\.?[0-9]+\\.?))(\\])"},{cN:"label",v:[{b:"N",e:"\\d+",i:"\\W"}]}];return{aliases:["nc"],cI:!0,l:N,k:c,c:[{cN:"preprocessor",b:i},r].concat(l)}});hljs.registerLanguage("vim",function(e){return{l:/[!#@\w]+/,k:{keyword:"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw d|0 delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu g|0 go gr grepa gu gv ha h|0 helpf helpg helpt hi hid his i|0 ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs n|0 new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf q|0 quita qa r|0 rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv s|0 sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync t|0 tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up v|0 ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank",built_in:"abs acos add and append argc argidx argv asin atan atan2 browse browsedir bufexists buflisted bufloaded bufname bufnr bufwinnr byte2line byteidx call ceil changenr char2nr cindent clearmatches col complete complete_add complete_check confirm copy cos cosh count cscope_connection cursor deepcopy delete did_filetype diff_filler diff_hlID empty escape eval eventhandler executable exists exp expand extend feedkeys filereadable filewritable filter finddir findfile float2nr floor fmod fnameescape fnamemodify foldclosed foldclosedend foldlevel foldtext foldtextresult foreground function garbagecollect get getbufline getbufvar getchar getcharmod getcmdline getcmdpos getcmdtype getcwd getfontname getfperm getfsize getftime getftype getline getloclist getmatches getpid getpos getqflist getreg getregtype gettabvar gettabwinvar getwinposx getwinposy getwinvar glob globpath has has_key haslocaldir hasmapto histadd histdel histget histnr hlexists hlID hostname iconv indent index input inputdialog inputlist inputrestore inputsave inputsecret insert invert isdirectory islocked items join keys len libcall libcallnr line line2byte lispindent localtime log log10 luaeval map maparg mapcheck match matchadd matcharg matchdelete matchend matchlist matchstr max min mkdir mode mzeval nextnonblank nr2char or pathshorten pow prevnonblank printf pumvisible py3eval pyeval range readfile reltime reltimestr remote_expr remote_foreground remote_peek remote_read remote_send remove rename repeat resolve reverse round screenattr screenchar screencol screenrow search searchdecl searchpair searchpairpos searchpos server2client serverlist setbufvar setcmdpos setline setloclist setmatches setpos setqflist setreg settabvar settabwinvar setwinvar sha256 shellescape shiftwidth simplify sin sinh sort soundfold spellbadword spellsuggest split sqrt str2float str2nr strchars strdisplaywidth strftime stridx string strlen strpart strridx strtrans strwidth submatch substitute synconcealed synID synIDattr synIDtrans synstack system tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname tolower toupper tr trunc type undofile undotree values virtcol visualmode wildmenumode winbufnr wincol winheight winline winnr winrestcmd winrestview winsaveview winwidth writefile xor"},i:/[{:]/,c:[e.NM,e.ASM,{cN:"string",b:/"((\\")|[^"\n])*("|\n)/},{cN:"variable",b:/[bwtglsav]:[\w\d_]*/},{cN:"function",bK:"function function!",e:"$",r:0,c:[e.TM,{cN:"params",b:"\\(",e:"\\)"}]}]}});hljs.registerLanguage("processing",function(e){return{k:{keyword:"BufferedReader PVector PFont PImage PGraphics HashMap boolean byte char color double float int long String Array FloatDict FloatList IntDict IntList JSONArray JSONObject Object StringDict StringList Table TableRow XML false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",constant:"P2D P3D HALF_PI PI QUARTER_PI TAU TWO_PI",variable:"displayHeight displayWidth mouseY mouseX mousePressed pmouseX pmouseY key keyCode pixels focused frameCount frameRate height width",title:"setup draw",built_in:"size createGraphics beginDraw createShape loadShape PShape arc ellipse line point quad rect triangle bezier bezierDetail bezierPoint bezierTangent curve curveDetail curvePoint curveTangent curveTightness shape shapeMode beginContour beginShape bezierVertex curveVertex endContour endShape quadraticVertex vertex ellipseMode noSmooth rectMode smooth strokeCap strokeJoin strokeWeight mouseClicked mouseDragged mouseMoved mousePressed mouseReleased mouseWheel keyPressed keyPressedkeyReleased keyTyped print println save saveFrame day hour millis minute month second year background clear colorMode fill noFill noStroke stroke alpha blue brightness color green hue lerpColor red saturation modelX modelY modelZ screenX screenY screenZ ambient emissive shininess specular add createImage beginCamera camera endCamera frustum ortho perspective printCamera printProjection cursor frameRate noCursor exit loop noLoop popStyle pushStyle redraw binary boolean byte char float hex int str unbinary unhex join match matchAll nf nfc nfp nfs split splitTokens trim append arrayCopy concat expand reverse shorten sort splice subset box sphere sphereDetail createInput createReader loadBytes loadJSONArray loadJSONObject loadStrings loadTable loadXML open parseXML saveTable selectFolder selectInput beginRaw beginRecord createOutput createWriter endRaw endRecord PrintWritersaveBytes saveJSONArray saveJSONObject saveStream saveStrings saveXML selectOutput popMatrix printMatrix pushMatrix resetMatrix rotate rotateX rotateY rotateZ scale shearX shearY translate ambientLight directionalLight lightFalloff lights lightSpecular noLights normal pointLight spotLight image imageMode loadImage noTint requestImage tint texture textureMode textureWrap blend copy filter get loadPixels set updatePixels blendMode loadShader PShaderresetShader shader createFont loadFont text textFont textAlign textLeading textMode textSize textWidth textAscent textDescent abs ceil constrain dist exp floor lerp log mag map max min norm pow round sq sqrt acos asin atan atan2 cos degrees radians sin tan noise noiseDetail noiseSeed random randomGaussian randomSeed"},c:[e.CLCM,e.CBCM,e.ASM,e.QSM,e.CNM]}});hljs.registerLanguage("mizar",function(e){return{k:"environ vocabularies notations constructors definitions registrations theorems schemes requirements begin end definition registration cluster existence pred func defpred deffunc theorem proof let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from be being by means equals implies iff redefine define now not or attr is mode suppose per cases set thesis contradiction scheme reserve struct correctness compatibility coherence symmetry assymetry reflexivity irreflexivity connectedness uniqueness commutativity idempotence involutiveness projectivity",c:[e.C("::","$")]}});hljs.registerLanguage("vbnet",function(e){return{aliases:["vb"],cI:!0,k:{keyword:"addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor",built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},i:"//|{|}|endif|gosub|variant|wend",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C("'","$",{rB:!0,c:[{cN:"xmlDocTag",b:"'''|",c:[e.PWM]},{cN:"xmlDocTag",b:"",c:[e.PWM]}]}),e.CNM,{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end region externalsource"}]}});hljs.registerLanguage("q",function(e){var s={keyword:"do while select delete by update from",constant:"0b 1b",built_in:"neg not null string reciprocal floor ceiling signum mod xbar xlog and or each scan over prior mmu lsq inv md5 ltime gtime count first var dev med cov cor all any rand sums prds mins maxs fills deltas ratios avgs differ prev next rank reverse iasc idesc asc desc msum mcount mavg mdev xrank mmin mmax xprev rotate distinct group where flip type key til get value attr cut set upsert raze union inter except cross sv vs sublist enlist read0 read1 hopen hclose hdel hsym hcount peach system ltrim rtrim trim lower upper ssr view tables views cols xcols keys xkey xcol xasc xdesc fkeys meta lj aj aj0 ij pj asof uj ww wj wj1 fby xgroup ungroup ej save load rsave rload show csv parse eval min max avg wavg wsum sin cos tan sum",typename:"`float `double int `timestamp `timespan `datetime `time `boolean `symbol `char `byte `short `long `real `month `date `minute `second `guid"};return{aliases:["k","kdb"],k:s,l:/\b(`?)[A-Za-z0-9_]+\b/,c:[e.CLCM,e.QSM,e.CNM]}});hljs.registerLanguage("livescript",function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger case default function var with then unless until loop of by when and or is isnt not it that otherwise from to til fallthrough super case default function var void const let enum export import native __hasProp __extends __slice __bind __indexOf",literal:"true false null undefined yes no on off it that void",built_in:"npm require console print module global window document"},s="[A-Za-z$_](?:-[0-9A-Za-z$_]|[0-9A-Za-z$_])*",i=e.inherit(e.TM,{b:s}),n={cN:"subst",b:/#\{/,e:/}/,k:t},r={cN:"subst",b:/#[A-Za-z$_]/,e:/(?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*/,k:t},c=[e.BNM,{cN:"number",b:"(\\b0[xX][a-fA-F0-9_]+)|(\\b\\d(\\d|_\\d)*(\\.(\\d(\\d|_\\d)*)?)?(_*[eE]([-+]\\d(_\\d|\\d)*)?)?[_a-z]*)",r:0,starts:{e:"(\\s*/)?",r:0}},{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,n,r]},{b:/"/,e:/"/,c:[e.BE,n,r]},{b:/\\/,e:/(\s|$)/,eE:!0}]},{cN:"pi",v:[{b:"//",e:"//[gim]*",c:[n,e.HCM]},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{cN:"property",b:"@"+s},{b:"``",e:"``",eB:!0,eE:!0,sL:"javascript"}];n.c=c;var a={cN:"params",b:"\\(",rB:!0,c:[{b:/\(/,e:/\)/,k:t,c:["self"].concat(c)}]};return{aliases:["ls"],k:t,i:/\/\*/,c:c.concat([e.C("\\/\\*","\\*\\/"),e.HCM,{cN:"function",c:[i,a],rB:!0,v:[{b:"("+s+"\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B\\->\\*?",e:"\\->\\*?"},{b:"("+s+"\\s*(?:=|:=)\\s*)?!?(\\(.*\\))?\\s*\\B[-~]{1,2}>\\*?",e:"[-~]{1,2}>\\*?"},{b:"("+s+"\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B!?[-~]{1,2}>\\*?",e:"!?[-~]{1,2}>\\*?"}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{cN:"attribute",b:s+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("haxe",function(e){var r="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";return{aliases:["hx"],k:{keyword:"break callback case cast catch class continue default do dynamic else enum extends extern for function here if implements import in inline interface never new override package private public return static super switch this throw trace try typedef untyped using var while",literal:"true false null"},c:[e.ASM,e.QSM,e.CLCM,e.CBCM,e.CNM,{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.TM]},{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end error"},{cN:"function",bK:"function",e:"[{;]",eE:!0,i:"\\S",c:[e.TM,{cN:"params",b:"\\(",e:"\\)",c:[e.ASM,e.QSM,e.CLCM,e.CBCM]},{cN:"type",b:":",e:r,r:10}]}]}});hljs.registerLanguage("monkey",function(e){var n={cN:"number",r:0,v:[{b:"[$][a-fA-F0-9]+"},e.NM]};return{cI:!0,k:{keyword:"public private property continue exit extern new try catch eachin not abstract final select case default const local global field end if then else elseif endif while wend repeat until forever for to step next return module inline throw",built_in:"DebugLog DebugStop Error Print ACos ACosr ASin ASinr ATan ATan2 ATan2r ATanr Abs Abs Ceil Clamp Clamp Cos Cosr Exp Floor Log Max Max Min Min Pow Sgn Sgn Sin Sinr Sqrt Tan Tanr Seed PI HALFPI TWOPI",literal:"true false null and or shl shr mod"},c:[e.C("#rem","#end"),e.C("'","$",{r:0}),{cN:"function",bK:"function method",e:"[(=:]|$",i:/\n/,c:[e.UTM]},{cN:"class",bK:"class interface",e:"$",c:[{bK:"extends implements"},e.UTM]},{cN:"variable",b:"\\b(self|super)\\b"},{cN:"preprocessor",bK:"import",e:"$"},{cN:"preprocessor",b:"\\s*#",e:"$",k:"if else elseif endif end then"},{cN:"pi",b:"^\\s*strict\\b"},{bK:"alias",e:"=",c:[e.UTM]},e.QSM,n]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});hljs.registerLanguage("erlang",function(e){var r="[a-z'][a-zA-Z0-9_']*",c="("+r+":"+r+"|"+r+")",a={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},n=e.C("%","$"),i={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},b={b:"fun\\s+"+r+"/\\d+"},d={b:c+"\\(",e:"\\)",rB:!0,r:0,c:[{cN:"function_name",b:c,r:0},{b:"\\(",e:"\\)",eW:!0,rE:!0,r:0}]},o={cN:"tuple",b:"{",e:"}",r:0},t={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0},l={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0},f={b:"#"+e.UIR,r:0,rB:!0,c:[{cN:"record_name",b:"#"+e.UIR,r:0},{b:"{",e:"}",r:0}]},s={bK:"fun receive if try case",e:"end",k:a};s.c=[n,b,e.inherit(e.ASM,{cN:""}),s,d,e.QSM,i,o,t,l,f];var u=[n,b,s,d,e.QSM,i,o,t,l,f];d.c[1].c=u,o.c=u,f.c[1].c=u;var v={cN:"params",b:"\\(",e:"\\)",c:u};return{aliases:["erl"],k:a,i:"(",rB:!0,i:"\\(|#|//|/\\*|\\\\|:|;",c:[v,e.inherit(e.TM,{b:r})],starts:{e:";|\\.",k:a,c:u}},n,{cN:"pp",b:"^-",e:"\\.",r:0,eE:!0,rB:!0,l:"-"+e.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[v]},i,e.QSM,f,t,l,o,{b:/\.$/}]}});hljs.registerLanguage("kotlin",function(e){var a="val var get set class trait object public open private protected final enum if else do while for when break continue throw try catch finally import package is as in return fun override default companion reified inline volatile transient native";return{k:{typename:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null",keyword:a},c:[e.CLCM,{cN:"javadoc",b:"/\\*\\*",e:"\\*//*",r:0,c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}]},e.CBCM,{cN:"type",b://,rB:!0,eE:!1,r:0},{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:a,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,r:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"type",b://,k:"reified",r:0},{cN:"params",b:/\(/,e:/\)/,k:a,r:0,i:/\([^\(,\s:]+,/,c:[{cN:"typename",b:/:\s*/,e:/\s*[=\)]/,eB:!0,rE:!0,r:0}]},e.CLCM,e.CBCM]},{cN:"class",bK:"class trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[e.UTM,{cN:"type",b://,eB:!0,eE:!0,r:0},{cN:"typename",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0}]},{cN:"variable",bK:"var val",e:/\s*[=:$]/,eE:!0},e.QSM,{cN:"shebang",b:"^#!/usr/bin/env",e:"$",i:"\n"},e.CNM]}});hljs.registerLanguage("stylus",function(t){var e={cN:"variable",b:"\\$"+t.IR},o={cN:"hexcolor",b:"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})",r:10},i=["charset","css","debug","extend","font-face","for","import","include","media","mixin","page","warn","while"],r=["after","before","first-letter","first-line","active","first-child","focus","hover","lang","link","visited"],n=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],a="[\\.\\s\\n\\[\\:,]",l=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","auto","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","clip-path","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-variant-ligatures","font-weight","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inherit","initial","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","mask","max-height","max-width","min-height","min-width","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","perspective","perspective-origin","pointer-events","position","quotes","resize","right","tab-size","table-layout","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","white-space","widows","width","word-break","word-spacing","word-wrap","z-index"],d=["\\{","\\}","\\?","(\\bReturn\\b)","(\\bEnd\\b)","(\\bend\\b)",";","#\\s","\\*\\s","===\\s","\\|","%"];return{aliases:["styl"],cI:!1,i:"("+d.join("|")+")",k:"if else for in",c:[t.QSM,t.ASM,t.CLCM,t.CBCM,o,{b:"\\.[a-zA-Z][a-zA-Z0-9_-]*"+a,rB:!0,c:[{cN:"class",b:"\\.[a-zA-Z][a-zA-Z0-9_-]*"}]},{b:"\\#[a-zA-Z][a-zA-Z0-9_-]*"+a,rB:!0,c:[{cN:"id",b:"\\#[a-zA-Z][a-zA-Z0-9_-]*"}]},{b:"\\b("+n.join("|")+")"+a,rB:!0,c:[{cN:"tag",b:"\\b[a-zA-Z][a-zA-Z0-9_-]*"}]},{cN:"pseudo",b:"&?:?:\\b("+r.join("|")+")"+a},{cN:"at_rule",b:"@("+i.join("|")+")\\b"},e,t.CSSNM,t.NM,{cN:"function",b:"\\b[a-zA-Z][a-zA-Z0-9_-]*\\(.*\\)",i:"[\\n]",rB:!0,c:[{cN:"title",b:"\\b[a-zA-Z][a-zA-Z0-9_-]*"},{cN:"params",b:/\(/,e:/\)/,c:[o,e,t.ASM,t.CSSNM,t.NM,t.QSM]}]},{cN:"attribute",b:"\\b("+l.reverse().join("|")+")\\b"}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("},r={cN:"rule",b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]};return{cI:!0,i:/[=\/|']/,c:[e.CBCM,r,{cN:"id",b:/\#[A-Za-z0-9_-]+/},{cN:"class",b:/\.[A-Za-z0-9_-]+/,r:0},{cN:"attr_selector",b:/\[/,e:/\]/,i:"$"},{cN:"pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:/\S/,r:0,c:[e.CBCM,r]}]}});hljs.registerLanguage("puppet",function(e){var s="augeas computer cron exec file filebucket host interface k5login macauthorization mailalias maillist mcx mount nagios_command nagios_contact nagios_contactgroup nagios_host nagios_hostdependency nagios_hostescalation nagios_hostextinfo nagios_hostgroup nagios_service firewall nagios_servicedependency nagios_serviceescalation nagios_serviceextinfo nagios_servicegroup nagios_timeperiod notify package resources router schedule scheduled_task selboolean selmodule service ssh_authorized_key sshkey stage tidy user vlan yumrepo zfs zone zpool",r="alias audit before loglevel noop require subscribe tag owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check en_address ip_address realname command environment hour monute month monthday special target weekday creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey sslverify mounted",a={keyword:"and case class default define else elsif false if in import enherits node or true undef unless main settings $string "+s,literal:r,built_in:"architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version"},i=e.C("#","$"),o={cN:"string",c:[e.BE],v:[{b:/'/,e:/'/},{b:/"/,e:/"/}]},n=[o,i,{cN:"keyword",bK:"class",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"(::)?[A-Za-z_]\\w*(::\\w+)*"}),i,o]},{cN:"keyword",b:"([a-zA-Z_(::)]+ *\\{)",c:[o,i],r:0},{cN:"keyword",b:"(\\}|\\{)",r:0},{cN:"function",b:"[a-zA-Z_]+\\s*=>"},{cN:"constant",b:"(::)?(\\b[A-Z][a-z_]*(::)?)+",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0}];return{aliases:["pp"],k:a,c:n}});hljs.registerLanguage("nimrod",function(t){return{aliases:["nim"],k:{keyword:"addr and as asm bind block break|0 case|0 cast const|0 continue|0 converter discard distinct|10 div do elif else|0 end|0 enum|0 except export finally for from generic if|0 import|0 in include|0 interface is isnot|10 iterator|10 let|0 macro method|10 mixin mod nil not notin|10 object|0 of or out proc|10 ptr raise ref|10 return shl shr static template|10 try|0 tuple type|0 using|0 var|0 when while|0 with without xor yield",literal:"shared guarded stdin stdout stderr result|10 true false"},c:[{cN:"decorator",b:/{\./,e:/\.}/,r:10},{cN:"string",b:/[a-zA-Z]\w*"/,e:/"/,c:[{b:/""/}]},{cN:"string",b:/([a-zA-Z]\w*)?"""/,e:/"""/},t.QSM,{cN:"type",b:/\b[A-Z]\w+\b/,r:0},{cN:"type",b:/\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float|float32|float64|bool|char|string|cstring|pointer|expr|stmt|void|auto|any|range|array|openarray|varargs|seq|set|clong|culong|cchar|cschar|cshort|cint|csize|clonglong|cfloat|cdouble|clongdouble|cuchar|cushort|cuint|culonglong|cstringarray|semistatic)\b/},{cN:"number",b:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/,r:0},t.HCM]}});hljs.registerLanguage("smalltalk",function(a){var r="[a-z][a-zA-Z0-9_]*",s={cN:"char",b:"\\$.{1}"},c={cN:"symbol",b:"#"+a.UIR};return{aliases:["st"],k:"self super nil true false thisContext",c:[a.C('"','"'),a.ASM,{cN:"class",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},{cN:"method",b:r+":",r:0},a.CNM,c,s,{cN:"localvars",b:"\\|[ ]*"+r+"([ ]+"+r+")*[ ]*\\|",rB:!0,e:/\|/,i:/\S/,c:[{b:"(\\|[ ]*)?"+r}]},{cN:"array",b:"\\#\\(",e:"\\)",c:[a.ASM,s,a.CNM,c]}]}});hljs.registerLanguage("x86asm",function(s){return{cI:!0,l:"\\.?"+s.IR,k:{keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",literal:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l",pseudo:"db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times",preprocessor:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public ",built_in:"bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[s.C(";","$",{r:0}),{cN:"number",b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{cN:"number",b:"\\$[0-9][0-9A-Fa-f]*",r:0},{cN:"number",b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[HhXx]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{cN:"number",b:"\\b(?:0[HhXx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"},s.QSM,{cN:"string",b:"'",e:"[^\\\\]'",r:0},{cN:"string",b:"`",e:"[^\\\\]`",r:0},{cN:"string",b:"\\.[A-Za-z0-9]+",r:0},{cN:"label",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0},{cN:"label",b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:",r:0},{cN:"argument",b:"%[0-9]+",r:0},{cN:"built_in",b:"%!S+",r:0}]}});hljs.registerLanguage("roboconf",function(e){var n="[a-zA-Z-_][^\n{\r\n]+\\{";return{aliases:["graph","instances"],cI:!0,k:"import",c:[{cN:"facet",b:"^facet "+n,e:"}",k:"facet installer exports children extends",c:[e.HCM]},{cN:"instance-of",b:"^instance of "+n,e:"}",k:"name count channels instance-data instance-state instance of",c:[{cN:"keyword",b:"[a-zA-Z-_]+( | )*:"},e.HCM]},{cN:"component",b:"^"+n,e:"}",l:"\\(?[a-zA-Z]+\\)?",k:"installer exports children extends imports facets alias (optional)",c:[{cN:"string",b:"\\.[a-zA-Z-_]+",e:"\\s|,|;",eE:!0},e.HCM]},e.HCM]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:c}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,c:n.concat(N).concat(d)}});hljs.registerLanguage("typescript",function(e){return{aliases:["ts"],k:{keyword:"in if for while finally var new function|0 do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private get set super interface extendsstatic constructor implements enum export import declare type protected",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:0},e.ASM,e.QSM,e.CLCM,e.CBCM,e.CNM,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/\[|%/,r:0},{cN:"constructor",bK:"constructor",e:/\{/,eE:!0,r:10},{cN:"module",bK:"module",e:/\{/,eE:!0},{cN:"interface",bK:"interface",e:/\{/,eE:!0},{b:/\$[(.]/},{b:"\\."+e.IR,r:0}]}});hljs.registerLanguage("handlebars",function(e){var a="each in with if else unless bindattr action collection debugger log outlet template unbound view yield";return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",subLanguageMode:"continuous",c:[{cN:"expression",b:"{{",e:"}}",c:[{cN:"begin-block",b:"#[a-zA-Z- .]+",k:a},{cN:"string",b:'"',e:'"'},{cN:"end-block",b:"\\/[a-zA-Z- .]+",k:a},{cN:"variable",b:"[a-zA-Z-.]+",k:a}]}]}});hljs.registerLanguage("mercury",function(e){var i={keyword:"module use_module import_module include_module end_module initialise mutable initialize finalize finalise interface implementation pred mode func type inst solver any_pred any_func is semidet det nondet multi erroneous failure cc_nondet cc_multi typeclass instance where pragma promise external trace atomic or_else require_complete_switch require_det require_semidet require_multi require_nondet require_cc_multi require_cc_nondet require_erroneous require_failure",pragma:"inline no_inline type_spec source_file fact_table obsolete memo loop_check minimal_model terminates does_not_terminate check_termination promise_equivalent_clauses",preprocessor:"foreign_proc foreign_decl foreign_code foreign_type foreign_import_module foreign_export_enum foreign_export foreign_enum may_call_mercury will_not_call_mercury thread_safe not_thread_safe maybe_thread_safe promise_pure promise_semipure tabled_for_io local untrailed trailed attach_to_io_state can_pass_as_mercury_type stable will_not_throw_exception may_modify_trail will_not_modify_trail may_duplicate may_not_duplicate affects_liveness does_not_affect_liveness doesnt_affect_liveness no_sharing unknown_sharing sharing",built_in:"some all not if then else true fail false try catch catch_any semidet_true semidet_false semidet_fail impure_true impure semipure"},r={cN:"label",b:"XXX",e:"$",eW:!0,r:0},t=e.inherit(e.CLCM,{b:"%"}),_=e.inherit(e.CBCM,{r:0});t.c.push(r),_.c.push(r);var n={cN:"number",b:"0'.\\|0[box][0-9a-fA-F]*"},a=e.inherit(e.ASM,{r:0}),o=e.inherit(e.QSM,{r:0}),l={cN:"constant",b:"\\\\[abfnrtv]\\|\\\\x[0-9a-fA-F]*\\\\\\|%[-+# *.0-9]*[dioxXucsfeEgGp]",r:0};o.c.push(l);var s={cN:"built_in",v:[{b:"<=>"},{b:"<=",r:0},{b:"=>",r:0},{b:"/\\\\"},{b:"\\\\/"}]},c={cN:"built_in",v:[{b:":-\\|-->"},{b:"=",r:0}]};return{aliases:["m","moo"],k:i,c:[s,c,t,_,n,e.NM,a,o,{b:/:-/}]}});hljs.registerLanguage("fix",function(u){return{c:[{b:/[^\u2401\u0001]+/,e:/[\u2401\u0001]/,eE:!0,rB:!0,rE:!1,c:[{b:/([^\u2401\u0001=]+)/,e:/=([^\u2401\u0001=]+)/,rE:!0,rB:!1,cN:"attribute"},{b:/=/,e:/([\u2401\u0001])/,eE:!0,eB:!0,cN:"string"}]}],cI:!0}});hljs.registerLanguage("clojure",function(e){var t={built_in:"def cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={cN:"collection",b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"attribute",b:"[:]"+n},f={cN:"list",b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"keyword",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={cN:"variable",v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=e.C("^(__END__|__DATA__)","\\n$",{r:5}),o=[e.BE,r,n],a=[n,e.HCM,i,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:o,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,i,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];return r.c=a,s.c=a,{aliases:["pl"],k:t,c:a}});hljs.registerLanguage("twig",function(e){var t={cN:"params",b:"\\(",e:"\\)"},a="attribute block constant cycle date dump include max min parent random range source template_from_string",r={cN:"function",bK:a,r:0,c:[t]},c={cN:"filter",b:/\|[A-Za-z_]+:?/,k:"abs batch capitalize convert_encoding date date_modify default escape first format join json_encode keys last length lower merge nl2br number_format raw replace reverse round slice sort split striptags title trim upper url_encode",c:[r]},n="autoescape block do embed extends filter flush for if import include macro sandbox set spaceless use verbatim";return n=n+" "+n.split(" ").map(function(e){return"end"+e}).join(" "),{aliases:["craftcms"],cI:!0,sL:"xml",subLanguageMode:"continuous",c:[e.C(/\{#/,/#}/),{cN:"template_tag",b:/\{%/,e:/%}/,k:n,c:[c,r]},{cN:"variable",b:/\{\{/,e:/}}/,c:[c,r]}]}});hljs.registerLanguage("livecodeserver",function(e){var r={cN:"variable",b:"\\b[gtps][A-Z]+[A-Za-z0-9_\\-]*\\b|\\$_[A-Z]+",r:0},t=[e.CBCM,e.HCM,e.C("--","$"),e.C("[^:]//","$")],a=e.inherit(e.TM,{v:[{b:"\\b_*rig[A-Z]+[A-Za-z0-9_\\-]*"},{b:"\\b_[a-z0-9\\-]+"}]}),o=e.inherit(e.TM,{b:"\\b([A-Za-z0-9_\\-]+)\\b"});return{cI:!1,k:{keyword:"$_COOKIE $_FILES $_GET $_GET_BINARY $_GET_RAW $_POST $_POST_BINARY $_POST_RAW $_SESSION $_SERVER codepoint codepoints segment segments codeunit codeunits sentence sentences trueWord trueWords paragraph after byte bytes english the until http forever descending using line real8 with seventh for stdout finally element word words fourth before black ninth sixth characters chars stderr uInt1 uInt1s uInt2 uInt2s stdin string lines relative rel any fifth items from middle mid at else of catch then third it file milliseconds seconds second secs sec int1 int1s int4 int4s internet int2 int2s normal text item last long detailed effective uInt4 uInt4s repeat end repeat URL in try into switch to words https token binfile each tenth as ticks tick system real4 by dateItems without char character ascending eighth whole dateTime numeric short first ftp integer abbreviated abbr abbrev private case while if",constant:"SIX TEN FORMFEED NINE ZERO NONE SPACE FOUR FALSE COLON CRLF PI COMMA ENDOFFILE EOF EIGHT FIVE QUOTE EMPTY ONE TRUE RETURN CR LINEFEED RIGHT BACKSLASH NULL SEVEN TAB THREE TWO six ten formfeed nine zero none space four false colon crlf pi comma endoffile eof eight five quote empty one true return cr linefeed right backslash null seven tab three two RIVERSION RISTATE FILE_READ_MODE FILE_WRITE_MODE FILE_WRITE_MODE DIR_WRITE_MODE FILE_READ_UMASK FILE_WRITE_UMASK DIR_READ_UMASK DIR_WRITE_UMASK",operator:"div mod wrap and or bitAnd bitNot bitOr bitXor among not in a an within contains ends with begins the keys of keys",built_in:"put abs acos aliasReference annuity arrayDecode arrayEncode asin atan atan2 average avg avgDev base64Decode base64Encode baseConvert binaryDecode binaryEncode byteOffset byteToNum cachedURL cachedURLs charToNum cipherNames codepointOffset codepointProperty codepointToNum codeunitOffset commandNames compound compress constantNames cos date dateFormat decompress directories diskSpace DNSServers exp exp1 exp2 exp10 extents files flushEvents folders format functionNames geometricMean global globals hasMemory harmonicMean hostAddress hostAddressToName hostName hostNameToAddress isNumber ISOToMac itemOffset keys len length libURLErrorData libUrlFormData libURLftpCommand libURLLastHTTPHeaders libURLLastRHHeaders libUrlMultipartFormAddPart libUrlMultipartFormData libURLVersion lineOffset ln ln1 localNames log log2 log10 longFilePath lower macToISO matchChunk matchText matrixMultiply max md5Digest median merge millisec millisecs millisecond milliseconds min monthNames nativeCharToNum normalizeText num number numToByte numToChar numToCodepoint numToNativeChar offset open openfiles openProcesses openProcessIDs openSockets paragraphOffset paramCount param params peerAddress pendingMessages platform popStdDev populationStandardDeviation populationVariance popVariance processID random randomBytes replaceText result revCreateXMLTree revCreateXMLTreeFromFile revCurrentRecord revCurrentRecordIsFirst revCurrentRecordIsLast revDatabaseColumnCount revDatabaseColumnIsNull revDatabaseColumnLengths revDatabaseColumnNames revDatabaseColumnNamed revDatabaseColumnNumbered revDatabaseColumnTypes revDatabaseConnectResult revDatabaseCursors revDatabaseID revDatabaseTableNames revDatabaseType revDataFromQuery revdb_closeCursor revdb_columnbynumber revdb_columncount revdb_columnisnull revdb_columnlengths revdb_columnnames revdb_columntypes revdb_commit revdb_connect revdb_connections revdb_connectionerr revdb_currentrecord revdb_cursorconnection revdb_cursorerr revdb_cursors revdb_dbtype revdb_disconnect revdb_execute revdb_iseof revdb_isbof revdb_movefirst revdb_movelast revdb_movenext revdb_moveprev revdb_query revdb_querylist revdb_recordcount revdb_rollback revdb_tablenames revGetDatabaseDriverPath revNumberOfRecords revOpenDatabase revOpenDatabases revQueryDatabase revQueryDatabaseBlob revQueryResult revQueryIsAtStart revQueryIsAtEnd revUnixFromMacPath revXMLAttribute revXMLAttributes revXMLAttributeValues revXMLChildContents revXMLChildNames revXMLCreateTreeFromFileWithNamespaces revXMLCreateTreeWithNamespaces revXMLDataFromXPathQuery revXMLEvaluateXPath revXMLFirstChild revXMLMatchingNode revXMLNextSibling revXMLNodeContents revXMLNumberOfChildren revXMLParent revXMLPreviousSibling revXMLRootNode revXMLRPC_CreateRequest revXMLRPC_Documents revXMLRPC_Error revXMLRPC_GetHost revXMLRPC_GetMethod revXMLRPC_GetParam revXMLText revXMLRPC_Execute revXMLRPC_GetParamCount revXMLRPC_GetParamNode revXMLRPC_GetParamType revXMLRPC_GetPath revXMLRPC_GetPort revXMLRPC_GetProtocol revXMLRPC_GetRequest revXMLRPC_GetResponse revXMLRPC_GetSocket revXMLTree revXMLTrees revXMLValidateDTD revZipDescribeItem revZipEnumerateItems revZipOpenArchives round sampVariance sec secs seconds sentenceOffset sha1Digest shell shortFilePath sin specialFolderPath sqrt standardDeviation statRound stdDev sum sysError systemVersion tan tempName textDecode textEncode tick ticks time to tokenOffset toLower toUpper transpose truewordOffset trunc uniDecode uniEncode upper URLDecode URLEncode URLStatus uuid value variableNames variance version waitDepth weekdayNames wordOffset xsltApplyStylesheet xsltApplyStylesheetFromFile xsltLoadStylesheet xsltLoadStylesheetFromFile add breakpoint cancel clear local variable file word line folder directory URL close socket process combine constant convert create new alias folder directory decrypt delete variable word line folder directory URL dispatch divide do encrypt filter get include intersect kill libURLDownloadToFile libURLFollowHttpRedirects libURLftpUpload libURLftpUploadFile libURLresetAll libUrlSetAuthCallback libURLSetCustomHTTPHeaders libUrlSetExpect100 libURLSetFTPListCommand libURLSetFTPMode libURLSetFTPStopTime libURLSetStatusCallback load multiply socket prepare process post seek rel relative read from process rename replace require resetAll resolve revAddXMLNode revAppendXML revCloseCursor revCloseDatabase revCommitDatabase revCopyFile revCopyFolder revCopyXMLNode revDeleteFolder revDeleteXMLNode revDeleteAllXMLTrees revDeleteXMLTree revExecuteSQL revGoURL revInsertXMLNode revMoveFolder revMoveToFirstRecord revMoveToLastRecord revMoveToNextRecord revMoveToPreviousRecord revMoveToRecord revMoveXMLNode revPutIntoXMLNode revRollBackDatabase revSetDatabaseDriverPath revSetXMLAttribute revXMLRPC_AddParam revXMLRPC_DeleteAllDocuments revXMLAddDTD revXMLRPC_Free revXMLRPC_FreeAll revXMLRPC_DeleteDocument revXMLRPC_DeleteParam revXMLRPC_SetHost revXMLRPC_SetMethod revXMLRPC_SetPort revXMLRPC_SetProtocol revXMLRPC_SetSocket revZipAddItemWithData revZipAddItemWithFile revZipAddUncompressedItemWithData revZipAddUncompressedItemWithFile revZipCancel revZipCloseArchive revZipDeleteItem revZipExtractItemToFile revZipExtractItemToVariable revZipSetProgressCallback revZipRenameItem revZipReplaceItemWithData revZipReplaceItemWithFile revZipOpenArchive send set sort split start stop subtract union unload wait write"},c:[r,{cN:"keyword",b:"\\bend\\sif\\b"},{cN:"function",bK:"function",e:"$",c:[r,o,e.ASM,e.QSM,e.BNM,e.CNM,a]},{cN:"function",bK:"end",e:"$",c:[o,a]},{cN:"command",bK:"command on",e:"$",c:[r,o,e.ASM,e.QSM,e.BNM,e.CNM,a]},{cN:"command",bK:"end",e:"$",c:[o,a]},{cN:"preprocessor",b:"<\\?rev|<\\?lc|<\\?livecode",r:10},{cN:"preprocessor",b:"<\\?"},{cN:"preprocessor",b:"\\?>"},e.ASM,e.QSM,e.BNM,e.CNM,a].concat(t),i:";$|^\\[|^="}});hljs.registerLanguage("step21",function(e){var r="[A-Z_][A-Z0-9_.]*",i="END-ISO-10303-21;",l={literal:"",built_in:"",keyword:"HEADER ENDSEC DATA"},s={cN:"preprocessor",b:"ISO-10303-21;",r:10},t=[e.CLCM,e.CBCM,e.C("/\\*\\*!","\\*/"),e.CNM,e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null}),{cN:"string",b:"'",e:"'"},{cN:"label",v:[{b:"#",e:"\\d+",i:"\\W"}]}];return{aliases:["p21","step","stp"],cI:!0,l:r,k:l,c:[{cN:"preprocessor",b:i,r:10},s].concat(t)}});hljs.registerLanguage("cpp",function(t){var i={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary intmax_t uintmax_t int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t int_least8_t uint_least8_t int_least16_t uint_least16_t int_least32_t uint_least32_t int_least64_t uint_least64_t int_fast8_t uint_fast8_t int_fast16_t uint_fast16_t int_fast32_t uint_fast32_t int_fast64_t uint_fast64_t intptr_t uintptr_t atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong atomic_wchar_t atomic_char16_t atomic_char32_t atomic_intmax_t atomic_uintmax_t atomic_intptr_t atomic_uintptr_t atomic_size_t atomic_ptrdiff_t atomic_int_least8_t atomic_int_least16_t atomic_int_least32_t atomic_int_least64_t atomic_uint_least8_t atomic_uint_least16_t atomic_uint_least32_t atomic_uint_least64_t atomic_int_fast8_t atomic_int_fast16_t atomic_int_fast32_t atomic_int_fast64_t atomic_uint_fast8_t atomic_uint_fast16_t atomic_uint_fast32_t atomic_uint_fast64_t",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","cc","h","c++","h++","hpp"],k:i,i:""]',k:"include",i:"\\n"},t.CLCM]},{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:i,c:["self"]},{b:t.IR+"::",k:i},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"\\s+)+"+t.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:t.IR+"\\s*\\(",rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:i,r:0,c:[t.CBCM]},t.CLCM,t.CBCM]}]}});hljs.registerLanguage("vala",function(e){return{k:{keyword:"char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double bool struct enum string void weak unowned owned async signal static abstract interface override while do for foreach else switch case break default return try catch public private protected internal using new this get set const stdout stdin stderr var",built_in:"DBus GLib CCode Gee Object",literal:"false true null"},c:[{cN:"class",bK:"class interface delegate namespace",e:"{",eE:!0,i:"[^,:\\n\\s\\.]",c:[e.UTM]},e.CLCM,e.CBCM,{cN:"string",b:'"""',e:'"""',r:5},e.ASM,e.QSM,e.CNM,{cN:"preprocessor",b:"^#",e:"$",r:2},{cN:"constant",b:" [A-Z_]+ ",r:0}]}});hljs.registerLanguage("http",function(t){return{aliases:["https"],i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:!0}}]}});hljs.registerLanguage("avrasm",function(r){return{cI:!0,l:"\\.?"+r.IR,k:{keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",preprocessor:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"},c:[r.CBCM,r.C(";","$",{r:0}),r.CNM,r.BNM,{cN:"number",b:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},r.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"label",b:"^[A-Za-z0-9_.$]+:"},{cN:"preprocessor",b:"#",e:"$"},{cN:"localvars",b:"@[0-9]+"}]}});hljs.registerLanguage("aspectj",function(e){var t="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else extends implements break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws privileged aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization staticinitialization withincode target within execution getWithinTypeName handler thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents warning error soft precedence thisAspectInstance",i="get set args call";return{k:t,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",r:0,c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}]},e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"aspect",bK:"aspect",e:/[{;=]/,eE:!0,i:/[:;"\[\]]/,c:[{bK:"extends implements pertypewithin perthis pertarget percflowbelow percflow issingleton"},e.UTM,{b:/\([^\)]*/,e:/[)]+/,k:t+" "+i,eE:!1}]},{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,r:0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"pointcut after before around throwing returning",e:/[)]/,eE:!1,i:/["\[\]]/,c:[{b:e.UIR+"\\s*\\(",rB:!0,c:[e.UTM]}]},{b:/[:]/,rB:!0,e:/[{;]/,r:0,eE:!1,k:t,i:/["\[\]]/,c:[{b:e.UIR+"\\s*\\(",k:t+" "+i},e.QSM]},{bK:"new throw",r:0},{cN:"function",b:/\w+ +\w+(\.)?\w+\s*\([^\)]*\)\s*((throws)[\w\s,]+)?[\{;]/,rB:!0,e:/[{;=]/,k:t,eE:!0,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,r:0,k:t,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},e.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("rib",function(e){return{k:"ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry Hider Hyperboloid Identity Illuminate Imager Interior LightSource MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd TransformPoints Translate TrimCurve WorldBegin WorldEnd",i:">>|\.\.\.) /},b={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},l={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},c={cN:"params",b:/\(/,e:/\)/,c:["self",r,l,b]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[r,l,b,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,c]},{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("axapta",function(e){return{k:"false int abstract private char boolean static null if for true while long throw finally protected final return void enum else break new catch byte super case short default double public try this switch continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count order group by asc desc index hint like dispaly edit client server ttsbegin ttscommit str real date container anytype common div mod",c:[e.CLCM,e.CBCM,e.ASM,e.QSM,e.CNM,{cN:"preprocessor",b:"#",e:"$"},{cN:"class",bK:"class interface",e:"{",eE:!0,i:":",c:[{bK:"extends implements"},e.UTM]}]}});hljs.registerLanguage("nix",function(e){var t={keyword:"rec with let in inherit assert if else then",constant:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},i={cN:"subst",b:/\$\{/,e:/}/,k:t},r={cN:"variable",b:/[a-zA-Z0-9-_]+(\s*=)/},n={cN:"string",b:"''",e:"''",c:[i]},s={cN:"string",b:'"',e:'"',c:[i]},a=[e.NM,e.HCM,e.CBCM,n,s,r];return i.c=a,{aliases:["nixos"],k:t,c:a}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("parser3",function(r){var e=r.C("{","}",{c:["self"]});return{sL:"xml",r:0,c:[r.C("^#","$"),r.C("\\^rem{","}",{r:10,c:[e]}),{cN:"preprocessor",b:"^@(?:BASE|USE|CLASS|OPTIONS)$",r:10},{cN:"title",b:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{cN:"variable",b:"\\$\\{?[\\w\\-\\.\\:]+\\}?"},{cN:"keyword",b:"\\^[\\w\\-\\.\\:]+"},{cN:"number",b:"\\^#[0-9a-fA-F]+"},r.CNM]}});hljs.registerLanguage("django",function(e){var t={cN:"filter",b:/\|[A-Za-z]+:?/,k:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone",c:[{cN:"argument",b:/"/,e:/"/},{cN:"argument",b:/'/,e:/'/}]};return{aliases:["jinja"],cI:!0,sL:"xml",subLanguageMode:"continuous",c:[e.C(/\{%\s*comment\s*%}/,/\{%\s*endcomment\s*%}/),e.C(/\{#/,/#}/),{cN:"template_tag",b:/\{%/,e:/%}/,k:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor in ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup by as ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim",c:[t]},{cN:"variable",b:/\{\{/,e:/}}/,c:[t]}]}});hljs.registerLanguage("rust",function(e){var t=e.inherit(e.CBCM);return t.c.push("self"),{aliases:["rs"],k:{keyword:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self sizeof static struct super trait true type typeof unsafe unsized use virtual while yield int i8 i16 i32 i64 uint u8 u32 u64 float f32 f64 str char bool",built_in:"assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln!"},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("vhdl",function(e){var t="\\d(_|\\d)*",r="[eE][-+]?"+t,n=t+"(\\."+t+")?("+r+")?",o="\\w+",i=t+"#"+o+"(\\."+o+")?#("+r+")?",a="\\b("+i+"|"+n+")";return{cI:!0,k:{keyword:"abs access after alias all and architecture array assert attribute begin block body buffer bus case component configuration constant context cover disconnect downto default else elsif end entity exit fairness file for force function generate generic group guarded if impure in inertial inout is label library linkage literal loop map mod nand new next nor not null of on open or others out package port postponed procedure process property protected pure range record register reject release rem report restrict restrict_guarantee return rol ror select sequence severity shared signal sla sll sra srl strong subtype then to transport type unaffected units until use variable vmode vprop vunit wait when while with xnor xor",typename:"boolean bit character severity_level integer time delay_length natural positive string bit_vector file_open_kind file_open_status std_ulogic std_ulogic_vector std_logic std_logic_vector unsigned signed boolean_vector integer_vector real_vector time_vector"},i:"{",c:[e.CBCM,e.C("--","$"),e.QSM,{cN:"number",b:a,r:0},{cN:"literal",b:"'(U|X|0|1|Z|W|L|H|-)'",c:[e.BE]},{cN:"attribute",b:"'[A-Za-z](_?[A-Za-z0-9])*",c:[e.BE]}]}});hljs.registerLanguage("ocaml",function(e){return{aliases:["ml"],k:{keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",literal:"true false"},i:/\/\/|>>/,l:"[a-z_]\\w*!?",c:[{cN:"literal",b:"\\[(\\|\\|)?\\]|\\(\\)"},e.C("\\(\\*","\\*\\)",{c:["self"]}),{cN:"symbol",b:"'[A-Za-z_](?!')[\\w']*"},{cN:"tag",b:"`[A-Z][\\w']*"},{cN:"type",b:"\\b[A-Z][\\w']*",r:0},{b:"[a-z_]\\w*'[\\w']*"},e.inherit(e.ASM,{cN:"char",r:0}),e.inherit(e.QSM,{i:null}),{cN:"number",b:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",r:0},{b:/[-=]>/}]}});hljs.registerLanguage("cmake",function(e){return{aliases:["cmake.in"],cI:!0,k:{keyword:"add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_minimum_required cmake_policy configure_file create_test_sourcelist define_property else elseif enable_language enable_testing endforeach endfunction endif endmacro endwhile execute_process export find_file find_library find_package find_path find_program fltk_wrap_ui foreach function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property if include include_directories include_external_msproject include_regular_expression install link_directories load_cache load_command macro mark_as_advanced message option output_required_files project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_link_libraries try_compile try_run unset variable_watch while build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or",operator:"equal less greater strless strgreater strequal matches"},c:[{cN:"envvar",b:"\\${",e:"}"},e.HCM,e.QSM,e.NM]}});hljs.registerLanguage("1c",function(c){var e="[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*",r="возврат дата для если и или иначе иначеесли исключение конецесли конецпопытки конецпроцедуры конецфункции конеццикла константа не перейти перем перечисление по пока попытка прервать продолжить процедура строка тогда фс функция цикл число экспорт",t="ansitooem oemtoansi ввестивидсубконто ввестидату ввестизначение ввестиперечисление ввестипериод ввестиплансчетов ввестистроку ввестичисло вопрос восстановитьзначение врег выбранныйплансчетов вызватьисключение датагод датамесяц датачисло добавитьмесяц завершитьработусистемы заголовоксистемы записьжурналарегистрации запуститьприложение зафиксироватьтранзакцию значениевстроку значениевстрокувнутр значениевфайл значениеизстроки значениеизстрокивнутр значениеизфайла имякомпьютера имяпользователя каталогвременныхфайлов каталогиб каталогпользователя каталогпрограммы кодсимв командасистемы конгода конецпериодаби конецрассчитанногопериодаби конецстандартногоинтервала конквартала конмесяца коннедели лев лог лог10 макс максимальноеколичествосубконто мин монопольныйрежим названиеинтерфейса названиенабораправ назначитьвид назначитьсчет найти найтипомеченныенаудаление найтиссылки началопериодаби началостандартногоинтервала начатьтранзакцию начгода начквартала начмесяца начнедели номерднягода номерднянедели номернеделигода нрег обработкаожидания окр описаниеошибки основнойжурналрасчетов основнойплансчетов основнойязык открытьформу открытьформумодально отменитьтранзакцию очиститьокносообщений периодстр полноеимяпользователя получитьвремята получитьдатута получитьдокументта получитьзначенияотбора получитьпозициюта получитьпустоезначение получитьта прав праводоступа предупреждение префиксавтонумерации пустаястрока пустоезначение рабочаядаттьпустоезначение рабочаядата разделительстраниц разделительстрок разм разобратьпозициюдокумента рассчитатьрегистрына рассчитатьрегистрыпо сигнал симв символтабуляции создатьобъект сокрл сокрлп сокрп сообщить состояние сохранитьзначение сред статусвозврата стрдлина стрзаменить стрколичествострок стрполучитьстроку стрчисловхождений сформироватьпозициюдокумента счетпокоду текущаядата текущеевремя типзначения типзначениястр удалитьобъекты установитьтана установитьтапо фиксшаблон формат цел шаблон",i={cN:"dquote",b:'""'},n={cN:"string",b:'"',e:'"|$',c:[i]},a={cN:"string",b:"\\|",e:'"|$',c:[i]};return{cI:!0,l:e,k:{keyword:r,built_in:t},c:[c.CLCM,c.NM,n,a,{cN:"function",b:"(процедура|функция)",e:"$",l:e,k:"процедура функция",c:[c.inherit(c.TM,{b:e}),{cN:"tail",eW:!0,c:[{cN:"params",b:"\\(",e:"\\)",l:e,k:"знач",c:[n,a]},{cN:"export",b:"экспорт",eW:!0,l:e,k:"экспорт",c:[c.CLCM]}]},c.CLCM]},{cN:"preprocessor",b:"#",e:"$"},{cN:"date",b:"'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})'"}]}});hljs.registerLanguage("tcl",function(e){return{aliases:["tk"],k:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",c:[e.C(";[ \\t]*#","$"),e.C("^[ \\t]*#","$"),{bK:"proc",e:"[\\{]",eE:!0,c:[{cN:"symbol",b:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"[ \\t\\n\\r]",eW:!0,eE:!0}]},{cN:"variable",eE:!0,v:[{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)",e:"[^a-zA-Z0-9_\\}\\$]"},{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"(\\))?[^a-zA-Z0-9_\\}\\$]"}]},{cN:"string",c:[e.BE],v:[e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},{cN:"number",v:[e.BNM,e.CNM]}]}});hljs.registerLanguage("groovy",function(e){return{k:{typename:"byte short char int long boolean float double void",literal:"true false null",keyword:"def as in assert trait super this abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"},c:[e.CLCM,{cN:"javadoc",b:"/\\*\\*",e:"\\*//*",r:0,c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}]},e.CBCM,{cN:"string",b:'"""',e:'"""'},{cN:"string",b:"'''",e:"'''"},{cN:"string",b:"\\$/",e:"/\\$",r:10},e.ASM,{cN:"regexp",b:/~?\/[^\/\n]+\//,c:[e.BE]},e.QSM,{cN:"shebang",b:"^#!/usr/bin/env",e:"$",i:"\n"},e.BNM,{cN:"class",bK:"class interface trait enum",e:"{",i:":",c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"annotation",b:"@[A-Za-z]+"},{cN:"string",b:/[^\?]{0}[A-Za-z0-9_$]+ *:/},{b:/\?/,e:/\:/},{cN:"label",b:"^\\s*[A-Za-z0-9_$]+:",r:0}]}});hljs.registerLanguage("erlang-repl",function(r){return{k:{special_functions:"spawn spawn_link self",reserved:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"},c:[{cN:"prompt",b:"^[0-9]+> ",r:10},r.C("%","$"),{cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},r.ASM,r.QSM,{cN:"constant",b:"\\?(::)?([A-Z]\\w*(::)?)+"},{cN:"arrow",b:"->"},{cN:"ok",b:"ok"},{cN:"exclamation_mark",b:"!"},{cN:"function_or_atom",b:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",r:0},{cN:"variable",b:"[A-Z][a-zA-Z0-9_']*",r:0}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"title",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("mathematica",function(e){return{aliases:["mma"],l:"(\\$|\\b)"+e.IR+"\\b",k:"AbelianGroup Abort AbortKernels AbortProtect Above Abs Absolute AbsoluteCorrelation AbsoluteCorrelationFunction AbsoluteCurrentValue AbsoluteDashing AbsoluteFileName AbsoluteOptions AbsolutePointSize AbsoluteThickness AbsoluteTime AbsoluteTiming AccountingForm Accumulate Accuracy AccuracyGoal ActionDelay ActionMenu ActionMenuBox ActionMenuBoxOptions Active ActiveItem ActiveStyle AcyclicGraphQ AddOnHelpPath AddTo AdjacencyGraph AdjacencyList AdjacencyMatrix AdjustmentBox AdjustmentBoxOptions AdjustTimeSeriesForecast AffineTransform After AiryAi AiryAiPrime AiryAiZero AiryBi AiryBiPrime AiryBiZero AlgebraicIntegerQ AlgebraicNumber AlgebraicNumberDenominator AlgebraicNumberNorm AlgebraicNumberPolynomial AlgebraicNumberTrace AlgebraicRules AlgebraicRulesData Algebraics AlgebraicUnitQ Alignment AlignmentMarker AlignmentPoint All AllowedDimensions AllowGroupClose AllowInlineCells AllowKernelInitialization AllowReverseGroupClose AllowScriptLevelChange AlphaChannel AlternatingGroup AlternativeHypothesis Alternatives AmbientLight Analytic AnchoredSearch And AndersonDarlingTest AngerJ AngleBracket AngularGauge Animate AnimationCycleOffset AnimationCycleRepetitions AnimationDirection AnimationDisplayTime AnimationRate AnimationRepetitions AnimationRunning Animator AnimatorBox AnimatorBoxOptions AnimatorElements Annotation Annuity AnnuityDue Antialiasing Antisymmetric Apart ApartSquareFree Appearance AppearanceElements AppellF1 Append AppendTo Apply ArcCos ArcCosh ArcCot ArcCoth ArcCsc ArcCsch ArcSec ArcSech ArcSin ArcSinDistribution ArcSinh ArcTan ArcTanh Arg ArgMax ArgMin ArgumentCountQ ARIMAProcess ArithmeticGeometricMean ARMAProcess ARProcess Array ArrayComponents ArrayDepth ArrayFlatten ArrayPad ArrayPlot ArrayQ ArrayReshape ArrayRules Arrays Arrow Arrow3DBox ArrowBox Arrowheads AspectRatio AspectRatioFixed Assert Assuming Assumptions AstronomicalData Asynchronous AsynchronousTaskObject AsynchronousTasks AtomQ Attributes AugmentedSymmetricPolynomial AutoAction AutoDelete AutoEvaluateEvents AutoGeneratedPackage AutoIndent AutoIndentSpacings AutoItalicWords AutoloadPath AutoMatch Automatic AutomaticImageSize AutoMultiplicationSymbol AutoNumberFormatting AutoOpenNotebooks AutoOpenPalettes AutorunSequencing AutoScaling AutoScroll AutoSpacing AutoStyleOptions AutoStyleWords Axes AxesEdge AxesLabel AxesOrigin AxesStyle Axis BabyMonsterGroupB Back Background BackgroundTasksSettings Backslash Backsubstitution Backward Band BandpassFilter BandstopFilter BarabasiAlbertGraphDistribution BarChart BarChart3D BarLegend BarlowProschanImportance BarnesG BarOrigin BarSpacing BartlettHannWindow BartlettWindow BaseForm Baseline BaselinePosition BaseStyle BatesDistribution BattleLemarieWavelet Because BeckmannDistribution Beep Before Begin BeginDialogPacket BeginFrontEndInteractionPacket BeginPackage BellB BellY Below BenfordDistribution BeniniDistribution BenktanderGibratDistribution BenktanderWeibullDistribution BernoulliB BernoulliDistribution BernoulliGraphDistribution BernoulliProcess BernsteinBasis BesselFilterModel BesselI BesselJ BesselJZero BesselK BesselY BesselYZero Beta BetaBinomialDistribution BetaDistribution BetaNegativeBinomialDistribution BetaPrimeDistribution BetaRegularized BetweennessCentrality BezierCurve BezierCurve3DBox BezierCurve3DBoxOptions BezierCurveBox BezierCurveBoxOptions BezierFunction BilateralFilter Binarize BinaryFormat BinaryImageQ BinaryRead BinaryReadList BinaryWrite BinCounts BinLists Binomial BinomialDistribution BinomialProcess BinormalDistribution BiorthogonalSplineWavelet BipartiteGraphQ BirnbaumImportance BirnbaumSaundersDistribution BitAnd BitClear BitGet BitLength BitNot BitOr BitSet BitShiftLeft BitShiftRight BitXor Black BlackmanHarrisWindow BlackmanNuttallWindow BlackmanWindow Blank BlankForm BlankNullSequence BlankSequence Blend Block BlockRandom BlomqvistBeta BlomqvistBetaTest Blue Blur BodePlot BohmanWindow Bold Bookmarks Boole BooleanConsecutiveFunction BooleanConvert BooleanCountingFunction BooleanFunction BooleanGraph BooleanMaxterms BooleanMinimize BooleanMinterms Booleans BooleanTable BooleanVariables BorderDimensions BorelTannerDistribution Bottom BottomHatTransform BoundaryStyle Bounds Box BoxBaselineShift BoxData BoxDimensions Boxed Boxes BoxForm BoxFormFormatTypes BoxFrame BoxID BoxMargins BoxMatrix BoxRatios BoxRotation BoxRotationPoint BoxStyle BoxWhiskerChart Bra BracketingBar BraKet BrayCurtisDistance BreadthFirstScan Break Brown BrownForsytheTest BrownianBridgeProcess BrowserCategory BSplineBasis BSplineCurve BSplineCurve3DBox BSplineCurveBox BSplineCurveBoxOptions BSplineFunction BSplineSurface BSplineSurface3DBox BubbleChart BubbleChart3D BubbleScale BubbleSizes BulletGauge BusinessDayQ ButterflyGraph ButterworthFilterModel Button ButtonBar ButtonBox ButtonBoxOptions ButtonCell ButtonContents ButtonData ButtonEvaluator ButtonExpandable ButtonFrame ButtonFunction ButtonMargins ButtonMinHeight ButtonNote ButtonNotebook ButtonSource ButtonStyle ButtonStyleMenuListing Byte ByteCount ByteOrdering C CachedValue CacheGraphics CalendarData CalendarType CallPacket CanberraDistance Cancel CancelButton CandlestickChart Cap CapForm CapitalDifferentialD CardinalBSplineBasis CarmichaelLambda Cases Cashflow Casoratian Catalan CatalanNumber Catch CauchyDistribution CauchyWindow CayleyGraph CDF CDFDeploy CDFInformation CDFWavelet Ceiling Cell CellAutoOverwrite CellBaseline CellBoundingBox CellBracketOptions CellChangeTimes CellContents CellContext CellDingbat CellDynamicExpression CellEditDuplicate CellElementsBoundingBox CellElementSpacings CellEpilog CellEvaluationDuplicate CellEvaluationFunction CellEventActions CellFrame CellFrameColor CellFrameLabelMargins CellFrameLabels CellFrameMargins CellGroup CellGroupData CellGrouping CellGroupingRules CellHorizontalScrolling CellID CellLabel CellLabelAutoDelete CellLabelMargins CellLabelPositioning CellMargins CellObject CellOpen CellPrint CellProlog Cells CellSize CellStyle CellTags CellularAutomaton CensoredDistribution Censoring Center CenterDot CentralMoment CentralMomentGeneratingFunction CForm ChampernowneNumber ChanVeseBinarize Character CharacterEncoding CharacterEncodingsPath CharacteristicFunction CharacteristicPolynomial CharacterRange Characters ChartBaseStyle ChartElementData ChartElementDataFunction ChartElementFunction ChartElements ChartLabels ChartLayout ChartLegends ChartStyle Chebyshev1FilterModel Chebyshev2FilterModel ChebyshevDistance ChebyshevT ChebyshevU Check CheckAbort CheckAll Checkbox CheckboxBar CheckboxBox CheckboxBoxOptions ChemicalData ChessboardDistance ChiDistribution ChineseRemainder ChiSquareDistribution ChoiceButtons ChoiceDialog CholeskyDecomposition Chop Circle CircleBox CircleDot CircleMinus CirclePlus CircleTimes CirculantGraph CityData Clear ClearAll ClearAttributes ClearSystemCache ClebschGordan ClickPane Clip ClipboardNotebook ClipFill ClippingStyle ClipPlanes ClipRange Clock ClockGauge ClockwiseContourIntegral Close Closed CloseKernels ClosenessCentrality Closing ClosingAutoSave ClosingEvent ClusteringComponents CMYKColor Coarse Coefficient CoefficientArrays CoefficientDomain CoefficientList CoefficientRules CoifletWavelet Collect Colon ColonForm ColorCombine ColorConvert ColorData ColorDataFunction ColorFunction ColorFunctionScaling Colorize ColorNegate ColorOutput ColorProfileData ColorQuantize ColorReplace ColorRules ColorSelectorSettings ColorSeparate ColorSetter ColorSetterBox ColorSetterBoxOptions ColorSlider ColorSpace Column ColumnAlignments ColumnBackgrounds ColumnForm ColumnLines ColumnsEqual ColumnSpacings ColumnWidths CommonDefaultFormatTypes Commonest CommonestFilter CommonUnits CommunityBoundaryStyle CommunityGraphPlot CommunityLabels CommunityRegionStyle CompatibleUnitQ CompilationOptions CompilationTarget Compile Compiled CompiledFunction Complement CompleteGraph CompleteGraphQ CompleteKaryTree CompletionsListPacket Complex Complexes ComplexExpand ComplexInfinity ComplexityFunction ComponentMeasurements ComponentwiseContextMenu Compose ComposeList ComposeSeries Composition CompoundExpression CompoundPoissonDistribution CompoundPoissonProcess CompoundRenewalProcess Compress CompressedData Condition ConditionalExpression Conditioned Cone ConeBox ConfidenceLevel ConfidenceRange ConfidenceTransform ConfigurationPath Congruent Conjugate ConjugateTranspose Conjunction Connect ConnectedComponents ConnectedGraphQ ConnesWindow ConoverTest ConsoleMessage ConsoleMessagePacket ConsolePrint Constant ConstantArray Constants ConstrainedMax ConstrainedMin ContentPadding ContentsBoundingBox ContentSelectable ContentSize Context ContextMenu Contexts ContextToFilename ContextToFileName Continuation Continue ContinuedFraction ContinuedFractionK ContinuousAction ContinuousMarkovProcess ContinuousTimeModelQ ContinuousWaveletData ContinuousWaveletTransform ContourDetect ContourGraphics ContourIntegral ContourLabels ContourLines ContourPlot ContourPlot3D Contours ContourShading ContourSmoothing ContourStyle ContraharmonicMean Control ControlActive ControlAlignment ControllabilityGramian ControllabilityMatrix ControllableDecomposition ControllableModelQ ControllerDuration ControllerInformation ControllerInformationData ControllerLinking ControllerManipulate ControllerMethod ControllerPath ControllerState ControlPlacement ControlsRendering ControlType Convergents ConversionOptions ConversionRules ConvertToBitmapPacket ConvertToPostScript ConvertToPostScriptPacket Convolve ConwayGroupCo1 ConwayGroupCo2 ConwayGroupCo3 CoordinateChartData CoordinatesToolOptions CoordinateTransform CoordinateTransformData CoprimeQ Coproduct CopulaDistribution Copyable CopyDirectory CopyFile CopyTag CopyToClipboard CornerFilter CornerNeighbors Correlation CorrelationDistance CorrelationFunction CorrelationTest Cos Cosh CoshIntegral CosineDistance CosineWindow CosIntegral Cot Coth Count CounterAssignments CounterBox CounterBoxOptions CounterClockwiseContourIntegral CounterEvaluator CounterFunction CounterIncrements CounterStyle CounterStyleMenuListing CountRoots CountryData Covariance CovarianceEstimatorFunction CovarianceFunction CoxianDistribution CoxIngersollRossProcess CoxModel CoxModelFit CramerVonMisesTest CreateArchive CreateDialog CreateDirectory CreateDocument CreateIntermediateDirectories CreatePalette CreatePalettePacket CreateScheduledTask CreateTemporary CreateWindow CriticalityFailureImportance CriticalitySuccessImportance CriticalSection Cross CrossingDetect CrossMatrix Csc Csch CubeRoot Cubics Cuboid CuboidBox Cumulant CumulantGeneratingFunction Cup CupCap Curl CurlyDoubleQuote CurlyQuote CurrentImage CurrentlySpeakingPacket CurrentValue CurvatureFlowFilter CurveClosed Cyan CycleGraph CycleIndexPolynomial Cycles CyclicGroup Cyclotomic Cylinder CylinderBox CylindricalDecomposition D DagumDistribution DamerauLevenshteinDistance DampingFactor Darker Dashed Dashing DataCompression DataDistribution DataRange DataReversed Date DateDelimiters DateDifference DateFunction DateList DateListLogPlot DateListPlot DatePattern DatePlus DateRange DateString DateTicksFormat DaubechiesWavelet DavisDistribution DawsonF DayCount DayCountConvention DayMatchQ DayName DayPlus DayRange DayRound DeBruijnGraph Debug DebugTag Decimal DeclareKnownSymbols DeclarePackage Decompose Decrement DedekindEta Default DefaultAxesStyle DefaultBaseStyle DefaultBoxStyle DefaultButton DefaultColor DefaultControlPlacement DefaultDuplicateCellStyle DefaultDuration DefaultElement DefaultFaceGridsStyle DefaultFieldHintStyle DefaultFont DefaultFontProperties DefaultFormatType DefaultFormatTypeForStyle DefaultFrameStyle DefaultFrameTicksStyle DefaultGridLinesStyle DefaultInlineFormatType DefaultInputFormatType DefaultLabelStyle DefaultMenuStyle DefaultNaturalLanguage DefaultNewCellStyle DefaultNewInlineCellStyle DefaultNotebook DefaultOptions DefaultOutputFormatType DefaultStyle DefaultStyleDefinitions DefaultTextFormatType DefaultTextInlineFormatType DefaultTicksStyle DefaultTooltipStyle DefaultValues Defer DefineExternal DefineInputStreamMethod DefineOutputStreamMethod Definition Degree DegreeCentrality DegreeGraphDistribution DegreeLexicographic DegreeReverseLexicographic Deinitialization Del Deletable Delete DeleteBorderComponents DeleteCases DeleteContents DeleteDirectory DeleteDuplicates DeleteFile DeleteSmallComponents DeleteWithContents DeletionWarning Delimiter DelimiterFlashTime DelimiterMatching Delimiters Denominator DensityGraphics DensityHistogram DensityPlot DependentVariables Deploy Deployed Depth DepthFirstScan Derivative DerivativeFilter DescriptorStateSpace DesignMatrix Det DGaussianWavelet DiacriticalPositioning Diagonal DiagonalMatrix Dialog DialogIndent DialogInput DialogLevel DialogNotebook DialogProlog DialogReturn DialogSymbols Diamond DiamondMatrix DiceDissimilarity DictionaryLookup DifferenceDelta DifferenceOrder DifferenceRoot DifferenceRootReduce Differences DifferentialD DifferentialRoot DifferentialRootReduce DifferentiatorFilter DigitBlock DigitBlockMinimum DigitCharacter DigitCount DigitQ DihedralGroup Dilation Dimensions DiracComb DiracDelta DirectedEdge DirectedEdges DirectedGraph DirectedGraphQ DirectedInfinity Direction Directive Directory DirectoryName DirectoryQ DirectoryStack DirichletCharacter DirichletConvolve DirichletDistribution DirichletL DirichletTransform DirichletWindow DisableConsolePrintPacket DiscreteChirpZTransform DiscreteConvolve DiscreteDelta DiscreteHadamardTransform DiscreteIndicator DiscreteLQEstimatorGains DiscreteLQRegulatorGains DiscreteLyapunovSolve DiscreteMarkovProcess DiscretePlot DiscretePlot3D DiscreteRatio DiscreteRiccatiSolve DiscreteShift DiscreteTimeModelQ DiscreteUniformDistribution DiscreteVariables DiscreteWaveletData DiscreteWaveletPacketTransform DiscreteWaveletTransform Discriminant Disjunction Disk DiskBox DiskMatrix Dispatch DispersionEstimatorFunction Display DisplayAllSteps DisplayEndPacket DisplayFlushImagePacket DisplayForm DisplayFunction DisplayPacket DisplayRules DisplaySetSizePacket DisplayString DisplayTemporary DisplayWith DisplayWithRef DisplayWithVariable DistanceFunction DistanceTransform Distribute Distributed DistributedContexts DistributeDefinitions DistributionChart DistributionDomain DistributionFitTest DistributionParameterAssumptions DistributionParameterQ Dithering Div Divergence Divide DivideBy Dividers Divisible Divisors DivisorSigma DivisorSum DMSList DMSString Do DockedCells DocumentNotebook DominantColors DOSTextFormat Dot DotDashed DotEqual Dotted DoubleBracketingBar DoubleContourIntegral DoubleDownArrow DoubleLeftArrow DoubleLeftRightArrow DoubleLeftTee DoubleLongLeftArrow DoubleLongLeftRightArrow DoubleLongRightArrow DoubleRightArrow DoubleRightTee DoubleUpArrow DoubleUpDownArrow DoubleVerticalBar DoublyInfinite Down DownArrow DownArrowBar DownArrowUpArrow DownLeftRightVector DownLeftTeeVector DownLeftVector DownLeftVectorBar DownRightTeeVector DownRightVector DownRightVectorBar Downsample DownTee DownTeeArrow DownValues DragAndDrop DrawEdges DrawFrontFaces DrawHighlighted Drop DSolve Dt DualLinearProgramming DualSystemsModel DumpGet DumpSave DuplicateFreeQ Dynamic DynamicBox DynamicBoxOptions DynamicEvaluationTimeout DynamicLocation DynamicModule DynamicModuleBox DynamicModuleBoxOptions DynamicModuleParent DynamicModuleValues DynamicName DynamicNamespace DynamicReference DynamicSetting DynamicUpdating DynamicWrapper DynamicWrapperBox DynamicWrapperBoxOptions E EccentricityCentrality EdgeAdd EdgeBetweennessCentrality EdgeCapacity EdgeCapForm EdgeColor EdgeConnectivity EdgeCost EdgeCount EdgeCoverQ EdgeDashing EdgeDelete EdgeDetect EdgeForm EdgeIndex EdgeJoinForm EdgeLabeling EdgeLabels EdgeLabelStyle EdgeList EdgeOpacity EdgeQ EdgeRenderingFunction EdgeRules EdgeShapeFunction EdgeStyle EdgeThickness EdgeWeight Editable EditButtonSettings EditCellTagsSettings EditDistance EffectiveInterest Eigensystem Eigenvalues EigenvectorCentrality Eigenvectors Element ElementData Eliminate EliminationOrder EllipticE EllipticExp EllipticExpPrime EllipticF EllipticFilterModel EllipticK EllipticLog EllipticNomeQ EllipticPi EllipticReducedHalfPeriods EllipticTheta EllipticThetaPrime EmitSound EmphasizeSyntaxErrors EmpiricalDistribution Empty EmptyGraphQ EnableConsolePrintPacket Enabled Encode End EndAdd EndDialogPacket EndFrontEndInteractionPacket EndOfFile EndOfLine EndOfString EndPackage EngineeringForm Enter EnterExpressionPacket EnterTextPacket Entropy EntropyFilter Environment Epilog Equal EqualColumns EqualRows EqualTilde EquatedTo Equilibrium EquirippleFilterKernel Equivalent Erf Erfc Erfi ErlangB ErlangC ErlangDistribution Erosion ErrorBox ErrorBoxOptions ErrorNorm ErrorPacket ErrorsDialogSettings EstimatedDistribution EstimatedProcess EstimatorGains EstimatorRegulator EuclideanDistance EulerE EulerGamma EulerianGraphQ EulerPhi Evaluatable Evaluate Evaluated EvaluatePacket EvaluationCell EvaluationCompletionAction EvaluationElements EvaluationMode EvaluationMonitor EvaluationNotebook EvaluationObject EvaluationOrder Evaluator EvaluatorNames EvenQ EventData EventEvaluator EventHandler EventHandlerTag EventLabels ExactBlackmanWindow ExactNumberQ ExactRootIsolation ExampleData Except ExcludedForms ExcludePods Exclusions ExclusionsStyle Exists Exit ExitDialog Exp Expand ExpandAll ExpandDenominator ExpandFileName ExpandNumerator Expectation ExpectationE ExpectedValue ExpGammaDistribution ExpIntegralE ExpIntegralEi Exponent ExponentFunction ExponentialDistribution ExponentialFamily ExponentialGeneratingFunction ExponentialMovingAverage ExponentialPowerDistribution ExponentPosition ExponentStep Export ExportAutoReplacements ExportPacket ExportString Expression ExpressionCell ExpressionPacket ExpToTrig ExtendedGCD Extension ExtentElementFunction ExtentMarkers ExtentSize ExternalCall ExternalDataCharacterEncoding Extract ExtractArchive ExtremeValueDistribution FaceForm FaceGrids FaceGridsStyle Factor FactorComplete Factorial Factorial2 FactorialMoment FactorialMomentGeneratingFunction FactorialPower FactorInteger FactorList FactorSquareFree FactorSquareFreeList FactorTerms FactorTermsList Fail FailureDistribution False FARIMAProcess FEDisableConsolePrintPacket FeedbackSector FeedbackSectorStyle FeedbackType FEEnableConsolePrintPacket Fibonacci FieldHint FieldHintStyle FieldMasked FieldSize File FileBaseName FileByteCount FileDate FileExistsQ FileExtension FileFormat FileHash FileInformation FileName FileNameDepth FileNameDialogSettings FileNameDrop FileNameJoin FileNames FileNameSetter FileNameSplit FileNameTake FilePrint FileType FilledCurve FilledCurveBox Filling FillingStyle FillingTransform FilterRules FinancialBond FinancialData FinancialDerivative FinancialIndicator Find FindArgMax FindArgMin FindClique FindClusters FindCurvePath FindDistributionParameters FindDivisions FindEdgeCover FindEdgeCut FindEulerianCycle FindFaces FindFile FindFit FindGeneratingFunction FindGeoLocation FindGeometricTransform FindGraphCommunities FindGraphIsomorphism FindGraphPartition FindHamiltonianCycle FindIndependentEdgeSet FindIndependentVertexSet FindInstance FindIntegerNullVector FindKClan FindKClique FindKClub FindKPlex FindLibrary FindLinearRecurrence FindList FindMaximum FindMaximumFlow FindMaxValue FindMinimum FindMinimumCostFlow FindMinimumCut FindMinValue FindPermutation FindPostmanTour FindProcessParameters FindRoot FindSequenceFunction FindSettings FindShortestPath FindShortestTour FindThreshold FindVertexCover FindVertexCut Fine FinishDynamic FiniteAbelianGroupCount FiniteGroupCount FiniteGroupData First FirstPassageTimeDistribution FischerGroupFi22 FischerGroupFi23 FischerGroupFi24Prime FisherHypergeometricDistribution FisherRatioTest FisherZDistribution Fit FitAll FittedModel FixedPoint FixedPointList FlashSelection Flat Flatten FlattenAt FlatTopWindow FlipView Floor FlushPrintOutputPacket Fold FoldList Font FontColor FontFamily FontForm FontName FontOpacity FontPostScriptName FontProperties FontReencoding FontSize FontSlant FontSubstitutions FontTracking FontVariations FontWeight For ForAll Format FormatRules FormatType FormatTypeAutoConvert FormatValues FormBox FormBoxOptions FortranForm Forward ForwardBackward Fourier FourierCoefficient FourierCosCoefficient FourierCosSeries FourierCosTransform FourierDCT FourierDCTFilter FourierDCTMatrix FourierDST FourierDSTMatrix FourierMatrix FourierParameters FourierSequenceTransform FourierSeries FourierSinCoefficient FourierSinSeries FourierSinTransform FourierTransform FourierTrigSeries FractionalBrownianMotionProcess FractionalPart FractionBox FractionBoxOptions FractionLine Frame FrameBox FrameBoxOptions Framed FrameInset FrameLabel Frameless FrameMargins FrameStyle FrameTicks FrameTicksStyle FRatioDistribution FrechetDistribution FreeQ FrequencySamplingFilterKernel FresnelC FresnelS Friday FrobeniusNumber FrobeniusSolve FromCharacterCode FromCoefficientRules FromContinuedFraction FromDate FromDigits FromDMS Front FrontEndDynamicExpression FrontEndEventActions FrontEndExecute FrontEndObject FrontEndResource FrontEndResourceString FrontEndStackSize FrontEndToken FrontEndTokenExecute FrontEndValueCache FrontEndVersion FrontFaceColor FrontFaceOpacity Full FullAxes FullDefinition FullForm FullGraphics FullOptions FullSimplify Function FunctionExpand FunctionInterpolation FunctionSpace FussellVeselyImportance GaborFilter GaborMatrix GaborWavelet GainMargins GainPhaseMargins Gamma GammaDistribution GammaRegularized GapPenalty Gather GatherBy GaugeFaceElementFunction GaugeFaceStyle GaugeFrameElementFunction GaugeFrameSize GaugeFrameStyle GaugeLabels GaugeMarkers GaugeStyle GaussianFilter GaussianIntegers GaussianMatrix GaussianWindow GCD GegenbauerC General GeneralizedLinearModelFit GenerateConditions GeneratedCell GeneratedParameters GeneratingFunction Generic GenericCylindricalDecomposition GenomeData GenomeLookup GeodesicClosing GeodesicDilation GeodesicErosion GeodesicOpening GeoDestination GeodesyData GeoDirection GeoDistance GeoGridPosition GeometricBrownianMotionProcess GeometricDistribution GeometricMean GeometricMeanFilter GeometricTransformation GeometricTransformation3DBox GeometricTransformation3DBoxOptions GeometricTransformationBox GeometricTransformationBoxOptions GeoPosition GeoPositionENU GeoPositionXYZ GeoProjectionData GestureHandler GestureHandlerTag Get GetBoundingBoxSizePacket GetContext GetEnvironment GetFileName GetFrontEndOptionsDataPacket GetLinebreakInformationPacket GetMenusPacket GetPageBreakInformationPacket Glaisher GlobalClusteringCoefficient GlobalPreferences GlobalSession Glow GoldenRatio GompertzMakehamDistribution GoodmanKruskalGamma GoodmanKruskalGammaTest Goto Grad Gradient GradientFilter GradientOrientationFilter Graph GraphAssortativity GraphCenter GraphComplement GraphData GraphDensity GraphDiameter GraphDifference GraphDisjointUnion GraphDistance GraphDistanceMatrix GraphElementData GraphEmbedding GraphHighlight GraphHighlightStyle GraphHub Graphics Graphics3D Graphics3DBox Graphics3DBoxOptions GraphicsArray GraphicsBaseline GraphicsBox GraphicsBoxOptions GraphicsColor GraphicsColumn GraphicsComplex GraphicsComplex3DBox GraphicsComplex3DBoxOptions GraphicsComplexBox GraphicsComplexBoxOptions GraphicsContents GraphicsData GraphicsGrid GraphicsGridBox GraphicsGroup GraphicsGroup3DBox GraphicsGroup3DBoxOptions GraphicsGroupBox GraphicsGroupBoxOptions GraphicsGrouping GraphicsHighlightColor GraphicsRow GraphicsSpacing GraphicsStyle GraphIntersection GraphLayout GraphLinkEfficiency GraphPeriphery GraphPlot GraphPlot3D GraphPower GraphPropertyDistribution GraphQ GraphRadius GraphReciprocity GraphRoot GraphStyle GraphUnion Gray GrayLevel GreatCircleDistance Greater GreaterEqual GreaterEqualLess GreaterFullEqual GreaterGreater GreaterLess GreaterSlantEqual GreaterTilde Green Grid GridBaseline GridBox GridBoxAlignment GridBoxBackground GridBoxDividers GridBoxFrame GridBoxItemSize GridBoxItemStyle GridBoxOptions GridBoxSpacings GridCreationSettings GridDefaultElement GridElementStyleOptions GridFrame GridFrameMargins GridGraph GridLines GridLinesStyle GroebnerBasis GroupActionBase GroupCentralizer GroupElementFromWord GroupElementPosition GroupElementQ GroupElements GroupElementToWord GroupGenerators GroupMultiplicationTable GroupOrbits GroupOrder GroupPageBreakWithin GroupSetwiseStabilizer GroupStabilizer GroupStabilizerChain Gudermannian GumbelDistribution HaarWavelet HadamardMatrix HalfNormalDistribution HamiltonianGraphQ HammingDistance HammingWindow HankelH1 HankelH2 HankelMatrix HannPoissonWindow HannWindow HaradaNortonGroupHN HararyGraph HarmonicMean HarmonicMeanFilter HarmonicNumber Hash HashTable Haversine HazardFunction Head HeadCompose Heads HeavisideLambda HeavisidePi HeavisideTheta HeldGroupHe HeldPart HelpBrowserLookup HelpBrowserNotebook HelpBrowserSettings HermiteDecomposition HermiteH HermitianMatrixQ HessenbergDecomposition Hessian HexadecimalCharacter Hexahedron HexahedronBox HexahedronBoxOptions HiddenSurface HighlightGraph HighlightImage HighpassFilter HigmanSimsGroupHS HilbertFilter HilbertMatrix Histogram Histogram3D HistogramDistribution HistogramList HistogramTransform HistogramTransformInterpolation HitMissTransform HITSCentrality HodgeDual HoeffdingD HoeffdingDTest Hold HoldAll HoldAllComplete HoldComplete HoldFirst HoldForm HoldPattern HoldRest HolidayCalendar HomeDirectory HomePage Horizontal HorizontalForm HorizontalGauge HorizontalScrollPosition HornerForm HotellingTSquareDistribution HoytDistribution HTMLSave Hue HumpDownHump HumpEqual HurwitzLerchPhi HurwitzZeta HyperbolicDistribution HypercubeGraph HyperexponentialDistribution Hyperfactorial Hypergeometric0F1 Hypergeometric0F1Regularized Hypergeometric1F1 Hypergeometric1F1Regularized Hypergeometric2F1 Hypergeometric2F1Regularized HypergeometricDistribution HypergeometricPFQ HypergeometricPFQRegularized HypergeometricU Hyperlink HyperlinkCreationSettings Hyphenation HyphenationOptions HypoexponentialDistribution HypothesisTestData I Identity IdentityMatrix If IgnoreCase Im Image Image3D Image3DSlices ImageAccumulate ImageAdd ImageAdjust ImageAlign ImageApply ImageAspectRatio ImageAssemble ImageCache ImageCacheValid ImageCapture ImageChannels ImageClip ImageColorSpace ImageCompose ImageConvolve ImageCooccurrence ImageCorners ImageCorrelate ImageCorrespondingPoints ImageCrop ImageData ImageDataPacket ImageDeconvolve ImageDemosaic ImageDifference ImageDimensions ImageDistance ImageEffect ImageFeatureTrack ImageFileApply ImageFileFilter ImageFileScan ImageFilter ImageForestingComponents ImageForwardTransformation ImageHistogram ImageKeypoints ImageLevels ImageLines ImageMargins ImageMarkers ImageMeasurements ImageMultiply ImageOffset ImagePad ImagePadding ImagePartition ImagePeriodogram ImagePerspectiveTransformation ImageQ ImageRangeCache ImageReflect ImageRegion ImageResize ImageResolution ImageRotate ImageRotated ImageScaled ImageScan ImageSize ImageSizeAction ImageSizeCache ImageSizeMultipliers ImageSizeRaw ImageSubtract ImageTake ImageTransformation ImageTrim ImageType ImageValue ImageValuePositions Implies Import ImportAutoReplacements ImportString ImprovementImportance In IncidenceGraph IncidenceList IncidenceMatrix IncludeConstantBasis IncludeFileExtension IncludePods IncludeSingularTerm Increment Indent IndentingNewlineSpacings IndentMaxFraction IndependenceTest IndependentEdgeSetQ IndependentUnit IndependentVertexSetQ Indeterminate IndexCreationOptions Indexed IndexGraph IndexTag Inequality InexactNumberQ InexactNumbers Infinity Infix Information Inherited InheritScope Initialization InitializationCell InitializationCellEvaluation InitializationCellWarning InlineCounterAssignments InlineCounterIncrements InlineRules Inner Inpaint Input InputAliases InputAssumptions InputAutoReplacements InputField InputFieldBox InputFieldBoxOptions InputForm InputGrouping InputNamePacket InputNotebook InputPacket InputSettings InputStream InputString InputStringPacket InputToBoxFormPacket Insert InsertionPointObject InsertResults Inset Inset3DBox Inset3DBoxOptions InsetBox InsetBoxOptions Install InstallService InString Integer IntegerDigits IntegerExponent IntegerLength IntegerPart IntegerPartitions IntegerQ Integers IntegerString Integral Integrate Interactive InteractiveTradingChart Interlaced Interleaving InternallyBalancedDecomposition InterpolatingFunction InterpolatingPolynomial Interpolation InterpolationOrder InterpolationPoints InterpolationPrecision Interpretation InterpretationBox InterpretationBoxOptions InterpretationFunction InterpretTemplate InterquartileRange Interrupt InterruptSettings Intersection Interval IntervalIntersection IntervalMemberQ IntervalUnion Inverse InverseBetaRegularized InverseCDF InverseChiSquareDistribution InverseContinuousWaveletTransform InverseDistanceTransform InverseEllipticNomeQ InverseErf InverseErfc InverseFourier InverseFourierCosTransform InverseFourierSequenceTransform InverseFourierSinTransform InverseFourierTransform InverseFunction InverseFunctions InverseGammaDistribution InverseGammaRegularized InverseGaussianDistribution InverseGudermannian InverseHaversine InverseJacobiCD InverseJacobiCN InverseJacobiCS InverseJacobiDC InverseJacobiDN InverseJacobiDS InverseJacobiNC InverseJacobiND InverseJacobiNS InverseJacobiSC InverseJacobiSD InverseJacobiSN InverseLaplaceTransform InversePermutation InverseRadon InverseSeries InverseSurvivalFunction InverseWaveletTransform InverseWeierstrassP InverseZTransform Invisible InvisibleApplication InvisibleTimes IrreduciblePolynomialQ IsolatingInterval IsomorphicGraphQ IsotopeData Italic Item ItemBox ItemBoxOptions ItemSize ItemStyle ItoProcess JaccardDissimilarity JacobiAmplitude Jacobian JacobiCD JacobiCN JacobiCS JacobiDC JacobiDN JacobiDS JacobiNC JacobiND JacobiNS JacobiP JacobiSC JacobiSD JacobiSN JacobiSymbol JacobiZeta JankoGroupJ1 JankoGroupJ2 JankoGroupJ3 JankoGroupJ4 JarqueBeraALMTest JohnsonDistribution Join Joined JoinedCurve JoinedCurveBox JoinForm JordanDecomposition JordanModelDecomposition K KagiChart KaiserBesselWindow KaiserWindow KalmanEstimator KalmanFilter KarhunenLoeveDecomposition KaryTree KatzCentrality KCoreComponents KDistribution KelvinBei KelvinBer KelvinKei KelvinKer KendallTau KendallTauTest KernelExecute KernelMixtureDistribution KernelObject Kernels Ket Khinchin KirchhoffGraph KirchhoffMatrix KleinInvariantJ KnightTourGraph KnotData KnownUnitQ KolmogorovSmirnovTest KroneckerDelta KroneckerModelDecomposition KroneckerProduct KroneckerSymbol KuiperTest KumaraswamyDistribution Kurtosis KuwaharaFilter Label Labeled LabeledSlider LabelingFunction LabelStyle LaguerreL LambdaComponents LambertW LanczosWindow LandauDistribution Language LanguageCategory LaplaceDistribution LaplaceTransform Laplacian LaplacianFilter LaplacianGaussianFilter Large Larger Last Latitude LatitudeLongitude LatticeData LatticeReduce Launch LaunchKernels LayeredGraphPlot LayerSizeFunction LayoutInformation LCM LeafCount LeapYearQ LeastSquares LeastSquaresFilterKernel Left LeftArrow LeftArrowBar LeftArrowRightArrow LeftDownTeeVector LeftDownVector LeftDownVectorBar LeftRightArrow LeftRightVector LeftTee LeftTeeArrow LeftTeeVector LeftTriangle LeftTriangleBar LeftTriangleEqual LeftUpDownVector LeftUpTeeVector LeftUpVector LeftUpVectorBar LeftVector LeftVectorBar LegendAppearance Legended LegendFunction LegendLabel LegendLayout LegendMargins LegendMarkers LegendMarkerSize LegendreP LegendreQ LegendreType Length LengthWhile LerchPhi Less LessEqual LessEqualGreater LessFullEqual LessGreater LessLess LessSlantEqual LessTilde LetterCharacter LetterQ Level LeveneTest LeviCivitaTensor LevyDistribution Lexicographic LibraryFunction LibraryFunctionError LibraryFunctionInformation LibraryFunctionLoad LibraryFunctionUnload LibraryLoad LibraryUnload LicenseID LiftingFilterData LiftingWaveletTransform LightBlue LightBrown LightCyan Lighter LightGray LightGreen Lighting LightingAngle LightMagenta LightOrange LightPink LightPurple LightRed LightSources LightYellow Likelihood Limit LimitsPositioning LimitsPositioningTokens LindleyDistribution Line Line3DBox LinearFilter LinearFractionalTransform LinearModelFit LinearOffsetFunction LinearProgramming LinearRecurrence LinearSolve LinearSolveFunction LineBox LineBreak LinebreakAdjustments LineBreakChart LineBreakWithin LineColor LineForm LineGraph LineIndent LineIndentMaxFraction LineIntegralConvolutionPlot LineIntegralConvolutionScale LineLegend LineOpacity LineSpacing LineWrapParts LinkActivate LinkClose LinkConnect LinkConnectedQ LinkCreate LinkError LinkFlush LinkFunction LinkHost LinkInterrupt LinkLaunch LinkMode LinkObject LinkOpen LinkOptions LinkPatterns LinkProtocol LinkRead LinkReadHeld LinkReadyQ Links LinkWrite LinkWriteHeld LiouvilleLambda List Listable ListAnimate ListContourPlot ListContourPlot3D ListConvolve ListCorrelate ListCurvePathPlot ListDeconvolve ListDensityPlot Listen ListFourierSequenceTransform ListInterpolation ListLineIntegralConvolutionPlot ListLinePlot ListLogLinearPlot ListLogLogPlot ListLogPlot ListPicker ListPickerBox ListPickerBoxBackground ListPickerBoxOptions ListPlay ListPlot ListPlot3D ListPointPlot3D ListPolarPlot ListQ ListStreamDensityPlot ListStreamPlot ListSurfacePlot3D ListVectorDensityPlot ListVectorPlot ListVectorPlot3D ListZTransform Literal LiteralSearch LocalClusteringCoefficient LocalizeVariables LocationEquivalenceTest LocationTest Locator LocatorAutoCreate LocatorBox LocatorBoxOptions LocatorCentering LocatorPane LocatorPaneBox LocatorPaneBoxOptions LocatorRegion Locked Log Log10 Log2 LogBarnesG LogGamma LogGammaDistribution LogicalExpand LogIntegral LogisticDistribution LogitModelFit LogLikelihood LogLinearPlot LogLogisticDistribution LogLogPlot LogMultinormalDistribution LogNormalDistribution LogPlot LogRankTest LogSeriesDistribution LongEqual Longest LongestAscendingSequence LongestCommonSequence LongestCommonSequencePositions LongestCommonSubsequence LongestCommonSubsequencePositions LongestMatch LongForm Longitude LongLeftArrow LongLeftRightArrow LongRightArrow Loopback LoopFreeGraphQ LowerCaseQ LowerLeftArrow LowerRightArrow LowerTriangularize LowpassFilter LQEstimatorGains LQGRegulator LQOutputRegulatorGains LQRegulatorGains LUBackSubstitution LucasL LuccioSamiComponents LUDecomposition LyapunovSolve LyonsGroupLy MachineID MachineName MachineNumberQ MachinePrecision MacintoshSystemPageSetup Magenta Magnification Magnify MainSolve MaintainDynamicCaches Majority MakeBoxes MakeExpression MakeRules MangoldtLambda ManhattanDistance Manipulate Manipulator MannWhitneyTest MantissaExponent Manual Map MapAll MapAt MapIndexed MAProcess MapThread MarcumQ MardiaCombinedTest MardiaKurtosisTest MardiaSkewnessTest MarginalDistribution MarkovProcessProperties Masking MatchingDissimilarity MatchLocalNameQ MatchLocalNames MatchQ Material MathematicaNotation MathieuC MathieuCharacteristicA MathieuCharacteristicB MathieuCharacteristicExponent MathieuCPrime MathieuGroupM11 MathieuGroupM12 MathieuGroupM22 MathieuGroupM23 MathieuGroupM24 MathieuS MathieuSPrime MathMLForm MathMLText Matrices MatrixExp MatrixForm MatrixFunction MatrixLog MatrixPlot MatrixPower MatrixQ MatrixRank Max MaxBend MaxDetect MaxExtraBandwidths MaxExtraConditions MaxFeatures MaxFilter Maximize MaxIterations MaxMemoryUsed MaxMixtureKernels MaxPlotPoints MaxPoints MaxRecursion MaxStableDistribution MaxStepFraction MaxSteps MaxStepSize MaxValue MaxwellDistribution McLaughlinGroupMcL Mean MeanClusteringCoefficient MeanDegreeConnectivity MeanDeviation MeanFilter MeanGraphDistance MeanNeighborDegree MeanShift MeanShiftFilter Median MedianDeviation MedianFilter Medium MeijerG MeixnerDistribution MemberQ MemoryConstrained MemoryInUse Menu MenuAppearance MenuCommandKey MenuEvaluator MenuItem MenuPacket MenuSortingValue MenuStyle MenuView MergeDifferences Mesh MeshFunctions MeshRange MeshShading MeshStyle Message MessageDialog MessageList MessageName MessageOptions MessagePacket Messages MessagesNotebook MetaCharacters MetaInformation Method MethodOptions MexicanHatWavelet MeyerWavelet Min MinDetect MinFilter MinimalPolynomial MinimalStateSpaceModel Minimize Minors MinRecursion MinSize MinStableDistribution Minus MinusPlus MinValue Missing MissingDataMethod MittagLefflerE MixedRadix MixedRadixQuantity MixtureDistribution Mod Modal Mode Modular ModularLambda Module Modulus MoebiusMu Moment Momentary MomentConvert MomentEvaluate MomentGeneratingFunction Monday Monitor MonomialList MonomialOrder MonsterGroupM MorletWavelet MorphologicalBinarize MorphologicalBranchPoints MorphologicalComponents MorphologicalEulerNumber MorphologicalGraph MorphologicalPerimeter MorphologicalTransform Most MouseAnnotation MouseAppearance MouseAppearanceTag MouseButtons Mouseover MousePointerNote MousePosition MovingAverage MovingMedian MoyalDistribution MultiedgeStyle MultilaunchWarning MultiLetterItalics MultiLetterStyle MultilineFunction Multinomial MultinomialDistribution MultinormalDistribution MultiplicativeOrder Multiplicity Multiselection MultivariateHypergeometricDistribution MultivariatePoissonDistribution MultivariateTDistribution N NakagamiDistribution NameQ Names NamespaceBox Nand NArgMax NArgMin NBernoulliB NCache NDSolve NDSolveValue Nearest NearestFunction NeedCurrentFrontEndPackagePacket NeedCurrentFrontEndSymbolsPacket NeedlemanWunschSimilarity Needs Negative NegativeBinomialDistribution NegativeMultinomialDistribution NeighborhoodGraph Nest NestedGreaterGreater NestedLessLess NestedScriptRules NestList NestWhile NestWhileList NevilleThetaC NevilleThetaD NevilleThetaN NevilleThetaS NewPrimitiveStyle NExpectation Next NextPrime NHoldAll NHoldFirst NHoldRest NicholsGridLines NicholsPlot NIntegrate NMaximize NMaxValue NMinimize NMinValue NominalVariables NonAssociative NoncentralBetaDistribution NoncentralChiSquareDistribution NoncentralFRatioDistribution NoncentralStudentTDistribution NonCommutativeMultiply NonConstants None NonlinearModelFit NonlocalMeansFilter NonNegative NonPositive Nor NorlundB Norm Normal NormalDistribution NormalGrouping Normalize NormalizedSquaredEuclideanDistance NormalsFunction NormFunction Not NotCongruent NotCupCap NotDoubleVerticalBar Notebook NotebookApply NotebookAutoSave NotebookClose NotebookConvertSettings NotebookCreate NotebookCreateReturnObject NotebookDefault NotebookDelete NotebookDirectory NotebookDynamicExpression NotebookEvaluate NotebookEventActions NotebookFileName NotebookFind NotebookFindReturnObject NotebookGet NotebookGetLayoutInformationPacket NotebookGetMisspellingsPacket NotebookInformation NotebookInterfaceObject NotebookLocate NotebookObject NotebookOpen NotebookOpenReturnObject NotebookPath NotebookPrint NotebookPut NotebookPutReturnObject NotebookRead NotebookResetGeneratedCells Notebooks NotebookSave NotebookSaveAs NotebookSelection NotebookSetupLayoutInformationPacket NotebooksMenu NotebookWrite NotElement NotEqualTilde NotExists NotGreater NotGreaterEqual NotGreaterFullEqual NotGreaterGreater NotGreaterLess NotGreaterSlantEqual NotGreaterTilde NotHumpDownHump NotHumpEqual NotLeftTriangle NotLeftTriangleBar NotLeftTriangleEqual NotLess NotLessEqual NotLessFullEqual NotLessGreater NotLessLess NotLessSlantEqual NotLessTilde NotNestedGreaterGreater NotNestedLessLess NotPrecedes NotPrecedesEqual NotPrecedesSlantEqual NotPrecedesTilde NotReverseElement NotRightTriangle NotRightTriangleBar NotRightTriangleEqual NotSquareSubset NotSquareSubsetEqual NotSquareSuperset NotSquareSupersetEqual NotSubset NotSubsetEqual NotSucceeds NotSucceedsEqual NotSucceedsSlantEqual NotSucceedsTilde NotSuperset NotSupersetEqual NotTilde NotTildeEqual NotTildeFullEqual NotTildeTilde NotVerticalBar NProbability NProduct NProductFactors NRoots NSolve NSum NSumTerms Null NullRecords NullSpace NullWords Number NumberFieldClassNumber NumberFieldDiscriminant NumberFieldFundamentalUnits NumberFieldIntegralBasis NumberFieldNormRepresentatives NumberFieldRegulator NumberFieldRootsOfUnity NumberFieldSignature NumberForm NumberFormat NumberMarks NumberMultiplier NumberPadding NumberPoint NumberQ NumberSeparator NumberSigns NumberString Numerator NumericFunction NumericQ NuttallWindow NValues NyquistGridLines NyquistPlot O ObservabilityGramian ObservabilityMatrix ObservableDecomposition ObservableModelQ OddQ Off Offset OLEData On ONanGroupON OneIdentity Opacity Open OpenAppend Opener OpenerBox OpenerBoxOptions OpenerView OpenFunctionInspectorPacket Opening OpenRead OpenSpecialOptions OpenTemporary OpenWrite Operate OperatingSystem OptimumFlowData Optional OptionInspectorSettings OptionQ Options OptionsPacket OptionsPattern OptionValue OptionValueBox OptionValueBoxOptions Or Orange Order OrderDistribution OrderedQ Ordering Orderless OrnsteinUhlenbeckProcess Orthogonalize Out Outer OutputAutoOverwrite OutputControllabilityMatrix OutputControllableModelQ OutputForm OutputFormData OutputGrouping OutputMathEditExpression OutputNamePacket OutputResponse OutputSizeLimit OutputStream Over OverBar OverDot Overflow OverHat Overlaps Overlay OverlayBox OverlayBoxOptions Overscript OverscriptBox OverscriptBoxOptions OverTilde OverVector OwenT OwnValues PackingMethod PaddedForm Padding PadeApproximant PadLeft PadRight PageBreakAbove PageBreakBelow PageBreakWithin PageFooterLines PageFooters PageHeaderLines PageHeaders PageHeight PageRankCentrality PageWidth PairedBarChart PairedHistogram PairedSmoothHistogram PairedTTest PairedZTest PaletteNotebook PalettePath Pane PaneBox PaneBoxOptions Panel PanelBox PanelBoxOptions Paneled PaneSelector PaneSelectorBox PaneSelectorBoxOptions PaperWidth ParabolicCylinderD ParagraphIndent ParagraphSpacing ParallelArray ParallelCombine ParallelDo ParallelEvaluate Parallelization Parallelize ParallelMap ParallelNeeds ParallelProduct ParallelSubmit ParallelSum ParallelTable ParallelTry Parameter ParameterEstimator ParameterMixtureDistribution ParameterVariables ParametricFunction ParametricNDSolve ParametricNDSolveValue ParametricPlot ParametricPlot3D ParentConnect ParentDirectory ParentForm Parenthesize ParentList ParetoDistribution Part PartialCorrelationFunction PartialD ParticleData Partition PartitionsP PartitionsQ ParzenWindow PascalDistribution PassEventsDown PassEventsUp Paste PasteBoxFormInlineCells PasteButton Path PathGraph PathGraphQ Pattern PatternSequence PatternTest PauliMatrix PaulWavelet Pause PausedTime PDF PearsonChiSquareTest PearsonCorrelationTest PearsonDistribution PerformanceGoal PeriodicInterpolation Periodogram PeriodogramArray PermutationCycles PermutationCyclesQ PermutationGroup PermutationLength PermutationList PermutationListQ PermutationMax PermutationMin PermutationOrder PermutationPower PermutationProduct PermutationReplace Permutations PermutationSupport Permute PeronaMalikFilter Perpendicular PERTDistribution PetersenGraph PhaseMargins Pi Pick PIDData PIDDerivativeFilter PIDFeedforward PIDTune Piecewise PiecewiseExpand PieChart PieChart3D PillaiTrace PillaiTraceTest Pink Pivoting PixelConstrained PixelValue PixelValuePositions Placed Placeholder PlaceholderReplace Plain PlanarGraphQ Play PlayRange Plot Plot3D Plot3Matrix PlotDivision PlotJoined PlotLabel PlotLayout PlotLegends PlotMarkers PlotPoints PlotRange PlotRangeClipping PlotRangePadding PlotRegion PlotStyle Plus PlusMinus Pochhammer PodStates PodWidth Point Point3DBox PointBox PointFigureChart PointForm PointLegend PointSize PoissonConsulDistribution PoissonDistribution PoissonProcess PoissonWindow PolarAxes PolarAxesOrigin PolarGridLines PolarPlot PolarTicks PoleZeroMarkers PolyaAeppliDistribution PolyGamma Polygon Polygon3DBox Polygon3DBoxOptions PolygonBox PolygonBoxOptions PolygonHoleScale PolygonIntersections PolygonScale PolyhedronData PolyLog PolynomialExtendedGCD PolynomialForm PolynomialGCD PolynomialLCM PolynomialMod PolynomialQ PolynomialQuotient PolynomialQuotientRemainder PolynomialReduce PolynomialRemainder Polynomials PopupMenu PopupMenuBox PopupMenuBoxOptions PopupView PopupWindow Position Positive PositiveDefiniteMatrixQ PossibleZeroQ Postfix PostScript Power PowerDistribution PowerExpand PowerMod PowerModList PowerSpectralDensity PowersRepresentations PowerSymmetricPolynomial Precedence PrecedenceForm Precedes PrecedesEqual PrecedesSlantEqual PrecedesTilde Precision PrecisionGoal PreDecrement PredictionRoot PreemptProtect PreferencesPath Prefix PreIncrement Prepend PrependTo PreserveImageOptions Previous PriceGraphDistribution PrimaryPlaceholder Prime PrimeNu PrimeOmega PrimePi PrimePowerQ PrimeQ Primes PrimeZetaP PrimitiveRoot PrincipalComponents PrincipalValue Print PrintAction PrintForm PrintingCopies PrintingOptions PrintingPageRange PrintingStartingPageNumber PrintingStyleEnvironment PrintPrecision PrintTemporary Prism PrismBox PrismBoxOptions PrivateCellOptions PrivateEvaluationOptions PrivateFontOptions PrivateFrontEndOptions PrivateNotebookOptions PrivatePaths Probability ProbabilityDistribution ProbabilityPlot ProbabilityPr ProbabilityScalePlot ProbitModelFit ProcessEstimator ProcessParameterAssumptions ProcessParameterQ ProcessStateDomain ProcessTimeDomain Product ProductDistribution ProductLog ProgressIndicator ProgressIndicatorBox ProgressIndicatorBoxOptions Projection Prolog PromptForm Properties Property PropertyList PropertyValue Proportion Proportional Protect Protected ProteinData Pruning PseudoInverse Purple Put PutAppend Pyramid PyramidBox PyramidBoxOptions QBinomial QFactorial QGamma QHypergeometricPFQ QPochhammer QPolyGamma QRDecomposition QuadraticIrrationalQ Quantile QuantilePlot Quantity QuantityForm QuantityMagnitude QuantityQ QuantityUnit Quartics QuartileDeviation Quartiles QuartileSkewness QueueingNetworkProcess QueueingProcess QueueProperties Quiet Quit Quotient QuotientRemainder RadialityCentrality RadicalBox RadicalBoxOptions RadioButton RadioButtonBar RadioButtonBox RadioButtonBoxOptions Radon RamanujanTau RamanujanTauL RamanujanTauTheta RamanujanTauZ Random RandomChoice RandomComplex RandomFunction RandomGraph RandomImage RandomInteger RandomPermutation RandomPrime RandomReal RandomSample RandomSeed RandomVariate RandomWalkProcess Range RangeFilter RangeSpecification RankedMax RankedMin Raster Raster3D Raster3DBox Raster3DBoxOptions RasterArray RasterBox RasterBoxOptions Rasterize RasterSize Rational RationalFunctions Rationalize Rationals Ratios Raw RawArray RawBoxes RawData RawMedium RayleighDistribution Re Read ReadList ReadProtected Real RealBlockDiagonalForm RealDigits RealExponent Reals Reap Record RecordLists RecordSeparators Rectangle RectangleBox RectangleBoxOptions RectangleChart RectangleChart3D RecurrenceFilter RecurrenceTable RecurringDigitsForm Red Reduce RefBox ReferenceLineStyle ReferenceMarkers ReferenceMarkerStyle Refine ReflectionMatrix ReflectionTransform Refresh RefreshRate RegionBinarize RegionFunction RegionPlot RegionPlot3D RegularExpression Regularization Reinstall Release ReleaseHold ReliabilityDistribution ReliefImage ReliefPlot Remove RemoveAlphaChannel RemoveAsynchronousTask Removed RemoveInputStreamMethod RemoveOutputStreamMethod RemoveProperty RemoveScheduledTask RenameDirectory RenameFile RenderAll RenderingOptions RenewalProcess RenkoChart Repeated RepeatedNull RepeatedString Replace ReplaceAll ReplaceHeldPart ReplaceImageValue ReplaceList ReplacePart ReplacePixelValue ReplaceRepeated Resampling Rescale RescalingTransform ResetDirectory ResetMenusPacket ResetScheduledTask Residue Resolve Rest Resultant ResumePacket Return ReturnExpressionPacket ReturnInputFormPacket ReturnPacket ReturnTextPacket Reverse ReverseBiorthogonalSplineWavelet ReverseElement ReverseEquilibrium ReverseGraph ReverseUpEquilibrium RevolutionAxis RevolutionPlot3D RGBColor RiccatiSolve RiceDistribution RidgeFilter RiemannR RiemannSiegelTheta RiemannSiegelZ Riffle Right RightArrow RightArrowBar RightArrowLeftArrow RightCosetRepresentative RightDownTeeVector RightDownVector RightDownVectorBar RightTee RightTeeArrow RightTeeVector RightTriangle RightTriangleBar RightTriangleEqual RightUpDownVector RightUpTeeVector RightUpVector RightUpVectorBar RightVector RightVectorBar RiskAchievementImportance RiskReductionImportance RogersTanimotoDissimilarity Root RootApproximant RootIntervals RootLocusPlot RootMeanSquare RootOfUnityQ RootReduce Roots RootSum Rotate RotateLabel RotateLeft RotateRight RotationAction RotationBox RotationBoxOptions RotationMatrix RotationTransform Round RoundImplies RoundingRadius Row RowAlignments RowBackgrounds RowBox RowHeights RowLines RowMinHeight RowReduce RowsEqual RowSpacings RSolve RudvalisGroupRu Rule RuleCondition RuleDelayed RuleForm RulerUnits Run RunScheduledTask RunThrough RuntimeAttributes RuntimeOptions RussellRaoDissimilarity SameQ SameTest SampleDepth SampledSoundFunction SampledSoundList SampleRate SamplingPeriod SARIMAProcess SARMAProcess SatisfiabilityCount SatisfiabilityInstances SatisfiableQ Saturday Save Saveable SaveAutoDelete SaveDefinitions SawtoothWave Scale Scaled ScaleDivisions ScaledMousePosition ScaleOrigin ScalePadding ScaleRanges ScaleRangeStyle ScalingFunctions ScalingMatrix ScalingTransform Scan ScheduledTaskActiveQ ScheduledTaskData ScheduledTaskObject ScheduledTasks SchurDecomposition ScientificForm ScreenRectangle ScreenStyleEnvironment ScriptBaselineShifts ScriptLevel ScriptMinSize ScriptRules ScriptSizeMultipliers Scrollbars ScrollingOptions ScrollPosition Sec Sech SechDistribution SectionGrouping SectorChart SectorChart3D SectorOrigin SectorSpacing SeedRandom Select Selectable SelectComponents SelectedCells SelectedNotebook Selection SelectionAnimate SelectionCell SelectionCellCreateCell SelectionCellDefaultStyle SelectionCellParentStyle SelectionCreateCell SelectionDebuggerTag SelectionDuplicateCell SelectionEvaluate SelectionEvaluateCreateCell SelectionMove SelectionPlaceholder SelectionSetStyle SelectWithContents SelfLoops SelfLoopStyle SemialgebraicComponentInstances SendMail Sequence SequenceAlignment SequenceForm SequenceHold SequenceLimit Series SeriesCoefficient SeriesData SessionTime Set SetAccuracy SetAlphaChannel SetAttributes Setbacks SetBoxFormNamesPacket SetDelayed SetDirectory SetEnvironment SetEvaluationNotebook SetFileDate SetFileLoadingContext SetNotebookStatusLine SetOptions SetOptionsPacket SetPrecision SetProperty SetSelectedNotebook SetSharedFunction SetSharedVariable SetSpeechParametersPacket SetStreamPosition SetSystemOptions Setter SetterBar SetterBox SetterBoxOptions Setting SetValue Shading Shallow ShannonWavelet ShapiroWilkTest Share Sharpen ShearingMatrix ShearingTransform ShenCastanMatrix Short ShortDownArrow Shortest ShortestMatch ShortestPathFunction ShortLeftArrow ShortRightArrow ShortUpArrow Show ShowAutoStyles ShowCellBracket ShowCellLabel ShowCellTags ShowClosedCellArea ShowContents ShowControls ShowCursorTracker ShowGroupOpenCloseIcon ShowGroupOpener ShowInvisibleCharacters ShowPageBreaks ShowPredictiveInterface ShowSelection ShowShortBoxForm ShowSpecialCharacters ShowStringCharacters ShowSyntaxStyles ShrinkingDelay ShrinkWrapBoundingBox SiegelTheta SiegelTukeyTest Sign Signature SignedRankTest SignificanceLevel SignPadding SignTest SimilarityRules SimpleGraph SimpleGraphQ Simplify Sin Sinc SinghMaddalaDistribution SingleEvaluation SingleLetterItalics SingleLetterStyle SingularValueDecomposition SingularValueList SingularValuePlot SingularValues Sinh SinhIntegral SinIntegral SixJSymbol Skeleton SkeletonTransform SkellamDistribution Skewness SkewNormalDistribution Skip SliceDistribution Slider Slider2D Slider2DBox Slider2DBoxOptions SliderBox SliderBoxOptions SlideView Slot SlotSequence Small SmallCircle Smaller SmithDelayCompensator SmithWatermanSimilarity SmoothDensityHistogram SmoothHistogram SmoothHistogram3D SmoothKernelDistribution SocialMediaData Socket SokalSneathDissimilarity Solve SolveAlways SolveDelayed Sort SortBy Sound SoundAndGraphics SoundNote SoundVolume Sow Space SpaceForm Spacer Spacings Span SpanAdjustments SpanCharacterRounding SpanFromAbove SpanFromBoth SpanFromLeft SpanLineThickness SpanMaxSize SpanMinSize SpanningCharacters SpanSymmetric SparseArray SpatialGraphDistribution Speak SpeakTextPacket SpearmanRankTest SpearmanRho Spectrogram SpectrogramArray Specularity SpellingCorrection SpellingDictionaries SpellingDictionariesPath SpellingOptions SpellingSuggestionsPacket Sphere SphereBox SphericalBesselJ SphericalBesselY SphericalHankelH1 SphericalHankelH2 SphericalHarmonicY SphericalPlot3D SphericalRegion SpheroidalEigenvalue SpheroidalJoiningFactor SpheroidalPS SpheroidalPSPrime SpheroidalQS SpheroidalQSPrime SpheroidalRadialFactor SpheroidalS1 SpheroidalS1Prime SpheroidalS2 SpheroidalS2Prime Splice SplicedDistribution SplineClosed SplineDegree SplineKnots SplineWeights Split SplitBy SpokenString Sqrt SqrtBox SqrtBoxOptions Square SquaredEuclideanDistance SquareFreeQ SquareIntersection SquaresR SquareSubset SquareSubsetEqual SquareSuperset SquareSupersetEqual SquareUnion SquareWave StabilityMargins StabilityMarginsStyle StableDistribution Stack StackBegin StackComplete StackInhibit StandardDeviation StandardDeviationFilter StandardForm Standardize StandbyDistribution Star StarGraph StartAsynchronousTask StartingStepSize StartOfLine StartOfString StartScheduledTask StartupSound StateDimensions StateFeedbackGains StateOutputEstimator StateResponse StateSpaceModel StateSpaceRealization StateSpaceTransform StationaryDistribution StationaryWaveletPacketTransform StationaryWaveletTransform StatusArea StatusCentrality StepMonitor StieltjesGamma StirlingS1 StirlingS2 StopAsynchronousTask StopScheduledTask StrataVariables StratonovichProcess StreamColorFunction StreamColorFunctionScaling StreamDensityPlot StreamPlot StreamPoints StreamPosition Streams StreamScale StreamStyle String StringBreak StringByteCount StringCases StringCount StringDrop StringExpression StringForm StringFormat StringFreeQ StringInsert StringJoin StringLength StringMatchQ StringPosition StringQ StringReplace StringReplaceList StringReplacePart StringReverse StringRotateLeft StringRotateRight StringSkeleton StringSplit StringTake StringToStream StringTrim StripBoxes StripOnInput StripWrapperBoxes StrokeForm StructuralImportance StructuredArray StructuredSelection StruveH StruveL Stub StudentTDistribution Style StyleBox StyleBoxAutoDelete StyleBoxOptions StyleData StyleDefinitions StyleForm StyleKeyMapping StyleMenuListing StyleNameDialogSettings StyleNames StylePrint StyleSheetPath Subfactorial Subgraph SubMinus SubPlus SubresultantPolynomialRemainders SubresultantPolynomials Subresultants Subscript SubscriptBox SubscriptBoxOptions Subscripted Subset SubsetEqual Subsets SubStar Subsuperscript SubsuperscriptBox SubsuperscriptBoxOptions Subtract SubtractFrom SubValues Succeeds SucceedsEqual SucceedsSlantEqual SucceedsTilde SuchThat Sum SumConvergence Sunday SuperDagger SuperMinus SuperPlus Superscript SuperscriptBox SuperscriptBoxOptions Superset SupersetEqual SuperStar Surd SurdForm SurfaceColor SurfaceGraphics SurvivalDistribution SurvivalFunction SurvivalModel SurvivalModelFit SuspendPacket SuzukiDistribution SuzukiGroupSuz SwatchLegend Switch Symbol SymbolName SymletWavelet Symmetric SymmetricGroup SymmetricMatrixQ SymmetricPolynomial SymmetricReduction Symmetrize SymmetrizedArray SymmetrizedArrayRules SymmetrizedDependentComponents SymmetrizedIndependentComponents SymmetrizedReplacePart SynchronousInitialization SynchronousUpdating Syntax SyntaxForm SyntaxInformation SyntaxLength SyntaxPacket SyntaxQ SystemDialogInput SystemException SystemHelpPath SystemInformation SystemInformationData SystemOpen SystemOptions SystemsModelDelay SystemsModelDelayApproximate SystemsModelDelete SystemsModelDimensions SystemsModelExtract SystemsModelFeedbackConnect SystemsModelLabels SystemsModelOrder SystemsModelParallelConnect SystemsModelSeriesConnect SystemsModelStateFeedbackConnect SystemStub Tab TabFilling Table TableAlignments TableDepth TableDirections TableForm TableHeadings TableSpacing TableView TableViewBox TabSpacings TabView TabViewBox TabViewBoxOptions TagBox TagBoxNote TagBoxOptions TaggingRules TagSet TagSetDelayed TagStyle TagUnset Take TakeWhile Tally Tan Tanh TargetFunctions TargetUnits TautologyQ TelegraphProcess TemplateBox TemplateBoxOptions TemplateSlotSequence TemporalData Temporary TemporaryVariable TensorContract TensorDimensions TensorExpand TensorProduct TensorQ TensorRank TensorReduce TensorSymmetry TensorTranspose TensorWedge Tetrahedron TetrahedronBox TetrahedronBoxOptions TeXForm TeXSave Text Text3DBox Text3DBoxOptions TextAlignment TextBand TextBoundingBox TextBox TextCell TextClipboardType TextData TextForm TextJustification TextLine TextPacket TextParagraph TextRecognize TextRendering TextStyle Texture TextureCoordinateFunction TextureCoordinateScaling Therefore ThermometerGauge Thick Thickness Thin Thinning ThisLink ThompsonGroupTh Thread ThreeJSymbol Threshold Through Throw Thumbnail Thursday Ticks TicksStyle Tilde TildeEqual TildeFullEqual TildeTilde TimeConstrained TimeConstraint Times TimesBy TimeSeriesForecast TimeSeriesInvertibility TimeUsed TimeValue TimeZone Timing Tiny TitleGrouping TitsGroupT ToBoxes ToCharacterCode ToColor ToContinuousTimeModel ToDate ToDiscreteTimeModel ToeplitzMatrix ToExpression ToFileName Together Toggle ToggleFalse Toggler TogglerBar TogglerBox TogglerBoxOptions ToHeldExpression ToInvertibleTimeSeries TokenWords Tolerance ToLowerCase ToNumberField TooBig Tooltip TooltipBox TooltipBoxOptions TooltipDelay TooltipStyle Top TopHatTransform TopologicalSort ToRadicals ToRules ToString Total TotalHeight TotalVariationFilter TotalWidth TouchscreenAutoZoom TouchscreenControlPlacement ToUpperCase Tr Trace TraceAbove TraceAction TraceBackward TraceDepth TraceDialog TraceForward TraceInternal TraceLevel TraceOff TraceOn TraceOriginal TracePrint TraceScan TrackedSymbols TradingChart TraditionalForm TraditionalFunctionNotation TraditionalNotation TraditionalOrder TransferFunctionCancel TransferFunctionExpand TransferFunctionFactor TransferFunctionModel TransferFunctionPoles TransferFunctionTransform TransferFunctionZeros TransformationFunction TransformationFunctions TransformationMatrix TransformedDistribution TransformedField Translate TranslationTransform TransparentColor Transpose TreeForm TreeGraph TreeGraphQ TreePlot TrendStyle TriangleWave TriangularDistribution Trig TrigExpand TrigFactor TrigFactorList Trigger TrigReduce TrigToExp TrimmedMean True TrueQ TruncatedDistribution TsallisQExponentialDistribution TsallisQGaussianDistribution TTest Tube TubeBezierCurveBox TubeBezierCurveBoxOptions TubeBox TubeBSplineCurveBox TubeBSplineCurveBoxOptions Tuesday TukeyLambdaDistribution TukeyWindow Tuples TuranGraph TuringMachine Transparent UnateQ Uncompress Undefined UnderBar Underflow Underlined Underoverscript UnderoverscriptBox UnderoverscriptBoxOptions Underscript UnderscriptBox UnderscriptBoxOptions UndirectedEdge UndirectedGraph UndirectedGraphQ UndocumentedTestFEParserPacket UndocumentedTestGetSelectionPacket Unequal Unevaluated UniformDistribution UniformGraphDistribution UniformSumDistribution Uninstall Union UnionPlus Unique UnitBox UnitConvert UnitDimensions Unitize UnitRootTest UnitSimplify UnitStep UnitTriangle UnitVector Unprotect UnsameQ UnsavedVariables Unset UnsetShared UntrackedVariables Up UpArrow UpArrowBar UpArrowDownArrow Update UpdateDynamicObjects UpdateDynamicObjectsSynchronous UpdateInterval UpDownArrow UpEquilibrium UpperCaseQ UpperLeftArrow UpperRightArrow UpperTriangularize Upsample UpSet UpSetDelayed UpTee UpTeeArrow UpValues URL URLFetch URLFetchAsynchronous URLSave URLSaveAsynchronous UseGraphicsRange Using UsingFrontEnd V2Get ValidationLength Value ValueBox ValueBoxOptions ValueForm ValueQ ValuesData Variables Variance VarianceEquivalenceTest VarianceEstimatorFunction VarianceGammaDistribution VarianceTest VectorAngle VectorColorFunction VectorColorFunctionScaling VectorDensityPlot VectorGlyphData VectorPlot VectorPlot3D VectorPoints VectorQ Vectors VectorScale VectorStyle Vee Verbatim Verbose VerboseConvertToPostScriptPacket VerifyConvergence VerifySolutions VerifyTestAssumptions Version VersionNumber VertexAdd VertexCapacity VertexColors VertexComponent VertexConnectivity VertexCoordinateRules VertexCoordinates VertexCorrelationSimilarity VertexCosineSimilarity VertexCount VertexCoverQ VertexDataCoordinates VertexDegree VertexDelete VertexDiceSimilarity VertexEccentricity VertexInComponent VertexInDegree VertexIndex VertexJaccardSimilarity VertexLabeling VertexLabels VertexLabelStyle VertexList VertexNormals VertexOutComponent VertexOutDegree VertexQ VertexRenderingFunction VertexReplace VertexShape VertexShapeFunction VertexSize VertexStyle VertexTextureCoordinates VertexWeight Vertical VerticalBar VerticalForm VerticalGauge VerticalSeparator VerticalSlider VerticalTilde ViewAngle ViewCenter ViewMatrix ViewPoint ViewPointSelectorSettings ViewPort ViewRange ViewVector ViewVertical VirtualGroupData Visible VisibleCell VoigtDistribution VonMisesDistribution WaitAll WaitAsynchronousTask WaitNext WaitUntil WakebyDistribution WalleniusHypergeometricDistribution WaringYuleDistribution WatershedComponents WatsonUSquareTest WattsStrogatzGraphDistribution WaveletBestBasis WaveletFilterCoefficients WaveletImagePlot WaveletListPlot WaveletMapIndexed WaveletMatrixPlot WaveletPhi WaveletPsi WaveletScale WaveletScalogram WaveletThreshold WeaklyConnectedComponents WeaklyConnectedGraphQ WeakStationarity WeatherData WeberE Wedge Wednesday WeibullDistribution WeierstrassHalfPeriods WeierstrassInvariants WeierstrassP WeierstrassPPrime WeierstrassSigma WeierstrassZeta WeightedAdjacencyGraph WeightedAdjacencyMatrix WeightedData WeightedGraphQ Weights WelchWindow WheelGraph WhenEvent Which While White Whitespace WhitespaceCharacter WhittakerM WhittakerW WienerFilter WienerProcess WignerD WignerSemicircleDistribution WilksW WilksWTest WindowClickSelect WindowElements WindowFloating WindowFrame WindowFrameElements WindowMargins WindowMovable WindowOpacity WindowSelected WindowSize WindowStatusArea WindowTitle WindowToolbars WindowWidth With WolframAlpha WolframAlphaDate WolframAlphaQuantity WolframAlphaResult Word WordBoundary WordCharacter WordData WordSearch WordSeparators WorkingPrecision Write WriteString Wronskian XMLElement XMLObject Xnor Xor Yellow YuleDissimilarity ZernikeR ZeroSymmetric ZeroTest ZeroWidthTimes Zeta ZetaZero ZipfDistribution ZTest ZTransform $Aborted $ActivationGroupID $ActivationKey $ActivationUserRegistered $AddOnsDirectory $AssertFunction $Assumptions $AsynchronousTask $BaseDirectory $BatchInput $BatchOutput $BoxForms $ByteOrdering $Canceled $CharacterEncoding $CharacterEncodings $CommandLine $CompilationTarget $ConditionHold $ConfiguredKernels $Context $ContextPath $ControlActiveSetting $CreationDate $CurrentLink $DateStringFormat $DefaultFont $DefaultFrontEnd $DefaultImagingDevice $DefaultPath $Display $DisplayFunction $DistributedContexts $DynamicEvaluation $Echo $Epilog $ExportFormats $Failed $FinancialDataSource $FormatType $FrontEnd $FrontEndSession $GeoLocation $HistoryLength $HomeDirectory $HTTPCookies $IgnoreEOF $ImagingDevices $ImportFormats $InitialDirectory $Input $InputFileName $InputStreamMethods $Inspector $InstallationDate $InstallationDirectory $InterfaceEnvironment $IterationLimit $KernelCount $KernelID $Language $LaunchDirectory $LibraryPath $LicenseExpirationDate $LicenseID $LicenseProcesses $LicenseServer $LicenseSubprocesses $LicenseType $Line $Linked $LinkSupported $LoadedFiles $MachineAddresses $MachineDomain $MachineDomains $MachineEpsilon $MachineID $MachineName $MachinePrecision $MachineType $MaxExtraPrecision $MaxLicenseProcesses $MaxLicenseSubprocesses $MaxMachineNumber $MaxNumber $MaxPiecewiseCases $MaxPrecision $MaxRootDegree $MessageGroups $MessageList $MessagePrePrint $Messages $MinMachineNumber $MinNumber $MinorReleaseNumber $MinPrecision $ModuleNumber $NetworkLicense $NewMessage $NewSymbol $Notebooks $NumberMarks $Off $OperatingSystem $Output $OutputForms $OutputSizeLimit $OutputStreamMethods $Packages $ParentLink $ParentProcessID $PasswordFile $PatchLevelID $Path $PathnameSeparator $PerformanceGoal $PipeSupported $Post $Pre $PreferencesDirectory $PrePrint $PreRead $PrintForms $PrintLiteral $ProcessID $ProcessorCount $ProcessorType $ProductInformation $ProgramName $RandomState $RecursionLimit $ReleaseNumber $RootDirectory $ScheduledTask $ScriptCommandLine $SessionID $SetParentLink $SharedFunctions $SharedVariables $SoundDisplay $SoundDisplayFunction $SuppressInputFormHeads $SynchronousEvaluation $SyntaxHandler $System $SystemCharacterEncoding $SystemID $SystemWordLength $TemporaryDirectory $TemporaryPrefix $TextStyle $TimedOut $TimeUnit $TimeZone $TopDirectory $TraceOff $TraceOn $TracePattern $TracePostAction $TracePreAction $Urgent $UserAddOnsDirectory $UserBaseDirectory $UserDocumentsDirectory $UserName $Version $VersionNumber", -c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{cN:"list",b:/\{/,e:/\}/,i:/:/}]}});hljs.registerLanguage("fsharp",function(e){var t={b:"<",e:">",c:[e.inherit(e.TM,{b:/'[a-zA-Z0-9_]+/})]};return{aliases:["fs"],k:"yield! return! let! do!abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",c:[{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},{cN:"string",b:'"""',e:'"""'},e.C("\\(\\*","\\*\\)"),{cN:"class",bK:"type",e:"\\(|=|$",eE:!0,c:[e.UTM,t]},{cN:"annotation",b:"\\[<",e:">\\]",r:10},{cN:"attribute",b:"\\B('[A-Za-z])\\b",c:[e.BE]},e.CLCM,e.inherit(e.QSM,{i:null}),e.CNM]}});hljs.registerLanguage("verilog",function(e){return{aliases:["v"],cI:!0,k:{keyword:"always and assign begin buf bufif0 bufif1 case casex casez cmos deassign default defparam disable edge else end endcase endfunction endmodule endprimitive endspecify endtable endtask event for force forever fork function if ifnone initial inout input join macromodule module nand negedge nmos nor not notif0 notif1 or output parameter pmos posedge primitive pulldown pullup rcmos release repeat rnmos rpmos rtran rtranif0 rtranif1 specify specparam table task timescale tran tranif0 tranif1 wait while xnor xor",typename:"highz0 highz1 integer large medium pull0 pull1 real realtime reg scalared signed small strong0 strong1 supply0 supply0 supply1 supply1 time tri tri0 tri1 triand trior trireg vectored wand weak0 weak1 wire wor"},c:[e.CBCM,e.CLCM,e.QSM,{cN:"number",b:"\\b(\\d+'(b|h|o|d|B|H|O|D))?[0-9xzXZ]+",c:[e.BE],r:0},{cN:"typename",b:"\\.\\w+",r:0},{cN:"value",b:"#\\((?!parameter).+\\)"},{cN:"keyword",b:"\\+|-|\\*|/|%|<|>|=|#|`|\\!|&|\\||@|:|\\^|~|\\{|\\}",r:0}]}});hljs.registerLanguage("dos",function(e){var r=e.C(/@?rem\b/,/$/,{r:10}),t={cN:"label",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0};return{aliases:["bat","cmd"],cI:!0,k:{flow:"if else goto for in do call exit not exist errorlevel defined",operator:"equ neq lss leq gtr geq",keyword:"shift cd dir echo setlocal endlocal set pause copy",stream:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux",winutils:"ping net ipconfig taskkill xcopy ren del",built_in:"append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shiftsort start subst time title tree type ver verify vol"},c:[{cN:"envvar",b:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{cN:"function",b:t.b,e:"goto:eof",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),r]},{cN:"number",b:"\\b\\d+",r:0},r]}});hljs.registerLanguage("gherkin",function(e){return{aliases:["feature"],k:"Feature Background Ability Business Need Scenario Scenarios Scenario Outline Scenario Template Examples Given And Then But When",c:[{cN:"keyword",b:"\\*"},e.C("@[^@\r\n ]+","$"),{cN:"string",b:"\\|",e:"\\$"},{cN:"variable",b:"<",e:">"},e.HCM,{cN:"string",b:'"""',e:'"""'},e.QSM]}});hljs.registerLanguage("xml",function(t){var e="[A-Za-z0-9\\._:-]+",s={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"},c={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[c],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[c],starts:{e:"",rE:!0,sL:""}},s,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},c]}]}});hljs.registerLanguage("autohotkey",function(e){var r={cN:"escape",b:"`[\\s\\S]"},c=e.C(";","$",{r:0}),n=[{cN:"built_in",b:"A_[a-zA-Z0-9]+"},{cN:"built_in",bK:"ComSpec Clipboard ClipboardAll ErrorLevel"}];return{cI:!0,k:{keyword:"Break Continue Else Gosub If Loop Return While",literal:"A true false NOT AND OR"},c:n.concat([r,e.inherit(e.QSM,{c:[r]}),c,{cN:"number",b:e.NR,r:0},{cN:"var_expand",b:"%",e:"%",i:"\\n",c:[r]},{cN:"label",c:[r],v:[{b:'^[^\\n";]+::(?!=)'},{b:'^[^\\n";]+:(?!=)',r:0}]},{b:",\\s*,",r:10}])}});hljs.registerLanguage("r",function(e){var r="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[e.HCM,{b:r,l:r,k:{keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},r:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",r:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",r:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",r:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{b:"`",e:"`",r:0},{cN:"string",c:[e.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("cs",function(e){var r="abstract as base bool break byte case catch char checked const continue decimal dynamic default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null when object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield",t=e.IR+"(<"+e.IR+">)?";return{aliases:["csharp"],k:r,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},e.ASM,e.QSM,e.CNM,{bK:"class namespace interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("nsis",function(e){var t={cN:"symbol",b:"\\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)"},n={cN:"constant",b:"\\$+{[a-zA-Z0-9_]+}"},i={cN:"variable",b:"\\$+[a-zA-Z0-9_]+",i:"\\(\\){}"},r={cN:"constant",b:"\\$+\\([a-zA-Z0-9_]+\\)"},o={cN:"params",b:"(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)"},l={cN:"constant",b:"\\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversionsystem|ifdef|ifmacrodef|ifmacrondef|ifndef|if|include|insertmacro|macroend|macro|makensis|packhdr|searchparse|searchreplace|tempfile|undef|verbose|warning)"};return{cI:!1,k:{keyword:"Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileSeek FileWrite FileWriteByte FileWriteUTF16LE FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText IntCmp IntCmpU IntFmt IntOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionGroupEnd SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetPluginUnload SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption SubSectionEnd Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegStr WriteUninstaller XPStyle",literal:"admin all auto both colored current false force hide highest lastused leave listonly none normal notset off on open print show silent silentlog smooth textonly true user "},c:[e.HCM,e.CBCM,{cN:"string",b:'"',e:'"',i:"\\n",c:[{cN:"symbol",b:"\\$(\\\\(n|r|t)|\\$)"},t,n,i,r]},e.C(";","$",{r:0}),{cN:"function",bK:"Function PageEx Section SectionGroup SubSection",e:"$"},l,n,i,r,o,e.NM,{cN:"literal",b:e.IR+"::"+e.IR}]}});hljs.registerLanguage("less",function(e){var r="[\\w-]+",t="("+r+"|@{"+r+"})",a=[],c=[],n=function(e){return{cN:"string",b:"~?"+e+".*?"+e}},i=function(e,r,t){return{cN:e,b:r,r:t}},s=function(r,t,a){return e.inherit({cN:r,b:t+"\\(",e:"\\(",rB:!0,eE:!0,r:0},a)},b={b:"\\(",e:"\\)",c:c,r:0};c.push(e.CLCM,e.CBCM,n("'"),n('"'),e.CSSNM,i("hexcolor","#[0-9A-Fa-f]+\\b"),s("function","(url|data-uri)",{starts:{cN:"string",e:"[\\)\\n]",eE:!0}}),s("function",r),b,i("variable","@@?"+r,10),i("variable","@{"+r+"}"),i("built_in","~?`[^`]*?`"),{cN:"attribute",b:r+"\\s*:",e:":",rB:!0,eE:!0});var o=c.concat({b:"{",e:"}",c:a}),u={bK:"when",eW:!0,c:[{bK:"and not"}].concat(c)},C={cN:"attribute",b:t,e:":",eE:!0,c:[e.CLCM,e.CBCM],i:/\S/,starts:{e:"[;}]",rE:!0,c:c,i:"[<=$]"}},l={cN:"at_rule",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:c,r:0}},d={cN:"variable",v:[{b:"@"+r+"\\s*:",r:15},{b:"@"+r}],starts:{e:"[;}]",rE:!0,c:o}},p={v:[{b:"[\\.#:&\\[]",e:"[;{}]"},{b:t+"[^;]*{",e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",c:[e.CLCM,e.CBCM,u,i("keyword","all\\b"),i("variable","@{"+r+"}"),i("tag",t+"%?",0),i("id","#"+t),i("class","\\."+t,0),i("keyword","&",0),s("pseudo",":not"),s("keyword",":extend"),i("pseudo","::?"+t),{cN:"attr_selector",b:"\\[",e:"\\]"},{b:"\\(",e:"\\)",c:o},{b:"!important"}]};return a.push(e.CLCM,e.CBCM,l,d,p,C),{cI:!0,i:"[=>'/<($\"]",c:a}});hljs.registerLanguage("pf",function(t){var o={cN:"variable",b:/\$[\w\d#@][\w\d_]*/},e={cN:"variable",b://};return{aliases:["pf.conf"],l:/[a-z0-9_<>-]+/,k:{built_in:"block match pass load anchor|5 antispoof|10 set table",keyword:"in out log quick on rdomain inet inet6 proto from port os to routeallow-opts divert-packet divert-reply divert-to flags group icmp-typeicmp6-type label once probability recieved-on rtable prio queuetos tag tagged user keep fragment for os dropaf-to|10 binat-to|10 nat-to|10 rdr-to|10 bitmask least-stats random round-robinsource-hash static-portdup-to reply-to route-toparent bandwidth default min max qlimitblock-policy debug fingerprints hostid limit loginterface optimizationreassemble ruleset-optimization basic none profile skip state-defaultsstate-policy timeoutconst counters persistno modulate synproxy state|5 floating if-bound no-sync pflow|10 sloppysource-track global rule max-src-nodes max-src-states max-src-connmax-src-conn-rate overload flushscrub|5 max-mss min-ttl no-df|10 random-id",literal:"all any no-route self urpf-failed egress|5 unknown"},c:[t.HCM,t.NM,t.QSM,o,e]}});hljs.registerLanguage("lasso",function(e){var r="[a-zA-Z_][a-zA-Z0-9_.]*",a="<\\?(lasso(script)?|=)",t="\\]|\\?>",s={literal:"true false none minimal full all void and or not bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft",built_in:"array date decimal duration integer map pair string tag xml null boolean bytes keyword list locale queue set stack staticarray local var variable global data self inherited",keyword:"error_code error_msg error_pop error_push error_reset cache database_names database_schemanames database_tablenames define_tag define_type email_batch encode_set html_comment handle handle_error header if inline iterate ljax_target link link_currentaction link_currentgroup link_currentrecord link_detail link_firstgroup link_firstrecord link_lastgroup link_lastrecord link_nextgroup link_nextrecord link_prevgroup link_prevrecord log loop namespace_using output_none portal private protect records referer referrer repeating resultset rows search_args search_arguments select sort_args sort_arguments thread_atomic value_list while abort case else if_empty if_false if_null if_true loop_abort loop_continue loop_count params params_up return return_value run_children soap_definetag soap_lastrequest soap_lastresponse tag_name ascending average by define descending do equals frozen group handle_failure import in into join let match max min on order parent protected provide public require returnhome skip split_thread sum take thread to trait type where with yield yieldhome"},n=e.C("",{r:0}),o={cN:"preprocessor",b:"\\[noprocess\\]",starts:{cN:"markup",e:"\\[/noprocess\\]",rE:!0,c:[n]}},i={cN:"preprocessor",b:"\\[/noprocess|"+a},l={cN:"variable",b:"'"+r+"'"},c=[e.CLCM,{cN:"javadoc",b:"/\\*\\*!",e:"\\*/",c:[e.PWM]},e.CBCM,e.inherit(e.CNM,{b:e.CNR+"|(-?infinity|nan)\\b"}),e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null}),{cN:"string",b:"`",e:"`"},{cN:"variable",v:[{b:"[#$]"+r},{b:"#",e:"\\d+",i:"\\W"}]},{cN:"tag",b:"::\\s*",e:r,i:"\\W"},{cN:"attribute",v:[{b:"-"+e.UIR,r:0},{b:"(\\.\\.\\.)"}]},{cN:"subst",v:[{b:"->\\s*",c:[l]},{b:":=|/(?!\\w)=?|[-+*%=<>&|!?\\\\]+",r:0}]},{cN:"built_in",b:"\\.\\.?\\s*",r:0,c:[l]},{cN:"class",bK:"define",rE:!0,e:"\\(|=>",c:[e.inherit(e.TM,{b:e.UIR+"(=(?!>))?"})]}];return{aliases:["ls","lassoscript"],cI:!0,l:r+"|&[lg]t;",k:s,c:[{cN:"preprocessor",b:t,r:0,starts:{cN:"markup",e:"\\[|"+a,rE:!0,r:0,c:[n]}},o,i,{cN:"preprocessor",b:"\\[no_square_brackets",starts:{e:"\\[/no_square_brackets\\]",l:r+"|&[lg]t;",k:s,c:[{cN:"preprocessor",b:t,r:0,starts:{cN:"markup",e:"\\[noprocess\\]|"+a,rE:!0,c:[n]}},o,i].concat(c)}},{cN:"preprocessor",b:"\\[",r:0},{cN:"shebang",b:"^#!.+lasso9\\b",r:10}].concat(c)}});hljs.registerLanguage("prolog",function(c){var r={cN:"atom",b:/[a-z][A-Za-z0-9_]*/,r:0},b={cN:"name",v:[{b:/[A-Z][a-zA-Z0-9_]*/},{b:/_[A-Za-z0-9_]*/}],r:0},a={b:/\(/,e:/\)/,r:0},e={b:/\[/,e:/\]/},n={cN:"comment",b:/%/,e:/$/,c:[c.PWM]},t={cN:"string",b:/`/,e:/`/,c:[c.BE]},g={cN:"string",b:/0\'(\\\'|.)/},N={cN:"string",b:/0\'\\s/},o={b:/:-/},s=[r,b,a,o,e,n,c.CBCM,c.QSM,c.ASM,t,g,N,c.CNM];return a.c=s,e.c=s,{c:s.concat([{b:/\.$/}])}});hljs.registerLanguage("oxygene",function(e){var r="abstract add and array as asc aspect assembly async begin break block by case class concat const copy constructor continue create default delegate desc distinct div do downto dynamic each else empty end ensure enum equals event except exit extension external false final finalize finalizer finally flags for forward from function future global group has if implementation implements implies in index inherited inline interface into invariants is iterator join locked locking loop matching method mod module namespace nested new nil not notify nullable of old on operator or order out override parallel params partial pinned private procedure property protected public queryable raise read readonly record reintroduce remove repeat require result reverse sealed select self sequence set shl shr skip static step soft take then to true try tuple type union unit unsafe until uses using var virtual raises volatile where while with write xor yield await mapped deprecated stdcall cdecl pascal register safecall overload library platform reference packed strict published autoreleasepool selector strong weak unretained",t=e.C("{","}",{r:0}),a=e.C("\\(\\*","\\*\\)",{r:10}),n={cN:"string",b:"'",e:"'",c:[{b:"''"}]},o={cN:"string",b:"(#\\d+)+"},i={cN:"function",bK:"function constructor destructor procedure method",e:"[:;]",k:"function constructor|10 destructor|10 procedure|10 method|10",c:[e.TM,{cN:"params",b:"\\(",e:"\\)",k:r,c:[n,o]},t,a]};return{cI:!0,k:r,i:'("|\\$[G-Zg-z]|\\/\\*||->)',c:[t,a,e.CLCM,n,o,e.NM,i,{cN:"class",b:"=\\bclass\\b",e:"end;",k:r,c:[n,o,t,a,e.CLCM,i]}]}});hljs.registerLanguage("applescript",function(e){var t=e.inherit(e.QSM,{i:""}),r={cN:"params",b:"\\(",e:"\\)",c:["self",e.CNM,t]},o=e.C("--","$"),n=e.C("\\(\\*","\\*\\)",{c:["self",o]}),a=[o,n,e.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",constant:"AppleScript false linefeed return pi quote result space tab true",type:"alias application boolean class constant date file integer list number real record string text",command:"activate beep count delay launch log offset read round run say summarize write",property:"character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[t,e.CNM,{cN:"type",b:"\\bPOSIX file\\b"},{cN:"command",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"constant",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference))\\b"},{cN:"property",b:"\\b(POSIX path|(date|time) string|quoted form)\\b"},{cN:"function_start",bK:"on",i:"[${=;\\n]",c:[e.UTM,r]}].concat(a),i:"//|->|=>"}});hljs.registerLanguage("makefile",function(e){var a={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[a]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,a]}]}});hljs.registerLanguage("dust",function(e){var a="if eq ne lt lte gt gte select default math sep";return{aliases:["dst"],cI:!0,sL:"xml",subLanguageMode:"continuous",c:[{cN:"expression",b:"{",e:"}",r:0,c:[{cN:"begin-block",b:"#[a-zA-Z- .]+",k:a},{cN:"string",b:'"',e:'"'},{cN:"end-block",b:"\\/[a-zA-Z- .]+",k:a},{cN:"variable",b:"[a-zA-Z-.]+",k:a,r:0}]}]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"prompt",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure",subLanguageMode:"continuous"}}]}});hljs.registerLanguage("dart",function(e){var t={cN:"subst",b:"\\$\\{",e:"}",k:"true false null this is new super"},r={cN:"string",v:[{b:"r'''",e:"'''"},{b:'r"""',e:'"""'},{b:"r'",e:"'",i:"\\n"},{b:'r"',e:'"',i:"\\n"},{b:"'''",e:"'''",c:[e.BE,t]},{b:'"""',e:'"""',c:[e.BE,t]},{b:"'",e:"'",i:"\\n",c:[e.BE,t]},{b:'"',e:'"',i:"\\n",c:[e.BE,t]}]};t.c=[e.CNM,r];var n={keyword:"assert break case catch class const continue default do else enum extends false final finally for if in is new null rethrow return super switch this throw true try var void while with",literal:"abstract as dynamic export external factory get implements import library operator part set static typedef",built_in:"print Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double int num document window querySelector querySelectorAll Element ElementList"};return{k:n,c:[r,{cN:"dartdoc",b:"/\\*\\*",e:"\\*/",sL:"markdown",subLanguageMode:"continuous"},{cN:"dartdoc",b:"///",e:"$",sL:"markdown",subLanguageMode:"continuous"},e.CLCM,e.CBCM,{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"annotation",b:"@[A-Za-z]+"},{b:"=>"}]}}); \ No newline at end of file diff --git a/js/jquery-2.1.1.min.js b/js/jquery-2.1.1.min.js deleted file mode 100644 index e5ace116..00000000 --- a/js/jquery-2.1.1.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
    ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) -},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("