diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index 6d3d7273..a859684d 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -2,6 +2,12 @@ set -o pipefail exec 5>&1 +if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." + sleep 365d + exec $(readlink -f "$0") +fi + ACME_BASE=/var/lib/acme SSL_EXAMPLE=/var/lib/ssl-example @@ -102,11 +108,6 @@ while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do done while true; do - if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." - sleep 365d - exec $(readlink -f "$0") - fi if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then SKIP_IP_CHECK=y fi diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py index aabcaf31..b406b5ee 100644 --- a/data/Dockerfiles/dockerapi/server.py +++ b/data/Dockerfiles/dockerapi/server.py @@ -1,6 +1,7 @@ from flask import Flask from flask_restful import Resource, Api from flask import jsonify +from flask import request from threading import Thread import docker import signal @@ -13,17 +14,23 @@ api = Api(app) class containers_get(Resource): def get(self): containers = {} - for container in docker_client.containers.list(all=True): - containers.update({container.attrs['Id']: container.attrs}) - return containers + try: + for container in docker_client.containers.list(all=True): + containers.update({container.attrs['Id']: container.attrs}) + return containers + except Exception as e: + return jsonify(type='danger', msg=e) class container_get(Resource): def get(self, container_id): if container_id and container_id.isalnum(): - for container in docker_client.containers.list(all=True, filters={"id": container_id}): - return container.attrs + try: + for container in docker_client.containers.list(all=True, filters={"id": container_id}): + return container.attrs + except Exception as e: + return jsonify(type='danger', msg=e) else: - return jsonify(message='No or invalid id defined') + return jsonify(type='danger', msg='no or invalid id defined') class container_post(Resource): def post(self, container_id, post_action): @@ -32,30 +39,51 @@ class container_post(Resource): try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.stop() - except: - return 'Error' - else: - return 'OK' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + elif post_action == 'start': try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.start() - except: - return 'Error' - else: - return 'OK' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + elif post_action == 'restart': try: for container in docker_client.containers.list(all=True, filters={"id": container_id}): container.restart() - except: - return 'Error' + return jsonify(type='success', msg='command completed successfully') + except Exception as e: + return jsonify(type='danger', msg=e) + + elif post_action == 'exec': + + if not request.json or not 'cmd' in request.json: + return jsonify(type='danger', msg='cmd is missing') + + if request.json['cmd'] == 'sieve_list' and request.json['username']: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') + except Exception as e: + return jsonify(type='danger', msg=e) + elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail') + except Exception as e: + return jsonify(type='danger', msg=e) else: - return 'OK' + return jsonify(type='danger', msg='Unknown command') + else: - return jsonify(message='Invalid action') + return jsonify(type='danger', msg='invalid action') + else: - return jsonify(message='Invalid container id or missing action') + return jsonify(type='danger', msg='invalid container id or missing action') class GracefulKiller: kill_now = False diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index d7f9cf38..d2cdd0e3 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C -ENV DOVECOT_VERSION 2.2.32 -ENV PIGEONHOLE_VERSION 0.4.20 +ENV DOVECOT_VERSION 2.2.33.2 +ENV PIGEONHOLE_VERSION 0.4.21 RUN apt-get update && apt-get -y install \ automake \ @@ -40,10 +40,11 @@ RUN apt-get update && apt-get -y install \ libtest-pod-perl \ libtest-simple-perl \ libunicode-string-perl \ - libproc-processtable-perl \ + libproc-processtable-perl \ liburi-perl \ lzma-dev \ make \ + procps \ supervisor \ syslog-ng \ syslog-ng-core \ @@ -64,7 +65,8 @@ RUN curl https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz | && make -j3 \ && make install \ && make clean \ - && cd .. && rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION + && cd .. \ + && rm -rf dovecot-2.2-pigeonhole-$PIGEONHOLE_VERSION RUN cpanm Data::Uniqid Mail::IMAPClient String::Util RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync @@ -98,8 +100,6 @@ RUN touch /etc/default/locale RUN apt-get purge -y build-essential automake autotools-dev \ && apt-get autoremove --purge -y -EXPOSE 24 10001 - ENTRYPOINT ["/docker-entrypoint.sh"] CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 4e9fe14b..9f8f5313 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -15,7 +15,7 @@ sed -i "/^\$DBNAME/c\\\$DBNAME='${DBNAME}';" /usr/local/bin/imapsync_cron.pl DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') # Create quota dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql.conf +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/quota/storage @@ -31,8 +31,54 @@ map { } EOF +# Create dict used for sieve pre and postfilters +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf +connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +map { + pattern = priv/sieve/name/\$script_name + table = sieve_before + username_field = username + value_field = id + fields { + script_name = \$script_name + } +} +map { + pattern = priv/sieve/data/\$id + table = sieve_before + username_field = username + value_field = script_data + fields { + id = \$id + } +} +EOF + +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf +connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" +map { + pattern = priv/sieve/name/\$script_name + table = sieve_after + username_field = username + value_field = id + fields { + script_name = \$script_name + } +} +map { + pattern = priv/sieve/data/\$id + table = sieve_after + username_field = username + value_field = script_data + fields { + id = \$id + } +} +EOF + + # Create user and pass dict for Dovecot -cat < /usr/local/etc/dovecot/sql/dovecot-mysql.conf +cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = mysql connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" default_pass_scheme = SSHA256 diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 132e536e..ddd4746a 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -54,6 +54,10 @@ while ($row = $sth->fetchrow_arrayref()) { $delete1 = @$row[12]; $delete2 = @$row[13]; + $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?"); + $is_running->bind_param( 1, ${id} ); + $is_running->execute(); + if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } my $template = $run_dir . '/imapsync.XXXXXXX'; @@ -83,7 +87,7 @@ while ($row = $sth->fetchrow_arrayref()) { "--passfile2", $passfile2->filename, '--no-modulesversion'], ">", \my $stdout; - $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW() WHERE id = ?"); + $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${id} ); $update->execute(); diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index 33d488b8..f517c388 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -1,5 +1,6 @@ [supervisord] nodaemon=true +user=root [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py index f1954489..6be96023 100644 --- a/data/Dockerfiles/fail2ban/logwatch.py +++ b/data/Dockerfiles/fail2ban/logwatch.py @@ -147,6 +147,9 @@ def watch(): result = re.search(rule_regex, item['data']) if result: addr = result.group(1) + ip = ipaddress.ip_address(addr.decode('ascii')) + if ip.is_private or ip.is_loopback: + continue print "%s matched rule id %d" % (addr, rule_id) log['time'] = int(round(time.time())) log['priority'] = "warn" diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index fc6552fd..84a1f67b 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -32,17 +32,20 @@ RUN apk add -U --no-cache libxml2-dev \ imagemagick-dev \ imagemagick \ libtool \ + gettext-dev \ + openldap-dev \ librsvg \ && pear install channel://pear.php.net/Net_IDNA2-0.2.0 \ channel://pear.php.net/Auth_SASL-1.1.0 \ Net_IMAP \ + Net_Sieve \ NET_SMTP \ Mail_mime \ && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \ && docker-php-ext-enable redis apcu memcached imagick \ && pecl clear-cache \ && docker-php-ext-configure intl \ - && docker-php-ext-install -j 4 intl pdo pdo_mysql xmlrpc gd zip pcntl opcache \ + && docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ && apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev \ @@ -56,7 +59,6 @@ RUN apk add -U --no-cache libxml2-dev \ echo 'opcache.revalidate_freq=1'; \ } > /usr/local/etc/php/conf.d/opcache-recommended.ini - COPY ./docker-entrypoint.sh / EXPOSE 9000 diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index d7351936..902f66dc 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -28,13 +28,19 @@ progress() { [[ ${CURRENT} -gt ${TOTAL} ]] && return [[ ${CURRENT} -lt 0 ]] && CURRENT=0 PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} )) - echo -ne "$(date) - ${SERVICE} health level: \e[7m${PERCENT}%\e[0m (${CURRENT}/${TOTAL}), health trend: " - [[ ${DIFF} =~ ^-[1-9] ]] && echo -en '[\e[41m \e[0m] ' || echo -en '[\e[42m \e[0m] ' - echo "(${DIFF})" + log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" + log_data "$(printf "%d,%d,%d,%d" ${PERCENT} ${CURRENT} ${TOTAL} ${DIFF})" "${SERVICE}" } -log_to_redis() { - redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" +log_msg() { + redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" > /dev/null + echo $(date) $(printf '%s\n' "${1}") +} + +log_data() { + [[ -z ${1} ]] && return 1 + [[ -z ${2} ]] && return 2 + redis-cli -h redis LPUSH WATCHDOG_DATA "{\"time\":\"$(date +%s)\",\"service\":\"data\",\"$(printf '%s' "${2}")\":\"$(printf '%s' "${1}")\"}" > /dev/null } function mail_error() { @@ -43,8 +49,7 @@ function mail_error() { RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'}) RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}') if [[ -z ${RCPT_MX} ]]; then - log_to_redis "Cannot determine MX for ${1}, skipping email notification..." - echo "Cannot determine MX for ${1}" + log_msg "Cannot determine MX for ${1}, skipping email notification..." return 1 fi ./smtp-cli --missing-modules-ok \ @@ -54,6 +59,7 @@ function mail_error() { --from="watchdog@${MAILCOW_HOSTNAME}" \ --server="${RCPT_MX}" \ --hello-host=${MAILCOW_HOSTNAME} + log_msg "Sent notification email to ${1}" } @@ -66,8 +72,8 @@ get_container_ip() { sleep 1 CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${1}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then - CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${CONTAINER_ID}/json | jq -r '.NetworkSettings.Networks[].IPAddress') - fi + CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${CONTAINER_ID}/json | jq -r '.NetworkSettings.Networks[].IPAddress') + fi LOOP_C=$((LOOP_C + 1)) done [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP} @@ -253,9 +259,8 @@ dns_checks() { ( while true; do if ! nginx_checks; then - log_to_redis "Nginx hit error limit" + log_msg "Nginx hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow" - echo -e "\e[31m$(date) - Nginx hit error limit\e[0m" echo nginx-mailcow > /tmp/com_pipe fi done @@ -265,9 +270,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! mysql_checks; then - log_to_redis "MySQL hit error limit" + log_msg "MySQL hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow" - echo -e "\e[31m$(date) - MySQL hit error limit\e[0m" echo mysql-mailcow > /tmp/com_pipe fi done @@ -277,9 +281,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! phpfpm_checks; then - log_to_redis "PHP-FPM hit error limit" + log_msg "PHP-FPM hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow" - echo -e "\e[31m$(date) - PHP-FPM hit error limit\e[0m" echo php-fpm-mailcow > /tmp/com_pipe fi done @@ -289,9 +292,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! sogo_checks; then - log_to_redis "SOGo hit error limit" + log_msg "SOGo hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow" - echo -e "\e[31m$(date) - SOGo hit error limit\e[0m" echo sogo-mailcow > /tmp/com_pipe fi done @@ -301,9 +303,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! postfix_checks; then - log_to_redis "Postfix hit error limit" + log_msg "Postfix hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow" - echo -e "\e[31m$(date) - Postfix hit error limit\e[0m" echo postfix-mailcow > /tmp/com_pipe fi done @@ -313,9 +314,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! dovecot_checks; then - log_to_redis "Dovecot hit error limit" + log_msg "Dovecot hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow" - echo -e "\e[31m$(date) - Dovecot hit error limit\e[0m" echo dovecot-mailcow > /tmp/com_pipe fi done @@ -325,9 +325,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! dns_checks; then - log_to_redis "Unbound hit error limit" + log_msg "Unbound hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow" - echo -e "\e[31m$(date) - Unbound hit error limit\e[0m" #echo unbound-mailcow > /tmp/com_pipe fi done @@ -337,9 +336,8 @@ BACKGROUND_TASKS+=($!) ( while true; do if ! rspamd_checks; then - log_to_redis "Rspamd hit error limit" + log_msg "Rspamd hit error limit" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow" - echo -e "\e[31m$(date) - Rspamd hit error limit\e[0m" echo rspamd-mailcow > /tmp/com_pipe fi done @@ -351,8 +349,7 @@ BACKGROUND_TASKS+=($!) while true; do for bg_task in ${BACKGROUND_TASKS[*]}; do if ! kill -0 ${bg_task} 1>&2; then - echo "Worker ${bg_task} died, stopping watchdog and waiting for respawn..." - log_to_redis "Worker ${bg_task} died, stopping watchdog and waiting for respawn..." + log_msg "Worker ${bg_task} died, stopping watchdog and waiting for respawn..." kill -TERM 1 fi sleep 10 @@ -366,7 +363,7 @@ while true; do while nc -z dockerapi 8080; do sleep 3 done - echo "Cannot find dockerapi-mailcow, waiting to recover..." + log_msg "Cannot find dockerapi-mailcow, waiting to recover..." kill -STOP ${BACKGROUND_TASKS[*]} until nc -z dockerapi 8080; do sleep 3 @@ -385,11 +382,10 @@ while true; do sleep 3 CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then - log_to_redis "Sending restart command to ${CONTAINER_ID}..." - echo "Sending restart command to ${CONTAINER_ID}..." + log_msg "Sending restart command to ${CONTAINER_ID}..." curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart fi - echo "Wait for restarted container to settle and continue watching..." + log_msg "Wait for restarted container to settle and continue watching..." sleep 30s kill -CONT ${BACKGROUND_TASKS[*]} kill -USR1 ${BACKGROUND_TASKS[*]} diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 59362958..6cf1897b 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -32,7 +32,7 @@ passdb { pass = yes } passdb { - args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf + args = /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = sql } # Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing) @@ -173,6 +173,9 @@ service dict { group = vmail } } +service log { + user = dovenull +} service auth { inet_listener auth-inet { port = 10001 @@ -185,7 +188,6 @@ service auth { mode = 0600 user = vmail } - user = root } service managesieve-login { inet_listener sieve { @@ -193,10 +195,19 @@ service managesieve-login { } service_count = 1 process_min_avail = 2 - vsz_limit = 128M + vsz_limit = 64M +} +service imap-login { + service_count = 1 + vsz_limit = 64M + user = dovenull +} +service pop3-login { + service_count = 1 } service imap { executable = imap imap-postlogin + user = dovenull } service managesieve { process_limit = 256 @@ -211,7 +222,7 @@ listen = *,[::] ssl_cert =
-
Postfix +
Postfix
- - + + +
@@ -475,12 +474,11 @@ $tfa_data = get_tfa();
-
Dovecot +
Dovecot
- - + + +
@@ -493,12 +491,11 @@ $tfa_data = get_tfa();
-
SOGo +
SOGo
- - + + +
@@ -511,12 +508,11 @@ $tfa_data = get_tfa();
-
Fail2ban +
Fail2ban
- - + + +
@@ -529,17 +525,16 @@ $tfa_data = get_tfa();
-
Rspamd history +
Rspamd history
- - + + +
-
+
@@ -547,12 +542,11 @@ $tfa_data = get_tfa();
-
Autodiscover +
Autodiscover
- - + + +
diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 5e0ccf30..2f5fb2c9 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -35,7 +35,8 @@ $opt = [ ]; $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); -$login_role = check_login($login_user, $_SERVER['PHP_AUTH_PW']); +$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW'])); +$login_role = check_login($login_user, $login_pass); if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { try { diff --git a/data/web/css/footable.bootstrap.min.css b/data/web/css/footable.bootstrap.min.css index a030a7ff..00e96c91 100644 --- a/data/web/css/footable.bootstrap.min.css +++ b/data/web/css/footable.bootstrap.min.css @@ -1 +1,4 @@ -table.footable-details,table.footable>thead>tr.footable-filtering>th div.form-group{margin-bottom:0}table.footable,table.footable-details{position:relative;width:100%;border-spacing:0;border-collapse:collapse}table.footable-hide-fouc{display:none}table>tbody>tr>td>span.footable-toggle{margin-right:8px;opacity:.3}table>tbody>tr>td>span.footable-toggle.last-column{margin-left:8px;float:right}table.table-condensed>tbody>tr>td>span.footable-toggle{margin-right:5px}table.footable-details>tbody>tr>th:nth-child(1){min-width:40px;width:120px}table.footable-details>tbody>tr>td:nth-child(2){word-break:break-all}table.footable-details>tbody>tr:first-child>td,table.footable-details>tbody>tr:first-child>th,table.footable-details>tfoot>tr:first-child>td,table.footable-details>tfoot>tr:first-child>th,table.footable-details>thead>tr:first-child>td,table.footable-details>thead>tr:first-child>th{border-top-width:0}table.footable-details.table-bordered>tbody>tr:first-child>td,table.footable-details.table-bordered>tbody>tr:first-child>th,table.footable-details.table-bordered>tfoot>tr:first-child>td,table.footable-details.table-bordered>tfoot>tr:first-child>th,table.footable-details.table-bordered>thead>tr:first-child>td,table.footable-details.table-bordered>thead>tr:first-child>th{border-top-width:1px}div.footable-loader{vertical-align:middle;text-align:center;height:300px;position:relative}div.footable-loader>span.fooicon{display:inline-block;opacity:.3;font-size:30px;line-height:32px;width:32px;height:32px;margin-top:-16px;margin-left:-16px;position:absolute;top:50%;left:50%;-webkit-animation:fooicon-spin-r 2s infinite linear;animation:fooicon-spin-r 2s infinite linear}table.footable>tbody>tr.footable-empty>td{vertical-align:middle;text-align:center;font-size:30px}table.footable>tbody>tr>td,table.footable>tbody>tr>th{display:none}table.footable>tbody>tr.footable-detail-row>td,table.footable>tbody>tr.footable-detail-row>th,table.footable>tbody>tr.footable-empty>td,table.footable>tbody>tr.footable-empty>th{display:table-cell}@-webkit-keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fooicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings'!important;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fooicon:after,.fooicon:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fooicon-loader:before{content:"\e030"}.fooicon-plus:before{content:"\2b"}.fooicon-minus:before{content:"\2212"}.fooicon-search:before{content:"\e003"}.fooicon-remove:before{content:"\e014"}.fooicon-sort:before{content:"\e150"}.fooicon-sort-asc:before{content:"\e155"}.fooicon-sort-desc:before{content:"\e156"}.fooicon-pencil:before{content:"\270f"}.fooicon-trash:before{content:"\e020"}.fooicon-eye-close:before{content:"\e106"}.fooicon-flash:before{content:"\e162"}.fooicon-cog:before{content:"\e019"}.fooicon-stats:before{content:"\e185"}table.footable>thead>tr.footable-filtering>th{border-bottom-width:1px;font-weight:400}table.footable.footable-filtering-right>thead>tr.footable-filtering>th,table.footable>thead>tr.footable-filtering>th{text-align:right}table.footable.footable-filtering-left>thead>tr.footable-filtering>th{text-align:left}table.footable-paging-center>tfoot>tr.footable-paging>td,table.footable.footable-filtering-center>thead>tr.footable-filtering>th,table.footable>tfoot>tr.footable-paging>td{text-align:center}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:5px}table.footable>thead>tr.footable-filtering>th div.input-group{width:100%}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox{margin:0;display:block;position:relative}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox>label{display:block;padding-left:20px}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox input[type=checkbox]{position:absolute;margin-left:-20px}@media (min-width:768px){table.footable>thead>tr.footable-filtering>th div.input-group{width:auto}table.footable>thead>tr.footable-filtering>th div.form-group{margin-left:2px;margin-right:2px}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:0}}table.footable>tbody>tr>td.footable-sortable,table.footable>tbody>tr>th.footable-sortable,table.footable>tfoot>tr>td.footable-sortable,table.footable>tfoot>tr>th.footable-sortable,table.footable>thead>tr>td.footable-sortable,table.footable>thead>tr>th.footable-sortable{position:relative;padding-right:30px;cursor:pointer}td.footable-sortable>span.fooicon,th.footable-sortable>span.fooicon{position:absolute;right:6px;top:50%;margin-top:-7px;opacity:0;transition:opacity .3s ease-in}td.footable-sortable.footable-asc>span.fooicon,td.footable-sortable.footable-desc>span.fooicon,td.footable-sortable:hover>span.fooicon,th.footable-sortable.footable-asc>span.fooicon,th.footable-sortable.footable-desc>span.fooicon,th.footable-sortable:hover>span.fooicon{opacity:1}table.footable-sorting-disabled td.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled td.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled td.footable-sortable:hover>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled th.footable-sortable:hover>span.fooicon{opacity:0;visibility:hidden}table.footable>tfoot>tr.footable-paging>td>ul.pagination{margin:10px 0 0}table.footable>tfoot>tr.footable-paging>td>span.label{display:inline-block;margin:0 0 10px;padding:4px 10px}table.footable-paging-left>tfoot>tr.footable-paging>td{text-align:left}table.footable-editing-right td.footable-editing,table.footable-editing-right tr.footable-editing,table.footable-paging-right>tfoot>tr.footable-paging>td{text-align:right}ul.pagination>li.footable-page{display:none}ul.pagination>li.footable-page.visible{display:inline}td.footable-editing{width:90px;max-width:90px}table.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit td.footable-editing,table.footable-editing-no-view td.footable-editing{width:70px;max-width:70px}table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit.footable-editing-no-view td.footable-editing{width:50px;max-width:50px}table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing{width:0;max-width:0;display:none!important}table.footable-editing-left td.footable-editing,table.footable-editing-left tr.footable-editing{text-align:left}table.footable-editing button.footable-add,table.footable-editing button.footable-hide,table.footable-editing-show button.footable-show,table.footable-editing.footable-editing-always-show button.footable-hide,table.footable-editing.footable-editing-always-show button.footable-show,table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing{display:none}table.footable-editing.footable-editing-always-show button.footable-add,table.footable-editing.footable-editing-show button.footable-add,table.footable-editing.footable-editing-show button.footable-hide{display:inline-block} \ No newline at end of file +table.footable-details,table.footable>thead>tr.footable-filtering>th div.form-group{margin-bottom:0}table.footable,table.footable-details{position:relative;width:100%;border-spacing:0;border-collapse:collapse}table.footable-hide-fouc{display:none}table>tbody>tr>td>span.footable-toggle{margin-right:8px;opacity:.3}table>tbody>tr>td>span.footable-toggle.last-column{margin-left:8px;float:right}table.table-condensed>tbody>tr>td>span.footable-toggle{margin-right:5px}table.footable-details>tbody>tr>th:nth-child(1){min-width:40px;width:120px}table.footable-details>tbody>tr>td:nth-child(2){word-break:break-all}table.footable-details>tbody>tr:first-child>td,table.footable-details>tbody>tr:first-child>th,table.footable-details>tfoot>tr:first-child>td,table.footable-details>tfoot>tr:first-child>th,table.footable-details>thead>tr:first-child>td,table.footable-details>thead>tr:first-child>th{border-top-width:0}table.footable-details.table-bordered>tbody>tr:first-child>td,table.footable-details.table-bordered>tbody>tr:first-child>th,table.footable-details.table-bordered>tfoot>tr:first-child>td,table.footable-details.table-bordered>tfoot>tr:first-child>th,table.footable-details.table-bordered>thead>tr:first-child>td,table.footable-details.table-bordered>thead>tr:first-child>th{border-top-width:1px}div.footable-loader{vertical-align:middle;text-align:center;height:300px;position:relative}div.footable-loader>span.fooicon{display:inline-block;opacity:.3;font-size:30px;line-height:32px;width:32px;height:32px;margin-top:-16px;margin-left:-16px;position:absolute;top:50%;left:50%;-webkit-animation:fooicon-spin-r 2s infinite linear;animation:fooicon-spin-r 2s infinite linear}table.footable>tbody>tr.footable-empty>td{vertical-align:middle;text-align:center;font-size:30px}table.footable>tbody>tr>td,table.footable>tbody>tr>th{display:none}table.footable>tbody>tr.footable-detail-row>td,table.footable>tbody>tr.footable-detail-row>th,table.footable>tbody>tr.footable-empty>td,table.footable>tbody>tr.footable-empty>th{display:table-cell}@-webkit-keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fooicon-spin-r{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fooicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings'!important;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fooicon:after,.fooicon:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.fooicon-loader:before{content:"\e030"}.fooicon-plus:before{content:"\2b"}.fooicon-minus:before{content:"\2212"}.fooicon-search:before{content:"\e003"}.fooicon-remove:before{content:"\e014"}.fooicon-sort:before{content:"\e150"}.fooicon-sort-asc:before{content:"\e155"}.fooicon-sort-desc:before{content:"\e156"}.fooicon-pencil:before{content:"\270f"}.fooicon-trash:before{content:"\e020"}.fooicon-eye-close:before{content:"\e106"}.fooicon-flash:before{content:"\e162"}.fooicon-cog:before{content:"\e019"}.fooicon-stats:before{content:"\e185"}table.footable>thead>tr.footable-filtering>th{border-bottom-width:1px;font-weight:400}table.footable.footable-filtering-right>thead>tr.footable-filtering>th,table.footable>thead>tr.footable-filtering>th{text-align:right}table.footable.footable-filtering-left>thead>tr.footable-filtering>th{text-align:left}table.footable-paging-center>tfoot>tr.footable-paging>td,table.footable.footable-filtering-center>thead>tr.footable-filtering>th,table.footable>tfoot>tr.footable-paging>td{text-align:center}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:5px}table.footable>thead>tr.footable-filtering>th div.input-group{width:100%}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox{margin:0;display:block;position:relative}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox>label{display:block;padding-left:20px}table.footable>thead>tr.footable-filtering>th ul.dropdown-menu>li>a.checkbox input[type=checkbox]{position:absolute;margin-left:-20px}@media (min-width:768px){table.footable>thead>tr.footable-filtering>th div.input-group{width:auto}table.footable>thead>tr.footable-filtering>th div.form-group{margin-left:2px;margin-right:2px}table.footable>thead>tr.footable-filtering>th div.form-group+div.form-group{margin-top:0}}table.footable>tbody>tr>td.footable-sortable,table.footable>tbody>tr>th.footable-sortable,table.footable>tfoot>tr>td.footable-sortable,table.footable>tfoot>tr>th.footable-sortable,table.footable>thead>tr>td.footable-sortable,table.footable>thead>tr>th.footable-sortable{position:relative;padding-right:30px;cursor:pointer}td.footable-sortable>span.fooicon,th.footable-sortable>span.fooicon{position:absolute;right:6px;top:50%;margin-top:-7px;opacity:0;transition:opacity .3s ease-in}td.footable-sortable.footable-asc>span.fooicon,td.footable-sortable.footable-desc>span.fooicon,td.footable-sortable:hover>span.fooicon,th.footable-sortable.footable-asc>span.fooicon,th.footable-sortable.footable-desc>span.fooicon,th.footable-sortable:hover>span.fooicon{opacity:1}table.footable-sorting-disabled td.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled td.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled td.footable-sortable:hover>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-asc>span.fooicon,table.footable-sorting-disabled th.footable-sortable.footable-desc>span.fooicon,table.footable-sorting-disabled th.footable-sortable:hover>span.fooicon{opacity:0;visibility:hidden}table.footable>tfoot>tr.footable-paging>td>ul.pagination{margin:10px 0 0}table.footable>tfoot>tr.footable-paging>td>span.label{display:inline-block;margin:0 0 10px;padding:4px 10px}table.footable-paging-left>tfoot>tr.footable-paging>td{text-align:left}table.footable-editing-right td.footable-editing,table.footable-editing-right tr.footable-editing,table.footable-paging-right>tfoot>tr.footable-paging>td{text-align:right}ul.pagination>li.footable-page{display:none}ul.pagination>li.footable-page.visible{display:inline}td.footable-editing{width:90px;max-width:90px}table.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit td.footable-editing,table.footable-editing-no-view td.footable-editing{width:70px;max-width:70px}table.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete td.footable-editing,table.footable-editing-no-edit.footable-editing-no-view td.footable-editing{width:50px;max-width:50px}table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view td.footable-editing,table.footable-editing-no-edit.footable-editing-no-delete.footable-editing-no-view th.footable-editing{width:0;max-width:0;display:none!important}table.footable-editing-left td.footable-editing,table.footable-editing-left tr.footable-editing{text-align:left}table.footable-editing button.footable-add,table.footable-editing button.footable-hide,table.footable-editing-show button.footable-show,table.footable-editing.footable-editing-always-show button.footable-hide,table.footable-editing.footable-editing-always-show button.footable-show,table.footable-editing.footable-editing-always-show.footable-editing-no-add tr.footable-editing{display:none}table.footable-editing.footable-editing-always-show button.footable-add,table.footable-editing.footable-editing-show button.footable-add,table.footable-editing.footable-editing-show button.footable-hide{display:inline-block} +table > tbody > tr > td > span.footable-toggle { + opacity: 0.7; +} \ No newline at end of file diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css index 9f07debe..5004cda1 100644 --- a/data/web/css/mailbox.css +++ b/data/web/css/mailbox.css @@ -34,4 +34,4 @@ table.footable>tbody>tr.footable-empty>td { } .inputMissingAttr { border-color: #FF4136; -} +} \ No newline at end of file diff --git a/data/web/css/numberedtextarea.min.css b/data/web/css/numberedtextarea.min.css new file mode 100644 index 00000000..c147b16b --- /dev/null +++ b/data/web/css/numberedtextarea.min.css @@ -0,0 +1 @@ +div.numberedtextarea-wrapper{position:relative}div.numberedtextarea-wrapper textarea{display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}div.numberedtextarea-line-numbers{position:absolute;top:0;left:0;right:0;bottom:0;width:50px;border-right:none;color:rgba(0,0,0,.4);overflow:hidden}div.numberedtextarea-number{padding-right:6px;text-align:right}textarea#script_data{font-family:Monospace} \ No newline at end of file diff --git a/data/web/css/user.css b/data/web/css/user.css index 07d4e745..e24bebe4 100644 --- a/data/web/css/user.css +++ b/data/web/css/user.css @@ -30,3 +30,10 @@ table.footable>tbody>tr.footable-empty>td { .inputMissingAttr { border-color: #FF4136; } +#logText { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} \ No newline at end of file diff --git a/data/web/diagnostics.php b/data/web/diagnostics.php new file mode 100644 index 00000000..d35c4d47 --- /dev/null +++ b/data/web/diagnostics.php @@ -0,0 +1,241 @@ + $mailcow_hostname, 'port' => array_pop(explode(':', getenv('SIEVE_PORT')))); +} + +$records[] = array('_25._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], 25, 1)); +$records[] = array('_' . $https_port . '._tcp.' . $mailcow_hostname, 'TLSA', generate_tlsa_digest($mailcow_hostname, $https_port)); +$records[] = array('_' . $autodiscover_config['pop3']['tlsport'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['tlsport'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['smtp']['port'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['port'])); +$records[] = array('_' . $autodiscover_config['smtp']['tlsport'] . '._tcp.' . $autodiscover_config['smtp']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['smtp']['server'], $autodiscover_config['smtp']['tlsport'], 1)); +$records[] = array('_' . $autodiscover_config['imap']['port'] . '._tcp.' . $autodiscover_config['imap']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['imap']['server'], $autodiscover_config['imap']['port'])); +$records[] = array('_' . $autodiscover_config['pop3']['port'] . '._tcp.' . $autodiscover_config['pop3']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['pop3']['server'], $autodiscover_config['pop3']['port'])); +$records[] = array('_' . $autodiscover_config['sieve']['port'] . '._tcp.' . $autodiscover_config['sieve']['server'], 'TLSA', generate_tlsa_digest($autodiscover_config['sieve']['server'], $autodiscover_config['sieve']['port'], 1)); + +foreach ($domains as $domain) { + $records[] = array($domain, 'MX', $mailcow_hostname); + $records[] = array('autodiscover.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array('_autodiscover._tcp.' . $domain, 'SRV', $mailcow_hostname . ' ' . $https_port); + $records[] = array('autoconfig.' . $domain, 'CNAME', $mailcow_hostname); + $records[] = array($domain, 'TXT', 'SPF Record Syntax', state_optional); + $records[] = array('_dmarc.' . $domain, 'TXT', 'DMARC Assistant', state_optional); + + if (!empty($dkim = dkim('details', $domain))) { + $records[] = array($dkim['dkim_selector'] . '._domainkey.' . $domain, 'TXT', $dkim['dkim_txt']); + } + + $current_records = dns_get_record('_pop3._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['tlsport'] != '110') { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['tlsport']); + } + } else { + $records[] = array('_pop3._tcp.' . $domain, 'SRV', '. 0'); + } + $current_records = dns_get_record('_pop3s._tcp.' . $domain, DNS_SRV); + if (count($current_records) == 0 || $current_records[0]['target'] != '') { + if ($autodiscover_config['pop3']['port'] != '995') { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', $autodiscover_config['pop3']['server'] . ' ' . $autodiscover_config['pop3']['port']); + } + } else { + $records[] = array('_pop3s._tcp.' . $domain, 'SRV', '. 0'); + } + if ($autodiscover_config['imap']['tlsport'] != '143') { + $records[] = array('_imap._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['tlsport']); + } + if ($autodiscover_config['imap']['port'] != '993') { + $records[] = array('_imaps._tcp.' . $domain, 'SRV', $autodiscover_config['imap']['server'] . ' ' . $autodiscover_config['imap']['port']); + } + if ($autodiscover_config['smtp']['tlsport'] != '587') { + $records[] = array('_submission._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['tlsport']); + } + if ($autodiscover_config['smtp']['port'] != '465') { + $records[] = array('_smtps._tcp.' . $domain, 'SRV', $autodiscover_config['smtp']['server'] . ' ' . $autodiscover_config['smtp']['port']); + } + if ($autodiscover_config['sieve']['port'] != '4190') { + $records[] = array('_sieve._tcp.' . $domain, 'SRV', $autodiscover_config['sieve']['server'] . ' ' . $autodiscover_config['sieve']['port']); + } +} + +$record_types = array( + 'A' => DNS_A, + 'AAAA' => DNS_AAAA, + 'CNAME' => DNS_CNAME, + 'MX' => DNS_MX, + 'PTR' => DNS_PTR, + 'SRV' => DNS_SRV, + 'TXT' => DNS_TXT, +); +$data_field = array( + 'A' => 'ip', + 'AAAA' => 'ipv6', + 'CNAME' => 'target', + 'MX' => 'target', + 'PTR' => 'target', + 'SRV' => 'data', + 'TLSA' => 'data', + 'TXT' => 'txt', +); +?> +
+

+

+
+ + + 0 && count($cname) > 0) { + if ($a[0]['ip'] == $cname[0]['ip']) { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $record[2])); + + $aaaa = dns_get_record($record[0], DNS_AAAA); + $cname = dns_get_record($record[2], DNS_AAAA); + if (count($aaaa) == 0 || count($cname) == 0 || $aaaa[0]['ipv6'] != $cname[0]['ipv6']) { + $currents[0]['target'] = $aaaa[0]['ipv6']; + } + } else { + $currents = array(array('host' => $record[0], 'class' => 'IN', 'type' => 'CNAME', 'target' => $a[0]['ip'])); + } + } + } + + foreach ($currents as $current) { + $current['type'] == strtoupper($current['type']); + if ($current['type'] != $record[1]) + { + continue; + } + + elseif ($current['type'] == 'TXT' && strpos($record[0], '_dmarc.') === 0) { + $state = state_optional . '
' . $current[$data_field[$current['type']]]; + } + else if ($current['type'] == 'TXT' && strpos($current['txt'], 'v=spf1') === 0) { + $state = state_optional . '
' . $current[$data_field[$current['type']]]; + } + else if ($current['type'] != 'TXT' && isset($data_field[$current['type']]) && $state != state_good) { + $state = state_nomatch; + if ($current[$data_field[$current['type']]] == $record[2]) + $state = state_good; + } + } + + if (isset($record[3]) && $record[3] == state_optional && ($state == state_missing || $state == state_nomatch)) { + $state = state_optional; + } + + if ($state == state_nomatch) { + $state = array(); + foreach ($currents as $current) { + $state[] = $current[$data_field[$current['type']]]; + } + $state = implode('
', $state); + } + + echo sprintf('', $record[0], $record[1], $record[2], $state); +} +?> +
%s%s%s%s
+
+
+ diff --git a/data/web/edit.php b/data/web/edit.php index fd6682ff..e06a0229 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -484,19 +484,25 @@ if (isset($_SESSION['mailcow_cc_role'])) {
-
- - -
-
- -
-
- +
+
+

Ratelimit

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

Filter

+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + diff --git a/data/web/inc/relay_check.php b/data/web/inc/ajax/relay_check.php similarity index 100% rename from data/web/inc/relay_check.php rename to data/web/inc/ajax/relay_check.php diff --git a/data/web/inc/ajax/sieve_validation.php b/data/web/inc/ajax/sieve_validation.php new file mode 100644 index 00000000..d24a95dc --- /dev/null +++ b/data/web/inc/ajax/sieve_validation.php @@ -0,0 +1,23 @@ + 'danger', 'msg' => 'Script cannot be empty')); + exit(); + } + $sieve->parse($_GET['script']); + } + catch (Exception $e) { + echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage())); + exit(); + } + echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success'])); +} +?> diff --git a/data/web/inc/ajax/sogo_ctrl.php b/data/web/inc/ajax/sogo_ctrl.php new file mode 100644 index 00000000..e238d9c0 --- /dev/null +++ b/data/web/inc/ajax/sogo_ctrl.php @@ -0,0 +1,39 @@ +OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Already running' : $last_response; +} + +if ($_GET['ACTION'] == "stop") { + $retry = 0; + while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) { + $response = docker('sogo-mailcow', 'post', 'stop'); + $response = json_decode($response, true); + $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Not running' : $last_response; +} + +?> diff --git a/data/web/inc/ajax/syncjob_logs.php b/data/web/inc/ajax/syncjob_logs.php new file mode 100644 index 00000000..a0568167 --- /dev/null +++ b/data/web/inc/ajax/syncjob_logs.php @@ -0,0 +1,15 @@ + diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 8740612c..9246d230 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -8,6 +8,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php'; +