public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
mailcow dockerized
5f04dc0b
.gitignore | 3 +
README.md | 95 +
build-all.sh | 8 +
build-dovecot.sh | 52 +
build-memcached.sh | 27 +
build-mysql.sh | 80 +
build-network.sh | 8 +
build-nginx.sh | 38 +
build-php-fpm.sh | 32 +
build-postfix.sh | 53 +
build-redis.sh | 37 +
build-rmilter.sh | 35 +
build-rspamd.sh | 38 +
build-sogo.sh | 42 +
data/Dockerfiles/dovecot/Dockerfile | 20 +
data/Dockerfiles/mysql/.empty | 0
data/Dockerfiles/postfix/Dockerfile | 30 +
data/Dockerfiles/postfix/postfix.sh | 18 +
data/Dockerfiles/postfix/supervisord.conf | 17 +
data/Dockerfiles/redis/.empty | 0
data/Dockerfiles/rmilter/Dockerfile | 16 +
data/Dockerfiles/rspamd/Dockerfile | 16 +
data/Dockerfiles/sogo/Dockerfile | 15 +
data/assets/mysql/init.sql | 247 ++
data/assets/mysql/pw.sql | 2 +
data/assets/ssl/dhparams.pem | 8 +
data/assets/ssl/mail.crt | 32 +
data/assets/ssl/mail.key | 51 +
data/conf/dovecot/dovecot.conf | 220 ++
data/conf/dovecot/sql/dovecot-dict-sql.conf | 15 +
data/conf/dovecot/sql/dovecot-mysql.conf | 6 +
data/conf/mysql/my.cnf | 13 +
data/conf/nginx/site.conf | 81 +
data/conf/postfix/main.cf | 89 +
data/conf/postfix/master.cf | 45 +
data/conf/postfix/postscreen_access.cidr | 654 +++++
data/conf/postfix/smtp_dsn_filter | 6 +
.../conf/postfix/sql/mysql_relay_recipient_maps.cf | 5 +
.../postfix/sql/mysql_tls_enforce_in_policy.cf | 5 +
.../postfix/sql/mysql_tls_enforce_out_policy.cf | 5 +
.../mysql_virtual_alias_domain_catchall_maps.cf | 6 +
.../sql/mysql_virtual_alias_domain_mailbox_maps.cf | 5 +
.../postfix/sql/mysql_virtual_alias_domain_maps.cf | 5 +
data/conf/postfix/sql/mysql_virtual_alias_maps.cf | 5 +
.../conf/postfix/sql/mysql_virtual_domains_maps.cf | 5 +
.../sql/mysql_virtual_mailbox_limit_maps.cf | 5 +
.../conf/postfix/sql/mysql_virtual_mailbox_maps.cf | 5 +
.../postfix/sql/mysql_virtual_mxdomain_maps.cf | 5 +
data/conf/postfix/sql/mysql_virtual_sender_acl.cf | 5 +
.../postfix/sql/mysql_virtual_spamalias_maps.cf | 5 +
data/conf/rmilter/rmilter.conf | 42 +
data/conf/rspamd/local.d/dkim.conf | 19 +
data/conf/rspamd/local.d/metrics.conf | 14 +
data/conf/rspamd/local.d/redis.conf | 1 +
data/conf/rspamd/local.d/statistic.conf | 59 +
data/conf/rspamd/lua/rspamd.local.lua | 9 +
data/conf/rspamd/override.d/logging.inc | 3 +
data/conf/rspamd/override.d/worker-controller.inc | 2 +
data/conf/rspamd/override.d/worker-normal.inc | 1 +
data/conf/sogo/sogo.conf | 93 +
data/dkim/keys/mailcow.de.default | 15 +
data/dkim/txt/default_mailcow.de | 1 +
data/web/add.php | 276 ++
data/web/admin.php | 272 ++
data/web/autoconfig/mail/config-v1.1.xml_rc | 85 +
data/web/autoconfig/mail/config-v1.1.xml_sogo | 79 +
data/web/autodiscover.php | 121 +
data/web/delete.php | 165 ++
data/web/edit.php | 544 ++++
data/web/favicon.png | Bin 0 -> 6856 bytes
data/web/img/cow_mailcow.svg | 197 ++
data/web/inc/footer.inc.php | 81 +
data/web/inc/functions.inc.php | 2656 ++++++++++++++++++++
data/web/inc/header.inc.php | 207 ++
data/web/inc/languages.min.css | 1 +
data/web/inc/languages.png | Bin 0 -> 45164 bytes
data/web/inc/prerequisites.inc.php | 71 +
data/web/inc/triggers.inc.php | 122 +
data/web/inc/vars.inc.php | 36 +
data/web/index.php | 89 +
data/web/js/add.js | 16 +
data/web/js/admin.js | 31 +
data/web/js/index.js | 3 +
data/web/js/mailbox.js | 52 +
data/web/js/sorttable.js | 236 ++
data/web/js/user.js | 28 +
data/web/lang/lang.de.php | 358 +++
data/web/lang/lang.en.php | 361 +++
data/web/lang/lang.nl.php | 358 +++
data/web/lang/lang.pt.php | 355 +++
data/web/mailbox.php | 500 ++++
data/web/robots.txt | 2 +
data/web/user.php | 325 +++
fix-permissions.sh | 4 +
mailcow.conf | 55 +
print-status.sh | 3 +
96 files changed, 10163 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100755 build-all.sh
create mode 100755 build-dovecot.sh
create mode 100755 build-memcached.sh
create mode 100755 build-mysql.sh
create mode 100755 build-network.sh
create mode 100755 build-nginx.sh
create mode 100755 build-php-fpm.sh
create mode 100755 build-postfix.sh
create mode 100755 build-redis.sh
create mode 100755 build-rmilter.sh
create mode 100755 build-rspamd.sh
create mode 100755 build-sogo.sh
create mode 100644 data/Dockerfiles/dovecot/Dockerfile
create mode 100644 data/Dockerfiles/mysql/.empty
create mode 100644 data/Dockerfiles/postfix/Dockerfile
create mode 100755 data/Dockerfiles/postfix/postfix.sh
create mode 100644 data/Dockerfiles/postfix/supervisord.conf
create mode 100644 data/Dockerfiles/redis/.empty
create mode 100644 data/Dockerfiles/rmilter/Dockerfile
create mode 100644 data/Dockerfiles/rspamd/Dockerfile
create mode 100644 data/Dockerfiles/sogo/Dockerfile
create mode 100644 data/assets/mysql/init.sql
create mode 100644 data/assets/mysql/pw.sql
create mode 100644 data/assets/ssl/dhparams.pem
create mode 100644 data/assets/ssl/mail.crt
create mode 100644 data/assets/ssl/mail.key
create mode 100644 data/conf/dovecot/dovecot.conf
create mode 100644 data/conf/dovecot/sql/dovecot-dict-sql.conf
create mode 100644 data/conf/dovecot/sql/dovecot-mysql.conf
create mode 100644 data/conf/mysql/my.cnf
create mode 100644 data/conf/nginx/site.conf
create mode 100644 data/conf/postfix/main.cf
create mode 100644 data/conf/postfix/master.cf
create mode 100644 data/conf/postfix/postscreen_access.cidr
create mode 100644 data/conf/postfix/smtp_dsn_filter
create mode 100644 data/conf/postfix/sql/mysql_relay_recipient_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_tls_enforce_in_policy.cf
create mode 100644 data/conf/postfix/sql/mysql_tls_enforce_out_policy.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_alias_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_domains_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_mailbox_limit_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_mailbox_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_mxdomain_maps.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_sender_acl.cf
create mode 100644 data/conf/postfix/sql/mysql_virtual_spamalias_maps.cf
create mode 100644 data/conf/rmilter/rmilter.conf
create mode 100644 data/conf/rspamd/local.d/dkim.conf
create mode 100644 data/conf/rspamd/local.d/metrics.conf
create mode 100644 data/conf/rspamd/local.d/redis.conf
create mode 100644 data/conf/rspamd/local.d/statistic.conf
create mode 100644 data/conf/rspamd/lua/rspamd.local.lua
create mode 100644 data/conf/rspamd/override.d/logging.inc
create mode 100644 data/conf/rspamd/override.d/worker-controller.inc
create mode 100644 data/conf/rspamd/override.d/worker-normal.inc
create mode 100644 data/conf/sogo/sogo.conf
create mode 100644 data/dkim/keys/mailcow.de.default
create mode 100644 data/dkim/txt/default_mailcow.de
create mode 100644 data/web/add.php
create mode 100644 data/web/admin.php
create mode 100644 data/web/autoconfig/mail/config-v1.1.xml_rc
create mode 100644 data/web/autoconfig/mail/config-v1.1.xml_sogo
create mode 100644 data/web/autodiscover.php
create mode 100644 data/web/delete.php
create mode 100644 data/web/edit.php
create mode 100644 data/web/favicon.png
create mode 100644 data/web/img/cow_mailcow.svg
create mode 100644 data/web/inc/footer.inc.php
create mode 100644 data/web/inc/functions.inc.php
create mode 100644 data/web/inc/header.inc.php
create mode 100644 data/web/inc/languages.min.css
create mode 100644 data/web/inc/languages.png
create mode 100644 data/web/inc/prerequisites.inc.php
create mode 100644 data/web/inc/triggers.inc.php
create mode 100644 data/web/inc/vars.inc.php
create mode 100644 data/web/index.php
create mode 100644 data/web/js/add.js
create mode 100644 data/web/js/admin.js
create mode 100644 data/web/js/index.js
create mode 100644 data/web/js/mailbox.js
create mode 100644 data/web/js/sorttable.js
create mode 100644 data/web/js/user.js
create mode 100644 data/web/lang/lang.de.php
create mode 100644 data/web/lang/lang.en.php
create mode 100644 data/web/lang/lang.nl.php
create mode 100644 data/web/lang/lang.pt.php
create mode 100644 data/web/mailbox.php
create mode 100644 data/web/robots.txt
create mode 100644 data/web/user.php
create mode 100755 fix-permissions.sh
create mode 100644 mailcow.conf
create mode 100755 print-status.sh
Diff
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..7d600de8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+data/db/mysql/*
+data/db/redis/*
+data/vmail/*
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..87f7ba0d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+# mailcow dockerized
+
+## Configuration
+
+1. Open mailcow.conf and change stuff, do not touch versions, do not use special chars in passwords for now.
+
+2. ./build-all.sh
+
+Done.
+
+The default username for mailcow is `admin` with password `moohoo`.
+
+## Usage
+### build-*.files
+
+(Re)build a container:
+```
+./build-$name.sh
+```
+
+**/!\** Any previous container with the same name will be stopped and removed.
+No persistent data is deleted at any time.
+If an image exists, you will be asked wether or not to repull/rebuild it.
+
+### MySQL
+
+Connect to MySQL database:
+```
+./build-mysql.sh client
+```
+
+Init schema (will also be installed when running `./build-mysql.sh` without parameters):
+```
+./build-mysql.sh --init-schema
+```
+
+Reset mailcow admin to `admin:moohoo`:
+```
+./build-mysql.sh --reset-admin
+```
+
+### Redis
+
+Connect to redis database:
+```
+./build-mysql.sh client
+```
+
+### rspamd
+
+Use rspamadm:
+```
+docker exec -it rspamd-mailcow /bin/bash -c "rspamadm --help"
+```
+
+Use rspamc:
+```
+docker exec -it rspamd-mailcow /bin/bash -c "rspamc --help"
+```
+
+Set rspamd controller password:
+```
+docker exec -it rspamd-mailcow /bin/bash -c "rspamadm pw"
+```
+Copy given hash to data/conf/rspamd/override.d/worker-controller.inc:
+```
+...
+enable_password = "myhash";
+....
+```
+
+### Remove persistent data
+
+MySQL:
+
+```
+docker stop mysql-mailcow
+docker rm mysql-mailcow
+rm -rf data/db/mysql/*
+./build-mysql.sh
+```
+
+Redis:
+
+```
+# If you feel hardcore:
+docker stop redis-mailcow
+docker rm redus-mailcow
+rm -rf data/db/redis/*
+./build-redis.sh
+
+## It is almost always enough to just flush all keys:
+./build-redis client
+# FLUSHALL [ENTER]
+```
diff --git a/build-all.sh b/build-all.sh
new file mode 100755
index 00000000..9f54c373
--- /dev/null
+++ b/build-all.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+/bin/bash build-network.sh
+for buildx in $(ls build-*.sh | grep -vE "all|network"); do
+ echo "Starting build file ${buildx} ..."
+ /bin/bash ${buildx}
+done
+/bin/bash fix-permissions.sh
diff --git a/build-dovecot.sh b/build-dovecot.sh
new file mode 100755
index 00000000..02113d6a
--- /dev/null
+++ b/build-dovecot.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="dovecot-mailcow"
+
+build() {
+ docker build --no-cache -t dovecot data/Dockerfiles/dovecot/.
+}
+
+if [[ ${1} == "--reconf" ]]; then
+ reconf
+ exit 0
+fi
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q dovecot)" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi dovecot
+ build
+ fi
+else
+ build
+fi
+
+sed -i "/^connect/c\connect = \"host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}\"" data/conf/dovecot/sql/*
+
+docker run \
+ -p ${IMAP_PORT}:143 \
+ -p ${IMAPS_PORT}:993 \
+ -p ${POP_PORT}:110 \
+ -p ${POPS_PORT}:995 \
+ -p ${SIEVE_PORT}:4190\
+ -v ${PWD}/data/conf/dovecot:/etc/dovecot:ro \
+ -v ${PWD}/data/vmail:/var/vmail \
+ -v ${PWD}/data/assets/ssl:/etc/ssl/mail/:ro \
+ --name ${NAME} \
+ --network=${DOCKER_NETWORK} \
+ --network-alias dovecot \
+ -h ${MAILCOW_HOSTNAME} \
+ -d dovecot
+
+echo "Fixing permissions..."
+chown -R 5000:5000 data/vmail
diff --git a/build-memcached.sh b/build-memcached.sh
new file mode 100755
index 00000000..d8c55d22
--- /dev/null
+++ b/build-memcached.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="memcached-mailcow"
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q rmilter)" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi memcached
+ fi
+fi
+
+docker run \
+ --network=${DOCKER_NETWORK} \
+ -h memcached \
+ --network-alias memcached \
+ --name=${NAME} \
+ -d memcached
diff --git a/build-mysql.sh b/build-mysql.sh
new file mode 100755
index 00000000..6e9d8d23
--- /dev/null
+++ b/build-mysql.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="mysql-mailcow"
+
+reconf() {
+ echo "Installing database schema (this will not overwrite existing data)"
+ echo "It may take a while for MySQL to warm up, please wait..."
+ until docker exec ${NAME} /bin/bash -c "mysql -u'${DBUSER}' -p'${DBPASS}' ${DBNAME} < /assets/init.sql"; do
+ echo "Trying again in 2 seconds..."
+ sleep 2
+ done
+ echo "Done."
+}
+
+insert_admin() {
+ echo 'Setting mailcow UI admin login to "admin:moohoo"...'
+ echo "It may take a while for MySQL to warm up, please wait..."
+ until docker exec ${NAME} /bin/bash -c "mysql -u'${DBUSER}' -p'${DBPASS}' ${DBNAME} < /assets/pw.sql"; do
+ echo "Trying again in 2 seconds..."
+ sleep 2
+ done
+ echo "Done."
+}
+
+client() {
+ echo "==============================="
+ echo "DB: ${DBNAME} - USER: ${DBUSER}"
+ echo "==============================="
+ docker exec -it ${NAME} /bin/bash -c "mysql -u'${DBUSER}' -p'${DBPASS}' ${DBNAME}"
+}
+
+if [[ ${1} == "--init-schema" ]]; then
+ reconf
+ exit 0
+elif [[ ${1} == "--client" ]]; then
+ client
+ exit 0
+elif [[ ${1} == "--reset-admin" ]]; then
+ insert_admin
+ exit 0
+fi
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q mysql:${DBVERS})" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi mysql:${DBVERS}
+ fi
+fi
+
+docker run \
+ -v ${PWD}/data/db/mysql/:/var/lib/mysql/ \
+ -v ${PWD}/data/conf/mysql/:/etc/mysql/conf.d/ \
+ -v ${PWD}/data/assets/mysql:/assets \
+ --name=${NAME} \
+ --network=${DOCKER_NETWORK} \
+ -h mysql \
+ --network-alias mysql \
+ -e MYSQL_ROOT_PASSWORD=${DBROOT} \
+ -e MYSQL_DATABASE=${DBNAME} \
+ -e MYSQL_USER=${DBUSER} \
+ -e MYSQL_PASSWORD=${DBPASS} \
+ -d mysql:${DBVERS}
+
+reconf
+
+read -r -p "Do you want to reset mailcow admin to admin:moohoo? [y/N] " response
+response=${response,,}
+if [[ $response =~ ^(yes|y)$ ]]; then
+ insert_admin
+fi
diff --git a/build-network.sh b/build-network.sh
new file mode 100755
index 00000000..386e147c
--- /dev/null
+++ b/build-network.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+. mailcow.conf
+
+if [[ -z $(docker network ls --filter "name=${DOCKER_NETWORK}" -q) ]]; then
+ docker network create ${DOCKER_NETWORK} --subnet ${DOCKER_SUBNET}
+else
+ exit 0
+fi
diff --git a/build-nginx.sh b/build-nginx.sh
new file mode 100755
index 00000000..6adf677e
--- /dev/null
+++ b/build-nginx.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="nginx-mailcow"
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q nginx:${NGINXVERS})" ]]; then
+ read -r -p "Found image locally. Rebuild/pull anyway? [y/N] " response
+ response=${response,,} # tolower
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi nginx:${NGINXVERS}
+ fi
+fi
+
+sed -i "s#database_name.*#database_name = \"${DBNAME}\";#" data/web/inc/vars.inc.php
+sed -i "s#database_user.*#database_user = \"${DBUSER}\";#" data/web/inc/vars.inc.php
+sed -i "s#database_pass.*#database_pass = \"${DBPASS}\";#" data/web/inc/vars.inc.php
+
+docker run \
+ -d -p ${HTTP_PORT}:80 \
+ --name ${NAME} \
+ -v ${PWD}/data/web:/web:ro \
+ -v ${PWD}/data/conf/nginx/:/etc/nginx/conf.d/:ro \
+ --network=${DOCKER_NETWORK} \
+ --network-alias nginx \
+ -h nginx \
+ -d nginx:${NGINXVERS}
+
+echo "Installaing SOGo web resource files..."
+docker exec -it ${NAME} /bin/bash -c 'apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 && apt-get update && apt-get -y --force-yes install apt-transport-https'
+docker exec -it ${NAME} /bin/bash -c 'echo "deb http://packages.inverse.ca/SOGo/nightly/3/debian/ jessie jessie" > /etc/apt/sources.list.d/sogo.list && apt-get update && apt-get -y --force-yes install sogo'
diff --git a/build-php-fpm.sh b/build-php-fpm.sh
new file mode 100755
index 00000000..f546a956
--- /dev/null
+++ b/build-php-fpm.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="php-fpm-mailcow"
+
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q php:${PHPVERS})" ]]; then
+ read -r -p "Found image locally. Rebuild/pull anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi php:${PHPVERS}
+ fi
+fi
+
+docker run \
+ -v ${PWD}/data/web:/web:ro \
+ -v ${PWD}/data/dkim/:/shared/dkim/ \
+ -d --network=${DOCKER_NETWORK} \
+ --name ${NAME} --network-alias phpfpm -h phpfpm php:${PHPVERS}
+
+echo "Installing intl and mysql pdo extension..."
+docker exec ${NAME} /bin/bash -c "apt-get update && apt-get install -y zlib1g-dev libicu-dev g++ libidn11-dev dovecot-core"
+docker exec ${NAME} docker-php-ext-configure intl pdo pdo_mysql
+docker exec ${NAME} docker-php-ext-install intl pdo pdo_mysql
+echo "Restarting container..."
+docker restart ${NAME}
diff --git a/build-postfix.sh b/build-postfix.sh
new file mode 100755
index 00000000..4ca874ec
--- /dev/null
+++ b/build-postfix.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="postfix-mailcow"
+
+build() {
+ docker build --no-cache -t postfix data/Dockerfiles/postfix/.
+}
+
+if [[ ${1} == "--reconf" ]]; then
+ reconf
+ exit 0
+fi
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q postfix)" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi postfix
+ build
+ fi
+else
+ build
+fi
+
+sed -i "/myhostname/c\myhostname=${MAILCOW_HOSTNAME}" data/conf/postfix/main.cf
+sed -i "/^user/c\user = ${DBUSER}" data/conf/postfix/sql/*
+sed -i "/^password/c\password = ${DBPASS}" data/conf/postfix/sql/*
+sed -i "/^dbname/c\dbname = ${DBNAME}" data/conf/postfix/sql/*
+
+if [[ -z $(cat data/conf/postfix/main.cf | grep ${DOCKER_SUBNET}) ]]; then
+ sed -i -e "s_^mynetworks.*_& ${DOCKER_SUBNET}_" data/conf/postfix/main.cf
+fi
+
+docker run \
+ -p ${SMTP_PORT}:25 \
+ -p ${SMTPS_PORT}:465 \
+ -p ${SUBMISSION_PORT}:587 \
+ -v ${PWD}/data/conf/postfix:/opt/postfix/conf:ro \
+ -v ${PWD}/data/assets/ssl:/etc/ssl/mail/:ro \
+ --name ${NAME} \
+ --network=${DOCKER_NETWORK} \
+ --network-alias postfix \
+ -h ${MAILCOW_HOSTNAME} \
+ -d postfix
diff --git a/build-redis.sh b/build-redis.sh
new file mode 100755
index 00000000..fcb00a97
--- /dev/null
+++ b/build-redis.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="redis-mailcow"
+
+client() {
+ docker exec -it ${NAME} /bin/bash -c "redis-cli"
+}
+
+if [[ ${1} == "--client" ]]; then
+ client
+ exit 0
+fi
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q redis:${DBVERS})" ]]; then
+ read -r -p "Found image locally. Rebuild/pull anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi redis:${DBVERS}
+ fi
+fi
+
+docker run \
+ -v ${PWD}/data/db/redis/:/data/ \
+ --network=${DOCKER_NETWORK} \
+ -h redis \
+ --network-alias redis \
+ --name=${NAME} \
+ -d redis:${REDISVERS} --appendonly yes
diff --git a/build-rmilter.sh b/build-rmilter.sh
new file mode 100755
index 00000000..600b1695
--- /dev/null
+++ b/build-rmilter.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="rmilter-mailcow"
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+build() {
+ docker build -t rmilter data/Dockerfiles/rmilter/.
+}
+
+if [[ ! -z "$(docker images -q rmilter)" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi rmilter
+ build
+ fi
+else
+ build
+fi
+
+docker run \
+ -v ${PWD}/data/conf/rmilter/:/etc/rmilter.conf.d/ \
+ --network=${DOCKER_NETWORK} \
+ --network-alias rmilter \
+ -h rmilter \
+ --name ${NAME} \
+ -d rmilter
diff --git a/build-rspamd.sh b/build-rspamd.sh
new file mode 100755
index 00000000..4d8d70eb
--- /dev/null
+++ b/build-rspamd.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="rspamd-mailcow"
+
+build() {
+ docker build -t rspamd data/Dockerfiles/rspamd/.
+}
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+if [[ ! -z "$(docker images -q rspamd)" ]]; then
+ read -r -p "Found image locally. Rebuild/pull anyway? [y/N] " response
+ response=${response,,}
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi rspamd
+ build
+ fi
+fi
+
+docker run \
+ -v ${PWD}/data/conf/rspamd/override.d/:/etc/rspamd/override.d/ \
+ -v ${PWD}/data/conf/rspamd/local.d/:/etc/rspamd/local.d/ \
+ -v ${PWD}/data/conf/rspamd/lua/:/etc/rspamd/lua/ \
+ -v ${PWD}/data/dkim/txt/:/etc/rspamd/dkim/txt/:ro \
+ -v ${PWD}/data/dkim/keys/:/etc/rspamd/dkim/keys/:ro \
+ --network=${DOCKER_NETWORK} \
+ --network-alias rspamd \
+ -h rspamd \
+ --name ${NAME} \
+ -d rspamd
+
diff --git a/build-sogo.sh b/build-sogo.sh
new file mode 100755
index 00000000..91db42bf
--- /dev/null
+++ b/build-sogo.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+. mailcow.conf
+./build-network.sh
+
+NAME="sogo-mailcow"
+
+echo "Stopping and removing containers with name tag ${NAME}..."
+if [[ ! -z $(docker ps -af "name=${NAME}" -q) ]]; then
+ docker stop $(docker ps -af "name=${NAME}" -q)
+ docker rm $(docker ps -af "name=${NAME}" -q)
+fi
+
+build() {
+ docker build -t sogo data/Dockerfiles/sogo/.
+}
+
+if [[ ! -z "$(docker images -q sogo)" ]]; then
+ read -r -p "Found image locally. Rebuild anyway? [y/N] " response
+ response=${response,,} # tolower
+ if [[ $response =~ ^(yes|y)$ ]]; then
+ docker rmi sogo
+ build
+ fi
+else
+ build
+fi
+
+sed -i "s#OCSEMailAlarmsFolderURL.*#OCSEMailAlarmsFolderURL = \"mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_alarms_folder\";#" data/conf/sogo/sogo.conf
+sed -i "s#OCSFolderInfoURL.*#OCSFolderInfoURL = \"mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_folder_info\";#" data/conf/sogo/sogo.conf
+sed -i "s#OCSSessionsFolderURL.*#OCSSessionsFolderURL = \"mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_sessions_folder\";#" data/conf/sogo/sogo.conf
+sed -i "s#SOGoProfileURL.*#SOGoProfileURL = \"mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_user_profile\";#" data/conf/sogo/sogo.conf
+sed -i "s#viewURL.*#viewURL = \"mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_view\";#" data/conf/sogo/sogo.conf
+sed -i "s#WOWorkersCount.*#WOWorkersCount = \"${SOGOCHILDS}\";#" data/conf/sogo/sogo.conf
+
+docker run \
+ -v ${PWD}/data/conf/sogo/:/etc/sogo/ \
+ --name ${NAME} \
+ --network=${DOCKER_NETWORK} \
+ --network-alias sogo \
+ -h sogo \
+ -d -t sogo
diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile
new file mode 100644
index 00000000..6cd7a8b1
--- /dev/null
+++ b/data/Dockerfiles/dovecot/Dockerfile
@@ -0,0 +1,20 @@
+From ubuntu:xenial
+MAINTAINER Andre Peters <[email protected]>
+
+# Set noninteractive mode for apt-get
+ENV DEBIAN_FRONTEND noninteractive
+
+# Update
+RUN apt-get update
+
+# Start editing
+# Install package here for cache
+RUN apt-get -y install dovecot-common dovecot-core dovecot-imapd dovecot-lmtpd dovecot-managesieved dovecot-sieve dovecot-mysql dovecot-pop3d
+
+RUN groupadd -g 5000 vmail
+RUN useradd -g vmail -u 5000 vmail -d /var/vmail
+
+EXPOSE 24 10001
+
+# Run
+CMD ["/usr/sbin/dovecot", "-F"]
diff --git a/data/Dockerfiles/mysql/.empty b/data/Dockerfiles/mysql/.empty
new file mode 100644
index 00000000..e69de29b
diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile
new file mode 100644
index 00000000..18d29cdb
--- /dev/null
+++ b/data/Dockerfiles/postfix/Dockerfile
@@ -0,0 +1,30 @@
+From ubuntu:xenial
+MAINTAINER Andre Peters <[email protected]>
+
+# Set noninteractive mode for apt-get
+ENV DEBIAN_FRONTEND noninteractive
+
+# Update
+RUN apt-get update
+
+# Start editing
+# Install package here for cache
+RUN apt-get -y install supervisor \
+ postfix \
+ sasl2-bin \
+ postfix \
+ postfix-mysql \
+ postfix-pcre \
+ rsyslog \
+ ca-certificates
+
+COPY supervisord.conf /etc/supervisor/supervisord.conf
+COPY postfix.sh /opt/postfix.sh
+
+RUN groupadd -g 5000 vmail
+RUN useradd -g vmail -u 5000 vmail -d /var/vmail
+
+EXPOSE 588
+
+# Run
+CMD /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
new file mode 100755
index 00000000..eabed030
--- /dev/null
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# http://superuser.com/questions/168412/using-supervisord-to-control-the-postfix-mta
+
+trap "postfix stop" SIGINT
+trap "postfix stop" SIGTERM
+trap "postfix reload" SIGHUP
+
+# start postfix
+postfix -c /opt/postfix/conf start
+
+# lets give postfix some time to start
+sleep 3
+
+# wait until postfix is dead (triggered by trap)
+while kill -0 $(cat /var/spool/postfix/pid/master.pid); do
+ sleep 5
+done
diff --git a/data/Dockerfiles/postfix/supervisord.conf b/data/Dockerfiles/postfix/supervisord.conf
new file mode 100644
index 00000000..f49940fd
--- /dev/null
+++ b/data/Dockerfiles/postfix/supervisord.conf
@@ -0,0 +1,17 @@
+[supervisord]
+nodaemon=true
+
+[program:rsyslog]
+command=/usr/sbin/rsyslogd -n
+autostart=true
+autorestart=true
+redirect_stderr=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..5906b587
--- /dev/null
+++ b/data/Dockerfiles/rmilter/Dockerfile
@@ -0,0 +1,16 @@
+FROM debian:jessie
+MAINTAINER Andre Peters <[email protected]>
+
+RUN apt-get update \
+ && apt-get install -y wget \
+ && wget -O- https://rspamd.com/apt-stable/gpg.key | apt-key add - \
+ && echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
+ && echo "deb-src http://rspamd.com/apt-stable/ jessie main" >> /etc/apt/sources.list.d/rspamd.list \
+ && apt-get update \
+ && apt-get --no-install-recommends -y --force-yes install rmilter
+
+CMD ["/usr/sbin/rmilter","-n", "-c", "/etc/rmilter.conf.d/rmilter.conf"]
+
+USER _rmilter
+
+EXPOSE 9000
diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile
new file mode 100644
index 00000000..225d9edf
--- /dev/null
+++ b/data/Dockerfiles/rspamd/Dockerfile
@@ -0,0 +1,16 @@
+FROM debian:jessie
+MAINTAINER Andre Peters <[email protected]>
+
+RUN apt-get update \
+ && apt-get install -y wget \
+ && wget -O- https://rspamd.com/apt-stable/gpg.key | apt-key add - \
+ && echo "deb http://rspamd.com/apt-stable/ jessie main" > /etc/apt/sources.list.d/rspamd.list \
+ && echo "deb-src http://rspamd.com/apt-stable/ jessie main" >> /etc/apt/sources.list.d/rspamd.list \
+ && apt-get update \
+ && apt-get --no-install-recommends -y --force-yes install rspamd
+
+CMD ["/usr/bin/rspamd","-f", "-u", "_rspamd", "-g", "_rspamd"]
+
+USER _rspamd
+
+EXPOSE 11333 11334
diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile
new file mode 100644
index 00000000..b879ed51
--- /dev/null
+++ b/data/Dockerfiles/sogo/Dockerfile
@@ -0,0 +1,15 @@
+FROM debian:jessie
+MAINTAINER Andre Peters <[email protected]>
+
+RUN apt-get update \
+ && apt-get -y --force-yes install apt-transport-https \
+ && apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \
+ && echo "deb http://packages.inverse.ca/SOGo/nightly/3/debian/ jessie jessie" > /etc/apt/sources.list.d/sogo.list \
+ && apt-get update \
+ && apt-get -y --force-yes install sogo sogo-activesync
+
+USER sogo
+
+CMD ["/usr/sbin/sogod"]
+
+EXPOSE 20000
diff --git a/data/assets/mysql/init.sql b/data/assets/mysql/init.sql
new file mode 100644
index 00000000..e7f15a19
--- /dev/null
+++ b/data/assets/mysql/init.sql
@@ -0,0 +1,247 @@
+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',
+ `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;
+
+DROP VIEW IF EXISTS grouped_mail_aliases;
+DROP VIEW IF EXISTS grouped_sender_acl;
+DROP VIEW IF EXISTS grouped_domain_alias_address;
+DROP VIEW IF EXISTS sogo_view;
+
+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 VIEW sogo_view (c_uid, c_name, c_password, c_cn, mail, aliases, ad_aliases, senderacl, home) AS
+SELECT mailbox.username, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), IFNULL(gs.send_as, ''), CONCAT('/var/vmail/', maildir)
+FROM mailbox
+LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username
+LEFT OUTER JOIN grouped_sender_acl gs ON gs.username = mailbox.username
+LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
+WHERE mailbox.active = '1';
+
+CREATE TABLE IF NOT EXISTS sogo_acl (
+ c_folder_id int(11) NOT NULL,
+ c_object varchar(255) NOT NULL,
+ c_uid varchar(255) NOT NULL,
+ c_role varchar(80) NOT NULL,
+ KEY sogo_acl_c_folder_id_idx (c_folder_id),
+ KEY sogo_acl_c_uid_idx (c_uid)
+) 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 varchar(2048) DEFAULT 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 int(11) NOT NULL,
+ c_name varchar(255) NOT NULL,
+ c_uid varchar(255) NOT NULL,
+ c_startdate int(11) DEFAULT NULL,
+ c_enddate int(11) DEFAULT NULL,
+ c_cycleenddate int(11) DEFAULT NULL,
+ c_title varchar(1000) NOT NULL,
+ c_participants text,
+ c_isallday int(11) DEFAULT NULL,
+ c_iscycle int(11) DEFAULT NULL,
+ c_cycleinfo text,
+ c_classification int(11) NOT NULL,
+ c_isopaque int(11) NOT NULL,
+ c_status int(11) NOT NULL,
+ c_priority int(11) DEFAULT NULL,
+ c_location varchar(255) DEFAULT NULL,
+ c_orgmail varchar(255) DEFAULT NULL,
+ c_partmails text,
+ c_partstates text,
+ c_category varchar(255) DEFAULT NULL,
+ c_sequence int(11) DEFAULT NULL,
+ c_component varchar(10) NOT NULL,
+ c_nextalarm int(11) DEFAULT NULL,
+ c_description text,
+ 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 int(11) NOT NULL,
+ c_name varchar(255) NOT NULL,
+ c_givenname varchar(255) DEFAULT NULL,
+ c_cn varchar(255) DEFAULT NULL,
+ c_sn varchar(255) DEFAULT NULL,
+ c_screenname varchar(255) DEFAULT NULL,
+ c_l varchar(255) DEFAULT NULL,
+ c_mail varchar(255) DEFAULT NULL,
+ c_o varchar(255) DEFAULT NULL,
+ c_ou varchar(255) DEFAULT NULL,
+ c_telephonenumber varchar(255) DEFAULT NULL,
+ c_categories varchar(255) DEFAULT NULL,
+ c_component varchar(10) NOT NULL,
+ 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 int(11) NOT NULL,
+ c_name varchar(255) NOT NULL DEFAULT '',
+ c_content mediumtext NOT NULL,
+ c_creationdate int(11) NOT NULL,
+ c_lastmodified int(11) NOT NULL,
+ c_version int(11) NOT NULL,
+ c_deleted int(11) DEFAULT NULL,
+ 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;
diff --git a/data/assets/mysql/pw.sql b/data/assets/mysql/pw.sql
new file mode 100644
index 00000000..f5a65466
--- /dev/null
+++ b/data/assets/mysql/pw.sql
@@ -0,0 +1,2 @@
+REPLACE INTO admin VALUES ('admin','{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1);
+REPLACE INTO domain_admins (username, domain, created, active) VALUES ('admin', 'ALL', NOW(), '1');
diff --git a/data/assets/ssl/dhparams.pem b/data/assets/ssl/dhparams.pem
new file mode 100644
index 00000000..cb1aba14
--- /dev/null
+++ b/data/assets/ssl/dhparams.pem
@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAytfW/P+fV4BLTcJhlHG49Vq7hQrmyUPP+NZ/6MUIG8FNlFaXxbFl
+NtarS/gfOpj+Q5LhS91gToQOqJIij03Jr7t3PdUkkDuIs11y5Ux6zsEQdBhok+yY
+tYvdYT4lbex1dLX36u/tn2VnPdh2jLltRjWN2jiUxjh/O+vXtfej8u4Rc2oOOOFS
+f0e2Ye2WeWXvQlhkcGu87kKIqklxbjmqVtE1fx5Ydvrl1P/HQiCq4YQLIx5skgQn
+e4LyvBdiuA44v1WhXSa0Lb4PcXUQcGhesGJZ/A3M1K/h/ZO47oUyL93odyAO8x3e
+mLHHsOWAh5MGO0ID2jANwuziri5LEeW4+wIBAg==
+-----END DH PARAMETERS-----
diff --git a/data/assets/ssl/mail.crt b/data/assets/ssl/mail.crt
new file mode 100644
index 00000000..c8529427
--- /dev/null
+++ b/data/assets/ssl/mail.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFezCCA2OgAwIBAgIJALl64rYl1fjjMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV
+BAYTAkRFMQwwCgYDVQQIDANOUlcxCzAJBgNVBAcMAktSMRIwEAYDVQQKDAlTZXJ2
+ZXJjb3cxFjAUBgNVBAMMDW1haWxjb3cubG9jYWwwHhcNMTYxMjA4MjEzMDM2WhcN
+MjYxMjA2MjEzMDM2WjBUMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQswCQYD
+VQQHDAJLUjESMBAGA1UECgwJU2VydmVyY293MRYwFAYDVQQDDA1tYWlsY293Lmxv
+Y2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvd/79BmtXZcgzwJw
+8i76C8e0waehYypibOkBqnFi4bF6Q7mhB1j/bA4LmXG4UpcX7ULlDozzaM7Hfi9Q
+v1STYR/S9ShXZNStwDYibOa1q/FG+b4qTjtFiWBW8wH/XxIv6JHX8/IjqwHIs/3B
+EVEl0LEs1RdNMKgSEJ9wbK3q+0pOvw9B6FnhCE2414SE1e7wYL50+NaKTHQcbft3
+ZcRGDTEh4euRKMmVTrBwmpYnNtiljJvHU4F9cdAFg8ZailwJerod1VXB93YX3Jtc
+qRQ9akNjFzLQ/6a4PhKAB8uaStEzri0yBdp+O0Qs/tbloAArAJW3dgE7Omxzso79
+Du4idDHyRmcLu5rsQzST+7kwaCHHWQ4c2mjlhibICGMUzwks39s1QI8CtjmU6AIy
+7F/XpYAJ70Fl7qy99ugrz8X50cPBFtLTYX18wZTUjl/s4qy+JPvUBt2MALPj/YnR
+fXck/emkwscmE1UhaycMW4U21/+5gOhWpFIBCKWnsvRn0SHi7lUzuWBnXvL5tmrA
+gsaFrm/L2JhW2WerZ61UpOVookYtUbk4Hr+Pq6yTgJShUw2i/B71Qr173PIxRV7u
+1qJeOWY3UMPLcfiAnEZFAo7cfLRvqZmHiNp6lALdmoiWllnVvzcRwR/DBvg4gaFt
+R6FeLDArhCdu04WENTd5E3XHRrUCAwEAAaNQME4wHQYDVR0OBBYEFFBMhsQlfxCI
+1GaT1ZGvGheUOGRkMB8GA1UdIwQYMBaAFFBMhsQlfxCI1GaT1ZGvGheUOGRkMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADujbXk9XVhkF6/WVTxANXVB
+tpIojCPEsXYqEhvMGtDGfqd8sJlEWM0vmuUvM52G7ULMf8aVfiOhLUkEFWpadL9v
+/uZ8EPUc+ZWxxBOEnJbszqrxs94u7K9dxmQnL1rjrW1UtkrT0ptuzJBBQcjdicwe
+VIl5Cn/eq+mkKZRVlctGtD4r1z8u5rUHoOE4RCOU5mfSafu15zzwiglh9wLuuXHC
+bi7Onau9gB1EfmhZwUAL2xZZwvlNGRc6Dz1LG+OXVIOgRHeyfnZQa1ErC4FY5J0Y
+NR+KT7JQW9zivyu0MsV3E2J7GzRAywKyP0m/F/qHJFWxPymILAyWVUlwtJswR5sE
+bT19zPeajrVrbpUMtQv3FhFObtSyw/eI/yRWUuhBapkk95DWl7OkffkQ5OUHG+fs
+QWj1d2Mdhf+jkpgqyL1DyPILsG7ADT0dL/3kZoJf1wjeqNfW3dDo0Ex9DlbznP2h
+ldnMeIQYuyNBqzNfaZGW2WManwHWtASV2Hn76QMVrMfLDnf3RRdEUplW3fsIfLQ0
+f2YVunLJNvll+2QGdCkmJUbLEvvvWmz0Ve+RalGtKi+VTd2I3u4fvFsAXad48wwq
+oK5xd6Se0MsTkcOukaPEkggjffmITyg5Hpqmg1yBSoaH5D/wujTy9X3QcQA30fU/
+ttoPblK2hlItcK4hHnkK
+-----END CERTIFICATE-----
diff --git a/data/assets/ssl/mail.key b/data/assets/ssl/mail.key
new file mode 100644
index 00000000..6ec56d8b
--- /dev/null
+++ b/data/assets/ssl/mail.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAvd/79BmtXZcgzwJw8i76C8e0waehYypibOkBqnFi4bF6Q7mh
+B1j/bA4LmXG4UpcX7ULlDozzaM7Hfi9Qv1STYR/S9ShXZNStwDYibOa1q/FG+b4q
+TjtFiWBW8wH/XxIv6JHX8/IjqwHIs/3BEVEl0LEs1RdNMKgSEJ9wbK3q+0pOvw9B
+6FnhCE2414SE1e7wYL50+NaKTHQcbft3ZcRGDTEh4euRKMmVTrBwmpYnNtiljJvH
+U4F9cdAFg8ZailwJerod1VXB93YX3JtcqRQ9akNjFzLQ/6a4PhKAB8uaStEzri0y
+Bdp+O0Qs/tbloAArAJW3dgE7Omxzso79Du4idDHyRmcLu5rsQzST+7kwaCHHWQ4c
+2mjlhibICGMUzwks39s1QI8CtjmU6AIy7F/XpYAJ70Fl7qy99ugrz8X50cPBFtLT
+YX18wZTUjl/s4qy+JPvUBt2MALPj/YnRfXck/emkwscmE1UhaycMW4U21/+5gOhW
+pFIBCKWnsvRn0SHi7lUzuWBnXvL5tmrAgsaFrm/L2JhW2WerZ61UpOVookYtUbk4
+Hr+Pq6yTgJShUw2i/B71Qr173PIxRV7u1qJeOWY3UMPLcfiAnEZFAo7cfLRvqZmH
+iNp6lALdmoiWllnVvzcRwR/DBvg4gaFtR6FeLDArhCdu04WENTd5E3XHRrUCAwEA
+AQKCAgEArhCYOb8QX6wcN6pVQLAwKnx6CM5T9UT11kIFdOtdaun42/1g0guUnMqD
+d7f48j3xgWDB/ATbYEmwOM3HiJ9QPMmf63+AHr+aSYtXI96czXPzTSA4SF+t77KS
+A1Thd5aEtQB+qPRiHnMUO211gRqTQC4sm20xJlntta90sSz/Lj+A0UZ7dTZwRdx6
+h5jE7hqN4yK2uSh0wIHxTiIp4vF8Brv0A9igynOCnRDDKfRdHrqdibmFkdgz2BKL
++7HrbsvRJOFaWCi2GNX6KhODbr1PUAtW2/2J+9QrMzxigsL0P4JpjlOAeD1FW6+0
+UCtRdsywn2ihN10JnxWtOxQ6iWVlzut52uDnwUa09GThSVnurJihV9mSWyk9lNuy
+0kILtSmYn6UbokOgmfjH0E2Ks1qbskD8GlI9g/wkhs5YC+ZYW2MP9FG39n4/QSnk
+boOTqht8JylWPVyzmvvcRf5nfEOZ5mF82L28Y/OfPn0gakYARxn1EnzpguF3ffFD
+NEn9lWzEAbldlnDslzi6YPOeyQwA6iLCesag5LSGdADrM7kAGHksJggeUb02BSd6
+Nmy6MVMF6tzQYdaqgKXoqKs5nRJLZR1k70ftju2UNWEN24aUd6U2lDOlkaYoucSk
+NohTUKXX0dibSGd9eU7hCNS75YoG1x2gCEOatVelG4EZQfIU/EECggEBAN6gBjv4
+kDuIZ1wk0BBt/ijARH8FAzHm0hr8oyWpq6Sdrq5y9iAbvFNwEXJ/ft6NNcCF9maT
+e5oG5NpoaV0FN5W8qQ8rGnESV/fZOxJEr1yJPEq4yDIspHEXkBjvTgYWjuXRve9n
+NtsSv1crRFxW5IizPkZklbJUZD7oH5iHB15UGfdpKr5Fx3JpsaXht3dMEJ4YQetF
+Mr9jcBGwYCYmlWgpKkD+HadgjbNdG4ztKTFEU7/ElEIIR86IDcqJsz0XsmZIjwUU
+3lsPhVo8Km8ohvGA/WqAaf6ebN9PXjiUFXfjHlveHPTtrd5MCutnxUk0kY4/srmB
+5avH3bxXbKiufiMCggEBANpXEGY9f020vHFUC0vNOeCym8XuXqFyvx6Cl6tTlj8S
+dZCWoHljnJg2HbbJcdh92rri9f+ahNNpZ9/0PQi9yBThWt9aP90Tw3+BhxUyvlPL
+FsFX5IdNq403Ls9iyZuj1Rf9lc65d9lr7TVC5CMI7+BN3CftjvOw3yGucJno+MLW
+AvENx3+NnZ2Hy9nNJp54lbDJe8anP57kvDIKcbmmvVW2ktQKcZqAyBUq0E8mOtkz
+66ZRV+/pSnwugb0Eols3s54OvtOoGBnq1r8GVhf/x23J0UvBoHqqURQFJ5oTKxQW
+zAJ7suGn3xUKBOatypXgg8ZL67rQqo0PxoNK0RcJuUcCggEAHWrf6ATMalF39wEW
+TVV7hD8DzhUHewyZLt+7XzqwZ6w+bObcBxojJJNmes7GIPpf4/TPvnY2mv/WNdYe
+NiB+W9b2L/7uG4rk/OdDmwJgecXYpbcNHTQw9pC6hdD5amyIrW2tv3jQEtrDVe1t
+txX0VOv6iqq37Tyhkn5xzmHpY1mRpNPMxh/KXyAATX8qEyWF/J4P99rI/elR4cSA
+sAnhLEZkQvpRSNDFaLIg9dpQ2yXAO1LqlF8rverUh7LycFw1QrbLz0wWpcnDQU05
+/j5Itpjo463cU7zzff6q4KcQvyrP1Cvhf6v4katSthCcTTQZF8brAwBbLPvYHQ8g
+WJnWKQKCAQAVJ8ZxAZhqIQ75NBl8GMB44xVw0i3dGs8l16V2djzik5lMjyuxV1N+
+9A9g/JfJUDh3TzJit8gS6+2ip3madTkDvOofJhF2DEou+o/qH+aNG+pyhV+hNIdg
+wW4Jrhq2t+MX1fxD8XiJWom7VWXhdyY255RjUgM93W9hRhOm9gnUZwQV8y3XUBNr
+hhLcYaJSTIDEhmE12FKzxJnvh0+Jm3xQ58XGQdTMEZpRYrqYUK33Ca7ViKAqoMIU
+0jTD6cUJbZY7xFX9EBZ1vGleTPDelmvuWVWsL3CrMgF1HSK/LQhJhAP0YaPtdWSK
+F1RuPXyZlQ1vkz+d9EXyMQsdAYzM3KZVAoIBAF0gvM4fY0EvSDKevWnZtLyINHZV
+TC2HhElAREmblbziQ1GO00nCw+RXYmA7fMHuMNnHMcB/QubpMQxEPetAbtcX9jXW
+iBNIpHTQwNWBe+IGd1I7n6FA6Cqis4tdNFmaWxXv1aMpzU7K/aVcO3sK3SsjSy6A
+4bDJ9mlGCnIv5zc1on3lpMARBUGRF8mAQ6ejMuUjubtPa8cSUhUv3hoH0xG9bLJh
+0VDZ6bZ7QFLpNxFUlX7muSj8DNsjR77TBuN+Buk+pI68GDl6177Gm6UkZRYx4yi5
+xFCP9932L2tufcQaRsiIHdNEFAGMMPe2M22DUmSI0cSNgx4xKuLGJI4PkTM=
+-----END RSA PRIVATE KEY-----
diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf
new file mode 100644
index 00000000..4faa9147
--- /dev/null
+++ b/data/conf/dovecot/dovecot.conf
@@ -0,0 +1,220 @@
+auth_mechanisms = plain login
+#mail_debug = yes
+log_path = /dev/stdout
+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
+auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
+ssl_protocols = !SSLv3 !SSLv2
+ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA
+# Automatically regenerates every week
+ssl_dh_parameters_length = 2048
+log_timestamp = "%Y-%m-%d %H:%M:%S "
+recipient_delimiter = +
+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 managesieve {
+ process_limit = 256
+}
+service lmtp {
+ inet_listener lmtp-inet {
+ port = 24
+ }
+ user = vmail
+}
+listen = *,[::]
+ssl_cert = </etc/ssl/mail/mail.crt
+ssl_key = </etc/ssl/mail/mail.key
+userdb {
+ args = /etc/dovecot/sql/dovecot-mysql.conf
+ driver = sql
+}
+protocol imap {
+ mail_plugins = quota imap_quota imap_acl acl
+}
+protocol lmtp {
+ mail_plugins = quota sieve acl
+ auth_socket_path = /var/run/dovecot/auth-master
+}
+protocol sieve {
+ managesieve_logout_format = bytes=%i/%o
+}
+plugin {
+ acl_anyone = allow
+ acl_shared_dict = file:/var/vmail/shared-mailboxes.db
+ acl = vfile
+ quota = dict:Userquota::proxy::sqlquota
+ quota_rule2 = Trash:storage=+100%%
+ sieve = /var/vmail/sieve/%u.sieve
+ sieve_after = /var/vmail/sieve/global.sieve
+ sieve_max_script_size = 1M
+ sieve_quota_max_scripts = 0
+ sieve_quota_max_storage = 0
+}
+dict {
+ sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql.conf
+}
+remote 127.0.0.1 {
+ disable_plaintext_auth = no
+}
+mail_max_userip_connections = 500
diff --git a/data/conf/dovecot/sql/dovecot-dict-sql.conf b/data/conf/dovecot/sql/dovecot-dict-sql.conf
new file mode 100644
index 00000000..0271326e
--- /dev/null
+++ b/data/conf/dovecot/sql/dovecot-dict-sql.conf
@@ -0,0 +1,15 @@
+connect = "host=mysql dbname=mailcow user=mailcow password=mysafepasswd"
+
+map {
+ pattern = priv/quota/storage
+ table = quota2
+ username_field = username
+ value_field = bytes
+}
+map {
+ pattern = priv/quota/messages
+ table = quota2
+ username_field = username
+ value_field = messages
+}
+
diff --git a/data/conf/dovecot/sql/dovecot-mysql.conf b/data/conf/dovecot/sql/dovecot-mysql.conf
new file mode 100644
index 00000000..edf5ca6c
--- /dev/null
+++ b/data/conf/dovecot/sql/dovecot-mysql.conf
@@ -0,0 +1,6 @@
+driver = mysql
+connect = "host=mysql dbname=mailcow user=mailcow password=mysafepasswd"
+default_pass_scheme = SSHA256
+password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1')
+user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1'
+iterate_query = SELECT username FROM mailbox WHERE active='1';
diff --git a/data/conf/mysql/my.cnf b/data/conf/mysql/my.cnf
new file mode 100644
index 00000000..6f27d195
--- /dev/null
+++ b/data/conf/mysql/my.cnf
@@ -0,0 +1,13 @@
+[mysqld]
+character-set-client-handshake = FALSE
+character-set-server = utf8mb4
+collation-server = utf8mb4_unicode_ci
+innodb_file_per_table = TRUE
+innodb_file_format = barracuda
+innodb_large_prefix = TRUE
+
+[client]
+default-character-set = utf8mb4
+
+[mysql]
+default-character-set = utf8mb4
diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf
new file mode 100644
index 00000000..cff5c513
--- /dev/null
+++ b/data/conf/nginx/site.conf
@@ -0,0 +1,81 @@
+server {
+ index index.php index.html;
+ server_name _;
+ error_log /var/log/nginx/error.log;
+ access_log /var/log/nginx/access.log;
+ root /web;
+
+ location ~ \.php$ {
+ try_files $uri =404;
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+ fastcgi_pass phpfpm:9000;
+ fastcgi_index index.php;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ }
+
+ location /rspamd/ {
+ proxy_pass http://rspamd:11334/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+
+ location ^~ /Microsoft-Server-ActiveSync {
+ proxy_pass http://sogo/SOGo/Microsoft-Server-ActiveSync;
+ proxy_connect_timeout 1000;
+ proxy_next_upstream timeout error;
+ proxy_send_timeout 1000;
+ proxy_read_timeout 1000;
+ proxy_buffer_size 8k;
+ proxy_buffers 4 32k;
+ proxy_temp_file_write_size 64k;
+ proxy_busy_buffers_size 64k;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $host;
+ proxy_set_header x-webobjects-server-protocol HTTP/1.0;
+ proxy_set_header x-webobjects-remote-host $remote_addr;
+ proxy_set_header x-webobjects-server-name $server_name;
+ proxy_set_header x-webobjects-server-url $scheme://$host;
+ proxy_set_header x-webobjects-server-port $server_port;
+ client_body_buffer_size 128k;
+ client_max_body_size 100m;
+ }
+
+ location ^~ /SOGo {
+ proxy_pass http://sogo:20000;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $host;
+ proxy_set_header x-webobjects-server-protocol HTTP/1.0;
+ proxy_set_header x-webobjects-remote-host $remote_addr;
+ proxy_set_header x-webobjects-server-name $server_name;
+ proxy_set_header x-webobjects-server-url $scheme://$host;
+ proxy_set_header x-webobjects-server-port $server_port;
+ #proxy_connect_timeout 90;
+ #proxy_send_timeout 90;
+ #proxy_read_timeout 90;
+ #proxy_buffer_size 4k;
+ #proxy_buffers 4 32k;
+ #proxy_busy_buffers_size 64k;
+ #proxy_temp_file_write_size 64k;
+ client_body_buffer_size 128k;
+ client_max_body_size 100m;
+ break;
+ }
+
+ location /SOGo.woa/WebServerResources/ {
+ alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+ allow all;
+ }
+
+ location /SOGo/WebServerResources/ {
+ alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+ allow all;
+ }
+
+ location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
+ alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
+ }
+}
diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf
new file mode 100644
index 00000000..fbf468d4
--- /dev/null
+++ b/data/conf/postfix/main.cf
@@ -0,0 +1,89 @@
+myhostname=mail.mailcow.de
+biff = no
+append_dot_mydomain = no
+smtpd_tls_cert_file = /etc/ssl/mail/mail.crt
+smtpd_tls_key_file = /etc/ssl/mail/mail.key
+smtpd_use_tls=yes
+smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
+smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
+alias_maps = hash:/etc/aliases
+alias_database = hash:/etc/aliases
+myhostname=mail.mailcow.de
+relayhost =
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.55.0.0/16
+mailbox_size_limit = 0
+recipient_delimiter = +
+inet_interfaces = all
+inet_protocols = all
+bounce_queue_lifetime = 1d
+broken_sasl_auth_clients = yes
+disable_vrfy_command = yes
+maximal_backoff_time = 1800s
+maximal_queue_lifetime = 1d
+message_size_limit = 26214400
+milter_default_action = accept
+milter_protocol = 6
+minimal_backoff_time = 300s
+plaintext_reject_code = 550
+postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/postscreen_access.cidr
+postscreen_bare_newline_enable = no
+postscreen_blacklist_action = drop
+postscreen_cache_cleanup_interval = 24h
+postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
+postscreen_dnsbl_action = enforce
+postscreen_dnsbl_sites = b.barracudacentral.org=127.0.0.2*7 dnsbl.inps.de=127.0.0.2*7 bl.mailspike.net=127.0.0.2*5 bl.mailspike.net=127.0.0.[10;11;12]*4 dnsbl.sorbs.net=127.0.0.10*8 dnsbl.sorbs.net=127.0.0.5*6 dnsbl.sorbs.net=127.0.0.7*3 dnsbl.sorbs.net=127.0.0.8*2 dnsbl.sorbs.net=127.0.0.6*2 dnsbl.sorbs.net=127.0.0.9*2 zen.spamhaus.org=127.0.0.[10;11]*8 zen.spamhaus.org=127.0.0.[4..7]*6 zen.spamhaus.org=127.0.0.3*4 zen.spamhaus.org=127.0.0.2*3 hostkarma.junkemailfilter.com=127.0.0.2*3 hostkarma.junkemailfilter.com=127.0.0.4*1 hostkarma.junkemailfilter.com=127.0.1.2*1 wl.mailspike.net=127.0.0.[18;19;20]*-2 hostkarma.junkemailfilter.com=127.0.0.1*-2
+postscreen_dnsbl_threshold = 8
+postscreen_dnsbl_ttl = 5m
+postscreen_greet_action = enforce
+postscreen_greet_banner = $smtpd_banner
+postscreen_greet_ttl = 2d
+postscreen_greet_wait = 3s
+postscreen_non_smtp_command_enable = no
+postscreen_pipelining_enable = no
+proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
+queue_run_delay = 300s
+relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mxdomain_maps.cf
+relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
+sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
+smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
+smtp_tls_cert_file = /etc/ssl/mail/mail.crt
+smtp_tls_key_file = /etc/ssl/mail/mail.key
+smtp_tls_loglevel = 1
+smtp_tls_security_level = may
+smtpd_data_restrictions = reject_unauth_pipelining, permit
+smtpd_delay_reject = yes
+smtpd_error_sleep_time = 10s
+smtpd_hard_error_limit = ${stress?1}${stress:5}
+smtpd_helo_required = yes
+smtpd_proxy_timeout = 600s
+smtpd_recipient_restrictions = check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, permit_sasl_authenticated, permit_mynetworks, reject_invalid_helo_hostname, reject_unknown_reverse_client_hostname, reject_unauth_destination
+smtpd_sasl_auth_enable = yes
+smtpd_sasl_authenticated_header = yes
+smtpd_sasl_path = inet:dovecot:10001
+smtpd_sasl_type = dovecot
+smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf
+smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_mynetworks, reject_sender_login_mismatch, permit_sasl_authenticated, reject_unlisted_sender, reject_unknown_sender_domain
+smtpd_soft_error_limit = 3
+smtpd_tls_auth_only = yes
+smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem
+smtpd_tls_eecdh_grade = strong
+smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL
+smtpd_tls_loglevel = 1
+smtpd_tls_mandatory_ciphers = high
+smtpd_tls_mandatory_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL
+smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
+smtpd_tls_protocols = !SSLv2, !SSLv3
+smtpd_tls_security_level = may
+tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA
+virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
+virtual_gid_maps = static:5000
+virtual_mailbox_base = /var/vmail/
+virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
+virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_mailbox_maps.cf
+virtual_minimum_uid = 104
+virtual_transport = lmtp:inet:dovecot:24
+virtual_uid_maps = static:5000
+smtpd_milters = inet:rmilter:9900
+non_smtpd_milters = inet:rmilter:9900
+milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf
new file mode 100644
index 00000000..e48ea92e
--- /dev/null
+++ b/data/conf/postfix/master.cf
@@ -0,0 +1,45 @@
+smtp inet n - n - 1 postscreen
+smtpd pass - - n - - smtpd
+ -o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname
+smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
+submission inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
+ -o smtpd_enforce_tls=yes
+ -o smtpd_tls_security_level=encrypt
+ -o tls_preempt_cipherlist=yes
+588 inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
+ -o smtpd_tls_auth_only=no
+smtp_enforced_tls unix - - n - - smtp
+ -o smtp_tls_security_level=encrypt
+ -o syslog_name=enforced-tls-smtp
+ -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
+tlsproxy unix - - n - 0 tlsproxy
+dnsblog unix - - n - 0 dnsblog
+pickup fifo n - n 60 1 pickup
+cleanup unix n - n - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+tlsmgr unix - - n 1000? 1 tlsmgr
+rewrite unix - - n - - trivial-rewrite
+bounce unix - - n - 0 bounce
+defer unix - - n - 0 bounce
+trace unix - - n - 0 bounce
+verify unix - - n - 1 verify
+flush unix n - n 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+showq unix n - n - - showq
+error unix - - n - - error
+retry unix - - n - - error
+discard unix - - n - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - n - - lmtp
+anvil unix - - n - 1 anvil
+scache unix - - n - 1 scache
+maildrop unix - n n - - pipe flags=DRhu
+ user=vmail argv=/usr/bin/maildrop -d ${recipient}
diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr
new file mode 100644
index 00000000..c76ec681
--- /dev/null
+++ b/data/conf/postfix/postscreen_access.cidr
@@ -0,0 +1,654 @@
+# Generated by Postwhite v1.30 on Thu Dec 8 21:11:27 CET 2016
+# https://github.com/stevejenkins/postwhite/
+# 651 total rules
+2a00:1450:4000::/36 permit
+2a01:111:f400::/48 permit
+2a04:35c0::/29 permit
+2c0f:fb50:4000::/36 permit
+5.135.24.0/24 permit
+8.20.114.31 permit
+8.25.194.0/23 permit
+8.25.196.0/23 permit
+12.130.86.238 permit
+13.111.0.0/19 permit
+17.36.0.0/16 permit
+17.41.0.0/16 permit
+17.110.0.0/15 permit
+17.120.0.0/16 permit
+17.133.0.0/16 permit
+17.139.0.0/16 permit
+17.142.0.0/15 permit
+17.151.1.0/24 permit
+17.158.0.0/15 permit
+17.162.0.0/15 permit
+17.164.0.0/16 permit
+17.171.37.0/24 permit
+17.172.0.0/16 permit
+23.21.83.90 permit
+23.23.237.213 permit
+23.96.52.53 permit
+23.100.122.175 permit
+23.103.128.0/19 permit
+23.103.131.7 permit
+23.103.191.0/24 permit
+23.103.198.0/23 permit
+23.103.200.0/21 permit
+23.103.208.0/21 permit
+23.103.224.0/19 permit
+23.253.182.0/24 permit
+23.253.182.103 permit
+23.253.183.0/24 permit
+23.253.183.145 permit
+23.253.183.146 permit
+23.253.183.147 permit
+23.253.183.148 permit
+23.253.183.150 permit
+27.126.146.0/24 permit
+37.59.69.128/25 permit
+37.59.249.0/24 permit
+37.188.97.188/32 permit
+40.92.0.0/14 permit
+40.96.32.50 permit
+40.97.113.34 permit
+40.97.113.210 permit
+40.97.153.146 permit
+40.97.155.26 permit
+40.97.156.114 permit
+40.97.160.2 permit
+40.97.164.146 permit
+40.97.166.138 permit
+40.97.170.154 permit
+40.107.0.0/17 permit
+40.107.128.0/18 permit
+41.74.192.0/22 permit
+41.74.196.0/22 permit
+41.74.200.0/22 permit
+41.74.201.0/24 permit
+41.74.204.0/22 permit
+41.74.205.0/24 permit
+46.19.168.0/23 permit
+50.18.45.249 permit
+50.18.121.236 permit
+50.18.121.248 permit
+50.18.123.221 permit
+50.18.124.70 permit
+50.18.125.97 permit
+50.18.125.237 permit
+50.18.126.162 permit
+50.22.164.201 permit
+50.23.218.192/27 permit
+50.31.32.0/19 permit
+50.31.36.197 permit
+50.31.36.199 permit
+50.31.36.205 permit
+50.31.36.208 permit
+50.31.36.213 permit
+50.31.60.1 permit
+50.31.156.96/27 permit
+50.207.218.237 permit
+51.4.71.62 permit
+52.0.20.102 permit
+52.95.48.152/29 permit
+52.95.49.88/29 permit
+52.205.61.79 permit
+54.172.97.247 permit
+54.173.229.38 permit
+54.214.39.184 permit
+54.240.0.0/18 permit
+54.241.16.209 permit
+54.243.205.80 permit
+54.244.242.0/24 permit
+62.17.146.128/26 permit
+63.80.14.0/23 permit
+63.111.28.137 permit
+63.128.21.0/24 permit
+64.4.22.64/26 permit
+64.18.0.0/20 permit
+64.20.241.45 permit
+64.34.47.128/27 permit
+64.34.57.192/26 permit
+64.79.155.0/24 permit
+64.79.155.192 permit
+64.127.115.252 permit
+64.132.88.0/23 permit
+64.132.92.0/24 permit
+64.135.77.0/24 permit
+64.135.83.0/24 permit
+64.233.160.0/19 permit
+65.39.215.0/24 permit
+65.54.51.64/26 permit
+65.54.61.64/26 permit
+65.54.121.120/29 permit
+65.54.121.124/31 permit
+65.54.190.0/24 permit
+65.54.241.0/24 permit
+65.55.33.64/28 permit
+65.55.34.0/24 permit
+65.55.42.224/28 permit
+65.55.52.224/27 permit
+65.55.77.28 permit
+65.55.78.128/25 permit
+65.55.81.48/28 permit
+65.55.81.54/31 permit
+65.55.85.12 permit
+65.55.88.0/24 permit
+65.55.90.0/24 permit
+65.55.94.0/25 permit
+65.55.111.0/24 permit
+65.55.113.64/26 permit
+65.55.116.0/25 permit
+65.55.126.0/25 permit
+65.55.169.0/24 permit
+65.55.174.0/25 permit
+65.55.178.128/27 permit
+65.55.234.192/26 permit
+65.110.161.77 permit
+65.212.180.36 permit
+65.242.92.0/24 permit
+65.242.92.15 permit
+66.77.16.201/32 permit
+66.102.0.0/20 permit
+66.135.215.0/24 permit
+66.135.222.1 permit
+66.211.168.230/31 permit
+66.211.184.0/23 permit
+66.220.144.128/25 permit
+66.220.155.0/24 permit
+66.220.155.128/25 permit
+66.220.157.0/25 permit
+66.231.80.0/20 permit
+66.249.80.0/20 permit
+67.23.31.6 permit
+67.72.99.26 permit
+67.221.168.65 permit
+67.228.2.24/30 permit
+67.228.21.184/29 permit
+67.228.37.4/30 permit
+67.228.50.32/27 permit
+67.228.50.54/31 permit
+67.231.145.42 permit
+67.231.153.30 permit
+68.232.192.0/20 permit
+69.63.178.128/25 permit
+69.63.179.25 permit
+69.63.184.0/25 permit
+69.65.42.195 permit
+69.65.49.192/29 permit
+69.162.98.0/24 permit
+69.171.232.0/24 permit
+69.171.232.128/25 permit
+69.171.244.0/24 permit
+70.37.151.128/25 permit
+70.42.149.35 permit
+72.3.185.0/24 permit
+72.3.237.64/28 permit
+72.5.230.111/32 permit
+72.14.192.0/18 permit
+72.21.192.0/19 permit
+72.21.212.0/25 permit
+72.21.217.142/32 permit
+72.32.154.0/24 permit
+72.32.217.0/24 permit
+72.32.243.0/24 permit
+72.249.147.250/32 permit
+74.63.63.115 permit
+74.63.63.121 permit
+74.63.194.126 permit
+74.63.234.75 permit
+74.63.236.0/24 permit
+74.86.113.28/30 permit
+74.86.129.240/30 permit
+74.86.131.208/30 permit
+74.86.132.208/30 permit
+74.86.160.160/30 permit
+74.86.164.188/30 permit
+74.86.171.192/30 permit
+74.86.195.28/30 permit
+74.86.207.36/30 permit
+74.86.226.216/30 permit
+74.86.236.240/30 permit
+74.86.241.250/31 permit
+74.112.64.26 permit
+74.112.67.243 permit
+74.112.170.21/32 permit
+74.125.0.0/16 permit
+74.201.84.0/24 permit
+74.201.152.59/32 permit
+74.201.154.0/24 permit
+74.201.155.25/32 permit
+74.201.155.26/32 permit
+74.201.155.27/32 permit
+74.201.155.28/32 permit
+74.201.155.79/32 permit
+74.202.227.52/32 permit
+74.208.4.192/26 permit
+74.208.5.64/26 permit
+74.208.122.0/26 permit
+74.209.250.0/24 permit
+74.209.250.84 permit
+75.126.200.128/27 permit
+75.126.253.0/24 permit
+75.126.253.48 permit
+80.231.25.0/24 permit
+80.231.219.0/24 permit
+81.223.46.0/27 permit
+82.165.159.0/24 permit
+85.222.130.192/26 permit
+85.222.138.192/26 permit
+86.61.88.25 permit
+87.238.80.0/21 permit
+87.253.232.0/21 permit
+91.194.248.0/23 permit
+91.198.22.0/24 permit
+91.211.240.0/24 permit
+91.211.242.0/24 permit
+91.211.243.0/24 permit
+91.220.42.0/24 permit
+94.236.119.0/26 permit
+94.245.112.0/27 permit
+94.245.112.10/31 permit
+94.245.120.64/26 permit
+96.43.144.0/20 permit
+96.43.144.64/28 permit
+96.43.144.64/31 permit
+96.43.147.64/28 permit
+96.43.148.64/28 permit
+96.43.148.64/31 permit
+96.43.151.64/28 permit
+96.43.152.64/27 permit
+96.43.153.64/27 permit
+96.46.150.192/27 permit
+101.53.164.192/26 permit
+103.11.200.0/22 permit
+103.13.69.0/24 permit
+103.28.42.0/24 permit
+103.237.104.0/22 permit
+104.40.211.35 permit
+104.43.195.251 permit
+104.47.0.0/17 permit
+104.130.96.0/28 permit
+104.130.122.0/23 permit
+104.245.209.192/26 permit
+106.50.16.0/28 permit
+107.0.11.224/27 permit
+108.174.0.0/24 permit
+108.174.0.215 permit
+108.174.3.0/24 permit
+108.174.6.0/24 permit
+108.175.18.45 permit
+108.175.30.45 permit
+108.177.8.0/21 permit
+108.177.96.0/19 permit
+111.221.23.128/25 permit
+111.221.26.0/27 permit
+111.221.66.0/25 permit
+111.221.69.128/25 permit
+111.221.112.0/21 permit
+124.47.150.0/24 permit
+124.47.189.0/24 permit
+129.41.77.70 permit
+129.41.169.249 permit
+131.107.0.0/16 permit
+131.107.1.18 permit
+131.107.1.19 permit
+131.107.1.20 permit
+131.107.1.37 permit
+131.107.1.44 permit
+131.107.1.48 permit
+131.107.1.56 permit
+131.253.30.0/24 permit
+131.253.121.20 permit
+134.170.113.0/26 permit
+134.170.140.0/24 permit
+134.170.141.64/26 permit
+134.170.143.0/24 permit
+134.170.174.0/24 permit
+136.146.128.64/27 permit
+136.146.208.16/28 permit
+136.146.210.16/28 permit
+136.147.46.192/26 permit
+136.147.62.192/26 permit
+136.147.128.0/20 permit
+136.147.176.0/20 permit
+146.88.28.0/24 permit
+146.101.78.0/24 permit
+147.243.1.47 permit
+147.243.1.48 permit
+147.243.1.153 permit
+147.243.128.24 permit
+147.243.128.26 permit
+151.101.37.140 permit
+157.55.0.192/26 permit
+157.55.1.128/26 permit
+157.55.2.0/25 permit
+157.55.9.128/25 permit
+157.55.11.0/25 permit
+157.55.49.0/25 permit
+157.55.61.0/24 permit
+157.55.157.128/25 permit
+157.55.158.0/23 permit
+157.55.225.0/25 permit
+157.55.234.0/24 permit
+157.56.24.0/25 permit
+157.56.110.0/23 permit
+157.56.112.0/24 permit
+157.56.120.128/26 permit
+157.56.172.28 permit
+157.56.232.0/21 permit
+157.56.240.0/20 permit
+157.56.248.0/21 permit
+157.151.208.65 permit
+162.88.4.0/24 permit
+162.88.36.0/24 permit
+162.248.185.121 permit
+163.47.180.0/22 permit
+165.254.167.152/30 permit
+165.254.167.156/31 permit
+165.254.167.162/31 permit
+165.254.168.66/31 permit
+165.254.168.68/31 permit
+165.254.168.70/31 permit
+165.254.168.72/31 permit
+166.78.68.0/22 permit
+166.78.68.221 permit
+166.78.69.146 permit
+166.78.69.169 permit
+166.78.69.170 permit
+166.78.71.131 permit
+167.89.0.0/17 permit
+167.89.16.30 permit
+167.89.16.183 permit
+167.89.16.245 permit
+167.89.25.84 permit
+167.89.32.5 permit
+167.89.32.50 permit
+167.89.46.159 permit
+167.89.46.185 permit
+167.89.60.95 permit
+167.89.62.118 permit
+167.89.64.9 permit
+167.89.65.0 permit
+167.89.65.53 permit
+167.89.65.100 permit
+167.89.74.233 permit
+167.89.75.33 permit
+167.89.75.126 permit
+167.89.75.136 permit
+167.89.75.164 permit
+167.89.101.2 permit
+167.89.101.192/28 permit
+167.220.67.238 permit
+172.217.0.0/19 permit
+173.0.84.224/28 permit
+173.0.94.244/30 permit
+173.193.132.0/23 permit
+173.193.132.134/31 permit
+173.193.210.32/27 permit
+173.194.0.0/16 permit
+173.203.79.182 permit
+173.203.81.39 permit
+173.224.160.128/25 permit
+173.224.161.128/25 permit
+173.228.155.0/24 permit
+174.36.80.208/28 permit
+174.36.84.8/29 permit
+174.36.84.16/29 permit
+174.36.84.32/29 permit
+174.36.84.144/29 permit
+174.36.84.240/29 permit
+174.36.85.248/30 permit
+174.36.92.96/27 permit
+174.36.114.128/30 permit
+174.36.114.140/30 permit
+174.36.114.148/30 permit
+174.36.114.152/29 permit
+174.37.67.28/30 permit
+174.37.226.64/27 permit
+174.129.194.241 permit
+174.129.203.189 permit
+174.137.46.0/24 permit
+176.32.105.0/24 permit
+176.32.127.0/24 permit
+178.32.48.128 permit
+178.33.111.144 permit
+178.33.137.208/28 permit
+178.33.221.0/24 permit
+178.236.10.128/26 permit
+178.249.98.16/29 permit
+178.249.202.16/29 permit
+180.189.28.0/24 permit
+182.50.76.0/22 permit
+182.50.78.64/28 permit
+184.173.105.0/24 permit
+184.173.153.0/24 permit
+185.4.120.0/24 permit
+185.4.122.0/24 permit
+185.12.80.0/22 permit
+185.28.196.0/22 permit
+185.90.20.0/22 permit
+188.172.128.0/20 permit
+191.239.213.197 permit
+192.28.128.0/18 permit
+192.30.252.0/22 permit
+192.64.236.0/24 permit
+192.64.237.0/24 permit
+192.64.238.0/24 permit
+192.161.144.0/20 permit
+192.230.81.86 permit
+192.237.158.0/23 permit
+192.237.159.42 permit
+192.237.159.43 permit
+192.254.112.0/20 permit
+192.254.112.60 permit
+192.254.112.98/31 permit
+192.254.113.10 permit
+192.254.113.101 permit
+192.254.114.176 permit
+192.254.115.72 permit
+192.254.118.63 permit
+193.28.178.0/25 permit
+194.64.234.128/27 permit
+194.64.234.129 permit
+194.154.193.192/27 permit
+195.54.172.0/23 permit
+195.130.217.0/24 permit
+198.2.128.0/18 permit
+198.2.128.0/24 permit
+198.2.132.0/22 permit
+198.2.136.0/23 permit
+198.2.177.0/24 permit
+198.2.178.0/24 permit
+198.2.179.0/24 permit
+198.2.180.0/24 permit
+198.2.186.0/23 permit
+198.21.0.0/21 permit
+198.21.3.166 permit
+198.21.4.224 permit
+198.37.144.0/20 permit
+198.37.145.250 permit
+198.37.149.128 permit
+198.37.151.26 permit
+198.61.254.0/23 permit
+198.61.254.231 permit
+198.178.234.57 permit
+198.245.80.0/20 permit
+199.15.176.173 permit
+199.15.212.0/22 permit
+199.15.214.169/32 permit
+199.16.156.0/22 permit
+199.19.0.0/21 permit
+199.59.148.0/22 permit
+199.83.132.86 permit
+199.101.161.130 permit
+199.101.162.0/25 permit
+199.122.120.0/21 permit
+199.127.232.0/22 permit
+199.187.117.209 permit
+199.187.117.233 permit
+199.187.117.234/31 permit
+199.187.117.236/31 permit
+199.187.118.201 permit
+199.187.118.202/31 permit
+199.187.118.204 permit
+199.187.118.209 permit
+199.201.64.23 permit
+199.201.65.23 permit
+199.255.192.0/22 permit
+202.129.242.0/23 permit
+202.177.148.100 permit
+202.177.148.110 permit
+203.32.4.25 permit
+203.55.21.0/24 permit
+203.62.195.0/24 permit
+203.81.17.0/24 permit
+203.122.32.250 permit
+203.145.57.160/27 permit
+204.13.11.48/29 permit
+204.13.11.48/30 permit
+204.13.248.0/22 permit
+204.14.232.0/21 permit
+204.14.232.64/28 permit
+204.14.234.64/28 permit
+204.14.238.0/27 permit
+204.29.186.0/23 permit
+204.75.142.0/24 permit
+204.92.114.187 permit
+204.92.114.203 permit
+204.92.114.204/31 permit
+204.153.121.0/24 permit
+205.139.110.0/23 permit
+205.201.128.0/20 permit
+205.201.131.128/25 permit
+205.201.132.14 permit
+205.201.134.128/25 permit
+205.201.136.0/23 permit
+205.201.137.229 permit
+205.201.139.0/24 permit
+205.201.140.14 permit
+205.207.104.0/22 permit
+205.217.25.132 permit
+205.217.25.135 permit
+205.251.233.32/32 permit
+205.251.233.36/32 permit
+206.25.247.143 permit
+206.25.247.155 permit
+206.165.246.80/29 permit
+206.191.224.0/19 permit
+206.246.157.1 permit
+207.46.4.128/25 permit
+207.46.22.35 permit
+207.46.22.98 permit
+207.46.22.101 permit
+207.46.50.72 permit
+207.46.50.82 permit
+207.46.50.192/26 permit
+207.46.50.224 permit
+207.46.51.64/26 permit
+207.46.52.71 permit
+207.46.52.79 permit
+207.46.58.128/25 permit
+207.46.100.0/24 permit
+207.46.101.128/26 permit
+207.46.116.128/29 permit
+207.46.117.0/24 permit
+207.46.132.128/27 permit
+207.46.163.0/24 permit
+207.46.198.0/25 permit
+207.46.200.0/27 permit
+207.67.38.0/24 permit
+207.67.98.192/27 permit
+207.68.176.0/26 permit
+207.68.176.96/27 permit
+207.82.80.0/24 permit
+207.126.144.0/20 permit
+207.171.160.0/19 permit
+207.211.30.0/24 permit
+207.211.31.0/25 permit
+207.211.41.113 permit
+207.218.90.0/24 permit
+207.250.68.0/24 permit
+208.40.232.70 permit
+208.43.21.28/30 permit
+208.43.21.64/29 permit
+208.43.21.72/30 permit
+208.43.239.136/30 permit
+208.64.132.0/22 permit
+208.66.139.0/25 permit
+208.74.204.0/22 permit
+208.74.204.9 permit
+208.75.120.0/22 permit
+208.75.122.246 permit
+208.76.56.0/21 permit
+208.78.68.0/22 permit
+208.82.236.96/28 permit
+208.82.237.96/28 permit
+208.82.238.96/28 permit
+208.85.50.137 permit
+208.89.13.233 permit
+208.89.13.234/31 permit
+208.89.13.236/31 permit
+208.89.14.201 permit
+208.89.14.202/31 permit
+208.89.14.204 permit
+208.89.14.209 permit
+208.117.48.0/20 permit
+208.185.229.45 permit
+208.201.241.163 permit
+209.43.22.0/28 permit
+209.46.117.168 permit
+209.46.117.179 permit
+209.61.151.0/24 permit
+209.67.98.46 permit
+209.67.98.59 permit
+209.85.128.0/17 permit
+212.4.136.0/26 permit
+212.123.28.40/32 permit
+212.227.15.0/24 permit
+212.227.17.0/27 permit
+212.227.126.128/25 permit
+213.165.64.0/23 permit
+213.167.75.0/24 permit
+213.167.81.0/24 permit
+213.199.128.139 permit
+213.199.128.145 permit
+213.199.138.181 permit
+213.199.138.191 permit
+213.199.154.0/24 permit
+213.199.161.128/27 permit
+213.199.177.0/26 permit
+213.199.180.0/24 permit
+216.17.150.242 permit
+216.17.150.251 permit
+216.32.180.0/23 permit
+216.46.168.197 permit
+216.46.168.222 permit
+216.58.192.0/19 permit
+216.99.5.67 permit
+216.99.5.68 permit
+216.113.160.0/24 permit
+216.113.172.0/25 permit
+216.113.175.0/24 permit
+216.136.162.65 permit
+216.136.162.120/29 permit
+216.136.168.80/28 permit
+216.146.32.0/20 permit
+216.198.0.0/18 permit
+216.203.30.55 permit
+216.203.33.178/31 permit
+216.205.24.0/24 permit
+216.229.156.0/25 permit
+216.239.32.0/19 permit
+217.72.207.0/27 permit
+217.77.141.52 permit
+217.77.141.59 permit
+217.175.193.0/24 permit
+217.175.194.0/23 permit
+217.175.196.0/24 permit
+2001:4860:4000::/36 permit
+2404:6800:4000::/36 permit
+2607:f8b0:4000::/36 permit
+2620:109:c003:104::/64 permit
+2620:109:c006:104::/64 permit
+2620:109:c00d:104::/64 permit
+2620:119:50c0:207::/64 permit
+2800:3f0:4000::/36 permit
diff --git a/data/conf/postfix/smtp_dsn_filter b/data/conf/postfix/smtp_dsn_filter
new file mode 100644
index 00000000..2fb5101e
--- /dev/null
+++ b/data/conf/postfix/smtp_dsn_filter
@@ -0,0 +1,6 @@
+/^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/
+ 5$1
+/^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/
+ 5$1
+/^4.7.5(.*)/
+ 5.7.5$1
diff --git a/data/conf/postfix/sql/mysql_relay_recipient_maps.cf b/data/conf/postfix/sql/mysql_relay_recipient_maps.cf
new file mode 100644
index 00000000..9eb7b159
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_relay_recipient_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT DISTINCT CASE WHEN '%d' IN (SELECT domain FROM domain WHERE relay_all_recipients=1 AND domain='%d' AND backupmx=1) THEN '%s' ELSE (SELECT goto FROM alias WHERE address='%s' AND active='1') END AS result;
diff --git a/data/conf/postfix/sql/mysql_tls_enforce_in_policy.cf b/data/conf/postfix/sql/mysql_tls_enforce_in_policy.cf
new file mode 100644
index 00000000..de40b580
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_tls_enforce_in_policy.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', 'DUNNO') AS 'tls_enforce_in';
diff --git a/data/conf/postfix/sql/mysql_tls_enforce_out_policy.cf b/data/conf/postfix/sql/mysql_tls_enforce_out_policy.cf
new file mode 100644
index 00000000..34d61331
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_tls_enforce_out_policy.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'DUNNO') AS 'tls_enforce_out';
diff --git a/data/conf/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
new file mode 100644
index 00000000..484a0eac
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
@@ -0,0 +1,6 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
+
diff --git a/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
new file mode 100644
index 00000000..03061109
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'
diff --git a/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf
new file mode 100644
index 00000000..2718bb00
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
diff --git a/data/conf/postfix/sql/mysql_virtual_alias_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_maps.cf
new file mode 100644
index 00000000..a72c8bd8
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_alias_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT goto FROM alias WHERE address='%s' AND active='1';
diff --git a/data/conf/postfix/sql/mysql_virtual_domains_maps.cf b/data/conf/postfix/sql/mysql_virtual_domains_maps.cf
new file mode 100644
index 00000000..22e00938
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_domains_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1' UNION SELECT domain FROM domain WHERE domain='%s' AND active = '1' AND backupmx = '0'
diff --git a/data/conf/postfix/sql/mysql_virtual_mailbox_limit_maps.cf b/data/conf/postfix/sql/mysql_virtual_mailbox_limit_maps.cf
new file mode 100644
index 00000000..bb50a46d
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_mailbox_limit_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'
diff --git a/data/conf/postfix/sql/mysql_virtual_mailbox_maps.cf b/data/conf/postfix/sql/mysql_virtual_mailbox_maps.cf
new file mode 100644
index 00000000..e370919e
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_mailbox_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
diff --git a/data/conf/postfix/sql/mysql_virtual_mxdomain_maps.cf b/data/conf/postfix/sql/mysql_virtual_mxdomain_maps.cf
new file mode 100644
index 00000000..6994d02d
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_mxdomain_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND active = '1'
diff --git a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf
new file mode 100644
index 00000000..3707a2b2
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1'
diff --git a/data/conf/postfix/sql/mysql_virtual_spamalias_maps.cf b/data/conf/postfix/sql/mysql_virtual_spamalias_maps.cf
new file mode 100644
index 00000000..ac8d78ac
--- /dev/null
+++ b/data/conf/postfix/sql/mysql_virtual_spamalias_maps.cf
@@ -0,0 +1,5 @@
+user = mailcow
+password = mysafepasswd
+hosts = mysql
+dbname = mailcow
+query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= 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/local.d/dkim.conf b/data/conf/rspamd/local.d/dkim.conf
new file mode 100644
index 00000000..ffebd389
--- /dev/null
+++ b/data/conf/rspamd/local.d/dkim.conf
@@ -0,0 +1,19 @@
+sign_condition =<<EOD
+return function(task)
+ local from = task:get_from('smtp')
+ if from and from[1]['addr'] then
+ lastAtSymbol = from[1]['addr']:find("[^%@]+$")
+ local domain = from[1]['addr']:sub(lastAtSymbol, #from[1]['addr'])
+ local keyfile = io.open("/etc/rspamd/dkim/keys/" .. domain .. ".default")
+ if keyfile then
+ keyfile:close()
+ return {
+ key = "/etc/rspamd/dkim/keys/" .. domain .. ".default",
+ domain = domain,
+ selector = "default"
+ }
+ end
+ end
+ return false
+end
+EOD;
diff --git a/data/conf/rspamd/local.d/metrics.conf b/data/conf/rspamd/local.d/metrics.conf
new file mode 100644
index 00000000..4c60ca44
--- /dev/null
+++ b/data/conf/rspamd/local.d/metrics.conf
@@ -0,0 +1,14 @@
+symbol "MAILCOW_AUTH" {
+ description = "mailcow authenticated";
+ score = -20.0;
+}
+group "bayes" {
+ symbol "BAYES_SPAM" {
+ weight = 7.5;
+ description = "Message probably spam, probability: ";
+ }
+ symbol "BAYES_HAM" {
+ weight = -2.5;
+ description = "Message probably ham, probability: ";
+ }
+}
diff --git a/data/conf/rspamd/local.d/redis.conf b/data/conf/rspamd/local.d/redis.conf
new file mode 100644
index 00000000..f5644a30
--- /dev/null
+++ b/data/conf/rspamd/local.d/redis.conf
@@ -0,0 +1 @@
+servers = "redis:6379";
diff --git a/data/conf/rspamd/local.d/statistic.conf b/data/conf/rspamd/local.d/statistic.conf
new file mode 100644
index 00000000..ee923742
--- /dev/null
+++ b/data/conf/rspamd/local.d/statistic.conf
@@ -0,0 +1,59 @@
+classifier "bayes" {
+ tokenizer {
+ name = "osb";
+ }
+
+ backend = "redis";
+ servers = "redis:6379";
+ min_tokens = 11;
+ min_learns = 200;
+ autolearn = true;
+
+ per_user = <<EOD
+return function(task)
+ local rcpt = task:get_recipients(1)
+
+if rcpt then
+ one_rcpt = rcpt[1]
+ if one_rcpt['domain'] then
+ return one_rcpt['domain']
+ end
+end
+
+return nil
+end
+EOD
+
+ statfile {
+ symbol = "BAYES_HAM";
+ spam = false;
+ }
+ statfile {
+ symbol = "BAYES_SPAM";
+ spam = true;
+ }
+ learn_condition =<<EOD
+return function(task, is_spam, is_unlearn)
+ local prob = task:get_mempool():get_variable('bayes_prob', 'double')
+
+ if prob then
+ local in_class = false
+ local cl
+ if is_spam then
+ cl = 'spam'
+ in_class = prob >= 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..727876b7
--- /dev/null
+++ b/data/conf/rspamd/lua/rspamd.local.lua
@@ -0,0 +1,9 @@
+rspamd_config.MAILCOW_AUTH = {
+ callback = function(task)
+ local uname = task:get_user()
+ if uname then
+ return 1
+ end
+ 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..381850a0
--- /dev/null
+++ b/data/conf/rspamd/override.d/worker-controller.inc
@@ -0,0 +1,2 @@
+bind_socket = "*:11334";
+enable_password ="$2$ibe1yt89kq5rtb9juy8z7cmkt1yg5d9w$bezuyyo8o4kge13rzj8epasdf6ojsgo1jgojce8msbt5bsq9n3dy";
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..fae8b7b3
--- /dev/null
+++ b/data/conf/sogo/sogo.conf
@@ -0,0 +1,93 @@
+{
+ // START
+ // WILL BE UPDATED AUTOMATICALLY WHEN RUNNING build_sogo.sh SRIPT
+ OCSEMailAlarmsFolderURL = "mysql://mailcow:mysafepasswd@mysql:3306/mailcow/sogo_alarms_folder";
+ OCSFolderInfoURL = "mysql://mailcow:mysafepasswd@mysql:3306/mailcow/sogo_folder_info";
+ OCSSessionsFolderURL = "mysql://mailcow:mysafepasswd@mysql:3306/mailcow/sogo_sessions_folder";
+ SOGoProfileURL = "mysql://mailcow:mysafepasswd@mysql:3306/mailcow/sogo_user_profile";
+ WOWorkersCount = "20";
+ SOGoMemcachedHost = "memcached:11211";
+ SOGoUserSources =
+ (
+ {
+ type = sql;
+ id = directory;
+ viewURL = "mysql://mailcow:mysafepasswd@mysql:3306/mailcow/sogo_view";
+ canAuthenticate = YES;
+ isAddressBook = YES;
+ MailFieldNames = (aliases, ad_aliases, senderacl);
+ displayName = "Domain";
+ userPasswordAlgorithm = SSHA256;
+ }
+ );
+ // END
+
+ SOGoCalendarDefaultRoles = (
+ PublicViewer,
+ ConfidentialDAndTViewer,
+ PrivateDAndTViewer
+ );
+
+ SOGoACLsSendEMailNotifications = YES;
+ SOGoAppointmentSendEMailNotifications = YES;
+ SOGoDraftsFolderName = "Drafts";
+ SOGoJunkFolderName= "Junk";
+ SOGoMailDomain = "sogo.local";
+ SOGoEnableEMailAlarms = YES;
+ SOGoFoldersSendEMailNotifications = YES;
+ SOGoForwardEnabled = YES;
+
+ SOGoIMAPServer = "imap://dovecot:143/?tls=YES";
+ SOGoSieveServer = "sieve://dovecot:4190/?tls=YES";
+ // Can be used by SOGo as DOCKER_SUBNET is in mynetworks, TLS auth. is disabled here
+ SOGoSMTPServer = "postfix:588";
+ // Binds to DOCKER_SUBNET IP, do not change to 127./localhost, port is not exposed
+ WOPort = "0.0.0.0:20000";
+
+ SOGoLanguage = English;
+ SOGoMailAuxiliaryUserAccountsEnabled = YES;
+ SOGoMailCustomFromEnabled = YES;
+ SOGoMailingMechanism = smtp;
+ SOGoSMTPAuthenticationType = plain;
+
+ SxVMemLimit = 512;
+
+ SOGoMaximumPingInterval = 354;
+
+ SOGoInternalSyncInterval = 30;
+ SOGoMaximumSyncInterval = 354;
+
+ SOGoMaximumSyncWindowSize = 0;
+ SOGoMaximumSyncResponseSize = 1024;
+ MySQL4Encoding = "utf8mb4";
+
+ WOWatchDogRequestTimeout = 10;
+ WOListenQueueSize = 300;
+ WONoDetach = YES;
+ WOPort = "0.0.0.0:20000";
+
+ SOGoIMAPAclConformsToIMAPExt = Yes;
+ SOGoPageTitle = "SOGo Moo";
+ SOGoFirstDayOfWeek = "1";
+
+ SOGoSieveFolderEncoding = "UTF-8";
+ SOGoPasswordChangeEnabled = NO;
+ SOGoSentFolderName = "Sent";
+ SOGoMailShowSubscribedFoldersOnly = NO;
+ NGImap4ConnectionStringSeparator = "/";
+ SOGoSieveScriptsEnabled = YES;
+ SOGoTimeZone = "Europe/Berlin";
+ SOGoTrashFolderName = "Trash";
+ SOGoVacationEnabled = YES;
+
+ //SOGoDebugRequests = YES;
+ //SoDebugBaseURL = YES;
+ //ImapDebugEnabled = YES;
+ //SOGoEASDebugEnabled = YES;
+ //LDAPDebugEnabled = YES;
+ //PGDebugEnabled = YES;
+ //MySQL4DebugEnabled = YES;
+ //SOGoUIxDebugEnabled = YES;
+ //WODontZipResponse = YES;
+ WOLogFile = -;
+}
diff --git a/data/dkim/keys/mailcow.de.default b/data/dkim/keys/mailcow.de.default
new file mode 100644
index 00000000..1db8156a
--- /dev/null
+++ b/data/dkim/keys/mailcow.de.default
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDFBIov3EMJ/Xtq1Fw5RDlMeAb01ByPGu5ySuyTQaBnKoAWa3IY
+CQKOPC90DxEt099GCRnn2hmCdDS4XImmpk53/PDwlRM5EGnWWMSVgIuv1AaHwIRC
+oKtVjb0aBVu4ozRtDbL1dbbsStQWdsndJ5nPEj6YD1uwUnO1WCH6vDUOewIDAQAB
+AoGAIIoHaLAwQk4jPBmmwa6K6B5Kx9TggqIoD6hgOlH0dBWI4isMxPt3+JXoIHr8
+k10S2zZVmP1kiS84JdriwStmehBBWX63C4bbL4j7zJoDVOv5ECn1OOQVXEy1QyGY
+RyWU54JyfnOl9YvMJOhx2URuCNLa24AXJhYo6ifFUqRyJoECQQD2vWMquDL271th
+ND9/v52xjcI8/rkjsZDvhUH7LEPxt0wADEt/DA75Gt8BP3JM0HTAbD+opawXQtB/
+NhiKMqMpAkEAzGlvhNUCvk9g8mBVfLsRNH6KB9c1Qly3IqCradg2fn7mmmARUNDe
+CNaHAfaaw+ijJORb7S0SfE11Ai5tmcWdAwJANKjY2E41ulP9WbKP9tDLdBCAKwpm
+MwL7ntL+8P9ShO0M0FnPZw8IxwuAGsESwOggcsznjTPGlbRR0USXWi9SeQJAdV2w
+b0dSzOyM0H2pd/V8unRRUpEpflH3wMUZxqsjFtxMEaVJK+rRIafzWpg6YnPngF4x
+vetcKszaewcnXNxO+wJANkayeasY40NHZR0d3cY47Z1rGywEGsrJPHCnQUhTnit3
+AcC5OFg4JxIQOx/1aOtjrlB/RTS4d+oogEz44kRmWw==
+-----END RSA PRIVATE KEY-----
diff --git a/data/dkim/txt/default_mailcow.de b/data/dkim/txt/default_mailcow.de
new file mode 100644
index 00000000..2ba732cb
--- /dev/null
+++ b/data/dkim/txt/default_mailcow.de
@@ -0,0 +1 @@
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFBIov3EMJ/Xtq1Fw5RDlMeAb01ByPGu5ySuyTQaBnKoAWa3IYCQKOPC90DxEt099GCRnn2hmCdDS4XImmpk53/PDwlRM5EGnWWMSVgIuv1AaHwIRCoKtVjb0aBVu4ozRtDbL1dbbsStQWdsndJ5nPEj6YD1uwUnO1WCH6vDUOewIDAQAB
\ No newline at end of file
diff --git a/data/web/add.php b/data/web/add.php
new file mode 100644
index 00000000..4d155dc4
--- /dev/null
+++ b/data/web/add.php
@@ -0,0 +1,276 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+$AuthUsers = array("admin", "domainadmin");
+if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
+ header('Location: /');
+ exit();
+}
+require_once("inc/header.inc.php");
+?>
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['add']['title'];?></h3>
+ </div>
+ <div class="panel-body">
+<?php
+if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
+ if (isset($_GET['domain']) && $_SESSION['mailcow_cc_role'] == "admin") {
+?>
+ <h4><?=$lang['add']['domain'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label>
+ <div class="col-sm-10">
+ <input type="text" autocorrect="off" autocapitalize="none" class="form-control" name="domain" id="domain">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="description" id="description">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="aliases"><?=$lang['add']['max_aliases'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="aliases" id="aliases" value="400">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="mailboxes"><?=$lang['add']['max_mailboxes'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="mailboxes" id="mailboxes" value="10">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="maxquota"><?=$lang['add']['mailbox_quota_m'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="maxquota" id="maxquota" value="3072">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="quota"><?=$lang['add']['domain_quota_m'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="quota" id="quota" value="10240">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2"><?=$lang['add']['backup_mx_options'];?></label>
+ <div class="col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="backupmx"> <?=$lang['add']['relay_domain'];?></label>
+ <br />
+ <label><input type="checkbox" name="relay_all_recipients"> <?=$lang['add']['relay_all'];?></label>
+ <p><?=$lang['add']['relay_all_info'];?></p>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['add']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="adddomain" class="btn btn-success"><?=$lang['add']['save'];?></button>
+ </div>
+ </div>
+ </form>
+<?php
+ }
+ elseif (isset($_GET['alias'])) {
+?>
+ <h4><?=$lang['add']['alias'];?></h4>
+ <p><?=$lang['add']['alias_spf_fail'];?></p>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="address"><?=$lang['add']['alias_address'];?></label>
+ <div class="col-sm-10">
+ <textarea autocorrect="off" autocapitalize="none" class="form-control" rows="5" name="address" id="address"></textarea>
+ <p><?=$lang['add']['alias_address_info'];?></p>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="goto"><?=$lang['add']['target_address'];?></label>
+ <div class="col-sm-10">
+ <textarea autocorrect="off" autocapitalize="none" class="form-control" rows="5" id="goto" name="goto"></textarea>
+ <p><?=$lang['add']['target_address_info'];?></p>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['add']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="addalias" class="btn btn-success "><?=$lang['add']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ elseif (isset($_GET['aliasdomain'])) {
+ ?>
+ <h4><?=$lang['add']['alias_domain'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="alias_domain"><?=$lang['add']['alias_domain'];?></label>
+ <div class="col-sm-10">
+ <textarea autocorrect="off" autocapitalize="none" class="form-control" rows="5" name="alias_domain" id="alias_domain"></textarea>
+ <p><?=$lang['add']['alias_domain_info'];?></p>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="target_domain"><?=$lang['add']['target_domain'];?></label>
+ <div class="col-sm-10">
+ <select name="target_domain" id="target_domain" title="<?=$lang['add']['select'];?>">
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
+ WHERE `domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :username
+ AND `active`='1'
+ )
+ OR 'admin' = :admin");
+ $stmt->execute(array(':username' => $_SESSION['mailcow_cc_username'], ':admin' => $_SESSION['mailcow_cc_role']));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row = array_shift($rows)) {
+ echo "<option>".htmlspecialchars($row['domain'])."</option>";
+ }
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['add']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="addaliasdomain" class="btn btn-success "><?=$lang['add']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ elseif (isset($_GET['mailbox'])) {
+ ?>
+ <h4><?=$lang['add']['mailbox'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="local_part"><?=$lang['add']['mailbox_username'];?></label>
+ <div class="col-sm-10">
+ <input type="text" pattern="[A-Za-z0-9\.!#$%&'*+/=?^_`{|}~-]+" autocorrect="off" autocapitalize="none" class="form-control" name="local_part" id="local_part" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="domain"><?=$lang['add']['domain'];?>:</label>
+ <div class="col-sm-10">
+ <select id="addSelectDomain" name="domain" id="domain" title="<?=$lang['add']['select'];?>" required>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
+ WHERE `domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :username
+ AND `active`='1'
+ )
+ OR 'admin' = :admin");
+ $stmt->execute(array(':username' => $_SESSION['mailcow_cc_username'], ':admin' => $_SESSION['mailcow_cc_role']));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row = array_shift($rows)) {
+ echo "<option>".htmlspecialchars($row['domain'])."</option>";
+ }
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="name"><?=$lang['add']['full_name'];?></label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="name" id="name">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="addInputQuota"><?=$lang['add']['quota_mb'];?>
+ <br /><span id="quotaBadge" class="badge">max. - MiB</span>
+ </label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="quota" min="1" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password" id="password" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password2"><?=$lang['add']['password_repeat'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password2" id="password2" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['add']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="addmailbox" class="btn btn-success "><?=$lang['add']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+}
+else {
+?>
+ <div class="alert alert-danger" role="alert"><?=$lang['danger']['access_denied'];?></div>
+<?php
+}
+?>
+ </div>
+ </div>
+ </div>
+ </div>
+<a href="<?=$_SESSION['return_to'];?>">← <?=$lang['add']['previous'];?></a>
+</div> <!-- /container -->
+<script src="js/add.js"></script>
+<?php
+require_once("inc/footer.inc.php");
+?>
diff --git a/data/web/admin.php b/data/web/admin.php
new file mode 100644
index 00000000..d960d222
--- /dev/null
+++ b/data/web/admin.php
@@ -0,0 +1,272 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+require_once("inc/header.inc.php");
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+?>
+<div class="container">
+<h4><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['admin']['access'];?></h4>
+
+<div class="panel-group" id="accordion_access">
+ <div class="panel panel-danger">
+ <div class="panel-heading"><?=$lang['admin']['admin_details'];?></div>
+ <div class="panel-body">
+ <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `username` FROM `admin`
+ WHERE `superadmin`='1' and active='1'");
+ $stmt->execute();
+ $AdminData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ ?>
+ <input type="hidden" name="admin_user_now" value="<?=htmlspecialchars($AdminData['username']);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="admin_user"><?=$lang['admin']['admin'];?>:</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="admin_user" id="admin_user" value="<?=htmlspecialchars($AdminData['username']);?>" required>
+ ↳ <kbd>a-z A-Z - _ .</kbd>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="admin_pass"><?=$lang['admin']['password'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="admin_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="admin_pass2" id="admin_pass2">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_set_admin" class="btn btn-default"><?=$lang['admin']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div style="cursor:pointer;" class="panel-heading" data-toggle="collapse" data-parent="#accordion_access" data-target="#collapseDomAdmins">
+ <span class="accordion-toggle"><?=$lang['admin']['domain_admins'];?></span>
+ </div>
+ <div id="collapseDomAdmins" class="panel-collapse collapse">
+ <div class="panel-body">
+ <form method="post">
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
+ <th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['admin']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->query("SELECT DISTINCT
+ `username`,
+ CASE WHEN `active`='1' THEN '".$lang['admin']['yes']."' ELSE '".$lang['admin']['no']."' END AS `active`
+ FROM `domain_admins`
+ WHERE `username` IN (
+ SELECT `username` FROM `admin`
+ WHERE `superadmin`!='1'
+ )");
+ $rows_username = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if(!empty($rows_username)):
+ while ($row_user_state = array_shift($rows_username)):
+ ?>
+ <tr id="data">
+ <td><?=htmlspecialchars(strtolower($row_user_state['username']));?></td>
+ <td>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins` WHERE `username` = :username");
+ $stmt->execute(array('username' => $row_user_state['username']));
+ $rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row_domain = array_shift($rows_domain)) {
+ echo htmlspecialchars($row_domain['domain']).'<br />';
+ }
+ ?>
+ </td>
+ <td><?=$row_user_state['active'];?></td>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="edit.php?domainadmin=<?=$row_user_state['username'];?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['admin']['edit'];?></a>
+ <a href="delete.php?domainadmin=<?=$row_user_state['username'];?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['admin']['remove'];?></a>
+ </div>
+ </td>
+ </td>
+ </tr>
+
+ <?php
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="4" style="text-align: center; font-style: italic;"><?=$lang['admin']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ </table>
+ </div>
+ </form>
+ <small>
+ <legend><?=$lang['admin']['add_domain_admin'];?></legend>
+ <form class="form-horizontal" role="form" method="post">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="username" id="username" required>
+ ↳ <kbd>a-z A-Z - _ .</kbd>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label>
+ <div class="col-sm-10">
+ <select title="<?=$lang['admin']['search_domain_da'];?>" style="width:100%" name="domain[]" size="5" multiple>
+ <?php
+ try {
+ $stmt = $pdo->query("SELECT domain FROM domain");
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row = array_shift($rows)) {
+ echo "<option>".htmlspecialchars($row['domain'])."</option>";
+ }
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password" id="password" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password2"><?=$lang['admin']['password_repeat'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password2" id="password2" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['admin']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_add_domain_admin" class="btn btn-default"><?=$lang['admin']['add'];?></button>
+ </div>
+ </div>
+ </form>
+ </small>
+ </div>
+ </div>
+ </div>
+</div>
+
+<h4><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> <?=$lang['admin']['configuration'];?></h4>
+<div class="panel panel-default">
+<div class="panel-heading"><?=$lang['admin']['dkim_keys'];?></div>
+<div id="collapseDKIM" class="panel-collapse">
+<div class="panel-body">
+ <?php
+ $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]);
+ $dnstxt_files = array_diff($dnstxt_folder, array('.', '..'));
+ foreach($dnstxt_files as $file) {
+ $str = file_get_contents($GLOBALS["MC_DKIM_TXTS"]."/".$file);
+ $str = preg_replace('/\r|\t|\n/', '', $str);
+ preg_match('/\(.*\)/im', $str, $matches);
+ $domain = explode("_", $file)[1];
+ $selector = explode("_", $file)[0];
+ if(isset($matches[0])) {
+ $str = str_replace(array(' ', '"', '(', ')'), '', $matches[0]);
+ }
+ ?>
+ <div class="row">
+ <div class="col-xs-2">
+ <p>Domain: <strong><?=htmlspecialchars($domain);?></strong> (<?=htmlspecialchars($selector);?>._domainkey)</p>
+ </div>
+ <div class="col-xs-9">
+ <pre>v=DKIM1;k=rsa;t=s;s=email;p=<?=$str;?></pre>
+ </div>
+ <div class="col-xs-1">
+ <form class="form-inline" role="form" method="post">
+ <a href="#" onclick="$(this).closest('form').submit()"><span class="glyphicon glyphicon-remove-circle"></span></a>
+ <input type="hidden" name="delete_dkim_record" value="<?=htmlspecialchars($file);?>">
+ <input type="hidden" name="dkim[domain]" value="<?=$domain;?>">
+ <input type="hidden" name="dkim[selector]" value="<?=$selector;?>">
+ </form>
+ </div>
+ </div>
+ <?php
+ }
+ ?>
+ <legend><?=$lang['admin']['dkim_add_key'];?></legend>
+ <form class="form-inline" role="form" method="post">
+ <div class="form-group">
+ <label for="dkim_domain">Domain</label>
+ <input class="form-control" id="dkim_domain" name="dkim[domain]" placeholder="example.org" required>
+ </div>
+ <div class="form-group">
+ <label for="dkim_selector">Selector</label>
+ <input class="form-control" id="dkim_selector" name="dkim[selector]" value="default" required>
+ </div>
+ <div class="form-group">
+ <select class="form-control" id="dkim_key_size" name="dkim[key_size]" title="<?=$lang['admin']['dkim_key_length'];?>" required>
+ <option>1024</option>
+ <option>2048</option>
+ </select>
+ </div>
+ <button type="submit" name="add_dkim_record" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
+ </form>
+</div>
+</div>
+</div>
+
+</div> <!-- /container -->
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" integrity="sha384-YWP9O4NjmcGo4oEJFXvvYSEzuHIvey+LbXkBNJ1Kd0yfugEZN9NCQNpRYBVC1RvA" crossorigin="anonymous"></script>
+<script src="js/sorttable.js"></script>
+<script src="js/admin.js"></script>
+<?php
+require_once("inc/footer.inc.php");
+} else {
+ header('Location: /');
+ exit();
+}
+?>
diff --git a/data/web/autoconfig/mail/config-v1.1.xml_rc b/data/web/autoconfig/mail/config-v1.1.xml_rc
new file mode 100644
index 00000000..4bb35685
--- /dev/null
+++ b/data/web/autoconfig/mail/config-v1.1.xml_rc
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+
+<!-- Mozilla Thunderbird Autoconfiguration file provided by mailcow
+
+ Further reading:
+ https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration
+ https://wiki.mozilla.org/Thunderbird:Autoconfiguration
+ https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
+-->
+
+<clientConfig version="1.1">
+ <emailProvider id="MAILCOW_DOMAIN">
+ <domain>MAILCOW_DOMAIN</domain>
+
+ <displayName>MAILCOW_DOMAIN mail server powered by mailcow</displayName>
+ <displayShortName>MAILCOW_DOMAIN mail server</displayShortName>
+
+ <incomingServer type="imap">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>993</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+ <incomingServer type="imap">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>143</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+
+ <incomingServer type="pop3">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>995</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+ <incomingServer type="pop3">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>110</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+
+ <outgoingServer type="smtp">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>465</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </outgoingServer>
+
+ <outgoingServer type="smtp">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>587</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </outgoingServer>
+
+ <enable visiturl="https://MAILCOW_HOST.MAILCOW_DOMAIN/admin.php">
+ <instruction>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.</instruction>
+ <instruction lang="de">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.</instruction>
+ </enable>
+
+ <documentation url="http://MAILCOW_HOST.MAILCOW_DOMAIN">
+ <descr lang="en">MAILCOW_DOMAIN mail server info</descr>
+ <descr lang="de">MAILCOW_DOMAIN Mailserver Info</descr>
+ </documentation>
+
+ </emailProvider>
+
+ <webMail>
+ <loginPage url="https://MAILCOW_HOST.MAILCOW_DOMAIN/rc/" />
+ <loginPageInfo url="https://MAILCOW_HOST.MAILCOW_DOMAIN/rc/">
+ <username>%EMAILADDRESS%</username>
+ <usernameField id="rcmloginuser" />
+ <passwordField id="rcmloginpwd" />
+ <loginButton id="rcmloginsubmit" />
+ </loginPageInfo>
+ </webMail>
+</clientConfig>
diff --git a/data/web/autoconfig/mail/config-v1.1.xml_sogo b/data/web/autoconfig/mail/config-v1.1.xml_sogo
new file mode 100644
index 00000000..adf5fcaa
--- /dev/null
+++ b/data/web/autoconfig/mail/config-v1.1.xml_sogo
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+
+<!-- Mozilla Thunderbird Autoconfiguration file provided by mailcow
+
+ Further reading:
+ https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration
+ https://wiki.mozilla.org/Thunderbird:Autoconfiguration
+ https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
+-->
+
+<clientConfig version="1.1">
+ <emailProvider id="MAILCOW_DOMAIN">
+ <domain>MAILCOW_DOMAIN</domain>
+
+ <displayName>MAILCOW_DOMAIN mail server powered by mailcow</displayName>
+ <displayShortName>MAILCOW_DOMAIN mail server</displayShortName>
+
+ <incomingServer type="imap">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>993</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+ <incomingServer type="imap">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>143</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+
+ <incomingServer type="pop3">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>995</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+ <incomingServer type="pop3">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>110</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+
+ <outgoingServer type="smtp">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>465</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </outgoingServer>
+
+ <outgoingServer type="smtp">
+ <hostname>MAILCOW_HOST.MAILCOW_DOMAIN</hostname>
+ <port>587</port>
+ <socketType>STARTTLS</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </outgoingServer>
+
+ <enable visiturl="https://MAILCOW_HOST.MAILCOW_DOMAIN/admin.php">
+ <instruction>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.</instruction>
+ <instruction lang="de">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.</instruction>
+ </enable>
+
+ <documentation url="http://MAILCOW_HOST.MAILCOW_DOMAIN">
+ <descr lang="en">MAILCOW_DOMAIN mail server info</descr>
+ <descr lang="de">MAILCOW_DOMAIN Mailserver Info</descr>
+ </documentation>
+
+ </emailProvider>
+
+ <webMail>
+ <loginPage url="https://MAILCOW_HOST.MAILCOW_DOMAIN/SOGo/" />
+ </webMail>
+</clientConfig>
diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php
new file mode 100644
index 00000000..45b738de
--- /dev/null
+++ b/data/web/autodiscover.php
@@ -0,0 +1,121 @@
+<?php
+header("Content-Type: application/xml");
+require_once "inc/vars.inc.php";
+$config = array(
+ 'useEASforOutlook' => 'yes',
+ 'autodiscoverType' => 'activesync',
+ 'imap' => array(
+ 'server' => 'MAILCOW_HOST.MAILCOW_DOMAIN',
+ 'port' => '993',
+ 'ssl' => 'on',
+ ),
+ 'smtp' => array(
+ 'server' => 'MAILCOW_HOST.MAILCOW_DOMAIN',
+ 'port' => '465',
+ 'ssl' => 'on'
+ ),
+ 'activesync' => array(
+ 'url' => 'https://MAILCOW_HOST.MAILCOW_DOMAIN/Microsoft-Server-ActiveSync'
+ )
+);
+// If useEASforOutlook == no, the autodiscoverType option will be replaced to imap.
+if ($config['useEASforOutlook'] == 'no') {
+ if (strpos($_SERVER['HTTP_USER_AGENT'], 'Outlook')) {
+ $config['autodiscoverType'] = 'imap';
+ }
+}
+// Workaround for short open tags
+echo '<?xml version="1.0" encoding="utf-8" ?>';
+?>
+<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
+<?php
+$data = trim(file_get_contents("php://input"));
+if(!$data) {
+ list($usec, $sec) = explode(' ', microtime());
+ echo '<Response>';
+ echo '<Error Time="' . date('H:i:s', $sec) . substr($usec, 0, strlen($usec) - 2) . '" Id="2477272013">';
+ echo '<ErrorCode>600</ErrorCode><Message>Invalid Request</Message><DebugData /></Error>';
+ echo '</Response>';
+ echo '</Autodiscover>';
+ exit(0);
+}
+
+$discover = new SimpleXMLElement($data);
+$email = $discover->Request->EMailAddress;
+
+if ($config['autodiscoverType'] == 'imap') {
+?>
+<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
+ <Account>
+ <AccountType>email</AccountType>
+ <Action>settings</Action>
+ <Protocol>
+ <Type>IMAP</Type>
+ <Server><?php echo $config['imap']['server']; ?></Server>
+ <Port><?php echo $config['imap']['port']; ?></Port>
+ <DomainRequired>off</DomainRequired>
+ <LoginName><?php echo $email; ?></LoginName>
+ <SPA>off</SPA>
+ <SSL><?php echo $config['imap']['ssl']; ?></SSL>
+ <AuthRequired>on</AuthRequired>
+ </Protocol>
+ <Protocol>
+ <Type>SMTP</Type>
+ <Server><?php echo $config['smtp']['server']; ?></Server>
+ <Port><?php echo $config['smtp']['port']; ?></Port>
+ <DomainRequired>off</DomainRequired>
+ <LoginName><?php echo $email; ?></LoginName>
+ <SPA>off</SPA>
+ <SSL><?php echo $config['smtp']['ssl']; ?></SSL>
+ <AuthRequired>on</AuthRequired>
+ <UsePOPAuth>on</UsePOPAuth>
+ <SMTPLast>off</SMTPLast>
+ </Protocol>
+ </Account>
+</Response>
+<?php
+}
+else if ($config['autodiscoverType'] == 'activesync') {
+ $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);
+ $username = trim($email);
+ try {
+ $stmt = $pdo->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;
+ }
+?>
+<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
+ <Culture>en:en</Culture>
+ <User>
+ <DisplayName><?php echo $displayname; ?></DisplayName>
+ <EMailAddress><?php echo $email; ?></EMailAddress>
+ </User>
+ <Action>
+ <Settings>
+ <Server>
+ <Type>MobileSync</Type>
+ <Url><?php echo $config['activesync']['url']; ?></Url>
+ <Name><?php echo $config['activesync']['url']; ?></Name>
+ </Server>
+ </Settings>
+ </Action>
+</Response>
+<?php
+}
+?>
+</Autodiscover>
diff --git a/data/web/delete.php b/data/web/delete.php
new file mode 100644
index 00000000..f8a53aeb
--- /dev/null
+++ b/data/web/delete.php
@@ -0,0 +1,165 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+$AuthUsers = array("admin", "domainadmin");
+if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
+ header('Location: /');
+ exit();
+}
+require_once("inc/header.inc.php");
+?>
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['delete']['title'];?></h3>
+ </div>
+ <div class="panel-body">
+<?php
+if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
+ // DELETE DOMAIN
+ if (isset($_GET["domain"]) &&
+ is_valid_domain_name($_GET["domain"]) &&
+ !empty($_GET["domain"]) &&
+ $_SESSION['mailcow_cc_role'] == "admin") {
+ $domain = $_GET["domain"];
+ ?>
+ <div class="alert alert-warning" role="alert"><?=sprintf($lang['delete']['remove_domain_warning'], htmlspecialchars($_GET["domain"]));?></div>
+ <p><?=$lang['delete']['remove_domain_details'];?></p>
+ <form class="form-horizontal" role="form" method="post" action="/mailbox.php">
+ <input type="hidden" name="domain" value="<?php echo htmlspecialchars($domain) ?>">
+ <div class="form-group">
+ <div class="col-sm-offset-1 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="deletedomain" class="btn btn-default btn-sm"><?=$lang['delete']['remove_button'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ // DELETE ALIAS
+ elseif (isset($_GET["alias"]) &&
+ (filter_var($_GET["alias"], FILTER_VALIDATE_EMAIL) || is_valid_domain_name(substr(strrchr($_GET["alias"], "@"), 1))) &&
+ !empty($_GET["alias"])) {
+ $domain = substr(strrchr($_GET["alias"], "@"), 1);
+ if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ ?>
+ <div class="alert alert-warning" role="alert"><?=sprintf($lang['delete']['remove_alias_warning'], htmlspecialchars($_GET["alias"]));?></div>
+ <p><?=$lang['delete']['remove_alias_details'];?></p>
+ <form class="form-horizontal" role="form" method="post" action="/mailbox.php">
+ <input type="hidden" name="address" value="<?php echo htmlspecialchars($_GET["alias"]) ?>">
+ <div class="form-group">
+ <div class="col-sm-offset-1 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="deletealias" class="btn btn-default btn-sm"><?=$lang['delete']['remove_button'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ // DELETE ALIAS DOMAIN
+ elseif (
+ isset($_GET["aliasdomain"]) &&
+ is_valid_domain_name($_GET["aliasdomain"]) &&
+ !empty($_GET["aliasdomain"])) {
+ $alias_domain = strtolower(trim($_GET["aliasdomain"]));
+ 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
+ );
+ }
+ if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
+ ?>
+ <div class="alert alert-warning" role="alert"><?=sprintf($lang['delete']['remove_domainalias_warning'], htmlspecialchars($_GET["aliasdomain"]));?></div>
+ <form class="form-horizontal" role="form" method="post" action="/mailbox.php">
+ <input type="hidden" name="alias_domain" value="<?php echo htmlspecialchars($alias_domain) ?>">
+ <div class="form-group">
+ <div class="col-sm-offset-1 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="deletealiasdomain" class="btn btn-default btn-sm"><?=$lang['delete']['remove_button'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ // DELETE DOMAIN ADMIN
+ elseif (isset($_GET["domainadmin"]) &&
+ ctype_alnum(str_replace(array('_', '.', '-'), '', $_GET["domainadmin"])) &&
+ !empty($_GET["domainadmin"]) &&
+ $_SESSION['mailcow_cc_role'] == "admin") {
+ $domain_admin = $_GET["domainadmin"];
+ ?>
+ <div class="alert alert-warning" role="alert"><?=sprintf($lang['delete']['remove_domainadmin_warning'], htmlspecialchars($_GET["domainadmin"]));?></div>
+ <form class="form-horizontal" role="form" method="post" action="/admin.php">
+ <input type="hidden" name="username" value="<?=htmlspecialchars($domain_admin);?>">
+ <div class="form-group">
+ <div class="col-sm-offset-1 col-sm-10">
+ <button type="submit" name="trigger_delete_domain_admin" class="btn btn-default btn-sm"><?=$lang['delete']['remove_button'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ // DELETE MAILBOX
+ elseif (isset($_GET["mailbox"]) &&
+ filter_var($_GET["mailbox"], FILTER_VALIDATE_EMAIL) &&
+ !empty($_GET["mailbox"])) {
+ $mailbox = $_GET["mailbox"];
+ $domain = substr(strrchr($mailbox, "@"), 1);
+ if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ ?>
+ <div class="alert alert-warning" role="alert"><?=sprintf($lang['delete']['remove_mailbox_warning'], htmlspecialchars($_GET["mailbox"]));?></div>
+ <p><?=$lang['delete']['remove_mailbox_details'];?></p>
+ <form class="form-horizontal" role="form" method="post" action="/mailbox.php">
+ <input type="hidden" name="username" value="<?=htmlspecialchars($mailbox);?>">
+ <div class="form-group">
+ <div class="col-sm-offset-1 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="deletemailbox" class="btn btn-default btn-sm"><?=$lang['delete']['remove_button'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+}
+else {
+?>
+ <div class="alert alert-danger" role="alert"><?=$lang['danger']['access_denied'];?></div>
+<?php
+}
+?>
+ </div>
+ </div>
+ </div>
+ </div>
+<a href="<?=$_SESSION['return_to'];?>">← <?=$lang['delete']['previous'];?></a>
+</div> <!-- /container -->
+<?php
+require_once("inc/footer.inc.php");
+?>
diff --git a/data/web/edit.php b/data/web/edit.php
new file mode 100644
index 00000000..a5629c0d
--- /dev/null
+++ b/data/web/edit.php
@@ -0,0 +1,544 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+$AuthUsers = array("admin", "domainadmin");
+if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
+ header('Location: /');
+ exit();
+}
+require_once("inc/header.inc.php");
+?>
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['edit']['title'];?></h3>
+ </div>
+ <div class="panel-body">
+<?php
+if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
+ if (isset($_GET["alias"]) &&
+ !empty($_GET["alias"])) {
+ $alias = $_GET["alias"];
+ $domain = substr(strrchr($alias, "@"), 1);
+ try {
+ $stmt = $pdo->prepare("SELECT * FROM `alias`
+ WHERE `address`= :address
+ AND `goto` != :goto
+ AND (
+ `domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `active`='1'
+ AND `username`= :username
+ )
+ OR 'admin'= :admin
+ )");
+ $stmt->execute(array(
+ ':address' => $alias,
+ ':goto' => $alias,
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role']
+ ));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if ($result !== false) {
+ ?>
+ <h4><?=$lang['edit']['alias'];?></h4>
+ <br />
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <input type="hidden" name="address" value="<?=htmlspecialchars($alias);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label>
+ <div class="col-sm-10">
+ <textarea class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="goto" name="goto"><?=htmlspecialchars($result['goto']) ?></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" <?php if (isset($result['active']) && $result['active']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="editalias" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ elseif (isset($_GET['domainadmin']) &&
+ ctype_alnum(str_replace(array('_', '.', '-'), '', $_GET["domainadmin"])) &&
+ !empty($_GET["domainadmin"]) &&
+ $_GET["domainadmin"] != 'admin' &&
+ $_SESSION['mailcow_cc_role'] == "admin") {
+ $domain_admin = $_GET["domainadmin"];
+ try {
+ $stmt = $pdo->prepare("SELECT * FROM `domain_admins` WHERE `username`= :domain_admin");
+ $stmt->execute(array(
+ ':domain_admin' => $domain_admin
+ ));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if ($result !== false) {
+ ?>
+ <h4><?=$lang['edit']['domain_admin'];?></h4>
+ <br />
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <input type="hidden" name="username" value="<?=htmlspecialchars($domain_admin);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="domain"><?=$lang['edit']['domains'];?></label>
+ <div class="col-sm-10">
+ <select id="domain" name="domain[]" multiple>
+ <?php
+ try {
+ $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);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row_selected = array_shift($rows)):
+ ?>
+ <option selected><?=htmlspecialchars($row_selected['domain']);?></option>
+ <?php
+ endwhile;
+ try {
+ $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);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($row_unselected = array_shift($rows)):
+ ?>
+ <option><?=htmlspecialchars($row_unselected['domain']);?></option>
+ <?php
+ endwhile;
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password" id="password" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password2" id="password2">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" <?php if (isset($result['active']) && $result['active']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_edit_domain_admin" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ elseif (isset($_GET['domain']) &&
+ is_valid_domain_name($_GET["domain"]) &&
+ !empty($_GET["domain"])) {
+ $domain = $_GET["domain"];
+ try {
+ $stmt = $pdo->prepare("SELECT * FROM `domain` WHERE `domain`='".$domain."'
+ AND (
+ `domain` IN (
+ SELECT `domain` from `domain_admins`
+ WHERE `active`='1'
+ AND `username` = :username
+ )
+ OR 'admin'= :admin
+ )");
+ $stmt->execute(array(
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role']
+ ));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if ($result !== false) {
+ ?>
+ <h4><?=$lang['edit']['domain'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <input type="hidden" name="domain" value="<?=htmlspecialchars($domain);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="description"><?=$lang['edit']['description'];?></label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="description" id="description" value="<?=htmlspecialchars($result['description']);?>">
+ </div>
+ </div>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin") {
+ ?>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="aliases"><?=$lang['edit']['max_aliases'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="aliases" id="aliases" value="<?=intval($result['aliases']);?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="mailboxes"><?=$lang['edit']['max_mailboxes'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="mailboxes" id="mailboxes" value="<?=intval($result['mailboxes']);?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="maxquota"><?=$lang['edit']['max_quota'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="maxquota" id="maxquota" value="<?=intval($result['maxquota']);?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="quota"><?=$lang['edit']['domain_quota'];?></label>
+ <div class="col-sm-10">
+ <input type="number" class="form-control" name="quota" id="quota" value="<?=intval($result['quota']);?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2"><?=$lang['edit']['backup_mx_options'];?></label>
+ <div class="col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="backupmx" <?php if (isset($result['backupmx']) && $result['backupmx']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['relay_domain'];?></label>
+ <br />
+ <label><input type="checkbox" name="relay_all_recipients" <?php if (isset($result['relay_all_recipients']) && $result['relay_all_recipients']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['relay_all'];?></label>
+ <p><?=$lang['edit']['relay_all_info'];?></p>
+ </div>
+ </div>
+ </div>
+ <?php
+ }
+ ?>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" <?php if (isset($result['active']) && $result['active']=="1") { echo "checked "; }; if ($_SESSION['mailcow_cc_role']=="domainadmin") { echo "disabled"; }; ?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="editdomain" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]);
+ $dnstxt_files = array_diff($dnstxt_folder, array('.', '..'));
+ foreach($dnstxt_files as $file) {
+ if (explode("_", $file)[1] == $domain) {
+ $str = file_get_contents($GLOBALS["MC_DKIM_TXTS"]."/".$file);
+ $str = preg_replace('/\r|\t|\n/', '', $str);
+ preg_match('/\(.*\)/im', $str, $matches);
+ if(isset($matches[0])) {
+ $str = str_replace(array(' ', '"', '(', ')'), '', $matches[0]);
+ }
+ ?>
+ <div class="row">
+ <div class="col-xs-2">
+ <p class="text-right"><?=$lang['edit']['dkim_signature'];?></p>
+ </div>
+ <div class="col-xs-10">
+ <div class="col-md-2"><b><?=$lang['edit']['dkim_txt_name'];?></b></div>
+ <div class="col-md-10">
+ <pre><?=htmlspecialchars(explode("_", $file)[0]);?>._domainkey</pre>
+ </div>
+ <div class="col-md-2"><b><?=$lang['edit']['dkim_txt_value'];?></b></div>
+ <div class="col-md-10">
+ <pre>v=DKIM1;k=rsa;t=s;s=email;p=<?=htmlspecialchars($str);?></pre>
+ <?=$lang['edit']['dkim_record_info'];?>
+ </div>
+ </div>
+ </div>
+ <?php
+ }
+ }
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ elseif (isset($_GET['aliasdomain']) &&
+ is_valid_domain_name($_GET["aliasdomain"]) &&
+ !empty($_GET["aliasdomain"])) {
+ $alias_domain = $_GET["aliasdomain"];
+ try {
+ $stmt = $pdo->prepare("SELECT * FROM `alias_domain`
+ WHERE `alias_domain`= :alias_domain
+ AND (
+ `target_domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `active`='1'
+ AND `username`= :username
+ )
+ OR 'admin'= :admin
+ )");
+ $stmt->execute(array(
+ ':alias_domain' => $alias_domain,
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role']
+ ));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if ($result !== false) {
+ ?>
+ <h4><?=$lang['edit']['edit_alias_domain'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <input type="hidden" name="alias_domain_now" value="<?=htmlspecialchars($alias_domain);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="alias_domain"><?=$lang['edit']['alias_domain'];?></label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="alias_domain" id="alias_domain" value="<?=htmlspecialchars($result['alias_domain']);?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" <?= (isset($result['active']) && $result['active']=="1") ? "checked" : null ?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="editaliasdomain" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]);
+ $dnstxt_files = array_diff($dnstxt_folder, array('.', '..'));
+ foreach($dnstxt_files as $file) {
+ if (explode("_", $file)[1] == $domain) {
+ $str = file_get_contents($GLOBALS["MC_DKIM_TXTS"]."/".$file);
+ $str = preg_replace('/\r|\t|\n/', '', $str);
+ preg_match('/\(.*\)/im', $str, $matches);
+ if(isset($matches[0])) {
+ $str = str_replace(array(' ', '"', '(', ')'), '', $matches[0]);
+ }
+ ?>
+ <div class="row">
+ <div class="col-xs-2">
+ <p class="text-right"><?=$lang['edit']['dkim_signature'];?></p>
+ </div>
+ <div class="col-xs-10">
+ <div class="col-md-2"><b><?=$lang['edit']['dkim_txt_name'];?></b></div>
+ <div class="col-md-10">
+ <pre><?=htmlspecialchars(explode("_", $file)[0]);?>._domainkey</pre>
+ </div>
+ <div class="col-md-2"><b><?=$lang['edit']['dkim_txt_value'];?></b></div>
+ <div class="col-md-10">
+ <pre><?=htmlspecialchars($str);?></pre>
+ <?=$lang['edit']['dkim_record_info'];?>
+ </div>
+ </div>
+ </div>
+ <?php
+ }
+ }
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ elseif (isset($_GET['mailbox']) && filter_var($_GET["mailbox"], FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
+ $mailbox = $_GET["mailbox"];
+ try {
+ $stmt = $pdo->prepare("SELECT `username`, `domain`, `name`, `quota`, `active` FROM `mailbox` WHERE `username` = :username1");
+ $stmt->execute(array(
+ ':username1' => $mailbox,
+ ));
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if ($result !== false && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $result['domain'])) {
+ $left_m = remaining_specs($result['domain'], $_GET['mailbox'])['left_m'];
+ ?>
+ <h4><?=$lang['edit']['mailbox'];?></h4>
+ <form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
+ <input type="hidden" name="username" value="<?=htmlspecialchars($result['username']);?>">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?>:</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="name" id="name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="quota"><?=$lang['edit']['quota_mb'];?>:
+ <br /><span id="quotaBadge" class="badge">max. <?=intval($left_m)?> MiB</span>
+ </label>
+ <div class="col-sm-10">
+ <input type="number" name="quota" id="quota" id="destroyable" style="width:100%" min="1" max="<?=intval($left_m);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="sender_acl"><?=$lang['edit']['sender_acl'];?>:</label>
+ <div class="col-sm-10">
+ <select style="width:100%" id="sender_acl" name="sender_acl[]" size="10" multiple>
+ <?php
+ $rows = get_sender_acl_handles($mailbox, "preselected");
+ while ($row_goto_from_alias = array_shift($rows)):
+ ?>
+ <option disabled selected><?=htmlspecialchars($row_goto_from_alias['address']);?></option>
+ <?php
+ endwhile;
+
+ // All manual selected
+ $rows = get_sender_acl_handles($mailbox, "selected");
+ while ($row_selected_sender_acl = array_shift($rows)):
+ if (!filter_var($row_selected_sender_acl['send_as'], FILTER_VALIDATE_EMAIL)):
+ ?>
+ <option data-divider="true"></option>
+ <option value="<?=htmlspecialchars($row_selected_sender_acl['send_as']);?>" selected><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], str_replace('@', '', $row_selected_sender_acl['send_as'])));?></option>
+ <option data-divider="true"></option>
+ <?php
+ else:
+ ?>
+ <option selected><?=htmlspecialchars($row_selected_sender_acl['send_as']);?></option>
+ <?php
+ endif;
+ endwhile;
+
+ // Unselected domains
+ $rows = get_sender_acl_handles($mailbox, "unselected-domains");
+ while ($row_unselected_sender_acl = array_shift($rows)):
+ ?>
+ <option data-divider="true"></option>
+ <option value="@<?=htmlspecialchars($row_unselected_sender_acl['domain']);?>"><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $row_unselected_sender_acl['domain']));?></option>
+ <option data-divider="true"></option>
+ <?php
+ endwhile;
+
+ // Unselected addresses
+ $rows = get_sender_acl_handles($mailbox, "unselected-addresses");
+ while ($row_unselected_sender_acl = array_shift($rows)):
+ ?>
+ <option><?=htmlspecialchars($row_unselected_sender_acl['address']);?></option>
+ <?php
+ endwhile;
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password" id="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password2" id="password2">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" <?=($result['active']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="trigger_mailbox_action" value="editmailbox" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+ }
+ else {
+ ?>
+ <div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
+ <?php
+ }
+}
+else {
+?>
+ <div class="alert alert-danger" role="alert"><?=$lang['danger']['access_denied'];?></div>
+<?php
+}
+?>
+ </div>
+ </div>
+ </div>
+ </div>
+<a href="<?=$_SESSION['return_to'];?>">← <?=$lang['edit']['previous'];?></a>
+</div> <!-- /container -->
+<?php
+require_once("inc/footer.inc.php");
+?>
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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ width="446.22601"
+ height="396.02499"
+ viewBox="0 0 446.226 396.02499"
+ enable-background="new 0 0 1600 1200"
+ xml:space="preserve"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="cow_mailcow.svg"><metadata
+ id="metadata144"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+ id="defs142" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1097"
+ inkscape:window-height="1138"
+ id="namedview140"
+ showgrid="false"
+ inkscape:zoom="1.1125147"
+ inkscape:cx="261.00704"
+ inkscape:cy="233.97883"
+ inkscape:window-x="814"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="Layer_1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" /><g
+ id="g3"
+ transform="translate(-576.88698,-401.988)"><g
+ id="g5"><g
+ id="g7"><g
+ id="email"><path
+ d="m 890.306,557.81 29.26,11.373 0,172.027 c 0,9.753 -7.895,17.649 -17.638,17.649 l -235.998,0 c -9.743,0 -17.638,-7.896 -17.638,-17.649 l 0,-172.026 29.259,-8.937"
+ id="path10"
+ inkscape:connector-curvature="0"
+ style="fill:#5a3620" /><path
+ d="M 758.871,656.221 649.49,747.45 c 2.507,6.648 8.901,11.409 16.44,11.409 l 235.998,0 c 7.536,0 13.933,-4.761 16.444,-11.409 l -107.402,-91.229 -52.099,0 z"
+ id="path12"
+ inkscape:connector-curvature="0"
+ style="fill:#fee70f;fill-opacity:0.89499998" /><g
+ id="g14"><path
+ d="m 810.391,656.686 107.981,90.764 c -0.331,0.881 -0.744,1.726 -1.205,2.536 l 0.028,0.035 c 1.501,-2.596 2.371,-5.594 2.371,-8.81 l 0,-172.004 -109.175,87.479 z"
+ id="path16"
+ inkscape:connector-curvature="0"
+ style="fill:#f9e82d;fill-opacity:1" /><path
+ d="m 649.49,747.45 108.864,-90.764 -110.061,-87.479 0,172.003 c 0,3.216 0.876,6.214 2.367,8.81 l 0.039,-0.035 c -0.466,-0.809 -0.877,-1.654 -1.209,-2.535 z"
+ id="path18"
+ inkscape:connector-curvature="0"
+ style="fill:#f9e82d;fill-opacity:1" /></g></g><path
+ d="m 961.81,681.214 c 0,0 -15.232,16.783 -42.244,14.73 l 0,28.14 c 13.328,-5.185 47.061,-20.036 56.854,-40.809 l -14.61,-2.061 z"
+ id="path20"
+ inkscape:connector-curvature="0"
+ style="fill:#b58765" /><path
+ d="m 984.594,658.413 c 3.59,-9.156 7.701,-11 9.346,-11.346 -49.276,4.542 -32.99,38.693 -32.99,38.693 0,0 6.229,14.728 26.532,13.892 27.063,0.461 35.631,-50.166 35.631,-50.166 -6.654,11.655 -26.404,9.876 -38.519,8.927 z"
+ id="path22"
+ inkscape:connector-curvature="0"
+ style="fill:#fef3df" /><ellipse
+ cx="787.75098"
+ cy="788.54498"
+ rx="210.864"
+ ry="9.4680004"
+ id="ellipse24"
+ style="fill:#f1f2f2" /><path
+ d="m 783.931,446.247 c -66.396,0 -120.223,53.827 -120.223,120.223 0,66.396 53.827,120.221 120.223,120.221 66.397,0 120.222,-53.825 120.222,-120.221 0,-66.395 -53.825,-120.223 -120.222,-120.223 z m -11.96,215.702 c -53.009,0 -95.982,-43.855 -95.982,-97.953 0,-54.098 42.973,-97.952 95.982,-97.952 53.007,0 95.98,43.855 95.98,97.952 -10e-4,54.098 -42.973,97.953 -95.98,97.953 z"
+ id="path26"
+ inkscape:connector-curvature="0"
+ style="opacity:0.1;fill:#3d5263" /><g
+ id="g28"><g
+ id="g30"><polyline
+ points="691.144,492.5 673.257,540.276 686.55,605.582 712.496,631.852 "
+ id="polyline32"
+ style="fill:#3d5263" /><g
+ id="g34"><g
+ id="g36"><polyline
+ points="658.248,450.81 673.501,487.076 693.836,496.903 724.04,458.731 "
+ id="polyline38"
+ style="fill:#fef3df" /><g
+ id="g40"><path
+ d="m 710.634,473.205 c 0,0 -22.482,-25.556 -49.793,-18.975 0,0 4.667,34.118 46.349,44.019 l 2.61,8.533 c 0,0 -65.612,-9.689 -59.339,-67.593 0,0 49.008,-19.884 72.598,15.106"
+ id="path42"
+ inkscape:connector-curvature="0"
+ style="fill:#b58765" /><polyline
+ points="909.697,450.81 894.447,487.076 874.114,496.903 843.907,458.731 "
+ id="polyline44"
+ style="fill:#fef3df" /><path
+ d="m 857.314,473.205 c 0,0 22.48,-25.556 49.79,-18.975 0,0 -4.664,34.118 -46.347,44.019 l -2.613,8.533 c 0,0 65.611,-9.689 59.339,-67.593 0,0 -49.006,-19.884 -72.6,15.106"
+ id="path46"
+ inkscape:connector-curvature="0"
+ style="fill:#b58765" /></g></g><path
+ d="m 726.619,647.067 55.945,0 16.40428,-204.81407 c -55.814,0 -112.41728,30.01707 -112.41728,77.85207 0,1.454 0.085,2.787 0.121,4.175 0.127,3.934 0.448,7.585 0.856,11.135 1.689,14.816 5.451,27.177 8.461,43.383 1.452,7.831 5.002,23.374 5.002,23.374 0.056,0.408 0.165,0.804 0.224,1.211 2.535,16.546 11.832,32.027 25.404,43.684 z"
+ id="path48"
+ inkscape:connector-curvature="0"
+ style="fill:#b58765"
+ sodipodi:nodetypes="cccscccccc" /><path
+ d="m 781.992,433.489 0,213.577 55.944,0 c 13.572,-11.657 22.867,-27.138 25.406,-43.684 0.058,-0.407 0.163,-0.803 0.221,-1.211 0,0 3.549,-15.543 5.002,-23.374 3.011,-16.206 6.774,-28.567 8.464,-43.381 0.405,-3.552 0.724,-7.203 0.846,-11.137 0.042,-1.388 0.126,-2.721 0.126,-4.175 0,-47.834 -40.191,-86.615 -96.009,-86.615 z"
+ id="path50"
+ inkscape:connector-curvature="0"
+ style="fill:#b58765" /><g
+ id="g52"><g
+ id="g54"><path
+ d="m 860.944,613.502 c 0,28.321 -35.091,51.289 -78.383,51.289 -43.299,0 -78.388,-22.968 -78.388,-51.289 0,-28.325 35.089,-51.289 78.388,-51.289 43.292,0 78.383,22.964 78.383,51.289 z"
+ id="path56"
+ inkscape:connector-curvature="0"
+ style="fill:#fef3df" /></g></g><g
+ id="g58"><g
+ id="g60"><g
+ id="g62"><path
+ d="m 747.044,605.582 c 0,6.215 -5.04,11.256 -11.261,11.256 -6.21,0 -11.253,-5.041 -11.253,-11.256 0,-6.223 5.043,-11.257 11.253,-11.257 6.22,0 11.261,5.034 11.261,11.257 z"
+ id="path64"
+ inkscape:connector-curvature="0"
+ style="fill:#5a3620" /></g></g><g
+ id="g66"><g
+ id="g68"><path
+ d="m 840.856,605.582 c 0,6.215 -5.037,11.256 -11.257,11.256 -6.218,0 -11.259,-5.041 -11.259,-11.256 0,-6.223 5.041,-11.257 11.259,-11.257 6.22,0 11.257,5.034 11.257,11.257 z"
+ id="path70"
+ inkscape:connector-curvature="0"
+ style="fill:#5a3620" /></g></g></g><g
+ id="g72"><path
+ d="m 875.228,525.835 c 0.354,-3.113 0.634,-6.311 0.743,-9.754 0.035,-1.218 0.109,-2.384 0.109,-3.661 0,-40.785 -33.369,-74.043 -80.237,-75.775 l -7.335,0.005 c -0.003,0 -0.003,0 -0.006,0 -0.007,0.018 -28.632,88.422 76.583,140.268 0.946,-4.317 2.078,-9.585 2.73,-13.088 2.64,-14.196 5.934,-25.021 7.413,-37.995 z"
+ id="path74"
+ inkscape:connector-curvature="0"
+ style="fill:#87654a" /></g><g
+ id="g76"><g
+ id="g78"><g
+ id="g80"><g
+ id="g82"><path
+ d="m 843.907,519.681 c 0,6.964 -5.65,12.611 -12.618,12.611 -6.963,0 -12.614,-5.646 -12.614,-12.611 0,-6.97 5.651,-12.614 12.614,-12.614 6.968,0 12.618,5.644 12.618,12.614 z"
+ id="path84"
+ inkscape:connector-curvature="0"
+ style="fill:#5a3620" /></g></g></g><g
+ id="g86"><g
+ id="g88"><g
+ id="g90"><path
+ d="m 752.028,519.681 c 0,6.964 -5.649,12.611 -12.612,12.611 -6.969,0 -12.612,-5.646 -12.612,-12.611 0,-6.97 5.642,-12.614 12.612,-12.614 6.964,0 12.612,5.644 12.612,12.614 z"
+ id="path92"
+ inkscape:connector-curvature="0"
+ style="fill:#5a3620" /></g></g></g><g
+ id="g94"><g
+ id="g96"><path
+ d="m 748.75,515.894 c 0,2.558 -2.071,4.629 -4.63,4.629 -2.558,0 -4.633,-2.072 -4.633,-4.629 0,-2.552 2.076,-4.626 4.633,-4.626 2.559,0 4.63,2.073 4.63,4.626 z"
+ id="path98"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" /></g></g><g
+ id="g100"><g
+ id="g102"><path
+ d="m 839.771,515.894 c 0,2.558 -2.073,4.629 -4.629,4.629 -2.558,0 -4.631,-2.072 -4.631,-4.629 0,-2.552 2.072,-4.626 4.631,-4.626 2.555,0 4.629,2.073 4.629,4.626 z"
+ id="path104"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" /></g></g></g></g><path
+ d="m 734.557,443.625 c 0,0 -18.236,-25.199 0,-41.637 0,0 13.125,32.012 40.242,31.502"
+ id="path106"
+ inkscape:connector-curvature="0"
+ style="fill:#fef3df" /><path
+ d="m 834.496,443.625 c 0,0 18.236,-25.199 0,-41.637 0,0 -13.126,32.012 -40.242,31.502"
+ id="path108"
+ inkscape:connector-curvature="0"
+ style="fill:#fef3df" /><path
+ d="m 786.264,431.965 c -66.396,0 -120.223,53.827 -120.223,120.223 0,66.396 53.827,120.221 120.223,120.221 66.397,0 120.222,-53.825 120.222,-120.221 10e-4,-66.395 -53.825,-120.223 -120.222,-120.223 z m -11.96,215.702 c -53.009,0 -95.982,-43.855 -95.982,-97.953 0,-54.098 42.973,-97.952 95.982,-97.952 53.007,0 95.979,43.855 95.979,97.952 0,54.098 -42.972,97.953 -95.979,97.953 z"
+ id="path110"
+ inkscape:connector-curvature="0"
+ style="fill:#f1f2f2" /></g><g
+ id="g112"><path
+ d="m 781.737,436.751 c 66.396,0 120.221,53.827 120.221,120.223 0,30.718 -11.526,58.74 -30.482,79.991 21.636,-21.74 35.01,-51.708 35.01,-84.803 0,-66.395 -53.825,-120.222 -120.222,-120.222 -35.678,0 -67.721,15.549 -89.739,40.233 21.772,-21.879 51.91,-35.422 85.212,-35.422 z"
+ id="path114"
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff" /></g></g><path
+ d="m 919.566,695.944 c 0,0 7.562,0.712 13.317,-0.502 l 13.013,16.12 c 0,0 -17.639,9.525 -26.33,12.523 l 0,-28.141 z"
+ id="path116"
+ inkscape:connector-curvature="0"
+ style="opacity:0.1;fill:#3d5263" /></g><path
+ d="m 648.292,659.614 0,81.645 c 0,9.72 7.88,17.6 17.6,17.6 l 236.073,0 c 9.72,0 17.6,-7.88 17.6,-17.6 l 0,-24.902 c 10e-4,0 -175.814,35.524 -271.273,-56.743 z"
+ id="path124"
+ inkscape:connector-curvature="0"
+ style="opacity:0.1;fill:#3d5263" /></g><g
+ id="g126" /></g></svg>
\ No newline at end of file
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
new file mode 100644
index 00000000..a62740ef
--- /dev/null
+++ b/data/web/inc/footer.inc.php
@@ -0,0 +1,81 @@
+<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/7.0.2/bootstrap-slider.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.4/js/bootstrap-select.js"></script>
+<script>
+// Select language and reopen active URL without POST
+function setLang(sel) {
+ $.post( "<?=$_SERVER['REQUEST_URI'];?>", {lang: sel} );
+ window.location.href = window.location.pathname + window.location.search;
+}
+
+$(document).ready(function() {
+ // Hide alerts after n seconds
+ $("#alert-fade").fadeTo(7000, 500).slideUp(500, function(){
+ $("#alert-fade").alert('close');
+ });
+
+ // Remember last navigation pill
+ (function () {
+ 'use strict';
+ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+ var id = $(this).parents('[role="tablist"]').attr('id');
+ var key = 'lastTag';
+ if (id) {
+ key += ':' + id;
+ }
+ localStorage.setItem(key, $(e.target).attr('href'));
+ });
+ $('[role="tablist"]').each(function (idx, elem) {
+ var id = $(elem).attr('id');
+ var key = 'lastTag';
+ if (id) {
+ key += ':' + id;
+ }
+ var lastTab = localStorage.getItem(key);
+ if (lastTab) {
+ $('[href="' + lastTab + '"]').tab('show');
+ }
+ });
+ })();
+
+ // Disable submit after submitting form
+ $('form').submit(function() {
+ if ($('form button[type="submit"]').data('submitted') == '1') {
+ return false;
+ } else {
+ $(this).find('button[type="submit"]').first().text('<?=$lang['footer']['loading'];?>');
+ $('form button[type="submit"]').attr('data-submitted', '1');
+ function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
+ $(document).on("keydown", disableF5);
+ }
+ });
+
+ // IE fix to hide scrollbars when table body is empty
+ $('tbody').filter(function (index) {
+ return $(this).children().length < 1;
+ }).remove();
+
+ // Init Bootstrap Selectpicker
+ $('select').selectpicker();
+
+});
+</script>
+<?php
+if (isset($_SESSION['return'])):
+?>
+<div class="container">
+ <div style="position:fixed;bottom:8px;right:25px;min-width:300px;max-width:350px;z-index:2000">
+ <div <?=($_SESSION['return']['type'] == 'danger') ? null : 'id="alert-fade"'?> class="alert alert-<?=$_SESSION['return']['type'];?>" role="alert">
+ <a href="#" class="close" data-dismiss="alert"> ×</a>
+ <?=htmlspecialchars($_SESSION['return']['msg']);?>
+ </div>
+ </div>
+</div>
+<?php
+unset($_SESSION['return']);
+endif;
+?>
+</body>
+</html>
+<?php $stmt = null; $pdo = null; ?>
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
new file mode 100644
index 00000000..e0067e31
--- /dev/null
+++ b/data/web/inc/functions.inc.php
@@ -0,0 +1,2656 @@
+<?php
+function hash_password($password) {
+ $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
+ if ($GLOBALS['HASHING'] == "SHA512-CRYPT") {
+ return "{SHA512-CRYPT}".crypt($password, '$6$'.$salt_str.'$');
+ }
+ else {
+ return "{SSHA256}".base64_encode(hash('sha256', $password.$salt_str, true).$salt_str);
+ }
+}
+function hasDomainAccess($username, $role, $domain) {
+ global $pdo;
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ return false;
+ }
+
+ if (!is_valid_domain_name($domain)) {
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins`
+ WHERE (
+ `active`='1'
+ AND `username` = :username
+ AND `domain` = :domain
+ )
+ OR 'admin' = :role");
+ $stmt->execute(array(':username' => $username, ':domain' => $domain, ':role' => $role));
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ } catch(PDOException $e) {
+ error_log($e);
+ return false;
+ }
+ if ($num_results != 0 && !empty($num_results)) {
+ return true;
+ }
+ 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;
+ }
+ if (!strpos(shell_exec("file --mime-encoding /usr/bin/doveadm"), "binary")) {
+ 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 (doveadm_authenticate($row['password'], $GLOBALS['HASHING'], $pass) !== false) {
+ 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 (doveadm_authenticate($row['password'], $GLOBALS['HASHING'], $pass) !== false) {
+ unset($_SESSION['ldelay']);
+ return "domainadmin";
+ }
+ }
+ $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+ WHERE `active`='1'
+ AND `username` = :user");
+ $stmt->execute(array(':user' => $user));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($rows as $row) {
+ if (doveadm_authenticate($row['password'], $GLOBALS['HASHING'], $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 dkim_table($action, $item) {
+ global $lang;
+ switch ($action) {
+ case "delete":
+ $domain = preg_replace('/[^A-Za-z0-9._\-]/', '_', $item['dkim']['domain']);
+ $selector = preg_replace('/[^A-Za-z0-9._\-]/', '_', $item['dkim']['selector']);
+ if (!ctype_alnum($selector) || !is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ break;
+ }
+ exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_TXTS'] . '/' . $selector . '_' . $domain), $out, $return);
+ if ($return != "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
+ );
+ break;
+ }
+ exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.' . $selector), $out, $return);
+ if ($return != "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
+ );
+ break;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['dkim_removed'])
+ );
+ break;
+ case "add":
+ $domain = preg_replace('/[^A-Za-z0-9._\-]/', '_', $item['dkim']['domain']);
+ $selector = preg_replace('/[^A-Za-z0-9._\-]/', '_', $item['dkim']['selector']);
+ $key_length = $item['dkim']['key_size'];
+ if (!ctype_alnum($selector) || !is_valid_domain_name($domain) || !is_numeric($key_length)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ break;
+ }
+
+ if (file_exists($GLOBALS['MC_DKIM_TXTS'] . '/' . $selector . '_' . $domain) ||
+ file_exists($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.' . $selector)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ break;
+ }
+
+ // Should be done native in PHP soon
+ $privKey = shell_exec("openssl genrsa -out /tmp/dkim-private.pem " . escapeshellarg($key_length) . " -outform PEM && cat /tmp/dkim-private.pem");
+ $pubKey = shell_exec('openssl rsa -in /tmp/dkim-private.pem -pubout -outform PEM 2>/dev/null | sed -e "1d" -e "\$d" | tr -d "\n"');
+ shell_exec('rm /tmp/dkim-private.pem');
+
+ file_put_contents($GLOBALS['MC_DKIM_TXTS'] . '/' . $selector . '_' . $domain, $pubKey);
+ file_put_contents($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.' . $selector, $privKey);
+
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['dkim_added'])
+ );
+ break;
+ }
+}
+function mailbox_add_domain($postarray) {
+ 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));
+ }
+ 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) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_add_alias($postarray) {
+ 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;
+
+ 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;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias`
+ WHERE `address`= :address");
+ $stmt->execute(array(':address' => $address));
+ $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']['is_alias_or_mailbox'], htmlspecialchars($address))
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `spamalias`
+ WHERE `address`= :address");
+ $stmt->execute(array(':address' => $address));
+ $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']['is_spam_alias'], htmlspecialchars($address))
+ );
+ 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;
+
+ 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) {
+ $_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) {
+ global $lang;
+ global $pdo;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+
+ if (!is_valid_domain_name($postarray['alias_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
+ );
+ return false;
+ }
+
+ if (!is_valid_domain_name($postarray['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'], $postarray['target_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ if ($postarray['alias_domain'] == $postarray['target_domain']) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
+ );
+ return false;
+ }
+
+ $alias_domain = strtolower(trim($postarray['alias_domain']));
+ $target_domain = strtolower(trim($postarray['target_domain']));
+
+ 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));
+ }
+ 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']['targetd_not_found'])
+ );
+ return false;
+ }
+
+ try {
+ $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));
+ }
+ 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_exists'])
+ );
+ 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) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+}
+function mailbox_edit_alias_domain($postarray) {
+ global $lang;
+ global $pdo;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ $alias_domain = idn_to_ascii($postarray['alias_domain']);
+ $alias_domain = strtolower(trim($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 WHERE `alias_domain` = :alias_domain_now");
+ $stmt->execute(array(
+ ':alias_domain' => $alias_domain,
+ ':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_add_mailbox($postarray) {
+ global $pdo;
+ global $lang;
+ $username = strtolower(trim($postarray['local_part'])).'@'.strtolower(trim($postarray['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;
+ }
+ $domain = strtolower(trim($postarray['domain']));
+ $password = $postarray['password'];
+ $password2 = $postarray['password2'];
+ $local_part = strtolower(trim($postarray['local_part']));
+ $name = $postarray['name'];
+ $quota_m = $postarray['quota'];
+
+ 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;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $domain));
+ $DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT
+ COUNT(*) as count,
+ COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota`
+ FROM `mailbox`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $domain));
+ $MailboxData = $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'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $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));
+ }
+ 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($username))
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :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;
+ }
+ if ($num_results != 0) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['is_alias'], htmlspecialchars($username))
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :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;
+ }
+ if ($num_results != 0) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($username))
+ );
+ 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;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain` 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) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => $lang['danger']['domain_not_found']
+ );
+ 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) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_edit_alias($postarray) {
+ 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 WHERE `address` = :address");
+ $stmt->execute(array(
+ ':goto' => $goto,
+ ':active' => $active,
+ ':address' => $address
+ ));
+ $_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) {
+ global $lang;
+ global $pdo;
+ $domain = $postarray['domain'];
+ $description = $postarray['description'];
+
+ $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;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+
+ try {
+ $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 domain= :domain");
+ $stmt->execute(array(':domain' => $domain));
+ $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+
+ try {
+ $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 (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ 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;
+ }
+
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ 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 edit_domain_admin($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $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;
+ }
+
+ 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;
+ }
+
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ 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 `modified` = :modified, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
+ $stmt->execute(array(
+ ':password_hashed' => $password_hashed,
+ ':username' => $username,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ else {
+ try {
+ $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `active` = :active WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ 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 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;
+ }
+
+ 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 (isset($postarray['sender_acl']) && is_array($postarray['sender_acl'])) {
+ foreach ($postarray['sender_acl'] as $sender_acl) {
+ if (!filter_var($sender_acl, FILTER_VALIDATE_EMAIL) &&
+ !is_valid_domain_name(str_replace('@', '', $sender_acl))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
+ );
+ return false;
+ }
+ }
+ foreach ($postarray['sender_acl'] as $sender_acl) {
+ 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;
+ }
+ }
+ }
+ 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_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 = 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 = substr(strrchr($address, "@"), 1);
+ 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;
+ if (!is_valid_domain_name($postarray['alias_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ $alias_domain = $postarray['alias_domain'];
+ 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,
+ ));
+ }
+ 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;
+ $domain = substr(strrchr($postarray['username'], "@"), 1);
+ $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 (!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 `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 `filterconf` WHERE `object` = :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 WHERE `address` = :address");
+ $stmt->execute(array(
+ ':goto' => $gotos_rebuild,
+ ':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 set_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;
+ }
+ $name = $postarray['admin_user'];
+ $name_now = $postarray['admin_user_now'];
+
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $name)) || empty ($name)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $name_now)) || empty ($name_now)) {
+ $_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` = :name
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':password_hashed' => $password_hashed,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':name' => $name,
+ ':username' => $name_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` = :name
+ WHERE `username` = :name_now");
+ $stmt->execute(array(
+ ':name' => $name,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':name_now' => $name_now
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ try {
+ $stmt = $pdo->prepare("UPDATE `domain_admins` SET
+ `domain` = :domain,
+ `username` = :name
+ WHERE `username` = :name_now");
+ $stmt->execute(array(
+ ':domain' => 'ALL',
+ ':name' => $name,
+ ':name_now' => $name_now
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['admin_modified'])
+ );
+}
+function set_time_limited_aliases($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $domain = substr($username, strpos($username, '@'));
+ if (($_SESSION['mailcow_cc_role'] != "user" &&
+ $_SESSION['mailcow_cc_role'] != "domainadmin") ||
+ empty($username) ||
+ empty($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ switch ($postarray["trigger_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 "delete":
+ 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 "extend":
+ 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;
+ }
+}
+function set_user_account($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $password_old = $postarray['user_old_pass'];
+ isset($postarray['togglePwNew']) ? $pwnew_active = '1' : $pwnew_active = '0';
+
+ if (isset($pwnew_active) && $pwnew_active == "1") {
+ $password_new = $postarray['user_new_pass'];
+ $password_new2 = $postarray['user_new_pass2'];
+ }
+
+ if (!check_login($username, $password_old) == "user") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ if ($_SESSION['mailcow_cc_role'] != "user" &&
+ $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ $_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'], $username)
+ );
+}
+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) {
+ $_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_spam_score($username) {
+ global $pdo;
+ $default = "5, 15";
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ return $default;
+ }
+ 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 ($num_results == 0 || 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 set_whitelist($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $whitelist_from = trim(strtolower($postarray['whitelist_from']));
+ $whitelist_from = preg_replace("/\.\*/", "*", $whitelist_from);
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if (!ctype_alnum(str_replace(array('@', '.', '-', '*'), '', $whitelist_from))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['whitelist_from_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT `object` FROM `filterconf`
+ WHERE `option` = 'whitelist_from'
+ AND `object` = :username
+ AND `value` = :whitelist_from");
+ $stmt->execute(array(':username' => $username, ':whitelist_from' => $whitelist_from));
+ $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']['whitelist_exists'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`)
+ VALUES (:username, 'whitelist_from', :whitelist_from)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':whitelist_from' => $whitelist_from
+ ));
+ }
+ 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 delete_whitelist($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $prefid = $postarray['wlid'];
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if (!is_numeric($prefid)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['whitelist_from_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username AND `prefid` = :prefid");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':prefid' => $prefid
+ ));
+ }
+ 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 set_blacklist($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $blacklist_from = trim(strtolower($postarray['blacklist_from']));
+ $blacklist_from = preg_replace("/\.\*/", "*", $blacklist_from);
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if (!ctype_alnum(str_replace(array('@', '.', '-', '*'), '', $blacklist_from))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['blacklist_from_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT `object` FROM `filterconf`
+ WHERE `option` = 'blacklist_from'
+ AND `object` = :username
+ AND `value` = :blacklist_from");
+ $stmt->execute(array(':username' => $username, ':blacklist_from' => $blacklist_from));
+ $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']['blacklist_exists'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`)
+ VALUES (:username, 'blacklist_from', :blacklist_from)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':blacklist_from' => $blacklist_from
+ ));
+ }
+ 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 delete_blacklist($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $prefid = $postarray['blid'];
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if (!is_numeric($prefid)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['blacklist_from_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username AND `prefid` = :prefid");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':prefid' => $prefid
+ ));
+ }
+ 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 set_spam_score($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $_SESSION['mailcow_cc_username'];
+ $lowspamlevel = explode(',', $postarray['score'])[0];
+ $highspamlevel = explode(',', $postarray['score'])[1];
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ 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) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
+ );
+}
+function set_tls_policy($postarray) {
+ global $lang;
+ global $pdo;
+ 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) {
+ global $lang;
+ global $pdo;
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT `tls_enforce_out`, `tls_enforce_in` FROM `mailbox` WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $TLSData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ return $TLSData;
+}
+function remaining_specs($domain, $object = null, $js = null) {
+ // left_m without object given = MiB left in domain
+ // left_m with object given = Max. MiB we can assign to given object
+ // limit_m = Domain limit in MiB
+ // left_c = Mailboxes we can create depending on domain quota
+ global $pdo;
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ 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 `in_use_m` FROM `mailbox` WHERE `domain` = :domain AND `username` != :object");
+ $stmt->execute(array(':domain' => $domain, ':object' => $object));
+ $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $quota_left_m = $DomainData['quota'] - $MailboxDataDomain['in_use_m'];
+ $mboxs_left = $DomainData['mailboxes'] - $MailboxDataDomain['count'];
+
+ if ($quota_left_m > $DomainData['maxquota']) {
+ $quota_left_m = $DomainData['maxquota'];
+ }
+
+ }
+ catch (PDOException $e) {
+ return false;
+ }
+ if (is_numeric($quota_left_m)) {
+ $spec['left_m'] = $quota_left_m;
+ $spec['limit_m'] = $DomainData['maxquota'];
+ }
+ if (is_numeric($mboxs_left)) {
+ $spec['left_c'] = $mboxs_left;
+ }
+ if (!empty($js)) {
+ echo $quota_left_m;
+ exit;
+ }
+ return $spec;
+}
+function get_sender_acl_handles($mailbox, $which) {
+ global $pdo;
+ if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ return false;
+ }
+ switch ($which) {
+ case "preselected":
+ 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);
+ return $rows;
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ break;
+ case "selected":
+ try {
+ $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as");
+ $stmt->execute(array(':logged_in_as' => $mailbox));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ return $rows;
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ break;
+ case "unselected-domains":
+ try {
+ if ($_SESSION['mailcow_cc_role'] == "admin" ) {
+ $stmt = $pdo->prepare("SELECT DISTINCT `domain` FROM `domain`
+ WHERE `domain` NOT IN (
+ SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl`
+ WHERE `logged_in_as` = :logged_in_as)
+ AND `domain` NOT IN (
+ SELECT REPLACE(`address`, '@', '') FROM `alias`
+ WHERE `goto` = :goto)");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ':goto' => $mailbox,
+ ));
+ }
+ else {
+ $stmt = $pdo->prepare("SELECT DISTINCT `domain` FROM `domain_admins`
+ WHERE `username` = :username
+ AND `domain` != 'ALL'
+ AND `domain` NOT IN (
+ SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl`
+ WHERE `logged_in_as` = :logged_in_as)");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ':username' => $_SESSION['mailcow_cc_username']
+ ));
+ }
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ return $rows;
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ break;
+ case "unselected-addresses":
+ try {
+ if ($_SESSION['mailcow_cc_role'] == "admin" ) {
+ $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)");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ':goto' => $mailbox
+ ));
+ }
+ else {
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias`
+ WHERE `goto` != :goto
+ AND `domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username` = :username)
+ AND `address` NOT IN (
+ SELECT `send_as` FROM `sender_acl`
+ WHERE `logged_in_as` = :logged_in_as)");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ':goto' => $mailbox,
+ ':username' => $_SESSION['mailcow_cc_username']
+ ));
+ }
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ return $rows;
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ break;
+ }
+ 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));
+}
+?>
diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php
new file mode 100644
index 00000000..3d5635bf
--- /dev/null
+++ b/data/web/inc/header.inc.php
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<html lang="<?= $_SESSION['mailcow_locale'] ?>">
+<head>
+<meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>mailcow UI - <?php echo gethostname() ?></title>
+<!--[if lt IE 9]>
+<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+<![endif]-->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js" integrity="sha384-XxcvoeNF5V0ZfksTnV+bejnCsJjOOIzN6UVwF85WBsAnU3zeYh5bloN+L4WLgeNE" crossorigin="anonymous"></script>
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.6/<?=strtolower(trim($DEFAULT_THEME));?>/bootstrap.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.4/css/bootstrap-select.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/7.0.2/css/bootstrap-slider.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap3/bootstrap-switch.min.css">
+<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700&subset=latin,latin-ext">
+<link rel="stylesheet" href="/inc/languages.min.css">
+<link rel="shortcut icon" href="/favicon.png" type="image/png">
+<link rel="icon" href="/favicon.png" type="image/png">
+<style>
+#maxmsgsize { min-width: 80px; }
+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 }
+#slider1 .slider-selection {
+ background: #FFD700;
+}
+#slider1 .slider-track-high {
+ background: #FF4500;
+}
+#slider1 .slider-track-low {
+ background: #66CD00;
+}
+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;
+}
+.btn {
+ text-transform: none;
+}
+#data td, #no-data td {
+ vertical-align: middle;
+}
+.sort-table:hover {
+ border-bottom-color: #00B7DC !important;
+}
+</style>
+<?php
+if (preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])):
+?>
+<style>
+.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;
+}
+</style>
+<?php
+endif;
+?>
+</head>
+<body style="padding-top:70px">
+<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
+ <div class="container-fluid">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="/"><img height="32" alt="mailcow-logo" style="margin-top:-5px;" src="/img/cow_mailcow.svg" /></a>
+ </div>
+ <div id="navbar" class="navbar-collapse collapse">
+ <ul class="nav navbar-nav navbar-right">
+ <?php
+ if (isset($_SESSION['mailcow_locale'])) {
+ ?>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="lang-sm lang-lbl" lang="<?=$_SESSION['mailcow_locale'];?>"></span><span class="caret"></span></a>
+ <ul class="dropdown-menu" role="menu">
+ <li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>> <a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
+ </ul>
+ </li>
+ <?php
+ }
+ if (isset($_SESSION['mailcow_cc_role'])) {
+ ?>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><?=$lang['header']['mailcow_settings'];?><span class="caret"></span></a>
+ <ul class="dropdown-menu" role="menu">
+ <?php
+ if (isset($_SESSION['mailcow_cc_role'])) {
+ if ($_SESSION['mailcow_cc_role'] == "admin") {
+ ?>
+ <li <?=(preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? 'class="active"' : ''?>><a href="/admin.php"><?=$lang['header']['administration'];?></a></li>
+ <?php
+ }
+ if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin") {
+ ?>
+ <li <?=(preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? 'class="active"' : ''?>><a href="/mailbox.php"><?=$lang['header']['mailboxes'];?></a></li>
+ <?php
+ }
+ if ($_SESSION['mailcow_cc_role'] == "user") {
+ ?>
+ <li <?=(preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? 'class="active"' : ''?>><a href="/user.php"><?=$lang['header']['user_settings'];?></a></li>
+ <?php
+ }
+ }
+ ?>
+ </ul>
+ </li>
+ <?php
+ }
+ if (isset($_SESSION['mailcow_cc_username'])):
+ ?>
+ <li><a style="border-left:1px solid #E7E7E7" href="#" onclick="logout.submit()"><?=sprintf($lang['header']['logged_in_as_logout'], $_SESSION['mailcow_cc_username']);?></a></li>
+ <?php
+ endif;
+ ?>
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div><!--/.container-fluid -->
+</nav>
+<form action="/" method="post" id="logout"><input type="hidden" name="logout"></form>
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/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
new file mode 100644
index 00000000..5fce44a1
--- /dev/null
+++ b/data/web/inc/prerequisites.inc.php
@@ -0,0 +1,71 @@
+<?php
+//ini_set("session.cookie_secure", 1);
+//ini_set("session.cookie_httponly", 1);
+session_start();
+if (isset($_POST["logout"])) {
+ session_unset();
+ session_destroy();
+ session_write_close();
+ setcookie(session_name(),'',0,'/');
+}
+
+require_once 'inc/vars.inc.php';
+
+if (file_exists('./inc/vars.local.inc.php')) {
+ include_once 'inc/vars.local.inc.php';
+}
+
+$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);
+
+$_SESSION['mailcow_locale'] = strtolower(trim($DEFAULT_LANG));
+setcookie('language', $DEFAULT_LANG);
+if (isset($_COOKIE['language'])) {
+ switch ($_COOKIE['language']) {
+ case "de":
+ $_SESSION['mailcow_locale'] = 'de';
+ setcookie('language', 'de');
+ break;
+ case "en":
+ $_SESSION['mailcow_locale'] = 'en';
+ setcookie('language', 'en');
+ break;
+ case "nl":
+ $_SESSION['mailcow_locale'] = 'nl';
+ setcookie('language', 'nl');
+ break;
+ case "pt":
+ $_SESSION['mailcow_locale'] = 'pt';
+ setcookie('language', 'pt');
+ break;
+ }
+}
+if (isset($_GET['lang'])) {
+ switch ($_GET['lang']) {
+ case "de":
+ $_SESSION['mailcow_locale'] = 'de';
+ setcookie('language', 'de');
+ break;
+ case "en":
+ $_SESSION['mailcow_locale'] = 'en';
+ setcookie('language', 'en');
+ break;
+ case "nl":
+ $_SESSION['mailcow_locale'] = 'nl';
+ setcookie('language', 'nl');
+ break;
+ case "pt":
+ $_SESSION['mailcow_locale'] = 'pt';
+ setcookie('language', 'pt');
+ break;
+ }
+}
+require_once 'lang/lang.en.php';
+include 'lang/lang.'.$_SESSION['mailcow_locale'].'.php';
+require_once 'inc/functions.inc.php';
+require_once 'inc/triggers.inc.php';
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
new file mode 100644
index 00000000..9fd62e68
--- /dev/null
+++ b/data/web/inc/triggers.inc.php
@@ -0,0 +1,122 @@
+<?php
+if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
+ $login_user = strtolower(trim($_POST["login_user"]));
+ $as = check_login($login_user, $_POST["pass_user"]);
+ if ($as == "admin") {
+ $_SESSION['mailcow_cc_username'] = $login_user;
+ $_SESSION['mailcow_cc_role'] = "admin";
+ header("Location: /admin.php");
+ }
+ elseif ($as == "domainadmin") {
+ $_SESSION['mailcow_cc_username'] = $login_user;
+ $_SESSION['mailcow_cc_role'] = "domainadmin";
+ header("Location: /mailbox.php");
+ }
+ elseif ($as == "user") {
+ $_SESSION['mailcow_cc_username'] = $login_user;
+ $_SESSION['mailcow_cc_role'] = "user";
+ header("Location: /user.php");
+ }
+ else {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => $lang['danger']['login_failed']
+ );
+ }
+}
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+ if (isset($_POST["trigger_set_admin"])) {
+ set_admin_account($_POST);
+ }
+ if (isset($_POST["delete_dkim_record"])) {
+ dkim_table("delete", $_POST);
+ }
+ if (isset($_POST["add_dkim_record"])) {
+ dkim_table("add", $_POST);
+ }
+ if (isset($_POST["trigger_add_domain_admin"])) {
+ add_domain_admin($_POST);
+ }
+ if (isset($_POST["trigger_delete_domain_admin"])) {
+ delete_domain_admin($_POST);
+ }
+ if (isset($_POST["trigger_edit_domain_admin"])) {
+ edit_domain_admin($_POST);
+ }
+}
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user") {
+ if (isset($_POST["trigger_set_user_account"])) {
+ set_user_account($_POST);
+ }
+ if (isset($_POST["trigger_set_spam_score"])) {
+ set_spam_score($_POST);
+ }
+ if (isset($_POST["trigger_set_whitelist"])) {
+ set_whitelist($_POST);
+ }
+ if (isset($_POST["trigger_delete_whitelist"])) {
+ delete_whitelist($_POST);
+ }
+ if (isset($_POST["trigger_set_blacklist"])) {
+ set_blacklist($_POST);
+ }
+ if (isset($_POST["trigger_delete_blacklist"])) {
+ delete_blacklist($_POST);
+ }
+ if (isset($_POST["trigger_set_tls_policy"])) {
+ set_tls_policy($_POST);
+ }
+ if (isset($_POST["trigger_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($_GET["js"])) {
+ switch ($_GET["js"]) {
+ case "remaining_specs":
+ remaining_specs($_GET['domain'], $_GET['object'], "y");
+ break;
+ }
+ }
+ if (isset($_POST["trigger_mailbox_action"])) {
+ switch ($_POST["trigger_mailbox_action"]) {
+ case "adddomain":
+ mailbox_add_domain($_POST);
+ break;
+ case "addalias":
+ mailbox_add_alias($_POST);
+ break;
+ case "editalias":
+ mailbox_edit_alias($_POST);
+ break;
+ case "addaliasdomain":
+ mailbox_add_alias_domain($_POST);
+ break;
+ case "addmailbox":
+ mailbox_add_mailbox($_POST);
+ break;
+ case "editdomain":
+ mailbox_edit_domain($_POST);
+ break;
+ case "editmailbox":
+ mailbox_edit_mailbox($_POST);
+ break;
+ case "deletedomain":
+ mailbox_delete_domain($_POST);
+ break;
+ case "deletealias":
+ mailbox_delete_alias($_POST);
+ break;
+ case "deletealiasdomain":
+ mailbox_delete_alias_domain($_POST);
+ break;
+ case "editaliasdomain":
+ mailbox_edit_alias_domain($_POST);
+ break;
+ case "deletemailbox":
+ mailbox_delete_mailbox($_POST);
+ break;
+ }
+ }
+}
+?>
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
new file mode 100644
index 00000000..6a033d4a
--- /dev/null
+++ b/data/web/inc/vars.inc.php
@@ -0,0 +1,36 @@
+<?php
+error_reporting(0);
+
+/*
+PLEASE USE THE FILE "vars.local.inc.php" TO OVERWRITE SETTINGS AND MAKE THEM PERSISTENT!
+This file will be reset on upgrades.
+*/
+
+// SQL database connection variables
+$database_type = "mysql";
+$database_host = "mysql";
+$database_user = "mailcow";
+$database_pass = "mysafepasswd";
+$database_name = "mailcow";
+
+// Where to go after adding and editing objects
+// Can be "form" or "previous"
+// "form" will stay in the current form, "previous" will redirect to previous page
+$FORM_ACTION = "previous";
+
+// File locations should not be changed
+$MC_DKIM_TXTS = "/shared/dkim/txt";
+$MC_DKIM_KEYS = "/shared/dkim/keys";
+
+// Change default language, "en", "pt", "de" or "nl"
+$DEFAULT_LANG = "en";
+
+// Change theme (default: lumen)
+// Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone,
+// simplex, slate, spacelab, superhero, united, yeti
+// See https://bootswatch.com/
+$DEFAULT_THEME = "lumen";
+
+$HASHING = "SSHA256";
+
+?>
diff --git a/data/web/index.php b/data/web/index.php
new file mode 100644
index 00000000..e6659149
--- /dev/null
+++ b/data/web/index.php
@@ -0,0 +1,89 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+ header('Location: /admin.php');
+ exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "domainadmin") {
+ header('Location: /mailbox.php');
+ exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user") {
+ header('Location: /user.php');
+ exit();
+}
+require_once("inc/header.inc.php");
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+?>
+<div class="container">
+ <div class="row">
+ <div class="col-md-offset-3 col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['login']['login'];?></div>
+ <div class="panel-body">
+ <center><img style="max-width:250px" src="/img/cow_mailcow.svg" alt="mailcow"></center>
+ <legend>mailcow UI</legend>
+ <form method="post" autofill="off">
+ <div class="form-group">
+ <label class="sr-only" for="login_user"><?=$lang['login']['username'];?></label>
+ <div class="input-group">
+ <div class="input-group-addon"><i class="glyphicon glyphicon-user"></i></div>
+ <input name="login_user" autocorrect="off" autocapitalize="none" type="name" id="login_user" class="form-control" placeholder="<?=$lang['login']['username'];?>" required="" autofocus="">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="sr-only" for="pass_user"><?=$lang['login']['password'];?></label>
+ <div class="input-group">
+ <div class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></div>
+ <input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="<?=$lang['login']['password'];?>" required="">
+ </div>
+ </div>
+ <div class="form-group">
+ <button type="submit" class="btn btn-success" value="Login"><?=$lang['login']['login'];?></button>
+ <div class="btn-group pull-right">
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ <span class="lang-sm lang-lbl" lang="<?=$_SESSION['mailcow_locale'];?>"></span> <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <li <?=($_SESSION['mailcow_locale'] == 'de') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "de"))) ?>"><span class="lang-xs lang-lbl-full" lang="de"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'en') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "en"))) ?>"><span class="lang-xs lang-lbl-full" lang="en"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'nl') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "nl"))) ?>"><span class="lang-xs lang-lbl-full" lang="nl"></span></a></li>
+ <li <?=($_SESSION['mailcow_locale'] == 'pt') ? 'class="active"' : ''?>><a href="?<?= http_build_query(array_merge($_GET, array("lang" => "pt"))) ?>"><span class="lang-xs lang-lbl-full" lang="pt"></span></a></li>
+ </ul>
+ </div>
+ </div>
+ </form>
+ <?php
+ if (isset($_SESSION['ldelay']) && $_SESSION['ldelay'] != "0"):
+ ?>
+ <p><div class="alert alert-info"><?=sprintf($lang['login']['delayed'], $_SESSION['ldelay']);?></b></div></p>
+ <?php
+ endif;
+ ?>
+ <legend>mailcow Apps</legend>
+ <a href="/SOGo/" role="button" class="btn btn-lg btn-default"><?=$lang['start']['start_sogo'];?></a>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-offset-3 col-md-6">
+ <div class="panel panel-default" style="">
+ <div class="panel-heading">
+ <a data-toggle="collapse" href="#collapse1"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> <?=$lang['start']['help'];?></a>
+ </div>
+ <div id="collapse1" class="panel-collapse collapse">
+ <div class="panel-body">
+ <p><span style="border-bottom: 1px dotted #999">mailcow UI</span></p>
+ <p><?=$lang['start']['mailcow_panel_detail'];?></p>
+ <p><span style="border-bottom: 1px dotted #999">mailcow Apps</span></p>
+ <p><?=$lang['start']['mailcow_apps_detail'];?></p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> <!-- /container -->
+<script src="js/index.js"></script>
+<?php
+require_once("inc/footer.inc.php");
+?>
diff --git a/data/web/js/add.js b/data/web/js/add.js
new file mode 100644
index 00000000..e3089c53
--- /dev/null
+++ b/data/web/js/add.js
@@ -0,0 +1,16 @@
+$(document).ready(function() {
+ // add.php
+ // Get max. possible quota for a domain when domain field changes
+ $('#addSelectDomain').on('change', function() {
+ $.get("add.php", { js:"remaining_specs", domain:this.value, object:"new" }, function(data){
+ if (data != '0') {
+ $("#quotaBadge").html('max. ' + data + ' MiB');
+ $('#addInputQuota').attr({"disabled": false, "value": "", "type": "number", "max": data});
+ }
+ else {
+ $("#quotaBadge").html('max. ' + data + ' 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 = $("<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 = $("<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/index.js b/data/web/js/index.js
new file mode 100644
index 00000000..568bbb8a
--- /dev/null
+++ b/data/web/js/index.js
@@ -0,0 +1,3 @@
+$(document).ready(function() {
+ $('nav').hide();
+});
\ No newline at end of file
diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js
new file mode 100644
index 00000000..df8e22d7
--- /dev/null
+++ b/data/web/js/mailbox.js
@@ -0,0 +1,52 @@
+$(document).ready(function() {
+ // Show element counter for tables
+ $('[data-toggle="tooltip"]').tooltip();
+ var rowCountDomainAlias = $('#domainaliastable >tbody >#data').length;
+ var rowCountDomain = $('#domaintable >tbody >#data').length;
+ var rowCountMailbox = $('#mailboxtable >tbody >#data').length;
+ var rowCountAlias = $('#aliastable >tbody >#data').length;
+ $("#numRowsDomainAlias").text(rowCountDomainAlias);
+ $("#numRowsDomain").text(rowCountDomain);
+ $("#numRowsMailbox").text(rowCountMailbox);
+ $("#numRowsAlias").text(rowCountAlias);
+
+ // 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 = $('<tr class="filterTable_no_results"><td colspan="100%">-</td></tr>')
+ $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/user.js b/data/web/js/user.js
new file mode 100644
index 00000000..503b5018
--- /dev/null
+++ b/data/web/js/user.js
@@ -0,0 +1,28 @@
+$(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
+ $('#trigger_set_time_limited_aliases').hide();
+ $('#validity').change(function(){
+ $('#trigger_set_time_limited_aliases').show();
+ });
+
+ // Init Bootstrap Switch
+ $.fn.bootstrapSwitch.defaults.onColor = 'success';
+ $("[name='tls_out']").bootstrapSwitch();
+ $("[name='tls_in']").bootstrapSwitch();
+});
\ 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..83ca8bdc
--- /dev/null
+++ b/data/web/lang/lang.de.php
@@ -0,0 +1,358 @@
+<?php
+/*
+//
+// German language file
+//
+*/
+$lang['footer']['loading'] = 'Einen Moment bitte...';
+$lang['getmail']['no_status'] = 'Keinen letzten Vorgang festgestellt.';
+$lang['dkim']['confirm'] = 'Sind Sie sicher?';
+$lang['danger']['dkim_not_found'] = 'DKIM-Record nicht gefunden';
+$lang['danger']['dkim_remove_failed'] = 'Kann DKIM-Record nicht entfernen';
+$lang['danger']['dkim_add_failed'] = 'Kann DKIM-Record 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-Record wurde entfernt';
+$lang['success']['dkim_added'] = 'DKIM-Record 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']['blacklist_exists'] = 'Ein Backlist-Eintrag mit diesem Wert existiert bereits';
+$lang['danger']['blacklist_from_invalid'] = 'Backlist-Eintrag hat ungültiges Format';
+$lang['danger']['whitelist_exists'] = 'Ein Whitelist-Eintrag mit diesem Wert existiert bereits';
+$lang['danger']['whitelist_from_invalid'] = 'Whitelist-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']['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']['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';
+$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']['mailbox_invalid_suggest'] = 'Mailboxname ist ungültig, meinten Sie vielleicht "%s"?';
+$lang['info']['fetchmail_planned'] = 'Aufgabe zur Mailabholung wurde geplant. Bitte prüfen Sie den Vorgangsstatus zu einem späteren Zeitpunkt noch einmal.';
+$lang['danger']['fetchmail_source_empty'] = 'Bitte geben Sie einen Quell-Ordner an';
+$lang['danger']['fetchmail_dest_empty'] = 'Bitte geben Sie einen Ziel-Ordner an';
+$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 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']['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['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']['fetchmail_active'] = 'Ein Vorgang zur Mailabholung ist bereits aktiv, bitte haben Sie etwas Geduld.';
+$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'] = '<b>Wussten Sie schon?</b> Sie können Ihre E-Mail-Adresse mit Tags versehen, etwa "ich+<b>Privat</b>@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']['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']['alias_select_validity'] = 'Bitte Gültigkeit auswählen';
+$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 <b>nicht</b> 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 <b>immer</b> 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']['tls_policy_warning'] = '<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br />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['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'] = '<b>Domain-Administratoren</b> erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.<br />
+ Als <b>Mailbox-Benutzer</b> 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 <a href="%s" target="_blank"><b>Mozilla Thunderbirds</b></a>.';
+$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.<br />
+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 <b>Mozilla Thunderbirds</b> zusammen mit der <a style="text-decoration:none" target="_blank" href="%s"><b>Sieve Erweiterung</b></a>.<br />Nach dem Herunterladen der Erweiterung starten Sie Thunderbird, öffnen das Fenster für Erweiterungen und ziehen die heruntergeladene Datei in das offene Fenster.<br />Der Servername lautet <b>%s</b>, als Port konfigurieren Sie bitte <b>4190</b>. 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 <b>%s</b> (abmelden)';
+$lang['header']['locale'] = 'Sprache';
+$lang['mailbox']['domain'] = 'Domain';
+$lang['mailbox']['alias'] = 'Alias';
+$lang['mailbox']['aliases'] = 'Aliasse';
+$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';
+$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_alias'] = 'Alias hinzufügen';
+
+$lang['info']['no_action'] = 'Keine Aktion anwendbar';
+
+$lang['delete']['title'] = 'Objekt entfernen';
+$lang['delete']['remove_domain_warning'] = '<b>Warnung:</b> Sie entfernen die Domain <b>%s</b>!';
+$lang['delete']['remove_domainalias_warning'] = '<b>Warnung:</b> Sie entfernen die Alias-Domain <b>%s</b>!';
+$lang['delete']['remove_domainadmin_warning'] = '<b>Warnung:</b> Sie entfernen den Domain-Administrator <b>%s</b>!';
+$lang['delete']['remove_alias_warning'] = '<b>Warnung:</b> Sie entfernen die Alias-Adresse <b>%s</b>!';
+$lang['delete']['remove_mailbox_warning'] = '<b>Warnung:</b> Sie entfernen die Mailbox <b>%s</b>!';
+$lang['delete']['remove_mailbox_details'] = 'Die Mailbox wird <b>vollständig und permanent</b> entfernt!';
+$lang['delete']['remove_domain_details'] = 'Diese Aktion entfernt ebenfalls Domain-Aliasse.<br /><br /><b>Eine Domain muss leer sein, um entfernt zu werden.</b>';
+$lang['delete']['remove_alias_details'] = 'Benutzer werden keine Nachrichten mehr von dieser Adresse erhalten und versenden koennen!</b>';
+$lang['delete']['remove_button'] = 'Entfernen';
+$lang['delete']['previous'] = 'Vorherige Seite';
+
+$lang['edit']['save'] = 'Änderungen speichern';
+$lang['edit']['archive'] = 'Archiv-Zugriff';
+$lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:';
+$lang['edit']['title'] = 'Objekt bearbeiten';
+$lang['edit']['target_address'] = 'Ziel-Adresse(n) <small>(getrennt durch Komma)</small>:';
+$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'] = '<small>Bitte hinterlegen Sie einen TXT-Record mit obigem Wert in den DNS-Einstellungen Ihrer Domainverwaltung.</small>';
+$lang['edit']['relay_all_info'] = '<small>Wenn Sie <b>nicht</b> alle Empfänger-Adressen relayen möchten, müssen Sie eine ("blinde") Mailbox für jede Adresse, die relayt werden soll, erstellen.</small>';
+$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['add']['title'] = 'Objekt anlegen';
+$lang['add']['domain'] = 'Domain';
+$lang['add']['active'] = 'Aktiv';
+$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'] = '<small>Wenn Sie <b>nicht</b> alle Empfänger-Adressen relayen möchten, müssen Sie eine Mailbox für jede Adresse, die relayt werden soll, erstellen.</small>';
+$lang['add']['alias'] = 'Alias(se)';
+$lang['add']['alias_spf_fail'] = '<b>Hinweis:</b> Wählen Sie ein externes Postfach als Ziel-Adresse, kann es unter Umständen zu fehlerhaften Spam-Erkennungen <b>beim Empfänger</b> kommen. Weitere Informationen zu diesem Thema finden Sie <a href="https://www.heinlein-support.de/blog/news/gmx-de-und-web-de-haben-mail-rejects-durch-spf/" target="_blank">hier.</a>';
+$lang['add']['alias_address'] = 'Alias-Adresse(n):';
+$lang['add']['alias_address_info'] = '<small>Vollständige E-Mail-Adresse(n) oder @example.com, um alle Nachrichten einer Domain weiterzuleiten. Getrennt durch Komma. <b>Nur eigene Domains</b>.</small>';
+$lang['add']['alias_domain_info'] = '<small>Nur gültige Domains. Getrennt durch Komma.</small>';
+$lang['add']['target_address'] = 'Ziel-Adresse(n):';
+$lang['add']['target_address_info'] = '<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>';
+$lang['add']['alias_domain'] = 'Alias-Domain';
+$lang['add']['select'] = 'Bitte auswählen';
+$lang['add']['target_domain'] = 'Ziel-Domain:';
+$lang['add']['mailbox'] = 'Mailbox';
+$lang['add']['mailbox_username'] = 'Benutzername (linker Teil der E-Mail-Adresse):';
+$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['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['login']['tfa'] = 'Zwei-Faktor-Authentifizierung';
+$lang['login']['tfa_details'] = 'Bitte bestätigen Sie Ihr Einmalpasswort im folgenden Feld';
+$lang['login']['confirm'] = 'Bestätigen';
+$lang['login']['otp'] = 'Einmalpasswort';
+$lang['login']['trash_login'] = 'Login verwerfen';
+
+$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.<br />Sie können ein Element in der Datei <code>inc/vars.local.inc.php</code> 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 <small>(alphanumerisch)</small>';
+$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-Record hinzufügen';
+$lang['admin']['dkim_keys'] = 'DKIM-Records';
+$lang['admin']['dkim_key_length'] = 'DKIM Schlüssellänge (Bits)';
+$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..23f02825
--- /dev/null
+++ b/data/web/lang/lang.en.php
@@ -0,0 +1,361 @@
+<?php
+/*
+//
+// English language file
+//
+*/
+$lang['footer']['loading'] = "Please wait...";
+$lang['getmail']['no_status'] = "No previous status found.";
+$lang['dkim']['confirm'] = "Are you sure?";
+$lang['danger']['dkim_not_found'] = "DKIM record not found";
+$lang['danger']['dkim_remove_failed'] = "Cannot remove selected DKIM record";
+$lang['danger']['dkim_add_failed'] = "Cannot add given DKIM record";
+$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 record has been removed";
+$lang['success']['dkim_added'] = "DKIM record 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']['goto_empty'] = "Goto address must not be empty";
+$lang['danger']['blacklist_exists'] = "A blacklist record with that name exists";
+$lang['danger']['blacklist_from_invalid'] = "Blacklist 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']['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";
+$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']['mailbox_invalid_suggest'] = 'Mailbox name is invalid, did you mean to type "%s"?';
+$lang['info']['fetchmail_planned'] = "Task to fetch emails has been planned. Please check the process at a later time.";
+$lang['danger']['fetchmail_source_empty'] = "Please define a source folder";
+$lang['danger']['fetchmail_dest_empty'] = "Please define a target folder";
+$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']['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['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']['fetchmail_active'] = "A process is already running, please wait for it to finish.";
+$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'] = '<b>Did you know?</b> You can use tags in your email address ("me+<b>privat</b>@example.com") to move messages to a folder automatically (example: "privat").';
+$lang['user']['spam_aliases'] = 'Temporary email aliases';
+$lang['user']['alias'] = 'Alias';
+$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']['alias_select_validity'] = 'Period of validity';
+$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 <b>never</b> classify as spam. Wildcards maybe used.';
+$lang['user']['spamfilter_bl'] = 'Blacklist';
+$lang['user']['spamfilter_bl_desc'] = 'Blacklisted email addresses to <b>always</b> 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_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']['tls_policy_warning'] = '<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />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['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'] = '<b>Domain administrators</b> create, modify or delete mailboxes and aliases, change domains and read further information about their assigned domains.<br />
+ <b>Mailbox users</b> 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 <a href="%s" target="_blank"><b>Mozilla Thunderbird</b></a>.';
+$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.<br />
+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 <b>Mozilla Thunderbird</b> with the <a style="text-decoration:none" target="_blank" href="%s"><b>nightly sieve extension</b></a>.<br />Start Thunderbird, open the add-on settings and drop the newly downloaded xpi file into the opened window.<br />The server name is <b>%s</b>, use port <b>4190</b> 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 <b>%s</b> (logout)';
+$lang['header']['locale'] = 'Language';
+$lang['mailbox']['domain'] = 'Domain';
+$lang['mailbox']['alias'] = 'Alias';
+$lang['mailbox']['aliases'] = 'Aliases';
+$lang['mailbox']['domains'] = 'Domains';
+$lang['mailbox']['mailboxes'] = 'Mailboxes';
+$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';
+$lang['mailbox']['add_domain'] = 'Add domain';
+$lang['mailbox']['add_domain_alias'] = 'Add domain alias';
+$lang['mailbox']['add_mailbox'] = 'Add mailbox';
+$lang['mailbox']['add_alias'] = 'Add alias';
+
+$lang['info']['no_action'] = 'No action applicable';
+
+$lang['delete']['title'] = 'Remove object';
+$lang['delete']['remove_domain_warning'] = '<b>Warning:</b> You are about to remove the domain <b>%s</b>!';
+$lang['delete']['remove_domainalias_warning'] = '<b>Warning:</b> You are about to remove the domain alias <b>%s</b>!';
+$lang['delete']['remove_domainadmin_warning'] = '<b>Warning:</b> You are about to remove the domain administrator <b>%s</b>!';
+$lang['delete']['remove_alias_warning'] = '<b>Warning:</b> You are about to remove the alias address <b>%s</b>!';
+$lang['delete']['remove_mailbox_warning'] = '<b>Warning:</b> You are about to remove the mailbox <b>%s</b>!';
+$lang['delete']['remove_mailbox_details'] = 'The mailbox will be <b>purged permanently</b>!';
+$lang['delete']['remove_domain_details'] = 'This also removes domain aliases.<br /><br /><b>A domain must be empty to be removed.</b>';
+$lang['delete']['remove_alias_details'] = 'Users will no longer be able to receive mail for or send mail from this address.</b>';
+$lang['delete']['remove_button'] = 'Remove';
+$lang['delete']['previous'] = 'Previous page';
+
+$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 <small>(comma-separated)</small>:';
+$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'] = '<small>Please add a TXT record with the given value to your DNS settings.</small>';
+$lang['edit']['relay_all_info'] = '<small>If you choose <b>not</b> to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.</small>';
+$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['add']['title'] = 'Add object';
+$lang['add']['domain'] = 'Domain';
+$lang['add']['active'] = 'Active';
+$lang['add']['save'] = 'Save changes';
+$lang['add']['description'] = 'Description:';
+$lang['add']['max_aliases'] = 'Max. possible aliases:';
+$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'] = '<small>If you choose <b>not</b> to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.</small>';
+$lang['add']['alias'] = 'Alias(es)';
+$lang['add']['alias_spf_fail'] = '<b>Note:</b> If your chosen destination address is an external mailbox, the <b>receiving mailserver</b> may reject your message due to an SPF failure.</a>';
+$lang['add']['alias_address'] = 'Alias address/es:';
+$lang['add']['alias_address_info'] = '<small>Full email address/es or @example.com, to catch all messages for a domain (comma-separated). <b>mailcow domains only</b>.</small>';
+$lang['add']['alias_domain_info'] = '<small>Valid domain names only (comma-separated).</small>';
+$lang['add']['target_address'] = 'Goto addresses:';
+$lang['add']['target_address_info'] = '<small>Full email address/es (comma-separated).</small>';
+$lang['add']['alias_domain'] = 'Alias domain';
+$lang['add']['select'] = 'Please select...';
+$lang['add']['target_domain'] = 'Target domain:';
+$lang['add']['mailbox'] = 'Mailbox';
+$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['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['login']['tfa'] = "Two-factor authentication";
+$lang['login']['tfa_details'] = "Please confirm your one-time password in the below field";
+$lang['login']['confirm'] = "Confirm";
+$lang['login']['otp'] = "One-time password";
+$lang['login']['trash_login'] = "Trash login";
+
+$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. <br />You can add new elements in <code>inc/vars.local.inc.php</code> 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 <small>(alphanumeric)</small>';
+$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']['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 record';
+$lang['admin']['dkim_keys'] = 'DKIM records';
+$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.nl.php b/data/web/lang/lang.nl.php
new file mode 100644
index 00000000..550e5470
--- /dev/null
+++ b/data/web/lang/lang.nl.php
@@ -0,0 +1,358 @@
+<?php
+/*
+//
+// Dutch language file
+*/
+$lang['footer']['loading'] = "Even geduld a.u.b. ...";
+$lang['getmail']['no_status'] = "Geen vorige status gevonden.";
+$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']['blacklist_exists'] = "Deze invoer staat op de zwarte lijst.";
+$lang['danger']['blacklist_from_invalid'] = "Zwarte lijst 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'] = '<b>Wist u dat?</b> U kunt tags in het e-mailadres gebruiken ("me+<b>prive</b>@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 <b>nooit</b> 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 <b>altijd</b> 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_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'] = '<strong>Attentie:</strong> Door versleutelde e-mails te forceren, worden mogelijk niet alle e-mails afgeleverd.<br />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['start']['dashboard'] = '%s - dashboard';
+$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'] = '<b>Domeinbeheerders</b> kunnen postvakken en aliassen aanmaken, wijzigen of verwijderen, domeinen veranderen of informatie krijgen over hun domein.<br />
+ <b>Gebruikers</b> 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 <a href="%s" target="_blank"><b>Mozilla Thunderbird</b></a> 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.<br />
+De aanmeldgegevens zullen door de server worden versleuteld.';
+$lang['start']['managesieve'] = 'ManageSieve';
+$lang['start']['managesieve_badge'] = 'Emailfilter';
+$lang['start']['managesieve_description'] = 'Gebruik <b>Mozilla Thunderbird</b> met een <a style="text-decoration:none" target="_blank" href="%s"><b>nightly sieve addon</b></a>.<br />Start Thunderbird, open de add-on instellingen en sleep het gedownloadde xpi-bestand naar dit venster.<br />Servernaam <b>%s</b>, Poort <b>4190</b>. 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 <b>%s</b> (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'] = '<b>Let op:</b> U staat op het punt domein <b>%s</b> te verwijderen!';
+$lang['delete']['remove_domainalias_warning'] = '<b>Let op:</b> U staat op het punt domeinalias <b>%s</b> te verwijderen!';
+$lang['delete']['remove_domainadmin_warning'] = '<b>Let op:</b> U staat op het punt domeinbeheerder <b>%s</b> te verwijderen!';
+$lang['delete']['remove_alias_warning'] = '<b>Let op:</b> U staat op het punt alias <b>%s</b> te verwijderen!';
+$lang['delete']['remove_mailbox_warning'] = '<b>Let op::</b> U staat op het punt postvak <b>%s</b> te verwijderen!';
+$lang['delete']['remove_mailbox_details'] = 'Het postvak zal <b>permanent</b> worden verwijderd!';
+$lang['delete']['remove_domain_details'] = 'Dit verwijdert ook de domeinaliassen. <br /><br /><b>Een domein moet leeg zijn alvorens deze verwijderd kan worden.</b>';
+$lang['delete']['remove_alias_details'] = '<b>Gebruikers zullen niet meer in staat zijn e-mails te ontvangen op -of te versturen vanaf- dit adres.</b>';
+$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) <small>(scheiden met komma)</small>:';
+$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'] = '<small>Voeg de volgende TXT-record toe aan de DNS-instellingen van uw domein.</small>';
+$lang['edit']['relay_all_info'] = '<small>Indien u ervoor kiest om <b>niet</b> alle ontvangers door te schakelen, dient u per ontvanger die u wenst door te schakelen een (lege) postvak aan te maken.</small>';
+$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'] = '<small>Indien u ervoor kiest om <b>niet</b> alle ontvangers door te schakelen, moet u voor iedere ontvanger die u wenst door te schakelen een een (lege) mailbox aanmaken.</small>';
+$lang['add']['alias'] = 'Alias(sen)';
+$lang['add']['alias_spf_fail'] = '<b>Opmerking:</b> Als het gekozen doeladres een extern postvak is, kan de <b>ontvangende mailservver</b> mogelijk het bericht weigeren vanwege niet kloppende SPF-gegevens.</a>';
+$lang['add']['alias_address'] = 'Aliasadres(sen):';
+$lang['add']['alias_address_info'] = '<small>Volledig(e) emailadres(sen) of @voorbeeld.nl om alle berichten op te vangen van een domein (scheiden met komma). <b>Alleen mailcow-domeinen!</b></small>';
+$lang['add']['alias_domain_info'] = '<small>Alleen geldige domeinen (scheiden met komma).</small>';
+$lang['add']['target_address'] = 'Doeladressen:';
+$lang['add']['target_address_info'] = '<small>Volledig(e) e-mailadres(sen) (scheiden met komma).</small>';
+$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['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.<br />U kunt nieuwe elementen toevoegen in <code>inc/vars.inc.php</code> 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 <small>(alphanumeriek)</small>';
+$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..d2e5cf9b
--- /dev/null
+++ b/data/web/lang/lang.pt.php
@@ -0,0 +1,355 @@
+<?php
+/*
+//
+// Portuguese (pt) language file - Português do Brasil (pt_BR) - ISO-8859-1
+//
+*/
+$lang['footer']['loading'] = "Aguarde...";
+$lang['getmail']['no_status'] = "Nenhum registro anterior encontrado";
+$lang['dkim']['confirm'] = "Tem certeza?";
+$lang['danger']['dkim_not_found'] = "Registro DKIM não encontrado";
+$lang['danger']['dkim_remove_failed'] = "Não foi possível remover o registro DKIM selecionado";
+$lang['danger']['dkim_add_failed'] = "Não foi possível adicionar o registro DKIM fornecido";
+$lang['danger']['dkim_domain_or_sel_invalid'] = " Registro DKIM inválido";
+$lang['danger']['dkim_key_length_invalid'] = "Registro DKIM com tamanho inválido";
+$lang['success']['dkim_removed'] = " Registro DKIM removido com sucesso";
+$lang['success']['dkim_added'] = "Registro DKIM salvo com sucesso";
+$lang['danger']['access_denied'] = "Acesso negado ou dados inválidos";
+$lang['danger']['domain_invalid'] = "Domínio inválido";
+$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. espaço excede o espaço do domínio";
+$lang['danger']['object_is_not_numeric'] = "Valor %s não é numérico";
+$lang['success']['domain_added'] = "Domínio adicionado %s";
+$lang['danger']['alias_empty'] = "Você deve preencher o campo do Apelido";
+$lang['danger']['goto_empty'] = "Você deve preencher o campo Encaminhar para";
+$lang['danger']['blacklist_exists'] = "O registro já existe na BlackList";
+$lang['danger']['blacklist_from_invalid'] = "O registro Blacklist possui formato inválido";
+$lang['danger']['whitelist_exists'] = "O registro já existe na WhiteList";
+$lang['danger']['whitelist_from_invalid'] = "O registro Whitelist possui formato inválido";
+$lang['danger']['alias_invalid'] = "O endereço digitado como Apelido é inválido";
+$lang['danger']['goto_invalid'] = "O endereço digitado como Encaminhar para é inválido";
+$lang['danger']['alias_domain_invalid'] = "O endereço do Encaminhamento de Domínio é inválido";
+$lang['danger']['target_domain_invalid'] = "O endereço de Domínio Destino é inválido";
+$lang['danger']['object_exists'] = "Objeto %s já existe";
+$lang['danger']['domain_exists'] = "Domínio %s já existe";
+$lang['danger']['alias_goto_identical'] = "o Apelido e o Encaminhar para devem ser diferentes";
+$lang['danger']['aliasd_targetd_identical'] = "o Encaminhamento de Domínio não pode ser igual ao Domínio Destino";
+$lang['success']['alias_added'] = "Apelido(s) adicionado(s) com sucesso";
+$lang['success']['alias_modified'] = "Apelido(s) alterados(s) com sucesso";
+$lang['success']['aliasd_modified'] = "Direcionamento de Domínio(s) alterados(s) com sucesso";
+$lang['success']['mailbox_modified'] = "A conta %s foi alterada com sucesso";
+$lang['success']['msg_size_saved'] = "Limite do tamanho de mensagem ajustado com sucesso";
+$lang['danger']['aliasd_not_found'] = "Encaminhamento de Domínio não encontrado";
+$lang['danger']['targetd_not_found'] = "Domínio de Destino não encontrado";
+$lang['danger']['aliasd_exists'] = "Encaminhamento de Domínio já existe";
+$lang['success']['aliasd_added'] = "Adicionado Encaminhamento de Domínio %s";
+$lang['success']['aliasd_modified'] = "Encaminhamento de Domínio %s alterado com sucesso";
+$lang['success']['domain_modified'] = "Domínio %s alterado com sucesso";
+$lang['success']['domain_admin_modified'] = "Changes to domain administrator %s have been saved";
+$lang['success']['domain_admin_added'] = "Domínio administrator %s has been added";
+$lang['success']['changes_general'] = 'Alteração efetuada com sucesso';
+$lang['success']['admin_modified'] = "Administrador alterado com sucesso";
+$lang['danger']['exit_code_not_null'] = "Falha: código de erro %d";
+$lang['danger']['mailbox_not_available'] = "Conta não disponível";
+$lang['danger']['username_invalid'] = "Nome de usuário inválido";
+$lang['danger']['password_mismatch'] = "As senhas não estão iguais";
+$lang['danger']['password_complexity'] = "A senha não atende aos parâmetros de segurança";
+$lang['danger']['password_empty'] = "A senha não pode ser vazia ou em branco";
+$lang['danger']['login_failed'] = "Login falhou";
+$lang['danger']['mailbox_invalid'] = "Conta inválida";
+$lang['danger']['mailbox_invalid_suggest'] = 'Conta inválida, sugestão: "%s"?';
+$lang['info']['fetchmail_planned'] = "Procedimento de retirada de emails foi agendado. Verifique o processo mais tarde.";
+$lang['danger']['fetchmail_source_empty'] = "Definir a pasta de origem";
+$lang['danger']['fetchmail_dest_empty'] = "Definir a pasta de destino";
+$lang['danger']['is_alias'] = "o endereço %s já é um Apelido";
+$lang['danger']['is_alias_or_mailbox'] = "o endereço %s já é uma Conta ou Apelido";
+$lang['danger']['is_spam_alias'] = "%s foi registrado como Apelido para Spam";
+$lang['danger']['quota_not_0_not_numeric'] = "Espaço deve ser um campo numérico >= 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'] = '<b>Você sabia?</b> Você pode usar tags no endereço de email ("conta+<b>privado</b>@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 <b>nunca</b> classificar como spam. Pode ser usado coringa *@example.com.';
+$lang['user']['spamfilter_bl'] = 'BlackList';
+$lang['user']['spamfilter_bl_desc'] = 'Endereços em BlackList <b>sempre</b> 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 <b>não é</b> spam';
+$lang['user']['spamfilter_yellow'] = 'Amarelo: essa mensagem <b>pode ser</b> spam, será marcada como spam e classificada na pasta Spam';
+$lang['user']['spamfilter_red'] = 'Vermelho: essa mensagem <b>é mesmo spam</b> 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'] = '<strong>Aviso:</strong> Se você selecionar para forçar o envio encryptado , alguns emails poderão ser rejeitados.<br />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'] = '<b>Administradores:</b> podem criar, alterar ou apagar contas e apelidos , alterar domínios e outras informações de seus domínios atribuídos.<br />
+ <b>Usuários:</b> 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 <a href="%s" target="_blank"><b>Mozilla Thunderbird</b></a>.';
+$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.<br />
+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 <b>Mozilla Thunderbird</b> com a <a style="text-decoration:none" target="_blank" href="%s"><b>extensão para sieve</b></a>.<br />Inicie o Thunderbird, acesse os Complementos e solte o arquivo xpi que foi baixado, na janela aberta.<br />Preencha com o servidor <b>%s</b>, porta <b>4190</b> 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á <b>%s</b> (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'] = '<b>Aviso:</b> Você está prestes a remover o Domínio <b>%s</b>!';
+$lang['delete']['remove_domainalias_warning'] = '<b>Aviso:</b> Você está prestes a remover o Encaminhamento de Domínio <b>%s</b>!';
+$lang['delete']['remove_domainadmin_warning'] = '<b>Aviso:</b> Você está prestes a remover o Administrador <b>%s</b>!';
+$lang['delete']['remove_alias_warning'] = '<b>Aviso:</b> Você está prestes a remover o Apelido <b>%s</b>!';
+$lang['delete']['remove_mailbox_warning'] = '<b>Aviso:</b> Você está prestes a remover a Conta <b>%s</b>!';
+$lang['delete']['remove_mailbox_details'] = 'A Conta será <b>excluída permanentemente</b>!';
+$lang['delete']['remove_domain_details'] = 'Esse procedimento removerá o Encaminhamento de Domínio.<br /><br /><b>O Domínio deve estar sem nenhuma configuração para ser removido.</b>';
+$lang['delete']['remove_alias_details'] = 'Os usuários não poderão mais enviar ou receber emails através deste endereço.</b>';
+$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 <small>(separar por vírgula)</small>:';
+$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'] = '<small>Adicione um registro TXT com o mesmo a mesma configuração dos registros DNS.</small>';
+$lang['edit']['relay_all_info'] = '<small>Se você escolher <b>não</b> direcionar todas as contas de email, você deve adicionar um ("blind") para cada uma das contas.</small>';
+$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'] = '<small>Se <b>não</b> selecionar para retransmitir todas as contas, você deve adicionar uma ("blind") para cada conta que será direcionada.</small>';
+$lang['add']['alias'] = 'Apelido(s)';
+$lang['add']['alias_spf_fail'] = '<b>Aviso:</b> Se você escolher uma conta externa, o <b>servidor externo</b> poderá rejeitar algumas mensagens por erro de SPF.</a>';
+$lang['add']['alias_address'] = 'Apelidos:';
+$lang['add']['alias_address_info'] = '<small>Endereço de email completo ou @example.com, para uma conta coringa -catch all. (separado por vírgula). <b>apenas domínios cadastrados</b>.</small>';
+$lang['add']['alias_domain_info'] = '<small>Domínios válidos apenas (separado por vírgulas).</small>';
+$lang['add']['target_address'] = 'Encaminhar para:';
+$lang['add']['target_address_info'] = '<small>Endereço de email completo (separado por vírgulas).</small>';
+$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. <br />You can add new elements in <code>inc/vars.local.inc.php</code> 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 <small>(alfa numérico)</small>';
+$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..af57b54e
--- /dev/null
+++ b/data/web/mailbox.php
@@ -0,0 +1,500 @@
+<?php
+require_once "inc/prerequisites.inc.php";
+
+if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin") {
+require_once "inc/header.inc.php";
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+?>
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['mailbox']['domains'];?> <span class="badge" id="numRowsDomain"></span></h3>
+ <div class="pull-right">
+ <span class="clickable filter" data-toggle="tooltip" title="<?=$lang['mailbox']['filter_table'];?>" data-container="body">
+ <i class="glyphicon glyphicon-filter"></i>
+ </span>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin"):
+ ?>
+ <a href="/add.php?domain"><span class="glyphicon glyphicon-plus"></span></a>
+ <?php
+ endif;
+ ?>
+ </div>
+ </div>
+ <div class="panel-body">
+ <input type="text" class="form-control" id="domaintable-filter" data-action="filter" data-filters="#domaintable" placeholder="Filter" />
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domaintable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 86px;"><?=$lang['mailbox']['domain'];?></th>
+ <th class="sort-table" style="min-width: 81px;"><?=$lang['mailbox']['aliases'];?></th>
+ <th class="sort-table" style="min-width: 99px;"><?=$lang['mailbox']['mailboxes'];?></th>
+ <th class="sort-table" style="min-width: 172px;"><?=$lang['mailbox']['mailbox_quota'];?></th>
+ <th class="sort-table" style="min-width: 117px;"><?=$lang['mailbox']['domain_quota'];?></th>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin"):
+ ?>
+ <th class="sort-table" style="min-width: 105px;"><?=$lang['mailbox']['backup_mx'];?></th>
+ <?php
+ endif;
+ ?>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['mailbox']['active'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['mailbox']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `domain`,
+ `aliases`,
+ `mailboxes`,
+ `maxquota` * 1048576 AS `maxquota`,
+ `quota` * 1048576 AS `quota`,
+ 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` IN (
+ SELECT `domain` FROM `domain_admins` WHERE `username`= :username AND `active`='1'
+ )
+ OR 'admin'= :admin");
+ $stmt->execute(array(
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role'],
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if(!empty($rows)):
+ while($row = array_shift($rows)):
+ try {
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias`
+ WHERE `domain`= :domain
+ AND `address` NOT IN (
+ SELECT `username` FROM `mailbox`)");
+ $stmt->execute(array(':domain' => $row['domain']));
+ $AliasData = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $stmt = $pdo->prepare("SELECT
+ COUNT(*) AS `count`,
+ COALESCE(SUM(`quota`), '0') AS `quota`
+ FROM `mailbox`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $row['domain']));
+ $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ ?>
+ <tr id="data">
+ <td><?=htmlspecialchars($row['domain']);?></td>
+ <td><?=intval($AliasData['count']);?> / <?=intval($row['aliases']);?></td>
+ <td><?=$MailboxData['count'];?> / <?=$row['mailboxes'];?></td>
+ <td><?=formatBytes(intval($row['maxquota']), 2);?></td>
+ <td><?=formatBytes(intval($MailboxData['quota']), 2);?> / <?=formatBytes(intval($row['quota']));?></td>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin"):
+ ?>
+ <td><?=$row['backupmx'];?></td>
+ <?php
+ endif;
+ ?>
+ <td><?=$row['active'];?></td>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin"):
+ ?>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="/edit.php?domain=<?=urlencode($row['domain']);?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['mailbox']['edit'];?></a>
+ <a href="/delete.php?domain=<?=urlencode($row['domain']);?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['mailbox']['remove'];?></a>
+ </div>
+ </td>
+ <?php
+ else:
+ ?>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="/edit.php?domain=<?=urlencode($row['domain']);?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['mailbox']['edit'];?></a>
+ </div>
+ </td>
+ </tr>
+ <?php
+ endif;
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ <?php
+ if ($_SESSION['mailcow_cc_role'] == "admin"):
+ ?>
+ <tfoot>
+ <tr id="no-data">
+ <td colspan="8" style="text-align: center; font-style: normal; border-top: 1px solid #e7e7e7;">
+ <a href="/add.php?domain" class="btn btn-xs btn-primary"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_domain'];?></a>
+ </td>
+ </tr>
+ </tfoot>
+ <?php
+ endif;
+ ?>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['mailbox']['domain_aliases'];?> <span class="badge" id="numRowsDomainAlias"></span></h3>
+ <div class="pull-right">
+ <span class="clickable filter" data-toggle="tooltip" title="<?=$lang['mailbox']['filter_table'];?>" data-container="body">
+ <i class="glyphicon glyphicon-filter"></i>
+ </span>
+ <a href="/add.php?aliasdomain"><span class="glyphicon glyphicon-plus"></span></a>
+ </div>
+ </div>
+ <div class="panel-body">
+ <input type="text" class="form-control" id="domainaliastable-filter" data-action="filter" data-filters="#domainaliastable" placeholder="Filter" />
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainaliastable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 67px;"><?=$lang['mailbox']['alias'];?></th>
+ <th class="sort-table" style="min-width: 127px;"><?=$lang['mailbox']['target_domain'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['mailbox']['active'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['mailbox']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `alias_domain`,
+ `target_domain`,
+ CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+ FROM `alias_domain`
+ WHERE `target_domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :username
+ AND `active`='1'
+ )
+ OR 'admin' = :admin");
+ $stmt->execute(array(
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role'],
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ } catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if(!empty($rows)):
+ while($row = array_shift($rows)):
+ ?>
+ <tr id="data">
+ <td><?=htmlspecialchars($row['alias_domain']);?></td>
+ <td><?=htmlspecialchars($row['target_domain']);?></td>
+ <td><?=$row['active'];?></td>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="/edit.php?aliasdomain=<?=urlencode($row['alias_domain']);?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['mailbox']['edit'];?></a>
+ <a href="/delete.php?aliasdomain=<?=urlencode($row['alias_domain']);?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['mailbox']['remove'];?></a>
+ </div>
+ </td>
+ </tr>
+ <?php
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="4" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ <tfoot>
+ <tr id="no-data">
+ <td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+ <a href="/add.php?aliasdomain" class="btn btn-xs btn-primary"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_domain_alias'];?></a>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['mailbox']['mailboxes'];?> <span class="badge" id="numRowsMailbox"></span></h3>
+ <div class="pull-right">
+ <span class="clickable filter" data-toggle="tooltip" title="<?=$lang['mailbox']['filter_table'];?>" data-container="body">
+ <i class="glyphicon glyphicon-filter"></i>
+ </span>
+ <a href="/add.php?mailbox"><span class="glyphicon glyphicon-plus"></span></a>
+ </div>
+ </div>
+ <div class="panel-body">
+ <input type="text" class="form-control" id="mailboxtable-filter" data-action="filter" data-filters="#mailboxtable" placeholder="Filter" />
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="mailboxtable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 100px;"><?=$lang['mailbox']['username'];?></th>
+ <th class="sort-table" style="min-width: 98px;"><?=$lang['mailbox']['fname'];?></th>
+ <th class="sort-table" style="min-width: 86px;"><?=$lang['mailbox']['domain'];?></th>
+ <th class="sort-table" style="min-width: 75px;"><?=$lang['mailbox']['quota'];?></th>
+ <th class="sort-table" style="min-width: 99px;"><?=$lang['mailbox']['in_use'];?></th>
+ <th class="sort-table" style="min-width: 100px;"><?=$lang['mailbox']['msg_num'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['mailbox']['active'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['mailbox']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `domain`.`backupmx`,
+ `mailbox`.`username`,
+ `mailbox`.`name`,
+ 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`.`username` = `quota2`.`username`)
+ AND (`domain`.`domain` = `mailbox`.`domain`)
+ AND (`mailbox`.`domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :username
+ AND `active`='1'
+ )
+ OR 'admin' = :admin)");
+ $stmt->execute(array(
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role'],
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if(!empty($rows)):
+ while($row = array_shift($rows)):
+ ?>
+ <tr id="data">
+ <?php
+ if ($row['backupmx'] == "0"):
+ ?>
+ <td><?=htmlspecialchars($row['username']);?></td>
+ <?php
+ else:
+ ?>
+ <td><span data-toggle="tooltip" title="Relayed"><i class="glyphicon glyphicon-forward"></i> <?=htmlspecialchars($row['username']);?></span></td>
+ <?php
+ endif;
+ ?>
+ <td><?=htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8');?></td>
+ <td><?=htmlspecialchars($row['domain']);?></td>
+ <td><?=formatBytes(intval($row['bytes']), 2);?> / <?=formatBytes(intval($row['quota']), 2);?></td>
+ <td style="min-width:120px;">
+ <?php
+ $percentInUse = round((intval($row['bytes']) / intval($row['quota'])) * 100);
+ if ($percentInUse >= 90) {
+ $pbar = "progress-bar-danger";
+ }
+ elseif ($percentInUse >= 75) {
+ $pbar = "progress-bar-warning";
+ }
+ else {
+ $pbar = "progress-bar-success";
+ }
+ ?>
+ <div class="progress">
+ <div class="progress-bar <?=$pbar;?>" role="progressbar" aria-valuenow="<?=$percentInUse;?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$percentInUse;?>%;">
+ <?=$percentInUse;?>%
+ </div>
+ </div>
+ </td>
+ <td><?=$row['messages'];?></td>
+ <td><?=$row['active'];?></td>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="/edit.php?mailbox=<?=urlencode($row['username']);?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['mailbox']['edit'];?></a>
+ <a href="/delete.php?mailbox=<?=urlencode($row['username']);?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['mailbox']['remove'];?></a>
+ </div>
+ </td>
+ </tr>
+ <?php
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ <tfoot>
+ <tr id="no-data">
+ <td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+ <a href="/add.php?mailbox" class="btn btn-xs btn-primary"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_mailbox'];?></a>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"><?=$lang['mailbox']['aliases'];?> <span class="badge" id="numRowsAlias"></span></h3>
+ <div class="pull-right">
+ <span class="clickable filter" data-toggle="tooltip" title="<?=$lang['mailbox']['filter_table'];?>" data-container="body">
+ <i class="glyphicon glyphicon-filter"></i>
+ </span>
+ <a href="/add.php?alias"><span class="glyphicon glyphicon-plus"></span></a>
+ </div>
+ </div>
+ <div class="panel-body">
+ <input type="text" class="form-control" id="aliastable-filter" data-action="filter" data-filters="#aliastable" placeholder="Filter" />
+ </div>
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="aliastable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 67px;"><?=$lang['mailbox']['alias'];?></th>
+ <th class="sort-table" style="min-width: 119px;"><?=$lang['mailbox']['target_address'];?></th>
+ <th class="sort-table" style="min-width: 86px;"><?=$lang['mailbox']['domain'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['mailbox']['active'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['mailbox']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `address`,
+ `goto`,
+ `domain`,
+ CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+ FROM alias
+ WHERE (
+ `address` NOT IN (
+ SELECT `username` FROM `mailbox`
+ )
+ AND `address` != `goto`
+ ) AND (`domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username` = :username
+ AND active='1'
+ )
+ OR 'admin' = :admin)");
+ $stmt->execute(array(
+ ':username' => $_SESSION['mailcow_cc_username'],
+ ':admin' => $_SESSION['mailcow_cc_role'],
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if(!empty($rows)):
+ while($row = array_shift($rows)):
+ ?>
+ <tr id="data">
+ <td>
+ <?php
+ if(!filter_var($row['address'], FILTER_VALIDATE_EMAIL)):
+ ?>
+ <span class="glyphicon glyphicon-pushpin" aria-hidden="true"></span> Catch-all @<?=htmlspecialchars($row['domain']);?>
+ <?php
+ else:
+ echo htmlspecialchars($row['address']);
+ endif;
+ ?>
+ </td>
+ <td>
+ <?php
+ foreach(explode(",", $row['goto']) as $goto) {
+ echo nl2br(htmlspecialchars($goto.PHP_EOL));
+ }
+ ?>
+ </td>
+ <td><?=htmlspecialchars($row['domain']);?></td>
+ <td><?=$row['active'];?></td>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="/edit.php?alias=<?=urlencode($row['address']);?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['mailbox']['edit'];?></a>
+ <a href="/delete.php?alias=<?=urlencode($row['address']);?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['mailbox']['remove'];?></a>
+ </div>
+ </td>
+ </tr>
+ <?php
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="5" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ <tfoot>
+ <tr id="no-data">
+ <td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+ <a href="/add.php?alias" class="btn btn-xs btn-primary"><span class="glyphicon glyphicon-plus"></span> <?=$lang['mailbox']['add_alias'];?></a>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> <!-- /container -->
+<script src="js/sorttable.js"></script>
+<script src="js/mailbox.js"></script>
+<?php
+require_once("inc/footer.inc.php");
+} else {
+ header('Location: /');
+ exit();
+}
+?>
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/user.php b/data/web/user.php
new file mode 100644
index 00000000..828b45d2
--- /dev/null
+++ b/data/web/user.php
@@ -0,0 +1,325 @@
+<?php
+require_once("inc/prerequisites.inc.php");
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+ require_once("inc/header.inc.php");
+ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+ $username = $_SESSION['mailcow_cc_username'];
+ $get_tls_policy = get_tls_policy($_SESSION['mailcow_cc_username']);
+?>
+<div class="container">
+<h3><?=$lang['user']['mailbox_settings'];?></h3>
+<p class="help-block"><?=$lang['user']['did_you_know'];?></p>
+
+<div class="panel panel-default">
+<div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div>
+<div class="panel-body">
+<form class="form-horizontal" role="form" method="post" autocomplete="off">
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="togglePwNew" id="togglePwNew"> <?=$lang['user']['change_password'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="passFields">
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" pattern="(?=.*[A-Za-z])(?=.*[0-9])\w{6,}" name="user_new_pass" id="user_new_pass" autocomplete="off" disabled="disabled">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_new_pass2"><?=$lang['user']['new_password_repeat'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" pattern="(?=.*[A-Za-z])(?=.*[0-9])\w{6,}" name="user_new_pass2" id="user_new_pass2" disabled="disabled" autocomplete="off">
+ <p class="help-block"><?=$lang['user']['new_password_description'];?></p>
+ </div>
+ </div>
+ <hr>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_old_pass"><?=$lang['user']['password_now'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" name="user_old_pass" id="user_old_pass" autocomplete="off" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <button type="submit" name="trigger_set_user_account" class="btn btn-success btn-default"><?=$lang['user']['save_changes'];?></button>
+ </div>
+ </div>
+</form>
+</div>
+</div>
+
+<!-- Nav tabs -->
+<ul class="nav nav-pills nav-justified" role="tablist">
+ <li role="presentation" class="active"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li>
+ <li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li>
+ <li role="presentation"><a href="#TLSPolicy" aria-controls="TLSPolicy" role="tab" data-toggle="tab"><?=$lang['user']['tls_policy'];?></a></li>
+</ul>
+<hr>
+
+<div class="tab-content">
+ <div role="tabpanel" class="tab-pane active" id="SpamAliases">
+ <form class="form-horizontal" role="form" method="post">
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="timelimitedaliases">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 96px;"><?=$lang['user']['alias'];?></th>
+ <th class="sort-table" style="min-width: 135px;"><?=$lang['user']['alias_valid_until'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `address`,
+ `goto`,
+ `validity`
+ FROM `spamalias`
+ WHERE `goto` = :username
+ AND `validity` >= :unixnow");
+ $stmt->execute(array(':username' => $username, ':unixnow' => time()));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if(!empty($rows)):
+ while ($row = array_shift($rows)):
+ ?>
+ <tr id="data">
+ <td><?=htmlspecialchars($row['address']);?></td>
+ <td><?=htmlspecialchars(date($lang['user']['alias_full_date'], $row['validity']));?></td>
+ </tr>
+ <?php
+ endwhile;
+ else:
+ ?>
+ <tr id="no-data"><td colspan="2" style="text-align: center; font-style: italic;"><?=$lang['user']['no_record'];?></td></tr>
+ <?php
+ endif;
+ ?>
+ </tbody>
+ </table>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-9">
+ <select id="validity" name="validity" title="<?=$lang['user']['alias_select_validity'];?>">
+ <option value="1">1 <?=$lang['user']['hour'];?></option>
+ <option value="6">6 <?=$lang['user']['hours'];?></option>
+ <option value="24">1 <?=$lang['user']['day'];?></option>
+ <option value="168">1 <?=$lang['user']['week'];?></option>
+ <option value="672">4 <?=$lang['user']['weeks'];?></option>
+ </select>
+ <button type="submit" id="trigger_set_time_limited_aliases" name="trigger_set_time_limited_aliases" value="generate" class="btn btn-success"><?=$lang['user']['alias_create_random'];?></button>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-12">
+ <button style="border-color:#f5f5f5;background:none;color:red" type="submit" name="trigger_set_time_limited_aliases" value="delete" class="btn btn-sm">
+ <span class="glyphicon glyphicon-remove" aria-hidden="true"></span> <?=$lang['user']['alias_remove_all'];?>
+ </button>
+ <button style="border-color:#f5f5f5;background:none;color:grey" type="submit" name="trigger_set_time_limited_aliases" value="extend" class="btn btn-sm">
+ <span class="glyphicon glyphicon-hourglass" aria-hidden="true"></span> <?=$lang['user']['alias_extend_all'];?>
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ <div role="tabpanel" class="tab-pane" id="Spamfilter">
+ <h4><?=$lang['user']['spamfilter_behavior'];?></h4>
+ <form class="form-horizontal" role="form" method="post">
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <input name="score" id="score" type="text"
+ data-provide="slider"
+ data-slider-min="1"
+ data-slider-max="30"
+ data-slider-step="0.5"
+ data-slider-range="true"
+ data-slider-id="slider1"
+ data-slider-value="[<?=get_spam_score($_SESSION['mailcow_cc_username']);?>]"
+ data-slider-step="1" />
+ <br /><br />
+ <ul>
+ <li><?=$lang['user']['spamfilter_green'];?></li>
+ <li><?=$lang['user']['spamfilter_yellow'];?></li>
+ <li><?=$lang['user']['spamfilter_red'];?></li>
+ </ul>
+ <p><i><?=$lang['user']['spamfilter_default_score'];?> 5:15</i></p>
+ <p><?=$lang['user']['spamfilter_hint'];?></p>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" id="trigger_set_spam_score" name="trigger_set_spam_score" class="btn btn-success"><?=$lang['user']['save_changes'];?></button>
+ </div>
+ </div>
+ </form>
+ <hr>
+ <div class="row">
+ <div class="col-sm-6">
+ <h4><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> <?=$lang['user']['spamfilter_wl'];?></h4>
+ <p><?=$lang['user']['spamfilter_wl_desc'];?></p>
+ <div class="row">
+ <div class="col-sm-6"><b><?=$lang['user']['spamfilter_table_rule'];?></b></div>
+ <div class="col-sm-6"><b><?=$lang['user']['spamfilter_table_action'];?></b></div>
+ </div>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND `object`= :username");
+ $stmt->execute(array(':username' => $username));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ while ($whitelistRow = array_shift($rows)):
+ ?>
+ <div class="row striped">
+ <form class="form-inline" method="post">
+ <div class="col-xs-6"><code><?=$whitelistRow['value'];?></code></div>
+ <div class="col-xs-6">
+ <input type="hidden" name="wlid" value="<?=$whitelistRow['prefid'];?>">
+ <?php
+ if ($whitelistRow['username'] != array_pop(explode('@', $username))):
+ ?>
+ <input type="hidden" id="trigger_delete_whitelist" name="trigger_delete_whitelist">
+ <a href="#n" onclick="$(this).closest('form').submit()"><?=$lang['user']['spamfilter_table_remove'];?></a>
+ <?php
+ else:
+ ?>
+ <span style="cursor:not-allowed"><?=$lang['user']['spamfilter_table_domain_policy'];?></span>
+ <?php
+ endif;
+ ?>
+ </div>
+ </form>
+ </div>
+
+ <?php
+ endwhile;
+ ?>
+ <hr style="margin:5px 0px 7px 0px">
+ <div class="row">
+ <form class="form-inline" method="post">
+ <div class="col-xs-6">
+ <input type="text" class="form-control input-sm" name="whitelist_from" id="whitelist_from" placeholder="*@example.org" required>
+ </div>
+ <div class="col-xs-6">
+ <button type="submit" id="trigger_set_whitelist" name="trigger_set_whitelist" class="btn btn-xs btn-default"><?=$lang['user']['spamfilter_table_add'];?></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <h4><span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span> <?=$lang['user']['spamfilter_bl'];?></h4>
+ <p><?=$lang['user']['spamfilter_bl_desc'];?></p>
+ <div class="row">
+ <div class="col-sm-6"><b><?=$lang['user']['spamfilter_table_rule'];?></b></div>
+ <div class="col-sm-6"><b><?=$lang['user']['spamfilter_table_action'];?></b></div>
+ </div>
+ <?php
+ try {
+ $stmt = $pdo->prepare("SELECT `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND `object`= :username");
+ $stmt->execute(array(':username' => $username));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ if (count($rows) == 0):
+ ?>
+ <div class="row">
+ <div class="col-sm-12"><i><?=$lang['user']['spamfilter_table_empty'];?></i></div>
+ </div>
+ <?php
+ endif;
+ while ($blacklistRow = array_shift($rows)):
+ ?>
+ <div class="row striped">
+ <form class="form-inline" method="post">
+ <div class="col-xs-6"><code><?=$blacklistRow['value'];?></code></div>
+ <div class="col-xs-6">
+ <input type="hidden" name="blid" value="<?=$blacklistRow['prefid'];?>">
+ <?php
+ if ($blacklistRow['username'] != array_pop(explode('@', $username))):
+ ?>
+ <input type="hidden" id="trigger_delete_blacklist" name="trigger_delete_blacklist">
+ <a href="#n" onclick="$(this).closest('form').submit()"><?=$lang['user']['spamfilter_table_remove'];?></a>
+ <?php
+ else:
+ ?>
+ <span style="cursor:not-allowed"><?=$lang['user']['spamfilter_table_domain_policy'];?></span>
+ <?php
+ endif;
+ ?>
+ </div>
+ </form>
+ </div>
+ <?php
+ endwhile;
+ ?>
+ <hr style="margin:5px 0px 7px 0px">
+ <div class="row">
+ <form class="form-inline" method="post">
+ <div class="col-xs-6">
+ <input type="text" class="form-control input-sm" name="blacklist_from" id="blacklist_from" placeholder="*@example.org" required>
+ </div>
+ <div class="col-xs-6">
+ <button type="submit" id="trigger_set_blacklist" name="trigger_set_blacklist" class="btn btn-xs btn-default"><?=$lang['user']['spamfilter_table_add'];?></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div role="tabpanel" class="tab-pane" id="TLSPolicy">
+ <form class="form-horizontal" role="form" method="post">
+ <p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
+ <div class="form-group">
+ <div class="col-sm-6">
+ <div class="checkbox">
+ <h4><span class="glyphicon glyphicon-download" aria-hidden="true"></span> <?=$lang['user']['tls_enforce_in'];?></h4>
+ <input type="checkbox" id="tls_in" name="tls_in" <?=($get_tls_policy['tls_enforce_in'] == "1") ? "checked" : null;?> data-on-text="<?=$lang['user']['on'];?>" data-off-text="<?=$lang['user']['off'];?>">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="checkbox">
+ <h4><span class="glyphicon glyphicon-upload" aria-hidden="true"></span> <?=$lang['user']['tls_enforce_out'];?></h4>
+ <input type="checkbox" id="tls_out" name="tls_out" <?=($get_tls_policy['tls_enforce_out'] == "1") ? "checked" : null;?> data-on-text="<?=$lang['user']['on'];?>" data-off-text="<?=$lang['user']['off'];?>">
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-12">
+ <button type="submit" id="trigger_set_tls_policy" name="trigger_set_tls_policy" class="btn btn-default"><?=$lang['user']['save_changes'];?></button>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+
+
+</div> <!-- /container -->
+<script src="js/sorttable.js"></script>
+<script src="js/user.js"></script>
+<?php
+require_once("inc/footer.inc.php");
+} else {
+ header('Location: /');
+ exit();
+}
+?>
diff --git a/fix-permissions.sh b/fix-permissions.sh
new file mode 100755
index 00000000..17cfa2a3
--- /dev/null
+++ b/fix-permissions.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+chown -R 5000:5000 data/vmail
+chown -R 33:33 data/dkim
diff --git a/mailcow.conf b/mailcow.conf
new file mode 100644
index 00000000..f408dca2
--- /dev/null
+++ b/mailcow.conf
@@ -0,0 +1,55 @@
+# 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=mail.mailcow.de
+
+# mailcow SQL database configuration
+
+DBNAME=mailcow
+DBUSER=mailcow
+DBPASS=mysafepasswd
+DBROOT=myverysafepasswd
+# MySQL
+DBVERS=5.5
+
+# SOGo configuration
+SOGOCHILDS=20
+
+# Webserver configuration
+HTTP_PORT=81
+PHPVERS="5.6-fpm"
+NGINXVERS="stable"
+
+# You should leave that alone
+# Can also be 1.2.3.4:25 for specific binding
+SMTP_PORT=26
+SMTPS_PORT=465
+SUBMISSION_PORT=587
+IMAP_PORT=143
+IMAPS_PORT=993
+POP_PORT=110
+POPS_PORT=995
+SIEVE_PORT=4190
+
+# Redis
+REDISVERS="latest"
+
+# Networking
+# You need to rebuild all containers after changing values.
+# Remove old networks manually.
+DOCKER_NETWORK="mailcow-network"
+DOCKER_SUBNET="172.55.0.0/16"
+
+# ======= ADVANCED ======
+# - not yet implemented -
+# =======================
+# Use existing containers
+# =======================
+
+# USE_REDIS="container-name-of-exisiting-redis"
+# USE_REDIS_NETWORK="docker-network-name-of-existing-redis-container"
+
+# USE_MEMCACHED="container-name-of-exisiting-memcached"
+# USE_MEMCACHED_NETWORK="docker-network-name-of-existing-memcached-container"
diff --git a/print-status.sh b/print-status.sh
new file mode 100755
index 00000000..7e2dc4a0
--- /dev/null
+++ b/print-status.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+# Soon