From 5f04dc0b04d71c4fed5965422c7a694377686804 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 9 Dec 2016 20:39:02 +0100 Subject: [PATCH] mailcow dockerized --- .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 + .../postfix/sql/mysql_relay_recipient_maps.cf | 5 + .../sql/mysql_tls_enforce_in_policy.cf | 5 + .../sql/mysql_tls_enforce_out_policy.cf | 5 + ...ysql_virtual_alias_domain_catchall_maps.cf | 6 + ...mysql_virtual_alias_domain_mailbox_maps.cf | 5 + .../sql/mysql_virtual_alias_domain_maps.cf | 5 + .../postfix/sql/mysql_virtual_alias_maps.cf | 5 + .../postfix/sql/mysql_virtual_domains_maps.cf | 5 + .../sql/mysql_virtual_mailbox_limit_maps.cf | 5 + .../postfix/sql/mysql_virtual_mailbox_maps.cf | 5 + .../sql/mysql_virtual_mxdomain_maps.cf | 5 + .../postfix/sql/mysql_virtual_sender_acl.cf | 5 + .../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 + .../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 --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 + +# 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 + +# 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 + +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 + +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 + +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----- +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 +-----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 = = 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 =<= 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 @@ + +
+
+
+
+
+

+
+
+ +

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

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

+

+
"> +
+ +
+ +

+
+
+
+ +
+ +

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

+
"> +
+ +
+ +

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

+
"> +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + +
+
+
+
+ +
+ + 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 @@ + +
+

+ +
+
+
+
+
+ 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 + ); + } + ?> + +
+ +
+ + ↳ a-z A-Z - _ . +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + 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)): + ?> + + + + + + + + + + + + +
+ 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']).'
'; + } + ?> +
+
+ + +
+
+
+
+ + +
+
+ +
+ + ↳ a-z A-Z - _ . +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +

+
+
+
+
+ +
+
+

Domain: (._domainkey)

+
+
+
v=DKIM1;k=rsa;t=s;s=email;p=
+
+
+
+ + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+ +
+ + + + + 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 @@ + + + + + + + MAILCOW_DOMAIN + + MAILCOW_DOMAIN mail server powered by mailcow + MAILCOW_DOMAIN mail server + + + MAILCOW_HOST.MAILCOW_DOMAIN + 993 + SSL + %EMAILADDRESS% + password-cleartext + + + MAILCOW_HOST.MAILCOW_DOMAIN + 143 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 995 + SSL + %EMAILADDRESS% + password-cleartext + + + MAILCOW_HOST.MAILCOW_DOMAIN + 110 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 465 + SSL + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 587 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now. + Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden. + + + + MAILCOW_DOMAIN mail server info + MAILCOW_DOMAIN Mailserver Info + + + + + + + + %EMAILADDRESS% + + + + + + 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 @@ + + + + + + + MAILCOW_DOMAIN + + MAILCOW_DOMAIN mail server powered by mailcow + MAILCOW_DOMAIN mail server + + + MAILCOW_HOST.MAILCOW_DOMAIN + 993 + SSL + %EMAILADDRESS% + password-cleartext + + + MAILCOW_HOST.MAILCOW_DOMAIN + 143 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 995 + SSL + %EMAILADDRESS% + password-cleartext + + + MAILCOW_HOST.MAILCOW_DOMAIN + 110 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 465 + SSL + %EMAILADDRESS% + password-cleartext + + + + MAILCOW_HOST.MAILCOW_DOMAIN + 587 + STARTTLS + %EMAILADDRESS% + password-cleartext + + + + If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now. + Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht ge�ndert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgr�nden ge�ndert werden. + + + + MAILCOW_DOMAIN mail server info + MAILCOW_DOMAIN Mailserver Info + + + + + + + + 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 @@ + '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 ''; +?> + +'; + echo ''; + echo '600Invalid Request'; + echo ''; + echo ''; + exit(0); +} + +$discover = new SimpleXMLElement($data); +$email = $discover->Request->EMailAddress; + +if ($config['autodiscoverType'] == 'imap') { +?> + + + email + settings + + IMAP + + + off + + off + + on + + + SMTP + + + off + + off + + on + on + off + + + + 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; + } +?> + + en:en + + + + + + + + MobileSync + + + + + + + + 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 @@ + +
+
+
+
+
+

+
+
+ + +

+
+ +
+
+ +
+
+
+ + +

+
+ "> +
+
+ +
+
+
+ + + 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'])) { + ?> + +
+ +
+
+ +
+
+
+ + + + +
+ +
+
+ +
+
+
+ + +

+
+ +
+
+ +
+
+
+ + + + + + + +
+
+
+
+ +
+ 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 @@ + +
+
+
+
+
+

+
+
+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) { + ?> +

+
+
"> + +
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + 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) { + ?> +

+
+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + 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) { + ?> +

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

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

+
+
+
+
+
._domainkey
+
+
+
+
v=DKIM1;k=rsa;t=s;s=email;p=
+ +
+
+
+ + + 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) { + ?> +

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

+
+
+
+
+
._domainkey
+
+
+
+
+ +
+
+
+ + + 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']; + ?> +

+
"> + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + +
+
+
+
+ +
+ diff --git a/data/web/favicon.png b/data/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..6390041d6004a1dd9c8793ba386ce2a2662d940d GIT binary patch literal 6856 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 D)kGR@ literal 0 HcmV?d00001 diff --git a/data/web/img/cow_mailcow.svg b/data/web/img/cow_mailcow.svg new file mode 100644 index 00000000..bea3f826 --- /dev/null +++ b/data/web/img/cow_mailcow.svg @@ -0,0 +1,197 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/data/web/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 @@ + + + + + + +
+
+
class="alert alert-" role="alert"> + × + +
+
+
+ + + + diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php new file mode 100644 index 00000000..e0067e31 --- /dev/null +++ b/data/web/inc/functions.inc.php @@ -0,0 +1,2656 @@ +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 @@ + + + + + + +mailcow UI - <?php echo gethostname() ?> + + + + + + + + + + + + + + + + + + +
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 0000000000000000000000000000000000000000..c88e039bee8b2e24ef686d866083f48acd32d961 GIT binary patch literal 45164 zcmV*OKw-a$P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 wAWEe*+<{907*qoM6N<$g7r|DWB>pF literal 0 HcmV?d00001 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 @@ + 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 @@ + '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 @@ + 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 @@ + +
+
+
+
+
+
+
mailcow
+ mailcow UI +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ + +
+
+ +

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

mailcow UI

+

+

mailcow Apps

+

+
+
+
+
+
+
+ + diff --git a/data/web/js/add.js b/data/web/js/add.js new file mode 100644 index 00000000..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 = $("").attr("type", "hidden").attr("name", "srr_value").val(srr_joined_vals); + $('#srr_form').append($(input)); + }); + $('#ssr_form').submit(function(){ + var ssr_joined_vals = $("[id^=ssr-sortable-active] li").map(function() { + return $(this).data("value"); + }).get().join(', '); + var input = $("").attr("type", "hidden").attr("name", "ssr_value").val(ssr_joined_vals); + $('#ssr_form').append($(input)); + }); +}); \ No newline at end of file diff --git a/data/web/js/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 = $('-') + $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 @@ += 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'] = 'Wussten Sie schon? Sie können Ihre E-Mail-Adresse mit Tags versehen, etwa "ich+Privat@example.com", um Nachrichten automatisch in einem Unterordner (Beispiel: "Privat") abzulegen.'; +$lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['aliases'] = 'Aliasse'; +$lang['user']['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 nicht erfasst werden sollen. Die Verwendung von Wildcards ist gestattet.'; +$lang['user']['spamfilter_bl'] = 'Blacklist'; +$lang['user']['spamfilter_bl_desc'] = 'Für E-Mail-Adressen, die vom Spamfilter immer als Spam erfasst und abgelehnt werden. Die Verwendung von Wildcards ist gestattet.'; +$lang['user']['spamfilter_table_rule'] = 'Regel'; +$lang['user']['spamfilter_table_action'] = 'Aktion'; +$lang['user']['spamfilter_table_empty'] = 'Keine Einträge vorhanden'; +$lang['user']['spamfilter_table_remove'] = 'entfernen'; +$lang['user']['spamfilter_table_add'] = 'Eintrag hinzufügen'; +$lang['user']['spamfilter_behavior'] = 'Bewertung'; +$lang['user']['spamfilter_default_score'] = 'Spam-Score:'; +$lang['user']['spamfilter_green'] = 'Grün: Die Nachricht ist kein Spam'; +$lang['user']['spamfilter_yellow'] = 'Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben'; +$lang['user']['spamfilter_red'] = 'Rot: Die Nachricht ist eindeutig Spam und wird vom Server abgelehnt'; +$lang['user']['spamfilter_default_score'] = 'Standardwert:'; +$lang['user']['spamfilter_hint'] = 'Der erste Wert beschreibt den "low spam score", der zweite Wert den "high spam score".'; + +$lang['user']['tls_policy_warning'] = 'Vorsicht: Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.
Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.'; +$lang['user']['tls_policy'] = 'Verschlüsselungsrichtlinie'; +$lang['user']['tls_enforce_in'] = 'TLS eingehend erzwingen'; +$lang['user']['tls_enforce_out'] = 'TLS ausgehend erzwingen'; +$lang['user']['no_record'] = 'Kein Eintrag'; + +$lang['user']['misc_settings'] = 'Sonstige Kontoeinstellungen'; +$lang['user']['misc_delete_profile'] = 'Sonstige Kontoeinstellungen'; +$lang['start']['dashboard'] = '%s - Dashboard'; +$lang['start']['start_rc'] = 'Roundcube öffnen'; +$lang['start']['start_sogo'] = 'SOGo öffnen'; +$lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender- und Kontakte zu verwalten und vieles mehr.'; +$lang['start']['mailcow_panel'] = 'mailcow UI starten'; +$lang['start']['mailcow_panel_description'] = 'Die mailcow Steuerung steht sowohl für Administratoren als auch Mailbox-Benutzer zur Verfügung.'; +$lang['start']['mailcow_panel_detail'] = 'Domain-Administratoren erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.
+ Als Mailbox-Benutzer erstellen Sie hier zeitlich limitierte Aliasse, ändern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.'; +$lang['start']['recommended_config'] = 'Empfohlene Software-Konfiguration (ohne ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- und SMTP-Server'; +$lang['start']['imap_smtp_server_description'] = 'Für eine optimale Verbindung empfehlen wir die Verwendung des Mozilla Thunderbirds.'; +$lang['start']['imap_smtp_server_badge'] = 'E-Mail lesen und schreiben'; +$lang['start']['imap_smtp_server_auth_info'] = 'Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.
+Ihre Anmeldedaten werden durch die obligatorische Verschlüsselung entgegen des Begriffes "PLAIN" nicht unverschlüsselt übertragen.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'E-Mail-Filter'; +$lang['start']['managesieve_description'] = 'Bitte verwenden Sie Mozilla Thunderbirds zusammen mit der Sieve Erweiterung.
Nach dem Herunterladen der Erweiterung starten Sie Thunderbird, öffnen das Fenster für Erweiterungen und ziehen die heruntergeladene Datei in das offene Fenster.
Der Servername lautet %s, als Port konfigurieren Sie bitte 4190. Die Anmeldedaten entsprechen dem E-Mail Login.'; +$lang['start']['service'] = 'Dienstname'; +$lang['start']['encryption'] = 'Verschlüsselungstyp'; +$lang['start']['help'] = 'Hilfe ein-/ausblenden'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Port'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Konfiguration'; +$lang['header']['administration'] = 'Administration'; +$lang['header']['mailboxes'] = 'Mailboxen'; +$lang['header']['user_settings'] = 'Benutzereinstellungen'; +$lang['header']['login'] = 'Anmeldung'; +$lang['header']['logged_in_as_logout'] = 'Eingeloggt als %s (abmelden)'; +$lang['header']['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'] = 'Warnung: Sie entfernen die Domain %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Warnung: Sie entfernen die Alias-Domain %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Warnung: Sie entfernen den Domain-Administrator %s!'; +$lang['delete']['remove_alias_warning'] = 'Warnung: Sie entfernen die Alias-Adresse %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Warnung: Sie entfernen die Mailbox %s!'; +$lang['delete']['remove_mailbox_details'] = 'Die Mailbox wird vollständig und permanent entfernt!'; +$lang['delete']['remove_domain_details'] = 'Diese Aktion entfernt ebenfalls Domain-Aliasse.

Eine Domain muss leer sein, um entfernt zu werden.'; +$lang['delete']['remove_alias_details'] = 'Benutzer werden keine Nachrichten mehr von dieser Adresse erhalten und versenden koennen!'; +$lang['delete']['remove_button'] = 'Entfernen'; +$lang['delete']['previous'] = 'Vorherige Seite'; + +$lang['edit']['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) (getrennt durch Komma):'; +$lang['edit']['active'] = 'Aktiv'; +$lang['edit']['target_domain'] = 'Ziel-Domain:'; +$lang['edit']['password'] = 'Passwort:'; +$lang['edit']['ratelimit'] = 'Limit ausgehender Nachrichten/Stunde:'; +$lang['danger']['ratelimt_less_one'] = 'Limit ausgehender Nachrichten/Stunde darf nicht kleiner als 1 sein'; +$lang['edit']['password_repeat'] = 'Passwort (Wiederholung):'; +$lang['edit']['domain_admin'] = 'Domain-Administrator bearbeiten'; +$lang['edit']['domain'] = 'Domain bearbeiten'; +$lang['edit']['edit_alias_domain'] = 'Alias-Domain bearbeiten'; +$lang['edit']['alias_domain'] = 'Alias-Domain'; +$lang['edit']['domains'] = 'Domains'; +$lang['edit']['destroy'] = 'Manuelle Eingabe des Wertes'; +$lang['edit']['alias'] = 'Alias bearbeiten'; +$lang['edit']['mailbox'] = 'Mailbox bearbeiten'; +$lang['edit']['description'] = 'Beschreibung:'; +$lang['edit']['max_aliases'] = 'Max. Aliasse:'; +$lang['edit']['max_quota'] = 'Max. Größe per Mailbox (MiB):'; +$lang['edit']['domain_quota'] = 'Domain Speicherplatz gesamt (MiB):'; +$lang['edit']['backup_mx_options'] = 'Backup MX Optionen:'; +$lang['edit']['relay_domain'] = 'Relay Domain'; +$lang['edit']['relay_all'] = 'Alle Empfänger-Adressen relayen'; +$lang['edit']['dkim_signature'] = 'DKIM-Signatur:'; +$lang['edit']['dkim_record_info'] = 'Bitte hinterlegen Sie einen TXT-Record mit obigem Wert in den DNS-Einstellungen Ihrer Domainverwaltung.'; +$lang['edit']['relay_all_info'] = 'Wenn Sie nicht alle Empfänger-Adressen relayen möchten, müssen Sie eine ("blinde") Mailbox für jede Adresse, die relayt werden soll, erstellen.'; +$lang['edit']['full_name'] = 'Voller Name'; +$lang['edit']['quota_mb'] = 'Speicherplatz (MiB)'; +$lang['edit']['sender_acl'] = 'Darf Nachrichten versenden als'; +$lang['edit']['sender_acl_info'] = 'Aliasse sind nicht abwählbar und vorausgewählt.'; +$lang['edit']['dkim_txt_name'] = 'TXT-Record Name:'; +$lang['edit']['dkim_txt_value'] = 'TXT-Record Wert:'; +$lang['edit']['previous'] = 'Vorherige Seite'; +$lang['edit']['unchanged_if_empty'] = 'Unverändert, wenn leer'; +$lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s nicht prüfen'; + +$lang['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'] = 'Wenn Sie nicht alle Empfänger-Adressen relayen möchten, müssen Sie eine Mailbox für jede Adresse, die relayt werden soll, erstellen.'; +$lang['add']['alias'] = 'Alias(se)'; +$lang['add']['alias_spf_fail'] = 'Hinweis: Wählen Sie ein externes Postfach als Ziel-Adresse, kann es unter Umständen zu fehlerhaften Spam-Erkennungen beim Empfänger kommen. Weitere Informationen zu diesem Thema finden Sie hier.'; +$lang['add']['alias_address'] = 'Alias-Adresse(n):'; +$lang['add']['alias_address_info'] = 'Vollständige E-Mail-Adresse(n) oder @example.com, um alle Nachrichten einer Domain weiterzuleiten. Getrennt durch Komma. Nur eigene Domains.'; +$lang['add']['alias_domain_info'] = 'Nur gültige Domains. Getrennt durch Komma.'; +$lang['add']['target_address'] = 'Ziel-Adresse(n):'; +$lang['add']['target_address_info'] = 'Vollständige E-Mail-Adresse(n). Getrennt durch Komma.'; +$lang['add']['alias_domain'] = 'Alias-Domain'; +$lang['add']['select'] = 'Bitte auswählen'; +$lang['add']['target_domain'] = 'Ziel-Domain:'; +$lang['add']['mailbox'] = 'Mailbox'; +$lang['add']['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.
Sie können ein Element in der Datei inc/vars.local.inc.php als bekannt hinzufügen, um es zu bewegen.'; +$lang['admin']['public_folders'] = 'Öffentliche Ordner'; +$lang['admin']['public_folders_text'] = 'Ein Namespace "Public" wird erstellt. Der untenstehende Ordnername betrifft den Namen der automatisch erstellten Mailbox in diesem Namespace.'; +$lang['admin']['public_folder_name'] = 'Ordnername (alphanumerisch)'; +$lang['admin']['public_folder_enable'] = 'Öffentliche Ordner aktivieren'; +$lang['admin']['public_folder_enable_text'] = 'Das Umschalten dieser Option entfernt keine Nachrichten aus den öffentlichen Ordnern.'; +$lang['admin']['public_folder_pusf'] = 'Aktiviere "per-user seen flag"'; +$lang['admin']['public_folder_pusf_text'] = 'Ein "per-user seen flag"-aktiviertes System markiert Nachrichten nicht als gelesen, wenn nur ein Benutzer sie gelesen hat. Jeder Benutzer verwaltet seine eigenen "seen flags".'; +$lang['admin']['privacy'] = 'Datenschutz'; +$lang['admin']['privacy_text'] = 'Diese Option aktiviert eine PCRE-Prüfung, die die Werte der Kopfzeilen "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" sowie "Received: from" durch "localhost" bzw. "127.0.0.1" ersetzt.'; +$lang['admin']['privacy_anon_mail'] = 'Anonymisiere ausgehende Kopfzeilen'; +$lang['admin']['msg_size'] = 'Aktuelles Limit der Nachrichtengröße'; +$lang['admin']['msg_size_limit'] = 'Aktuelles Limit der Nachrichtengröße'; +$lang['admin']['msg_size_limit_details'] = 'Diese Einstellung wird Postfix und den Webserver neuladen.'; +$lang['admin']['save'] = 'Änderungen speichern'; +$lang['admin']['maintenance'] = 'Wartung und Information'; +$lang['admin']['sys_info'] = 'Systeminformation'; +$lang['admin']['dkim_add_key'] = 'DKIM-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 @@ += 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'] = 'Did you know? You can use tags in your email address ("me+privat@example.com") to move messages to a folder automatically (example: "privat").'; +$lang['user']['spam_aliases'] = 'Temporary email aliases'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['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 never classify as spam. Wildcards maybe used.'; +$lang['user']['spamfilter_bl'] = 'Blacklist'; +$lang['user']['spamfilter_bl_desc'] = 'Blacklisted email addresses to always classify as spam and reject. Wildcards maybe used.'; +$lang['user']['spamfilter_behavior'] = 'Rating'; +$lang['user']['spamfilter_table_rule'] = 'Rule'; +$lang['user']['spamfilter_table_action'] = 'Action'; +$lang['user']['spamfilter_table_empty'] = 'No data to display'; +$lang['user']['spamfilter_table_remove'] = 'remove'; +$lang['user']['spamfilter_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'] = 'Warning: If you decide to enforce encrypted mail transfer, you may lose emails.
Messages to not satisfy the policy will be bounced with a hard fail by the mail system.'; +$lang['user']['tls_policy'] = 'Encryption policy'; +$lang['user']['tls_enforce_in'] = 'Enforce TLS incoming'; +$lang['user']['tls_enforce_out'] = 'Enforce TLS outgoing'; +$lang['user']['no_record'] = 'No Record'; + +$lang['user']['misc_settings'] = 'Other profile settings'; +$lang['user']['misc_delete_profile'] = 'Other profile settings'; +$lang['start']['dashboard'] = '%s - dashboard'; +$lang['start']['start_rc'] = 'Open Roundcube'; +$lang['start']['start_sogo'] = 'Open SOGo'; +$lang['start']['mailcow_apps_detail'] = 'Use a mailcow app to access your mails, calendar, contacts and more.'; +$lang['start']['mailcow_panel'] = 'Start mailcow UI'; +$lang['start']['mailcow_panel_description'] = 'The mailcow UI is available for administrators and mailbox users.'; +$lang['start']['mailcow_panel_detail'] = 'Domain administrators create, modify or delete mailboxes and aliases, change domains and read further information about their assigned domains.
+ Mailbox users are able to create time-limited aliases (spam aliases), change their password and spam filter settings.'; +$lang['start']['recommended_config'] = 'Recommended configuration (without ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- and SMTP server data'; +$lang['start']['imap_smtp_server_description'] = 'For the best experience we recommend to use Mozilla Thunderbird.'; +$lang['start']['imap_smtp_server_badge'] = 'Read/Write emails'; +$lang['start']['imap_smtp_server_auth_info'] = 'Please use your full email address and the PLAIN authentication mechanism.
+Your login data will be encrypted by the server-side mandatory encryption.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Email filter'; +$lang['start']['managesieve_description'] = 'Please use Mozilla Thunderbird with the nightly sieve extension.
Start Thunderbird, open the add-on settings and drop the newly downloaded xpi file into the opened window.
The server name is %s, use port 4190 if you are asked for. The login data match your email login.'; +$lang['start']['service'] = 'Service'; +$lang['start']['encryption'] = 'Encryption method'; +$lang['start']['help'] = 'Show/Hide help panel'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Port'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Configuration'; +$lang['header']['administration'] = 'Administration'; +$lang['header']['mailboxes'] = 'Mailboxes'; +$lang['header']['user_settings'] = 'User settings'; +$lang['header']['login'] = 'Login'; +$lang['header']['logged_in_as_logout'] = 'Logged in as %s (logout)'; +$lang['header']['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'] = 'Warning: You are about to remove the domain %s!'; +$lang['delete']['remove_domainalias_warning'] = 'Warning: You are about to remove the domain alias %s!'; +$lang['delete']['remove_domainadmin_warning'] = 'Warning: You are about to remove the domain administrator %s!'; +$lang['delete']['remove_alias_warning'] = 'Warning: You are about to remove the alias address %s!'; +$lang['delete']['remove_mailbox_warning'] = 'Warning: You are about to remove the mailbox %s!'; +$lang['delete']['remove_mailbox_details'] = 'The mailbox will be purged permanently!'; +$lang['delete']['remove_domain_details'] = 'This also removes domain aliases.

A domain must be empty to be removed.'; +$lang['delete']['remove_alias_details'] = 'Users will no longer be able to receive mail for or send mail from this address.'; +$lang['delete']['remove_button'] = 'Remove'; +$lang['delete']['previous'] = 'Previous page'; + +$lang['edit']['save'] = 'Save changes'; +$lang['edit']['archive'] = 'Archive access'; +$lang['edit']['max_mailboxes'] = 'Max. possible mailboxes:'; +$lang['edit']['title'] = 'Edit object'; +$lang['edit']['target_address'] = 'Goto address/es (comma-separated):'; +$lang['edit']['active'] = 'Active'; +$lang['edit']['target_domain'] = 'Target domain:'; +$lang['edit']['password'] = 'Password:'; +$lang['edit']['ratelimit'] = 'Outgoing rate limit/h:'; +$lang['danger']['ratelimt_less_one'] = 'Outgoing rate limit/h must not be less than 1'; +$lang['edit']['password_repeat'] = 'Confirmation password (repeat):'; +$lang['edit']['domain_admin'] = 'Edit domain administrator'; +$lang['edit']['domain'] = 'Edit domain'; +$lang['edit']['alias_domain'] = 'Alias domain'; +$lang['edit']['edit_alias_domain'] = 'Edit Alias domain'; +$lang['edit']['domains'] = 'Domains'; +$lang['edit']['destroy'] = 'Manual data input'; +$lang['edit']['alias'] = 'Edit alias'; +$lang['edit']['mailbox'] = 'Edit mailbox'; +$lang['edit']['description'] = 'Description:'; +$lang['edit']['max_aliases'] = 'Max. aliases:'; +$lang['edit']['max_quota'] = 'Max. quota per mailbox (MiB):'; +$lang['edit']['domain_quota'] = 'Domain quota:'; +$lang['edit']['backup_mx_options'] = 'Backup MX options:'; +$lang['edit']['relay_domain'] = 'Relay domain'; +$lang['edit']['relay_all'] = 'Relay all recipients'; +$lang['edit']['dkim_signature'] = 'DKIM signature:'; +$lang['edit']['dkim_record_info'] = 'Please add a TXT record with the given value to your DNS settings.'; +$lang['edit']['relay_all_info'] = 'If you choose not to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.'; +$lang['edit']['full_name'] = 'Full name'; +$lang['edit']['quota_mb'] = 'Quota (MiB)'; +$lang['edit']['sender_acl'] = 'Allow to send as'; +$lang['edit']['sender_acl_info'] = 'Aliases cannot be deselected.'; +$lang['edit']['dkim_txt_name'] = 'TXT record name:'; +$lang['edit']['dkim_txt_value'] = 'TXT record value:'; +$lang['edit']['previous'] = 'Previous page'; +$lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank'; +$lang['edit']['dont_check_sender_acl'] = 'Do not check sender for domain %s'; + +$lang['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'] = 'If you choose not to relay all recipients, you will need to add a ("blind") mailbox for every single recipient that should be relayed.'; +$lang['add']['alias'] = 'Alias(es)'; +$lang['add']['alias_spf_fail'] = 'Note: If your chosen destination address is an external mailbox, the receiving mailserver may reject your message due to an SPF failure.'; +$lang['add']['alias_address'] = 'Alias address/es:'; +$lang['add']['alias_address_info'] = 'Full email address/es or @example.com, to catch all messages for a domain (comma-separated). mailcow domains only.'; +$lang['add']['alias_domain_info'] = 'Valid domain names only (comma-separated).'; +$lang['add']['target_address'] = 'Goto addresses:'; +$lang['add']['target_address_info'] = 'Full email address/es (comma-separated).'; +$lang['add']['alias_domain'] = 'Alias domain'; +$lang['add']['select'] = 'Please select...'; +$lang['add']['target_domain'] = 'Target domain:'; +$lang['add']['mailbox'] = 'Mailbox'; +$lang['add']['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.
You can add new elements in inc/vars.local.inc.php to be able to toggle them.'; +$lang['admin']['public_folders'] = 'Public Folders'; +$lang['admin']['public_folders_text'] = 'A namespace "Public" is created. Below\'s public folder name indicates the name of the first auto-created mailbox within this namespace.'; +$lang['admin']['public_folder_name'] = 'Folder name (alphanumeric)'; +$lang['admin']['public_folder_enable'] = 'Enable public folder'; +$lang['admin']['public_folder_enable_text'] = 'Toggling this option does not delete mail in any public folder.'; +$lang['admin']['public_folder_pusf'] = 'Enable per-user seen flag'; +$lang['admin']['public_folder_pusf_text'] = 'A "per-user seen flag"-enabled system will not mark a mail as read for User B, when User A has seen it, but User B did not.'; +$lang['admin']['privacy'] = 'Privacy'; +$lang['admin']['privacy_text'] = 'This option enables a PCRE table to remove "User-Agent", "X-Enigmail", "X-Mailer", "X-Originating-IP" and replaces "Received: from" headers with localhost/127.0.0.1.'; +$lang['admin']['privacy_anon_mail'] = 'Anonymize outgoing mail'; +$lang['admin']['dkim_txt_name'] = 'TXT record name:'; +$lang['admin']['dkim_txt_value'] = 'TXT record value:'; +$lang['admin']['dkim_key_length'] = 'DKIM key length (bits)'; +$lang['admin']['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 @@ + 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'] = 'Wist u dat? U kunt tags in het e-mailadres gebruiken ("me+prive@voorbeeld.nl") om berichten automatisch naar een bijbehorende map te sturen (voorbeeld: "prive").'; +$lang['user']['spam_aliases'] = 'Tijdelijk e-mailadres'; +$lang['user']['alias'] = 'Alias'; +$lang['user']['alias_create_random'] = 'Creëer willekeurige alias'; +$lang['user']['alias_extend_all'] = 'Verleng alias met 1 uur'; +$lang['user']['alias_valid_until'] = 'Geldig tot'; +$lang['user']['alias_remove_all'] = 'Verwijder alle aliassen'; +$lang['user']['alias_time_left'] = 'Tijd over'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Geldigheid'; +$lang['user']['hour'] = 'Uur'; +$lang['user']['hours'] = 'Uren'; +$lang['user']['day'] = 'Dag'; +$lang['user']['week'] = 'Week'; +$lang['user']['weeks'] = 'Weken'; +$lang['user']['spamfilter'] = 'Spam filter'; +$lang['user']['spamfilter_wl'] = 'Witte lijst'; +$lang['user']['spamfilter_wl_desc'] = 'Zet e-mailadressen op de witte lijst om ze nooit als spam te classificeren. Wildcards (*) zijn toegestaan.'; +$lang['user']['spamfilter_bl'] = 'Zwarte lijst'; +$lang['user']['spamfilter_bl_desc'] = 'E-mailadressen op de zwarte lijst worden altijd als spam geclassificeerd en geweigerd. Wildcards (*) zijn toegestaan.'; +$lang['user']['spamfilter_behavior'] = 'Beoordeling'; +$lang['user']['spamfilter_table_rule'] = 'Regel'; +$lang['user']['spamfilter_table_action'] = 'Handeling'; +$lang['user']['spamfilter_table_empty'] = 'Geen gegevens om weer te geven.'; +$lang['user']['spamfilter_table_remove'] = 'verwijder'; +$lang['user']['spamfilter_default_score'] = 'Spamscore:'; +$lang['user']['spamfilter_green'] = 'Groen: Dit bericht is geen spam.'; +$lang['user']['spamfilter_yellow'] = 'Geel: Dit bericht is mogelijk spam, zal worden gelabeled en verplaatst worden naar de Junk-map.'; +$lang['user']['spamfilter_red'] = 'Rood: Dit bericht is spam en zal worden geweigerd.'; +$lang['user']['spamfilter_default_score'] = 'Standaardwaarden:'; +$lang['user']['spamfilter_hint'] = 'De eerste waarde omschrijft een "lage spam score", de tweede waarde een "hoge spam score".'; + +$lang['user']['tls_policy_warning'] = 'Attentie: Door versleutelde e-mails te forceren, worden mogelijk niet alle e-mails afgeleverd.
Berichten die niet aan het ingestelde beleid voldoen worden resoluut geweigerd (bounced met hard-fail).'; +$lang['user']['tls_policy'] = 'Versleutelbeleid'; +$lang['user']['tls_enforce_in'] = 'Forceer TLS-gebruik inkomend'; +$lang['user']['tls_enforce_out'] = 'Forceer TLS-gebruik uitgaand'; +$lang['user']['no_record'] = 'Geen vermelding.'; + +$lang['user']['misc_settings'] = 'Andere profielinstellingen'; +$lang['user']['misc_delete_profile'] = 'Andere profielinstellingen'; +$lang['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'] = 'Domeinbeheerders kunnen postvakken en aliassen aanmaken, wijzigen of verwijderen, domeinen veranderen of informatie krijgen over hun domein.
+ Gebruikers kunnen tijdsgelimiteerde aliassen (spam-aliasses) aanmaken, hun wachtwoord wijzigen en spamfilterinstellingen wijzigen.'; +$lang['start']['recommended_config'] = 'Aanbevoen instellingen (zonder ActiveSync)'; +$lang['start']['imap_smtp_server'] = 'IMAP- en SMTP-server gegevens'; +$lang['start']['imap_smtp_server_description'] = 'Voor de best mogelijke ervaring bevelen wij Mozilla Thunderbird aan.'; +$lang['start']['imap_smtp_server_badge'] = 'Lees/schrijf e-mails'; +$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik uw volledige e-mailadres en de onversleutelde (PLAIN) verificatiemechanisme.
+De aanmeldgegevens zullen door de server worden versleuteld.'; +$lang['start']['managesieve'] = 'ManageSieve'; +$lang['start']['managesieve_badge'] = 'Emailfilter'; +$lang['start']['managesieve_description'] = 'Gebruik Mozilla Thunderbird met een nightly sieve addon.
Start Thunderbird, open de add-on instellingen en sleep het gedownloadde xpi-bestand naar dit venster.
Servernaam %s, Poort 4190. De aanmeldgegevens zijn gelijk aan de gegevens voor uw e-mail.'; +$lang['start']['service'] = 'Service'; +$lang['start']['encryption'] = 'Versleutelmethode'; +$lang['start']['help'] = 'Toon/Verberg Hulppaneel'; +$lang['start']['hostname'] = 'Hostname'; +$lang['start']['port'] = 'Poort'; +$lang['start']['footer'] = ''; +$lang['header']['mailcow_settings'] = 'Instellingen'; +$lang['header']['administration'] = 'Beheer'; +$lang['header']['mailboxes'] = 'Postvakken'; +$lang['header']['user_settings'] = 'Gebruikersinstellingen'; +$lang['header']['login'] = 'Aanmelden'; +$lang['header']['logged_in_as_logout'] = 'Aangemeld als %s (Afmelden)'; +$lang['header']['locale'] = 'Taal'; +$lang['mailbox']['domain'] = 'Domein'; +$lang['mailbox']['alias'] = 'Alias'; +$lang['mailbox']['aliases'] = 'Aliassen'; +$lang['mailbox']['domains'] = 'Domeinen'; +$lang['mailbox']['mailboxes'] = 'Mailboxen'; +$lang['mailbox']['mailbox_quota'] = 'Max. grootte van een postvak'; +$lang['mailbox']['domain_quota'] = 'Quotum'; +$lang['mailbox']['active'] = 'Actief'; +$lang['mailbox']['action'] = 'Handeling'; +$lang['mailbox']['ratelimit'] = 'Maximale snelheid uitgaand/uur'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Domein-aliassen'; +$lang['mailbox']['target_domain'] = 'Doeldomein'; +$lang['mailbox']['target_address'] = 'Doeladres'; +$lang['mailbox']['username'] = 'Gebruikersnaam'; +$lang['mailbox']['fname'] = 'Volledige naam'; +$lang['mailbox']['filter_table'] = 'Filter tabel'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['quota'] = 'Quotum'; +$lang['mailbox']['in_use'] = 'In gebruik (%)'; +$lang['mailbox']['msg_num'] = 'Berichten #'; +$lang['mailbox']['remove'] = 'Verwijder'; +$lang['mailbox']['edit'] = 'Wijzig'; +$lang['mailbox']['archive'] = 'Archief'; +$lang['mailbox']['no_record'] = 'Geen vermelding'; +$lang['mailbox']['add_domain'] = 'Toevoegen domein'; +$lang['mailbox']['add_domain_alias'] = 'Toevoegen domein-alias'; +$lang['mailbox']['add_mailbox'] = 'Toevoegen postvak'; +$lang['mailbox']['add_alias'] = 'Toevoegen alias'; + +$lang['info']['no_action'] = 'Geen handelingen uitvoerbaar'; + +$lang['delete']['title'] = 'Verwijder object'; +$lang['delete']['remove_domain_warning'] = 'Let op: U staat op het punt domein %s te verwijderen!'; +$lang['delete']['remove_domainalias_warning'] = 'Let op: U staat op het punt domeinalias %s te verwijderen!'; +$lang['delete']['remove_domainadmin_warning'] = 'Let op: U staat op het punt domeinbeheerder %s te verwijderen!'; +$lang['delete']['remove_alias_warning'] = 'Let op: U staat op het punt alias %s te verwijderen!'; +$lang['delete']['remove_mailbox_warning'] = 'Let op:: U staat op het punt postvak %s te verwijderen!'; +$lang['delete']['remove_mailbox_details'] = 'Het postvak zal permanent worden verwijderd!'; +$lang['delete']['remove_domain_details'] = 'Dit verwijdert ook de domeinaliassen.

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

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

+
+ + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + 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; + } + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
/ / / +
+ + +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+

+
+ + + + +
+
+
+ +
+
+ + + + + + + + + + + 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)): + ?> + + + + + + + + + + + + + + + +
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+

+
+ + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + 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)): + ?> + + + + + + + + + + + + + + + + + + + + + + + +
/ + = 90) { + $pbar = "progress-bar-danger"; + } + elseif ($percentInUse >= 75) { + $pbar = "progress-bar-warning"; + } + else { + $pbar = "progress-bar-success"; + } + ?> +
+
+ % +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+

+
+ + + + +
+
+
+ +
+
+ + + + + + + + + + + + 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)): + ?> + + + + + + + + + + + + + + + + +
+ + Catch-all @ + + + + +
+ + +
+
+ +
+
+
+
+
+
+ + + 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 @@ + +
+

+

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

+
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ + + +
+ +
+
+
+
+ + + + + + + + + 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)): + ?> + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+

+
+
+
+ +

+
    +
  • +
  • +
  • +
+

5:15

+

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

+

+
+
+
+
+ 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)): + ?> +
+
+
+
+ + + + + + + +
+
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+
+

+

+
+
+
+
+ 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): + ?> +
+
+
+ +
+
+
+
+ + + + + + + +
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+

+
+
+
+

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

+ data-on-text="" data-off-text=""> +
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + 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