mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2025-01-24 05:16:55 +02:00
commit
b5db5dd0b4
@ -1,7 +1,8 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
|
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
RUN apk upgrade --no-cache \
|
RUN apk upgrade --no-cache \
|
||||||
&& apk add --update --no-cache \
|
&& apk add --update --no-cache \
|
||||||
bash \
|
bash \
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
FROM clamav/clamav:1.0.3_base
|
FROM alpine:3.19
|
||||||
|
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
RUN apk upgrade --no-cache \
|
RUN apk upgrade --no-cache \
|
||||||
&& apk add --update --no-cache \
|
&& apk add --update --no-cache \
|
||||||
rsync \
|
rsync \
|
||||||
|
clamav \
|
||||||
bind-tools \
|
bind-tools \
|
||||||
bash
|
bash \
|
||||||
|
tini
|
||||||
|
|
||||||
# init
|
# init
|
||||||
COPY clamd.sh /clamd.sh
|
COPY clamd.sh /clamd.sh
|
||||||
@ -14,7 +16,9 @@ RUN chmod +x /sbin/tini
|
|||||||
|
|
||||||
# healthcheck
|
# healthcheck
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
COPY healthcheck.sh /healthcheck.sh
|
||||||
|
COPY clamdcheck.sh /usr/local/bin
|
||||||
RUN chmod +x /healthcheck.sh
|
RUN chmod +x /healthcheck.sh
|
||||||
|
RUN chmod +x /usr/local/bin/clamdcheck.sh
|
||||||
HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
|
HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
|
||||||
|
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
|
14
data/Dockerfiles/clamd/clamdcheck.sh
Normal file
14
data/Dockerfiles/clamd/clamdcheck.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then
|
||||||
|
if [ "$(echo "PING" | nc localhost 3310)" != "PONG" ]; then
|
||||||
|
echo "ERROR: Unable to contact server"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Clamd is up"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
@ -1,7 +1,8 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
|
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --update --no-cache python3 \
|
RUN apk add --update --no-cache python3 \
|
||||||
@ -9,12 +10,13 @@ RUN apk add --update --no-cache python3 \
|
|||||||
openssl \
|
openssl \
|
||||||
tzdata \
|
tzdata \
|
||||||
py3-psutil \
|
py3-psutil \
|
||||||
|
py3-redis \
|
||||||
|
py3-async-timeout \
|
||||||
&& pip3 install --upgrade pip \
|
&& pip3 install --upgrade pip \
|
||||||
fastapi \
|
fastapi \
|
||||||
uvicorn \
|
uvicorn \
|
||||||
aiodocker \
|
aiodocker \
|
||||||
docker \
|
docker
|
||||||
aioredis
|
|
||||||
RUN mkdir /app/modules
|
RUN mkdir /app/modules
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /app/
|
COPY docker-entrypoint.sh /app/
|
||||||
|
@ -5,16 +5,63 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
import async_timeout
|
import async_timeout
|
||||||
import asyncio
|
import asyncio
|
||||||
import aioredis
|
|
||||||
import aiodocker
|
import aiodocker
|
||||||
import docker
|
import docker
|
||||||
import logging
|
import logging
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
from fastapi import FastAPI, Response, Request
|
from fastapi import FastAPI, Response, Request
|
||||||
from modules.DockerApi import DockerApi
|
from modules.DockerApi import DockerApi
|
||||||
|
from redis import asyncio as aioredis
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
dockerapi = None
|
dockerapi = None
|
||||||
app = FastAPI()
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
# Initialize a custom logger
|
||||||
|
logger = logging.getLogger("dockerapi")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
# Configure the logger to output logs to the terminal
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
logger.info("Init APP")
|
||||||
|
|
||||||
|
# Init redis client
|
||||||
|
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||||
|
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0")
|
||||||
|
else:
|
||||||
|
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0")
|
||||||
|
|
||||||
|
# Init docker clients
|
||||||
|
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
||||||
|
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
||||||
|
|
||||||
|
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
||||||
|
|
||||||
|
logger.info("Subscribe to redis channel")
|
||||||
|
# Subscribe to redis channel
|
||||||
|
dockerapi.pubsub = redis.pubsub()
|
||||||
|
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||||
|
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||||
|
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
# Close docker connections
|
||||||
|
dockerapi.sync_docker_client.close()
|
||||||
|
await dockerapi.async_docker_client.close()
|
||||||
|
|
||||||
|
# Close redis
|
||||||
|
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||||
|
await dockerapi.redis_client.close()
|
||||||
|
|
||||||
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
# Define Routes
|
# Define Routes
|
||||||
@app.get("/host/stats")
|
@app.get("/host/stats")
|
||||||
@ -144,53 +191,7 @@ async def post_container_update_stats(container_id : str):
|
|||||||
|
|
||||||
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
||||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||||
|
|
||||||
# Events
|
|
||||||
@app.on_event("startup")
|
|
||||||
async def startup_event():
|
|
||||||
global dockerapi
|
|
||||||
|
|
||||||
# Initialize a custom logger
|
|
||||||
logger = logging.getLogger("dockerapi")
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
# Configure the logger to output logs to the terminal
|
|
||||||
handler = logging.StreamHandler()
|
|
||||||
handler.setLevel(logging.INFO)
|
|
||||||
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
logger.addHandler(handler)
|
|
||||||
|
|
||||||
logger.info("Init APP")
|
|
||||||
|
|
||||||
# Init redis client
|
|
||||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
|
||||||
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0")
|
|
||||||
else:
|
|
||||||
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0")
|
|
||||||
|
|
||||||
# Init docker clients
|
|
||||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
|
||||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
|
||||||
|
|
||||||
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
|
||||||
|
|
||||||
logger.info("Subscribe to redis channel")
|
|
||||||
# Subscribe to redis channel
|
|
||||||
dockerapi.pubsub = redis.pubsub()
|
|
||||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
|
||||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
|
||||||
async def shutdown_event():
|
|
||||||
global dockerapi
|
|
||||||
|
|
||||||
# Close docker connections
|
|
||||||
dockerapi.sync_docker_client.close()
|
|
||||||
await dockerapi.async_docker_client.close()
|
|
||||||
|
|
||||||
# Close redis
|
|
||||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
|
||||||
await dockerapi.redis_client.close()
|
|
||||||
|
|
||||||
# PubSub Handler
|
# PubSub Handler
|
||||||
async def handle_pubsub_messages(channel: aioredis.client.PubSub):
|
async def handle_pubsub_messages(channel: aioredis.client.PubSub):
|
||||||
|
@ -1,119 +1,115 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM alpine:3.19
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced extractVersion=(?<version>.*)$
|
|
||||||
ARG DOVECOT=2.3.21
|
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
|
||||||
ARG GOSU_VERSION=1.16
|
ARG GOSU_VERSION=1.16
|
||||||
ENV LC_ALL C
|
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
# Add groups and users before installing Dovecot to not break compatibility
|
# Add groups and users before installing Dovecot to not break compatibility
|
||||||
RUN groupadd -g 5000 vmail \
|
RUN addgroup -g 5000 vmail \
|
||||||
&& groupadd -g 401 dovecot \
|
&& addgroup -g 401 dovecot \
|
||||||
&& groupadd -g 402 dovenull \
|
&& addgroup -g 402 dovenull \
|
||||||
&& groupadd -g 999 sogo \
|
&& sed -i "s/999/99/" /etc/group \
|
||||||
&& usermod -a -G sogo nobody \
|
&& addgroup -g 999 sogo \
|
||||||
&& useradd -g vmail -u 5000 vmail -d /var/vmail \
|
&& addgroup nobody sogo \
|
||||||
&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
|
&& adduser -D -u 5000 -G vmail -h /var/vmail vmail \
|
||||||
&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull \
|
&& adduser -D -G dovecot -u 401 -h /dev/null -s /sbin/nologin dovecot \
|
||||||
&& touch /etc/default/locale \
|
&& adduser -D -G dovenull -u 402 -h /dev/null -s /sbin/nologin dovenull \
|
||||||
&& apt-get update \
|
&& apk add --no-cache --update \
|
||||||
&& apt-get -y --no-install-recommends install \
|
bash \
|
||||||
build-essential \
|
bind-tools \
|
||||||
apt-transport-https \
|
findutils \
|
||||||
|
envsubst \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
cpanminus \
|
|
||||||
curl \
|
curl \
|
||||||
dnsutils \
|
|
||||||
dirmngr \
|
|
||||||
gettext \
|
|
||||||
gnupg2 \
|
|
||||||
jq \
|
jq \
|
||||||
libauthen-ntlm-perl \
|
lua \
|
||||||
libcgi-pm-perl \
|
lua-cjson \
|
||||||
libcrypt-openssl-rsa-perl \
|
|
||||||
libcrypt-ssleay-perl \
|
|
||||||
libdata-uniqid-perl \
|
|
||||||
libdbd-mysql-perl \
|
|
||||||
libdbi-perl \
|
|
||||||
libdigest-hmac-perl \
|
|
||||||
libdist-checkconflicts-perl \
|
|
||||||
libencode-imaputf7-perl \
|
|
||||||
libfile-copy-recursive-perl \
|
|
||||||
libfile-tail-perl \
|
|
||||||
libhtml-parser-perl \
|
|
||||||
libio-compress-perl \
|
|
||||||
libio-socket-inet6-perl \
|
|
||||||
libio-socket-ssl-perl \
|
|
||||||
libio-tee-perl \
|
|
||||||
libipc-run-perl \
|
|
||||||
libjson-webtoken-perl \
|
|
||||||
liblockfile-simple-perl \
|
|
||||||
libmail-imapclient-perl \
|
|
||||||
libmodule-implementation-perl \
|
|
||||||
libmodule-scandeps-perl \
|
|
||||||
libnet-ssleay-perl \
|
|
||||||
libpackage-stash-perl \
|
|
||||||
libpackage-stash-xs-perl \
|
|
||||||
libpar-packer-perl \
|
|
||||||
libparse-recdescent-perl \
|
|
||||||
libproc-processtable-perl \
|
|
||||||
libreadonly-perl \
|
|
||||||
libregexp-common-perl \
|
|
||||||
libssl-dev \
|
|
||||||
libsys-meminfo-perl \
|
|
||||||
libterm-readkey-perl \
|
|
||||||
libtest-deep-perl \
|
|
||||||
libtest-fatal-perl \
|
|
||||||
libtest-mock-guard-perl \
|
|
||||||
libtest-mockobject-perl \
|
|
||||||
libtest-nowarnings-perl \
|
|
||||||
libtest-pod-perl \
|
|
||||||
libtest-requires-perl \
|
|
||||||
libtest-simple-perl \
|
|
||||||
libtest-warn-perl \
|
|
||||||
libtry-tiny-perl \
|
|
||||||
libunicode-string-perl \
|
|
||||||
liburi-perl \
|
|
||||||
libwww-perl \
|
|
||||||
lua-sql-mysql \
|
|
||||||
lua-socket \
|
lua-socket \
|
||||||
|
lua-sql-mysql \
|
||||||
|
lua5.3-sql-mysql \
|
||||||
|
icu-data-full \
|
||||||
|
mariadb-connector-c \
|
||||||
|
gcompat \
|
||||||
mariadb-client \
|
mariadb-client \
|
||||||
|
perl \
|
||||||
|
perl-ntlm \
|
||||||
|
perl-cgi \
|
||||||
|
perl-crypt-openssl-rsa \
|
||||||
|
perl-utils \
|
||||||
|
perl-crypt-ssleay \
|
||||||
|
perl-data-uniqid \
|
||||||
|
perl-dbd-mysql \
|
||||||
|
perl-dbi \
|
||||||
|
perl-digest-hmac \
|
||||||
|
perl-dist-checkconflicts \
|
||||||
|
perl-encode-imaputf7 \
|
||||||
|
perl-file-copy-recursive \
|
||||||
|
perl-file-tail \
|
||||||
|
perl-io-socket-inet6 \
|
||||||
|
perl-io-gzip \
|
||||||
|
perl-io-socket-ssl \
|
||||||
|
perl-io-tee \
|
||||||
|
perl-ipc-run \
|
||||||
|
perl-json-webtoken \
|
||||||
|
perl-mail-imapclient \
|
||||||
|
perl-module-implementation \
|
||||||
|
perl-module-scandeps \
|
||||||
|
perl-net-ssleay \
|
||||||
|
perl-package-stash \
|
||||||
|
perl-package-stash-xs \
|
||||||
|
perl-par-packer \
|
||||||
|
perl-parse-recdescent \
|
||||||
|
perl-lockfile-simple --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ \
|
||||||
|
libproc \
|
||||||
|
perl-readonly \
|
||||||
|
perl-regexp-common \
|
||||||
|
perl-sys-meminfo \
|
||||||
|
perl-term-readkey \
|
||||||
|
perl-test-deep \
|
||||||
|
perl-test-fatal \
|
||||||
|
perl-test-mockobject \
|
||||||
|
perl-test-mock-guard \
|
||||||
|
perl-test-pod \
|
||||||
|
perl-test-requires \
|
||||||
|
perl-test-simple \
|
||||||
|
perl-test-warn \
|
||||||
|
perl-try-tiny \
|
||||||
|
perl-unicode-string \
|
||||||
|
perl-proc-processtable \
|
||||||
|
perl-app-cpanminus \
|
||||||
procps \
|
procps \
|
||||||
python3-pip \
|
python3 \
|
||||||
redis-server \
|
py3-mysqlclient \
|
||||||
supervisor \
|
py3-html2text \
|
||||||
|
py3-jinja2 \
|
||||||
|
py3-redis \
|
||||||
|
redis \
|
||||||
syslog-ng \
|
syslog-ng \
|
||||||
syslog-ng-core \
|
syslog-ng-redis \
|
||||||
syslog-ng-mod-redis \
|
syslog-ng-json \
|
||||||
|
supervisor \
|
||||||
|
tzdata \
|
||||||
wget \
|
wget \
|
||||||
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
dovecot \
|
||||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
dovecot-dev \
|
||||||
&& chmod +x /usr/local/bin/gosu \
|
|
||||||
&& gosu nobody true \
|
|
||||||
&& apt-key adv --fetch-keys https://repo.dovecot.org/DOVECOT-REPO-GPG \
|
|
||||||
&& echo "deb https://repo.dovecot.org/ce-${DOVECOT}/debian/bullseye bullseye main" > /etc/apt/sources.list.d/dovecot.list \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y --no-install-recommends install \
|
|
||||||
dovecot-lua \
|
|
||||||
dovecot-managesieved \
|
|
||||||
dovecot-sieve \
|
|
||||||
dovecot-lmtpd \
|
dovecot-lmtpd \
|
||||||
|
dovecot-lua \
|
||||||
dovecot-ldap \
|
dovecot-ldap \
|
||||||
dovecot-mysql \
|
dovecot-mysql \
|
||||||
dovecot-core \
|
dovecot-sql \
|
||||||
|
dovecot-submissiond \
|
||||||
|
dovecot-pigeonhole-plugin \
|
||||||
dovecot-pop3d \
|
dovecot-pop3d \
|
||||||
dovecot-imapd \
|
dovecot-fts-solr \
|
||||||
dovecot-solr \
|
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
|
||||||
&& pip3 install mysql-connector-python html2text jinja2 redis \
|
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$arch" \
|
||||||
&& apt-get autoremove --purge -y \
|
&& chmod +x /usr/local/bin/gosu \
|
||||||
&& apt-get autoclean \
|
&& gosu nobody true
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& rm -rf /tmp/* /var/tmp/* /root/.cache/
|
# RUN cpan LockFile::Simple
|
||||||
# imapsync dependencies
|
|
||||||
RUN cpan Crypt::OpenSSL::PKCS12
|
|
||||||
|
|
||||||
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
|
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
|
||||||
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
|
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
|
||||||
|
@ -432,4 +432,8 @@ done
|
|||||||
# May be related to something inside Docker, I seriously don't know
|
# May be related to something inside Docker, I seriously don't know
|
||||||
touch /etc/dovecot/lua/passwd-verify.lua
|
touch /etc/dovecot/lua/passwd-verify.lua
|
||||||
|
|
||||||
|
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||||
|
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
|
fi
|
||||||
|
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
import smtplib
|
import smtplib
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import mysql.connector
|
import MySQLdb
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import COMMASPACE, formatdate
|
from email.utils import COMMASPACE, formatdate
|
||||||
import cgi
|
|
||||||
import jinja2
|
import jinja2
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
import json
|
import json
|
||||||
@ -50,7 +49,7 @@ try:
|
|||||||
def query_mysql(query, headers = True, update = False):
|
def query_mysql(query, headers = True, update = False):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('%s - trying again...' % (ex))
|
print('%s - trying again...' % (ex))
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
@ -55,7 +55,7 @@ try:
|
|||||||
msg.attach(text_part)
|
msg.attach(text_part)
|
||||||
msg.attach(html_part)
|
msg.attach(html_part)
|
||||||
msg['To'] = username
|
msg['To'] = username
|
||||||
p = Popen(['/usr/lib/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
|
p = Popen(['/usr/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
|
||||||
p.communicate(input=bytes(msg.as_string(), 'utf-8'))
|
p.communicate(input=bytes(msg.as_string(), 'utf-8'))
|
||||||
|
|
||||||
domain = username.split("@")[-1]
|
domain = username.split("@")[-1]
|
||||||
|
@ -13,6 +13,10 @@ autostart=true
|
|||||||
|
|
||||||
[program:dovecot]
|
[program:dovecot]
|
||||||
command=/usr/sbin/dovecot -F
|
command=/usr/sbin/dovecot -F
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
autorestart=true
|
autorestart=true
|
||||||
|
|
||||||
[eventlistener:processes]
|
[eventlistener:processes]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@version: 3.28
|
@version: 4.5
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
@ -6,11 +6,11 @@ options {
|
|||||||
use_dns(no);
|
use_dns(no);
|
||||||
use_fqdn(no);
|
use_fqdn(no);
|
||||||
owner("root"); group("adm"); perm(0640);
|
owner("root"); group("adm"); perm(0640);
|
||||||
stats_freq(0);
|
stats(freq(0));
|
||||||
bad_hostname("^gconfd$");
|
bad_hostname("^gconfd$");
|
||||||
};
|
};
|
||||||
source s_src {
|
source s_dgram {
|
||||||
unix-stream("/dev/log");
|
unix-dgram("/dev/log");
|
||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
@ -36,7 +36,7 @@ filter f_replica {
|
|||||||
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_src);
|
source(s_dgram);
|
||||||
filter(f_replica);
|
filter(f_replica);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@version: 3.28
|
@version: 4.5
|
||||||
@include "scl.conf"
|
@include "scl.conf"
|
||||||
options {
|
options {
|
||||||
chain_hostnames(off);
|
chain_hostnames(off);
|
||||||
@ -6,11 +6,11 @@ options {
|
|||||||
use_dns(no);
|
use_dns(no);
|
||||||
use_fqdn(no);
|
use_fqdn(no);
|
||||||
owner("root"); group("adm"); perm(0640);
|
owner("root"); group("adm"); perm(0640);
|
||||||
stats_freq(0);
|
stats(freq(0));
|
||||||
bad_hostname("^gconfd$");
|
bad_hostname("^gconfd$");
|
||||||
};
|
};
|
||||||
source s_src {
|
source s_dgram {
|
||||||
unix-stream("/dev/log");
|
unix-dgram("/dev/log");
|
||||||
internal();
|
internal();
|
||||||
};
|
};
|
||||||
destination d_stdout { pipe("/dev/stdout"); };
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
@ -36,7 +36,7 @@ filter f_replica {
|
|||||||
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
||||||
};
|
};
|
||||||
log {
|
log {
|
||||||
source(s_src);
|
source(s_dgram);
|
||||||
filter(f_replica);
|
filter(f_replica);
|
||||||
destination(d_stdout);
|
destination(d_stdout);
|
||||||
filter(f_mail);
|
filter(f_mail);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
ENV XTABLES_LIBDIR /usr/lib/xtables
|
ENV XTABLES_LIBDIR /usr/lib/xtables
|
||||||
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
||||||
ENV IPTABLES_LIBDIR /usr/lib
|
ENV IPTABLES_LIBDIR /usr/lib
|
||||||
@ -14,6 +15,7 @@ RUN apk add --virtual .build-deps \
|
|||||||
openssl-dev \
|
openssl-dev \
|
||||||
&& apk add -U python3 \
|
&& apk add -U python3 \
|
||||||
iptables \
|
iptables \
|
||||||
|
iptables-dev \
|
||||||
ip6tables \
|
ip6tables \
|
||||||
xtables-addons \
|
xtables-addons \
|
||||||
nftables \
|
nftables \
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
#RUN addgroup -S olefy && adduser -S olefy -G olefy \
|
#RUN addgroup -S olefy && adduser -S olefy -G olefy \
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
FROM php:8.2-fpm-alpine3.17
|
FROM php:8.2-fpm-alpine3.19
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG APCU_PECL_VERSION=5.1.22
|
ARG APCU_PECL_VERSION=5.1.23
|
||||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG IMAGICK_PECL_VERSION=3.7.0
|
ARG IMAGICK_PECL_VERSION=3.7.0
|
||||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
@ -10,9 +10,9 @@ ARG MAILPARSE_PECL_VERSION=3.1.6
|
|||||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MEMCACHED_PECL_VERSION=3.2.0
|
ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG REDIS_PECL_VERSION=6.0.1
|
ARG REDIS_PECL_VERSION=6.0.2
|
||||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG COMPOSER_VERSION=2.6.5
|
ARG COMPOSER_VERSION=2.6.6
|
||||||
|
|
||||||
RUN apk add -U --no-cache autoconf \
|
RUN apk add -U --no-cache autoconf \
|
||||||
aspell-dev \
|
aspell-dev \
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ENV LC_ALL C
|
ENV LC_ALL C
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG CODENAME=bullseye
|
ARG CODENAME=bullseye
|
||||||
@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
dnsutils \
|
dnsutils \
|
||||||
netcat \
|
netcat \
|
||||||
&& apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \
|
&& apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \
|
||||||
&& echo "deb [arch=amd64] https://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list \
|
&& echo "deb https://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get --no-install-recommends -y install rspamd redis-tools procps nano \
|
&& apt-get --no-install-recommends -y install rspamd redis-tools procps nano \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
|
ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
ARG GOSU_VERSION=1.16
|
ARG GOSU_VERSION=1.17
|
||||||
ENV LC_ALL C
|
ENV LC_ALL C
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
@ -32,7 +32,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
|||||||
&& mkdir /usr/share/doc/sogo \
|
&& mkdir /usr/share/doc/sogo \
|
||||||
&& touch /usr/share/doc/sogo/empty.sh \
|
&& touch /usr/share/doc/sogo/empty.sh \
|
||||||
&& apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \
|
&& apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \
|
||||||
&& echo "deb ${SOGO_DEBIAN_REPOSITORY} bullseye bullseye" > /etc/apt/sources.list.d/sogo.list \
|
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} bullseye sogo-v5" > /etc/apt/sources.list.d/sogo.list \
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
sogo \
|
sogo \
|
||||||
sogo-activesync \
|
sogo-activesync \
|
||||||
|
@ -3,7 +3,7 @@ FROM solr:7.7-slim
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG GOSU_VERSION=1.16
|
ARG GOSU_VERSION=1.17
|
||||||
|
|
||||||
COPY solr.sh /
|
COPY solr.sh /
|
||||||
COPY solr-config-7.7.0.xml /
|
COPY solr-config-7.7.0.xml /
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
|
|
||||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
|
||||||
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
curl \
|
curl \
|
||||||
|
bind-tools \
|
||||||
|
netcat-openbsd \
|
||||||
unbound \
|
unbound \
|
||||||
bash \
|
bash \
|
||||||
openssl \
|
openssl \
|
||||||
@ -21,7 +23,7 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|||||||
# healthcheck (nslookup)
|
# healthcheck (nslookup)
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
COPY healthcheck.sh /healthcheck.sh
|
||||||
RUN chmod +x /healthcheck.sh
|
RUN chmod +x /healthcheck.sh
|
||||||
HEALTHCHECK --interval=30s --timeout=10s CMD [ "/healthcheck.sh" ]
|
HEALTHCHECK --interval=5s --timeout=10s CMD [ "/healthcheck.sh" ]
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
@ -1,12 +1,89 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
nslookup mailcow.email 127.0.0.1 1> /dev/null
|
# Declare log function for logfile inside container
|
||||||
|
function log_to_file() {
|
||||||
|
echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" > /var/log/healthcheck.log
|
||||||
|
}
|
||||||
|
|
||||||
if [ $? == 0 ]; then
|
# General Ping function to check general pingability
|
||||||
echo "DNS resolution is working!"
|
function check_ping() {
|
||||||
exit 0
|
declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9")
|
||||||
else
|
|
||||||
echo "DNS resolution is not working correctly..."
|
for ip in "${ipstoping[@]}" ; do
|
||||||
echo "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!"
|
ping -q -c 3 -w 5 "$ip"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_to_file "Healthcheck: Couldn't ping $ip for 5 seconds... Gave up!"
|
||||||
|
log_to_file "Please check your internet connection or firewall rules to fix this error, because a simple ping test should always go through from the unbound container!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_to_file "Healthcheck: Ping Checks WORKING properly!"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# General DNS Resolve Check against Unbound Resolver himself
|
||||||
|
function check_dns() {
|
||||||
|
declare -a domains=("mailcow.email" "github.com" "hub.docker.com")
|
||||||
|
|
||||||
|
for domain in "${domains[@]}" ; do
|
||||||
|
for ((i=1; i<=3; i++)); do
|
||||||
|
dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 > /dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_to_file "Healthcheck: DNS Resolution Failed on $i attempt! Trying again..."
|
||||||
|
if [ $i -eq 3 ]; then
|
||||||
|
log_to_file "Healthcheck: DNS Resolution not possible after $i attempts... Gave up!"
|
||||||
|
log_to_file "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
log_to_file "Healthcheck: DNS Resolver WORKING properly!"
|
||||||
|
return 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Simple Netcat Check to connect to common webports
|
||||||
|
function check_netcat() {
|
||||||
|
declare -a domains=("mailcow.email" "github.com" "hub.docker.com")
|
||||||
|
declare -a ports=("80" "443")
|
||||||
|
|
||||||
|
for domain in "${domains[@]}" ; do
|
||||||
|
for port in "${ports[@]}" ; do
|
||||||
|
nc -z -w 2 $domain $port
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_to_file "Healthcheck: Could not reach $domain on Port $port... Gave up!"
|
||||||
|
log_to_file "Please check your internet connection or firewall rules to fix this error."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
log_to_file "Healthcheck: Netcat Checks WORKING properly!"
|
||||||
|
return 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy)
|
||||||
|
check_ping
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
check_dns
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_netcat
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_to_file "Healthcheck: ALL CHECKS WERE SUCCESSFUL! Unbound is healthy!"
|
||||||
|
exit 0
|
@ -1,5 +1,5 @@
|
|||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
RUN apk add --update \
|
RUN apk add --update \
|
||||||
|
@ -716,8 +716,8 @@ rspamd_checks() {
|
|||||||
From: watchdog@localhost
|
From: watchdog@localhost
|
||||||
|
|
||||||
Empty
|
Empty
|
||||||
' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score)
|
' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score | sed 's/\..*//' )
|
||||||
if [[ ${SCORE} != "9999" ]]; then
|
if [[ ${SCORE} -ne 9999 ]]; then
|
||||||
echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2
|
echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2
|
||||||
err_count=$(( ${err_count} + 1))
|
err_count=$(( ${err_count} + 1))
|
||||||
else
|
else
|
||||||
|
@ -85,6 +85,7 @@ smtp_tls_security_level = dane
|
|||||||
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
||||||
smtpd_delay_reject = yes
|
smtpd_delay_reject = yes
|
||||||
smtpd_error_sleep_time = 10s
|
smtpd_error_sleep_time = 10s
|
||||||
|
smtpd_forbid_bare_newline = yes
|
||||||
smtpd_hard_error_limit = ${stress?1}${stress:5}
|
smtpd_hard_error_limit = ${stress?1}${stress:5}
|
||||||
smtpd_helo_required = yes
|
smtpd_helo_required = yes
|
||||||
smtpd_proxy_timeout = 600s
|
smtpd_proxy_timeout = 600s
|
||||||
@ -161,7 +162,8 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
|
|||||||
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
|
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
|
||||||
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
|
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
|
||||||
smtp_sasl_auth_soft_bounce = no
|
smtp_sasl_auth_soft_bounce = no
|
||||||
postscreen_discard_ehlo_keywords = silent-discard, dsn
|
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
|
||||||
|
smtpd_discard_ehlo_keywords = chunking
|
||||||
compatibility_level = 2
|
compatibility_level = 2
|
||||||
smtputf8_enable = no
|
smtputf8_enable = no
|
||||||
# Define protocols for SMTPS and submission service
|
# Define protocols for SMTPS and submission service
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Fri Dec 1 00:15:18 UTC 2023
|
# Whitelist generated by Postwhite v3.4 on Mon Jan 1 00:15:22 UTC 2024
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 2038 total rules
|
# 2052 total rules
|
||||||
2a00:1450:4000::/36 permit
|
2a00:1450:4000::/36 permit
|
||||||
2a01:111:f400::/48 permit
|
2a01:111:f400::/48 permit
|
||||||
2a01:111:f403:8000::/50 permit
|
2a01:111:f403:8000::/50 permit
|
||||||
@ -13,7 +13,7 @@
|
|||||||
3.70.123.177 permit
|
3.70.123.177 permit
|
||||||
3.93.157.0/24 permit
|
3.93.157.0/24 permit
|
||||||
3.129.120.190 permit
|
3.129.120.190 permit
|
||||||
3.137.78.75 permit
|
3.137.16.58 permit
|
||||||
3.210.190.0/24 permit
|
3.210.190.0/24 permit
|
||||||
8.20.114.31 permit
|
8.20.114.31 permit
|
||||||
8.25.194.0/23 permit
|
8.25.194.0/23 permit
|
||||||
@ -183,6 +183,8 @@
|
|||||||
50.18.125.237 permit
|
50.18.125.237 permit
|
||||||
50.18.126.162 permit
|
50.18.126.162 permit
|
||||||
50.31.32.0/19 permit
|
50.31.32.0/19 permit
|
||||||
|
50.56.130.220 permit
|
||||||
|
50.56.130.221 permit
|
||||||
51.137.58.21 permit
|
51.137.58.21 permit
|
||||||
51.140.75.55 permit
|
51.140.75.55 permit
|
||||||
51.144.100.179 permit
|
51.144.100.179 permit
|
||||||
@ -596,6 +598,7 @@
|
|||||||
74.208.5.64/26 permit
|
74.208.5.64/26 permit
|
||||||
74.208.122.0/26 permit
|
74.208.122.0/26 permit
|
||||||
74.209.250.0/24 permit
|
74.209.250.0/24 permit
|
||||||
|
75.2.70.75 permit
|
||||||
76.223.128.0/19 permit
|
76.223.128.0/19 permit
|
||||||
76.223.176.0/20 permit
|
76.223.176.0/20 permit
|
||||||
77.238.176.0/22 permit
|
77.238.176.0/22 permit
|
||||||
@ -1186,6 +1189,7 @@
|
|||||||
98.139.245.208/30 permit
|
98.139.245.208/30 permit
|
||||||
98.139.245.212/31 permit
|
98.139.245.212/31 permit
|
||||||
99.78.197.208/28 permit
|
99.78.197.208/28 permit
|
||||||
|
99.83.190.102 permit
|
||||||
103.2.140.0/22 permit
|
103.2.140.0/22 permit
|
||||||
103.9.96.0/22 permit
|
103.9.96.0/22 permit
|
||||||
103.28.42.0/24 permit
|
103.28.42.0/24 permit
|
||||||
@ -1460,6 +1464,8 @@
|
|||||||
144.178.38.0/24 permit
|
144.178.38.0/24 permit
|
||||||
145.253.228.160/29 permit
|
145.253.228.160/29 permit
|
||||||
145.253.239.128/29 permit
|
145.253.239.128/29 permit
|
||||||
|
146.20.14.105 permit
|
||||||
|
146.20.14.107 permit
|
||||||
146.20.112.0/26 permit
|
146.20.112.0/26 permit
|
||||||
146.20.113.0/24 permit
|
146.20.113.0/24 permit
|
||||||
146.20.191.0/24 permit
|
146.20.191.0/24 permit
|
||||||
@ -1534,6 +1540,10 @@
|
|||||||
163.47.180.0/23 permit
|
163.47.180.0/23 permit
|
||||||
163.114.130.16 permit
|
163.114.130.16 permit
|
||||||
163.114.132.120 permit
|
163.114.132.120 permit
|
||||||
|
164.177.132.168 permit
|
||||||
|
164.177.132.169 permit
|
||||||
|
164.177.132.170 permit
|
||||||
|
164.177.132.171 permit
|
||||||
165.173.128.0/24 permit
|
165.173.128.0/24 permit
|
||||||
166.78.68.0/22 permit
|
166.78.68.0/22 permit
|
||||||
166.78.68.221 permit
|
166.78.68.221 permit
|
||||||
@ -1726,6 +1736,7 @@
|
|||||||
199.34.22.36 permit
|
199.34.22.36 permit
|
||||||
199.59.148.0/22 permit
|
199.59.148.0/22 permit
|
||||||
199.67.80.2 permit
|
199.67.80.2 permit
|
||||||
|
199.67.82.2 permit
|
||||||
199.67.84.0/24 permit
|
199.67.84.0/24 permit
|
||||||
199.67.86.0/24 permit
|
199.67.86.0/24 permit
|
||||||
199.67.88.0/24 permit
|
199.67.88.0/24 permit
|
||||||
@ -1789,6 +1800,7 @@
|
|||||||
204.92.114.187 permit
|
204.92.114.187 permit
|
||||||
204.92.114.203 permit
|
204.92.114.203 permit
|
||||||
204.92.114.204/31 permit
|
204.92.114.204/31 permit
|
||||||
|
204.132.224.66 permit
|
||||||
204.141.32.0/23 permit
|
204.141.32.0/23 permit
|
||||||
204.141.42.0/23 permit
|
204.141.42.0/23 permit
|
||||||
204.220.160.0/20 permit
|
204.220.160.0/20 permit
|
||||||
@ -1832,6 +1844,8 @@
|
|||||||
207.67.98.192/27 permit
|
207.67.98.192/27 permit
|
||||||
207.68.176.0/26 permit
|
207.68.176.0/26 permit
|
||||||
207.68.176.96/27 permit
|
207.68.176.96/27 permit
|
||||||
|
207.97.204.96 permit
|
||||||
|
207.97.204.97 permit
|
||||||
207.126.144.0/20 permit
|
207.126.144.0/20 permit
|
||||||
207.171.160.0/19 permit
|
207.171.160.0/19 permit
|
||||||
207.211.30.64/26 permit
|
207.211.30.64/26 permit
|
||||||
|
@ -49,13 +49,14 @@ $from = $headers['From'];
|
|||||||
$empty_footer = json_encode(array(
|
$empty_footer = json_encode(array(
|
||||||
'html' => '',
|
'html' => '',
|
||||||
'plain' => '',
|
'plain' => '',
|
||||||
|
'skip_replies' => 0,
|
||||||
'vars' => array()
|
'vars' => array()
|
||||||
));
|
));
|
||||||
|
|
||||||
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
|
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude` FROM `domain_wide_footer`
|
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `skip_replies` FROM `domain_wide_footer`
|
||||||
WHERE `domain` = :domain");
|
WHERE `domain` = :domain");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain' => $domain
|
':domain' => $domain
|
||||||
|
@ -567,6 +567,14 @@ rspamd_config:register_symbol({
|
|||||||
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
||||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars)
|
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars)
|
||||||
|
|
||||||
|
if footer.skip_replies ~= 0 then
|
||||||
|
in_reply_to = task:get_header_raw('in-reply-to')
|
||||||
|
if in_reply_to then
|
||||||
|
rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local envfrom_mime = task:get_from(2)
|
local envfrom_mime = task:get_from(2)
|
||||||
local from_name = ""
|
local from_name = ""
|
||||||
if envfrom_mime and envfrom_mime[1].name then
|
if envfrom_mime and envfrom_mime[1].name then
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
SOGoJunkFolderName= "Junk";
|
SOGoJunkFolderName= "Junk";
|
||||||
SOGoMailDomain = "sogo.local";
|
SOGoMailDomain = "sogo.local";
|
||||||
SOGoEnableEMailAlarms = YES;
|
SOGoEnableEMailAlarms = YES;
|
||||||
|
SOGoMailHideInlineAttachments = YES;
|
||||||
SOGoFoldersSendEMailNotifications = YES;
|
SOGoFoldersSendEMailNotifications = YES;
|
||||||
SOGoForwardEnabled = YES;
|
SOGoForwardEnabled = YES;
|
||||||
|
|
||||||
|
@ -228,8 +228,8 @@ legend {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
.slave-info {
|
.slave-info {
|
||||||
padding: 15px 0px 15px 15px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: orange;
|
||||||
}
|
}
|
||||||
.alert-hr {
|
.alert-hr {
|
||||||
margin:3px 0px;
|
margin:3px 0px;
|
||||||
|
@ -175,6 +175,9 @@ pre {
|
|||||||
background-color: #282828;
|
background-color: #282828;
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
}
|
}
|
||||||
|
.form-control {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
input.form-control, textarea.form-control {
|
input.form-control, textarea.form-control {
|
||||||
color: #e2e2e2 !important;
|
color: #e2e2e2 !important;
|
||||||
background-color: #424242 !important;
|
background-color: #424242 !important;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
function customize($_action, $_item, $_data = null) {
|
function customize($_action, $_item, $_data = null) {
|
||||||
global $redis;
|
global $redis;
|
||||||
global $lang;
|
global $lang;
|
||||||
|
global $LOGO_LIMITS;
|
||||||
|
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
@ -35,6 +36,23 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ($_data[$_item]['size'] > $LOGO_LIMITS['max_size']) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => 'img_size_exceeded'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
list($width, $height) = getimagesize($_data[$_item]['tmp_name']);
|
||||||
|
if ($width > $LOGO_LIMITS['max_width'] || $height > $LOGO_LIMITS['max_height']) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => 'img_dimensions_exceeded'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$image = new Imagick($_data[$_item]['tmp_name']);
|
$image = new Imagick($_data[$_item]['tmp_name']);
|
||||||
if ($image->valid() !== true) {
|
if ($image->valid() !== true) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
|
@ -478,16 +478,24 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$DOMAIN_DEFAULT_ATTRIBUTES = null;
|
||||||
|
if ($_data['template']){
|
||||||
|
$DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates', $_data['template'])['attributes'];
|
||||||
|
}
|
||||||
|
if (empty($DOMAIN_DEFAULT_ATTRIBUTES)) {
|
||||||
|
$DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates')[0]['attributes'];
|
||||||
|
}
|
||||||
|
|
||||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
$description = $_data['description'];
|
$description = $_data['description'];
|
||||||
if (empty($description)) $description = $domain;
|
if (empty($description)) $description = $domain;
|
||||||
$tags = (array)$_data['tags'];
|
$tags = (isset($_data['tags'])) ? (array)$_data['tags'] : $DOMAIN_DEFAULT_ATTRIBUTES['tags'];
|
||||||
$aliases = (int)$_data['aliases'];
|
$aliases = (isset($_data['aliases'])) ? (int)$_data['aliases'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_aliases_for_domain'];
|
||||||
$mailboxes = (int)$_data['mailboxes'];
|
$mailboxes = (isset($_data['mailboxes'])) ? (int)$_data['mailboxes'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_mboxes_for_domain'];
|
||||||
$defquota = (int)$_data['defquota'];
|
$defquota = (isset($_data['defquota'])) ? (int)$_data['defquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['def_quota_for_mbox'] / 1024 ** 2;
|
||||||
$maxquota = (int)$_data['maxquota'];
|
$maxquota = (isset($_data['maxquota'])) ? (int)$_data['maxquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_mbox'] / 1024 ** 2;
|
||||||
$restart_sogo = (int)$_data['restart_sogo'];
|
$restart_sogo = (int)$_data['restart_sogo'];
|
||||||
$quota = (int)$_data['quota'];
|
$quota = (isset($_data['quota'])) ? (int)$_data['quota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_domain'] / 1024 ** 2;
|
||||||
if ($defquota > $maxquota) {
|
if ($defquota > $maxquota) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@ -520,11 +528,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$active = intval($_data['active']);
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $DOMAIN_DEFAULT_ATTRIBUTES['active'];
|
||||||
$relay_all_recipients = intval($_data['relay_all_recipients']);
|
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_all_recipients'];
|
||||||
$relay_unknown_only = intval($_data['relay_unknown_only']);
|
$relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only'];
|
||||||
$backupmx = intval($_data['backupmx']);
|
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx'];
|
||||||
$gal = intval($_data['gal']);
|
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal'];
|
||||||
if ($relay_all_recipients == 1) {
|
if ($relay_all_recipients == 1) {
|
||||||
$backupmx = '1';
|
$backupmx = '1';
|
||||||
}
|
}
|
||||||
@ -625,9 +633,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!empty(intval($_data['rl_value']))) {
|
$_data['rl_value'] = (isset($_data['rl_value'])) ? intval($_data['rl_value']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_value'];
|
||||||
|
$_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame'];
|
||||||
|
if (!empty($_data['rl_value']) && !empty($_data['rl_frame'])){
|
||||||
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain));
|
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain));
|
||||||
}
|
}
|
||||||
|
$_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size'];
|
||||||
|
$_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector'];
|
||||||
if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
||||||
if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) {
|
if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
@ -1006,11 +1018,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (empty($name)) {
|
||||||
|
$name = $local_part;
|
||||||
|
}
|
||||||
|
$template_attr = null;
|
||||||
|
if ($_data['template']){
|
||||||
|
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes'];
|
||||||
|
}
|
||||||
|
if (empty($template_attr)) {
|
||||||
|
$template_attr = mailbox('get', 'mailbox_templates')[0]['attributes'];
|
||||||
|
}
|
||||||
|
$MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr);
|
||||||
|
|
||||||
$password = $_data['password'];
|
$password = $_data['password'];
|
||||||
$password2 = $_data['password2'];
|
$password2 = $_data['password2'];
|
||||||
$name = ltrim(rtrim($_data['name'], '>'), '<');
|
$name = ltrim(rtrim($_data['name'], '>'), '<');
|
||||||
$tags = $_data['tags'];
|
$tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags'];
|
||||||
$quota_m = intval($_data['quota']);
|
$quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2;
|
||||||
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
|
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@ -1019,9 +1043,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (empty($name)) {
|
|
||||||
$name = $local_part;
|
|
||||||
}
|
|
||||||
if (isset($_data['protocol_access'])) {
|
if (isset($_data['protocol_access'])) {
|
||||||
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
||||||
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
|
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
|
||||||
@ -1029,7 +1051,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
}
|
}
|
||||||
$active = intval($_data['active']);
|
$active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']);
|
||||||
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
||||||
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
||||||
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
||||||
@ -1227,12 +1249,29 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
|
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
|
||||||
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
|
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
|
||||||
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
$_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']);
|
||||||
|
$_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']);
|
||||||
|
$_data['spam_score'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_score']);
|
||||||
|
$_data['spam_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_policy']);
|
||||||
|
$_data['delimiter_action'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_delimiter_action']);
|
||||||
|
$_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']);
|
||||||
|
$_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']);
|
||||||
|
$_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']);
|
||||||
|
$_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']);
|
||||||
|
$_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']);
|
||||||
|
$_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']);
|
||||||
|
$_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']);
|
||||||
|
$_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']);
|
||||||
|
$_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
||||||
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
||||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
||||||
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
||||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':spam_alias' => $_data['spam_alias'],
|
':spam_alias' => $_data['spam_alias'],
|
||||||
@ -1251,31 +1290,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':app_passwds' => $_data['app_passwds']
|
':app_passwds' => $_data['app_passwds']
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
else {
|
catch (PDOException $e) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
$_SESSION['return'][] = array(
|
||||||
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
'type' => 'danger',
|
||||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
'msg' => $e->getMessage()
|
||||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
);
|
||||||
$stmt->execute(array(
|
return false;
|
||||||
':username' => $username,
|
|
||||||
':spam_alias' => 0,
|
|
||||||
':tls_policy' => 0,
|
|
||||||
':spam_score' => 0,
|
|
||||||
':spam_policy' => 0,
|
|
||||||
':delimiter_action' => 0,
|
|
||||||
':syncjobs' => 0,
|
|
||||||
':eas_reset' => 0,
|
|
||||||
':sogo_profile_reset' => 0,
|
|
||||||
':pushover' => 0,
|
|
||||||
':quarantine' => 0,
|
|
||||||
':quarantine_attachments' => 0,
|
|
||||||
':quarantine_notification' => 0,
|
|
||||||
':quarantine_category' => 0,
|
|
||||||
':app_passwds' => 0
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_frame'];
|
||||||
|
$_data['rl_value'] = (isset($_data['rl_value'])) ? $_data['rl_value'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_value'];
|
||||||
if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
|
if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
|
||||||
ratelimit('edit', 'mailbox', array(
|
ratelimit('edit', 'mailbox', array(
|
||||||
'object' => $username,
|
'object' => $username,
|
||||||
@ -1524,17 +1549,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
||||||
if (isset($_data['protocol_access'])) {
|
if (isset($_data['protocol_access'])) {
|
||||||
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
||||||
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
||||||
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||||
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||||
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||||
}
|
}
|
||||||
if (isset($_data['acl'])) {
|
if (isset($_data['acl'])) {
|
||||||
$_data['acl'] = (array)$_data['acl'];
|
$_data['acl'] = (array)$_data['acl'];
|
||||||
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
|
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
|
||||||
@ -3411,6 +3436,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$footers = array();
|
$footers = array();
|
||||||
$footers['html'] = isset($_data['html']) ? $_data['html'] : '';
|
$footers['html'] = isset($_data['html']) ? $_data['html'] : '';
|
||||||
$footers['plain'] = isset($_data['plain']) ? $_data['plain'] : '';
|
$footers['plain'] = isset($_data['plain']) ? $_data['plain'] : '';
|
||||||
|
$footers['skip_replies'] = isset($_data['skip_replies']) ? (int)$_data['skip_replies'] : 0;
|
||||||
$footers['mbox_exclude'] = array();
|
$footers['mbox_exclude'] = array();
|
||||||
if (isset($_data["mbox_exclude"])){
|
if (isset($_data["mbox_exclude"])){
|
||||||
if (!is_array($_data["mbox_exclude"])) {
|
if (!is_array($_data["mbox_exclude"])) {
|
||||||
@ -3460,12 +3486,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("DELETE FROM `domain_wide_footer` WHERE `domain`= :domain");
|
$stmt = $pdo->prepare("DELETE FROM `domain_wide_footer` WHERE `domain`= :domain");
|
||||||
$stmt->execute(array(':domain' => $domain));
|
$stmt->execute(array(':domain' => $domain));
|
||||||
$stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`) VALUES (:domain, :html, :plain, :mbox_exclude)");
|
$stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`, `skip_replies`) VALUES (:domain, :html, :plain, :mbox_exclude, :skip_replies)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':html' => $footers['html'],
|
':html' => $footers['html'],
|
||||||
':plain' => $footers['plain'],
|
':plain' => $footers['plain'],
|
||||||
':mbox_exclude' => json_encode($footers['mbox_exclude']),
|
':mbox_exclude' => json_encode($footers['mbox_exclude']),
|
||||||
|
':skip_replies' => $footers['skip_replies'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
catch (PDOException $e) {
|
catch (PDOException $e) {
|
||||||
@ -4435,7 +4462,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$mailboxdata['active'] = $row['active'];
|
$mailboxdata['active'] = $row['active'];
|
||||||
$mailboxdata['active_int'] = $row['active'];
|
$mailboxdata['active_int'] = $row['active'];
|
||||||
$mailboxdata['domain'] = $row['domain'];
|
$mailboxdata['domain'] = $row['domain'];
|
||||||
$mailboxdata['relayhost'] = $row['relayhost'];
|
|
||||||
$mailboxdata['name'] = $row['name'];
|
$mailboxdata['name'] = $row['name'];
|
||||||
$mailboxdata['local_part'] = $row['local_part'];
|
$mailboxdata['local_part'] = $row['local_part'];
|
||||||
$mailboxdata['quota'] = $row['quota'];
|
$mailboxdata['quota'] = $row['quota'];
|
||||||
@ -4622,7 +4648,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude` FROM `domain_wide_footer`
|
$stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude`, `skip_replies` FROM `domain_wide_footer`
|
||||||
WHERE `domain` = :domain");
|
WHERE `domain` = :domain");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain' => $domain
|
':domain' => $domain
|
||||||
|
@ -3,7 +3,7 @@ function init_db_schema() {
|
|||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "21112023_1644";
|
$db_version = "08012024_1442";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
@ -273,6 +273,7 @@ function init_db_schema() {
|
|||||||
"html" => "LONGTEXT",
|
"html" => "LONGTEXT",
|
||||||
"plain" => "LONGTEXT",
|
"plain" => "LONGTEXT",
|
||||||
"mbox_exclude" => "JSON NOT NULL DEFAULT ('[]')",
|
"mbox_exclude" => "JSON NOT NULL DEFAULT ('[]')",
|
||||||
|
"skip_replies" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
|
622
data/web/inc/lib/ssp.class.php
Normal file
622
data/web/inc/lib/ssp.class.php
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper functions for building a DataTables server-side processing SQL query
|
||||||
|
*
|
||||||
|
* The static functions in this class are just helper functions to help build
|
||||||
|
* the SQL used in the DataTables demo server-side processing scripts. These
|
||||||
|
* functions obviously do not represent all that can be done with server-side
|
||||||
|
* processing, they are intentionally simple to show how it works. More complex
|
||||||
|
* server-side processing operations will likely require a custom script.
|
||||||
|
*
|
||||||
|
* See https://datatables.net/usage/server-side for full details on the server-
|
||||||
|
* side processing requirements of DataTables.
|
||||||
|
*
|
||||||
|
* @license MIT - https://datatables.net/license_mit
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SSP {
|
||||||
|
/**
|
||||||
|
* Create the data output array for the DataTables rows
|
||||||
|
*
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @param array $data Data from the SQL get
|
||||||
|
* @return array Formatted data in a row based format
|
||||||
|
*/
|
||||||
|
static function data_output ( $columns, $data )
|
||||||
|
{
|
||||||
|
$out = array();
|
||||||
|
|
||||||
|
for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
|
||||||
|
$row = array();
|
||||||
|
|
||||||
|
for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
|
||||||
|
$column = $columns[$j];
|
||||||
|
|
||||||
|
// Is there a formatter?
|
||||||
|
if ( isset( $column['formatter'] ) ) {
|
||||||
|
if(empty($column['db'])){
|
||||||
|
$row[ $column['dt'] ] = $column['formatter']( $data[$i] );
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(!empty($column['db']) && (!isset($column['dummy']) || $column['dummy'] !== true)){
|
||||||
|
$row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$row[ $column['dt'] ] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database connection
|
||||||
|
*
|
||||||
|
* Obtain an PHP PDO connection from a connection details array
|
||||||
|
*
|
||||||
|
* @param array $conn SQL connection details. The array should have
|
||||||
|
* the following properties
|
||||||
|
* * host - host name
|
||||||
|
* * db - database name
|
||||||
|
* * user - user name
|
||||||
|
* * pass - user password
|
||||||
|
* * Optional: `'charset' => 'utf8'` - you might need this depending on your PHP / MySQL config
|
||||||
|
* @return resource PDO connection
|
||||||
|
*/
|
||||||
|
static function db ( $conn )
|
||||||
|
{
|
||||||
|
if ( is_array( $conn ) ) {
|
||||||
|
return self::sql_connect( $conn );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paging
|
||||||
|
*
|
||||||
|
* Construct the LIMIT clause for server-side processing SQL query
|
||||||
|
*
|
||||||
|
* @param array $request Data sent to server by DataTables
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @return string SQL limit clause
|
||||||
|
*/
|
||||||
|
static function limit ( $request, $columns )
|
||||||
|
{
|
||||||
|
$limit = '';
|
||||||
|
|
||||||
|
if ( isset($request['start']) && $request['length'] != -1 ) {
|
||||||
|
$limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordering
|
||||||
|
*
|
||||||
|
* Construct the ORDER BY clause for server-side processing SQL query
|
||||||
|
*
|
||||||
|
* @param array $request Data sent to server by DataTables
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @return string SQL order by clause
|
||||||
|
*/
|
||||||
|
static function order ( $tableAS, $request, $columns )
|
||||||
|
{
|
||||||
|
$select = '';
|
||||||
|
$order = '';
|
||||||
|
|
||||||
|
if ( isset($request['order']) && count($request['order']) ) {
|
||||||
|
$selects = [];
|
||||||
|
$orderBy = [];
|
||||||
|
$dtColumns = self::pluck( $columns, 'dt' );
|
||||||
|
|
||||||
|
for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
|
||||||
|
// Convert the column index into the column data property
|
||||||
|
$columnIdx = intval($request['order'][$i]['column']);
|
||||||
|
$requestColumn = $request['columns'][$columnIdx];
|
||||||
|
|
||||||
|
$columnIdx = array_search( $columnIdx, $dtColumns );
|
||||||
|
$column = $columns[ $columnIdx ];
|
||||||
|
|
||||||
|
if ( $requestColumn['orderable'] == 'true' ) {
|
||||||
|
$dir = $request['order'][$i]['dir'] === 'asc' ?
|
||||||
|
'ASC' :
|
||||||
|
'DESC';
|
||||||
|
|
||||||
|
if(isset($column['order_subquery'])) {
|
||||||
|
$selects[] = '('.$column['order_subquery'].') AS `'.$column['db'].'_count`';
|
||||||
|
$orderBy[] = '`'.$column['db'].'_count` '.$dir;
|
||||||
|
} else {
|
||||||
|
$orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( count( $selects ) ) {
|
||||||
|
$select = ', '.implode(', ', $selects);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( count( $orderBy ) ) {
|
||||||
|
$order = 'ORDER BY '.implode(', ', $orderBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$select, $order];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searching / Filtering
|
||||||
|
*
|
||||||
|
* Construct the WHERE clause for server-side processing SQL query.
|
||||||
|
*
|
||||||
|
* NOTE this does not match the built-in DataTables filtering which does it
|
||||||
|
* word by word on any field. It's possible to do here performance on large
|
||||||
|
* databases would be very poor
|
||||||
|
*
|
||||||
|
* @param array $request Data sent to server by DataTables
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @param array $bindings Array of values for PDO bindings, used in the
|
||||||
|
* sql_exec() function
|
||||||
|
* @return string SQL where clause
|
||||||
|
*/
|
||||||
|
static function filter ( $tablesAS, $request, $columns, &$bindings )
|
||||||
|
{
|
||||||
|
$globalSearch = array();
|
||||||
|
$columnSearch = array();
|
||||||
|
$joins = array();
|
||||||
|
$dtColumns = self::pluck( $columns, 'dt' );
|
||||||
|
|
||||||
|
if ( isset($request['search']) && $request['search']['value'] != '' ) {
|
||||||
|
$str = $request['search']['value'];
|
||||||
|
|
||||||
|
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||||
|
$requestColumn = $request['columns'][$i];
|
||||||
|
$columnIdx = array_search( $i, $dtColumns );
|
||||||
|
$column = $columns[ $columnIdx ];
|
||||||
|
|
||||||
|
if ( $requestColumn['searchable'] == 'true' ) {
|
||||||
|
if(!empty($column['db'])){
|
||||||
|
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||||
|
|
||||||
|
if(isset($column['search']['join'])) {
|
||||||
|
$joins[] = $column['search']['join'];
|
||||||
|
$globalSearch[] = $column['search']['where_column'].' LIKE '.$binding;
|
||||||
|
} else {
|
||||||
|
$globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual column filtering
|
||||||
|
if ( isset( $request['columns'] ) ) {
|
||||||
|
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||||
|
$requestColumn = $request['columns'][$i];
|
||||||
|
$columnIdx = array_search( $requestColumn['data'], $dtColumns );
|
||||||
|
$column = $columns[ $columnIdx ];
|
||||||
|
|
||||||
|
$str = $requestColumn['search']['value'];
|
||||||
|
|
||||||
|
if ( $requestColumn['searchable'] == 'true' &&
|
||||||
|
$str != '' ) {
|
||||||
|
if(!empty($column['db'])){
|
||||||
|
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||||
|
$columnSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the filters into a single string
|
||||||
|
$where = '';
|
||||||
|
|
||||||
|
if ( count( $globalSearch ) ) {
|
||||||
|
$where = '('.implode(' OR ', $globalSearch).')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( count( $columnSearch ) ) {
|
||||||
|
$where = $where === '' ?
|
||||||
|
implode(' AND ', $columnSearch) :
|
||||||
|
$where .' AND '. implode(' AND ', $columnSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
$join = '';
|
||||||
|
if( count($joins) ) {
|
||||||
|
$join = implode(' ', $joins);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $where !== '' ) {
|
||||||
|
$where = 'WHERE '.$where;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$join, $where];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the SQL queries needed for an server-side processing requested,
|
||||||
|
* utilising the helper functions of this class, limit(), order() and
|
||||||
|
* filter() among others. The returned array is ready to be encoded as JSON
|
||||||
|
* in response to an SSP request, or can be modified if needed before
|
||||||
|
* sending back to the client.
|
||||||
|
*
|
||||||
|
* @param array $request Data sent to server by DataTables
|
||||||
|
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||||
|
* @param string $table SQL table to query
|
||||||
|
* @param string $primaryKey Primary key of the table
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @return array Server-side processing response array
|
||||||
|
*/
|
||||||
|
static function simple ( $request, $conn, $table, $primaryKey, $columns )
|
||||||
|
{
|
||||||
|
$bindings = array();
|
||||||
|
$db = self::db( $conn );
|
||||||
|
|
||||||
|
// Allow for a JSON string to be passed in
|
||||||
|
if (isset($request['json'])) {
|
||||||
|
$request = json_decode($request['json'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// table AS
|
||||||
|
$tablesAS = null;
|
||||||
|
if(is_array($table)) {
|
||||||
|
$tablesAS = $table[1];
|
||||||
|
$table = $table[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the SQL query string from the request
|
||||||
|
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||||
|
$limit = self::limit( $request, $columns );
|
||||||
|
list($join, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||||
|
|
||||||
|
// Main query to actually get the data
|
||||||
|
$data = self::sql_exec( $db, $bindings,
|
||||||
|
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||||
|
$select
|
||||||
|
FROM `$table` AS `$tablesAS`
|
||||||
|
$join
|
||||||
|
$where
|
||||||
|
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||||
|
$order
|
||||||
|
$limit"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Data set length after filtering
|
||||||
|
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||||
|
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||||
|
FROM `$table` AS `$tablesAS`
|
||||||
|
$join
|
||||||
|
$where"
|
||||||
|
);
|
||||||
|
$recordsFiltered = $resFilterLength[0][0];
|
||||||
|
|
||||||
|
// Total data set length
|
||||||
|
$resTotalLength = self::sql_exec( $db,
|
||||||
|
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||||
|
FROM `$table` AS `$tablesAS`"
|
||||||
|
);
|
||||||
|
$recordsTotal = $resTotalLength[0][0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output
|
||||||
|
*/
|
||||||
|
return array(
|
||||||
|
"draw" => isset ( $request['draw'] ) ?
|
||||||
|
intval( $request['draw'] ) :
|
||||||
|
0,
|
||||||
|
"recordsTotal" => intval( $recordsTotal ),
|
||||||
|
"recordsFiltered" => intval( $recordsFiltered ),
|
||||||
|
"data" => self::data_output( $columns, $data )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The difference between this method and the `simple` one, is that you can
|
||||||
|
* apply additional `where` conditions to the SQL queries. These can be in
|
||||||
|
* one of two forms:
|
||||||
|
*
|
||||||
|
* * 'Result condition' - This is applied to the result set, but not the
|
||||||
|
* overall paging information query - i.e. it will not effect the number
|
||||||
|
* of records that a user sees they can have access to. This should be
|
||||||
|
* used when you want apply a filtering condition that the user has sent.
|
||||||
|
* * 'All condition' - This is applied to all queries that are made and
|
||||||
|
* reduces the number of records that the user can access. This should be
|
||||||
|
* used in conditions where you don't want the user to ever have access to
|
||||||
|
* particular records (for example, restricting by a login id).
|
||||||
|
*
|
||||||
|
* In both cases the extra condition can be added as a simple string, or if
|
||||||
|
* you are using external values, as an assoc. array with `condition` and
|
||||||
|
* `bindings` parameters. The `condition` is a string with the SQL WHERE
|
||||||
|
* condition and `bindings` is an assoc. array of the binding names and
|
||||||
|
* values.
|
||||||
|
*
|
||||||
|
* @param array $request Data sent to server by DataTables
|
||||||
|
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||||
|
* @param string|array $table SQL table to query, if array second key is AS
|
||||||
|
* @param string $primaryKey Primary key of the table
|
||||||
|
* @param array $columns Column information array
|
||||||
|
* @param string $join JOIN sql string
|
||||||
|
* @param string|array $whereResult WHERE condition to apply to the result set
|
||||||
|
* @return array Server-side processing response array
|
||||||
|
*/
|
||||||
|
static function complex (
|
||||||
|
$request,
|
||||||
|
$conn,
|
||||||
|
$table,
|
||||||
|
$primaryKey,
|
||||||
|
$columns,
|
||||||
|
$join=null,
|
||||||
|
$whereResult=null
|
||||||
|
) {
|
||||||
|
$bindings = array();
|
||||||
|
$db = self::db( $conn );
|
||||||
|
|
||||||
|
// table AS
|
||||||
|
$tablesAS = null;
|
||||||
|
if(is_array($table)) {
|
||||||
|
$tablesAS = $table[1];
|
||||||
|
$table = $table[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the SQL query string from the request
|
||||||
|
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||||
|
$limit = self::limit( $request, $columns );
|
||||||
|
list($join_filter, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||||
|
|
||||||
|
// whereResult can be a simple string, or an assoc. array with a
|
||||||
|
// condition and bindings
|
||||||
|
if ( $whereResult ) {
|
||||||
|
$str = $whereResult;
|
||||||
|
|
||||||
|
if ( is_array($whereResult) ) {
|
||||||
|
$str = $whereResult['condition'];
|
||||||
|
|
||||||
|
if ( isset($whereResult['bindings']) ) {
|
||||||
|
self::add_bindings($bindings, $whereResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = $where ?
|
||||||
|
$where .' AND '.$str :
|
||||||
|
'WHERE '.$str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main query to actually get the data
|
||||||
|
$data = self::sql_exec( $db, $bindings,
|
||||||
|
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||||
|
$select
|
||||||
|
FROM `$table` AS `$tablesAS`
|
||||||
|
$join
|
||||||
|
$join_filter
|
||||||
|
$where
|
||||||
|
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||||
|
$order
|
||||||
|
$limit"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Data set length after filtering
|
||||||
|
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||||
|
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||||
|
FROM `$table` AS `$tablesAS`
|
||||||
|
$join
|
||||||
|
$join_filter
|
||||||
|
$where"
|
||||||
|
);
|
||||||
|
$recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0;
|
||||||
|
|
||||||
|
// Total data set length
|
||||||
|
$resTotalLength = self::sql_exec( $db, $bindings,
|
||||||
|
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||||
|
FROM `$table` AS `$tablesAS`
|
||||||
|
$join
|
||||||
|
$join_filter
|
||||||
|
$where"
|
||||||
|
);
|
||||||
|
$recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Output
|
||||||
|
*/
|
||||||
|
return array(
|
||||||
|
"draw" => isset ( $request['draw'] ) ?
|
||||||
|
intval( $request['draw'] ) :
|
||||||
|
0,
|
||||||
|
"recordsTotal" => intval( $recordsTotal ),
|
||||||
|
"recordsFiltered" => intval( $recordsFiltered ),
|
||||||
|
"data" => self::data_output( $columns, $data )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the database
|
||||||
|
*
|
||||||
|
* @param array $sql_details SQL server connection details array, with the
|
||||||
|
* properties:
|
||||||
|
* * host - host name
|
||||||
|
* * db - database name
|
||||||
|
* * user - user name
|
||||||
|
* * pass - user password
|
||||||
|
* @return resource Database connection handle
|
||||||
|
*/
|
||||||
|
static function sql_connect ( $sql_details )
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = @new PDO(
|
||||||
|
"mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
|
||||||
|
$sql_details['user'],
|
||||||
|
$sql_details['pass'],
|
||||||
|
array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (PDOException $e) {
|
||||||
|
self::fatal(
|
||||||
|
"An error occurred while connecting to the database. ".
|
||||||
|
"The error reported by the server was: ".$e->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an SQL query on the database
|
||||||
|
*
|
||||||
|
* @param resource $db Database handler
|
||||||
|
* @param array $bindings Array of PDO binding values from bind() to be
|
||||||
|
* used for safely escaping strings. Note that this can be given as the
|
||||||
|
* SQL query string if no bindings are required.
|
||||||
|
* @param string $sql SQL query to execute.
|
||||||
|
* @return array Result from the query (all rows)
|
||||||
|
*/
|
||||||
|
static function sql_exec ( $db, $bindings, $sql=null )
|
||||||
|
{
|
||||||
|
// Argument shifting
|
||||||
|
if ( $sql === null ) {
|
||||||
|
$sql = $bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare( $sql );
|
||||||
|
|
||||||
|
// Bind parameters
|
||||||
|
if ( is_array( $bindings ) ) {
|
||||||
|
for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
|
||||||
|
$binding = $bindings[$i];
|
||||||
|
$stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
try {
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
catch (PDOException $e) {
|
||||||
|
self::fatal( "An SQL error occurred: ".$e->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all
|
||||||
|
return $stmt->fetchAll( PDO::FETCH_BOTH );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* Internal methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw a fatal error.
|
||||||
|
*
|
||||||
|
* This writes out an error message in a JSON string which DataTables will
|
||||||
|
* see and show to the user in the browser.
|
||||||
|
*
|
||||||
|
* @param string $msg Message to send to the client
|
||||||
|
*/
|
||||||
|
static function fatal ( $msg )
|
||||||
|
{
|
||||||
|
echo json_encode( array(
|
||||||
|
"error" => $msg
|
||||||
|
) );
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a PDO binding key which can be used for escaping variables safely
|
||||||
|
* when executing a query with sql_exec()
|
||||||
|
*
|
||||||
|
* @param array &$a Array of bindings
|
||||||
|
* @param * $val Value to bind
|
||||||
|
* @param int $type PDO field type
|
||||||
|
* @return string Bound key to be used in the SQL where this parameter
|
||||||
|
* would be used.
|
||||||
|
*/
|
||||||
|
static function bind ( &$a, $val, $type )
|
||||||
|
{
|
||||||
|
$key = ':binding_'.count( $a );
|
||||||
|
|
||||||
|
$a[] = array(
|
||||||
|
'key' => $key,
|
||||||
|
'val' => $val,
|
||||||
|
'type' => $type
|
||||||
|
);
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function add_bindings(&$bindings, $vals)
|
||||||
|
{
|
||||||
|
foreach($vals['bindings'] as $key => $value) {
|
||||||
|
$bindings[] = array(
|
||||||
|
'key' => $key,
|
||||||
|
'val' => $value,
|
||||||
|
'type' => PDO::PARAM_STR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull a particular property from each assoc. array in a numeric array,
|
||||||
|
* returning and array of the property values from each item.
|
||||||
|
*
|
||||||
|
* @param array $a Array to get data from
|
||||||
|
* @param string $prop Property to read
|
||||||
|
* @return array Array of property values
|
||||||
|
*/
|
||||||
|
static function pluck ( $a, $prop )
|
||||||
|
{
|
||||||
|
$out = array();
|
||||||
|
|
||||||
|
for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
|
||||||
|
if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( $prop == 'db' && isset($a[$i]['dummy']) && $a[$i]['dummy'] === true ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//removing the $out array index confuses the filter method in doing proper binding,
|
||||||
|
//adding it ensures that the array data are mapped correctly
|
||||||
|
$out[$i] = $a[$i][$prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string from an array or a string
|
||||||
|
*
|
||||||
|
* @param array|string $a Array to join
|
||||||
|
* @param string $join Glue for the concatenation
|
||||||
|
* @return string Joined string
|
||||||
|
*/
|
||||||
|
static function _flatten ( $a, $join = ' AND ' )
|
||||||
|
{
|
||||||
|
if ( ! $a ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
else if ( $a && is_array($a) ) {
|
||||||
|
return implode( $join, $a );
|
||||||
|
}
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -126,6 +126,15 @@ $MAILCOW_APPS = array(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Logo max file size in bytes
|
||||||
|
$LOGO_LIMITS['max_size'] = 15 * 1024 * 1024; // 15MB
|
||||||
|
|
||||||
|
// Logo max width in pixels
|
||||||
|
$LOGO_LIMITS['max_width'] = 1920;
|
||||||
|
|
||||||
|
// Logo max height in pixels
|
||||||
|
$LOGO_LIMITS['max_height'] = 1920;
|
||||||
|
|
||||||
// Rows until pagination begins
|
// Rows until pagination begins
|
||||||
$PAGINATION_SIZE = 25;
|
$PAGINATION_SIZE = 25;
|
||||||
|
|
||||||
|
@ -435,7 +435,7 @@ jQuery(function($){
|
|||||||
var table = $('#domain_table').DataTable({
|
var table = $('#domain_table').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: true,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
pageLength: pagination_size,
|
pageLength: pagination_size,
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
@ -447,9 +447,9 @@ jQuery(function($){
|
|||||||
},
|
},
|
||||||
ajax: {
|
ajax: {
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: "/api/v1/get/domain/all",
|
url: "/api/v1/get/domain/datatables",
|
||||||
dataSrc: function(json){
|
dataSrc: function(json){
|
||||||
$.each(json, function(i, item) {
|
$.each(json.data, function(i, item) {
|
||||||
item.domain_name = escapeHtml(item.domain_name);
|
item.domain_name = escapeHtml(item.domain_name);
|
||||||
|
|
||||||
item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain;
|
item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain;
|
||||||
@ -498,7 +498,7 @@ jQuery(function($){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return json;
|
return json.data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
@ -528,17 +528,20 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.aliases,
|
title: lang.aliases,
|
||||||
data: 'aliases',
|
data: 'aliases',
|
||||||
|
searchable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.mailboxes,
|
title: lang.mailboxes,
|
||||||
data: 'mailboxes',
|
data: 'mailboxes',
|
||||||
|
searchable: false,
|
||||||
responsivePriority: 4,
|
responsivePriority: 4,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.domain_quota,
|
title: lang.domain_quota,
|
||||||
data: 'quota',
|
data: 'quota',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
render: function (data, type) {
|
render: function (data, type) {
|
||||||
data = data.split("/");
|
data = data.split("/");
|
||||||
@ -548,6 +551,7 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.stats,
|
title: lang.stats,
|
||||||
data: 'stats',
|
data: 'stats',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
render: function (data, type) {
|
render: function (data, type) {
|
||||||
data = data.split("/");
|
data = data.split("/");
|
||||||
@ -557,53 +561,67 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.mailbox_defquota,
|
title: lang.mailbox_defquota,
|
||||||
data: 'def_quota_for_mbox',
|
data: 'def_quota_for_mbox',
|
||||||
|
searchable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.mailbox_quota,
|
title: lang.mailbox_quota,
|
||||||
data: 'max_quota_for_mbox',
|
data: 'max_quota_for_mbox',
|
||||||
|
searchable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'RL',
|
title: 'RL',
|
||||||
data: 'rl',
|
data: 'rl',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.backup_mx,
|
title: lang.backup_mx,
|
||||||
data: 'backupmx',
|
data: 'backupmx',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
redner: function (data, type){
|
render: function (data, type){
|
||||||
return 1==value ? '<i class="bi bi-check-lg"></i>' : 0==value && '<i class="bi bi-x-lg"></i>';
|
return 1==data ? '<i class="bi bi-check-lg"></i>' : 0==data && '<i class="bi bi-x-lg"></i>';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.domain_admins,
|
title: lang.domain_admins,
|
||||||
data: 'domain_admins',
|
data: 'domain_admins',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
className: 'none'
|
className: 'none'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.created_on,
|
title: lang.created_on,
|
||||||
data: 'created',
|
data: 'created',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
className: 'none'
|
className: 'none'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.last_modified,
|
title: lang.last_modified,
|
||||||
data: 'modified',
|
data: 'modified',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
className: 'none'
|
className: 'none'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
data: 'tags',
|
data: 'tags',
|
||||||
|
searchable: true,
|
||||||
|
orderable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
className: 'none'
|
className: 'none'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.active,
|
title: lang.active,
|
||||||
data: 'active',
|
data: 'active',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 6,
|
responsivePriority: 6,
|
||||||
render: function (data, type) {
|
render: function (data, type) {
|
||||||
@ -613,6 +631,8 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.action,
|
title: lang.action,
|
||||||
data: 'action',
|
data: 'action',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||||
responsivePriority: 5,
|
responsivePriority: 5,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
@ -844,7 +864,7 @@ jQuery(function($){
|
|||||||
var table = $('#mailbox_table').DataTable({
|
var table = $('#mailbox_table').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: true,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
pageLength: pagination_size,
|
pageLength: pagination_size,
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
@ -853,13 +873,12 @@ jQuery(function($){
|
|||||||
language: lang_datatables,
|
language: lang_datatables,
|
||||||
initComplete: function(settings, json){
|
initComplete: function(settings, json){
|
||||||
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
|
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
|
||||||
filterByDomain(json, 8, table);
|
|
||||||
},
|
},
|
||||||
ajax: {
|
ajax: {
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: "/api/v1/get/mailbox/reduced",
|
url: "/api/v1/get/mailbox/datatables",
|
||||||
dataSrc: function(json){
|
dataSrc: function(json){
|
||||||
$.each(json, function (i, item) {
|
$.each(json.data, function (i, item) {
|
||||||
item.quota = {
|
item.quota = {
|
||||||
sortBy: item.quota_used,
|
sortBy: item.quota_used,
|
||||||
value: item.quota
|
value: item.quota
|
||||||
@ -945,7 +964,7 @@ jQuery(function($){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return json;
|
return json.data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
@ -975,13 +994,14 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.domain_quota,
|
title: lang.domain_quota,
|
||||||
data: 'quota.value',
|
data: 'quota.value',
|
||||||
|
searchable: false,
|
||||||
responsivePriority: 8,
|
responsivePriority: 8,
|
||||||
defaultContent: '',
|
defaultContent: ''
|
||||||
orderData: 23
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.last_mail_login,
|
title: lang.last_mail_login,
|
||||||
data: 'last_mail_login',
|
data: 'last_mail_login',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 7,
|
responsivePriority: 7,
|
||||||
render: function (data, type) {
|
render: function (data, type) {
|
||||||
@ -994,15 +1014,16 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.last_pw_change,
|
title: lang.last_pw_change,
|
||||||
data: 'last_pw_change',
|
data: 'last_pw_change',
|
||||||
|
searchable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.in_use,
|
title: lang.in_use,
|
||||||
data: 'in_use.value',
|
data: 'in_use.value',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 9,
|
responsivePriority: 9,
|
||||||
className: 'dt-data-w100',
|
className: 'dt-data-w100'
|
||||||
orderData: 24
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.fname,
|
title: lang.fname,
|
||||||
@ -1067,6 +1088,7 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.msg_num,
|
title: lang.msg_num,
|
||||||
data: 'messages',
|
data: 'messages',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 5
|
responsivePriority: 5
|
||||||
},
|
},
|
||||||
@ -1085,12 +1107,14 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
data: 'tags',
|
data: 'tags',
|
||||||
|
searchable: true,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
className: 'none'
|
className: 'none'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.active,
|
title: lang.active,
|
||||||
data: 'active',
|
data: 'active',
|
||||||
|
searchable: false,
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 4,
|
responsivePriority: 4,
|
||||||
render: function (data, type) {
|
render: function (data, type) {
|
||||||
@ -1100,22 +1124,12 @@ jQuery(function($){
|
|||||||
{
|
{
|
||||||
title: lang.action,
|
title: lang.action,
|
||||||
data: 'action',
|
data: 'action',
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||||
responsivePriority: 6,
|
responsivePriority: 6,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
}
|
||||||
{
|
|
||||||
title: "",
|
|
||||||
data: 'quota.sortBy',
|
|
||||||
defaultContent: '',
|
|
||||||
className: "d-none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "",
|
|
||||||
data: 'in_use.sortBy',
|
|
||||||
defaultContent: '',
|
|
||||||
className: "d-none"
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ function api_log($_data) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = json_decode($value, true);
|
$value = json_decode($value, true);
|
||||||
if ($value) {
|
if ($value) {
|
||||||
if (is_array($value)) unset($value["csrf_token"]);
|
if (is_array($value)) unset($value["csrf_token"]);
|
||||||
foreach ($value as $key => &$val) {
|
foreach ($value as $key => &$val) {
|
||||||
@ -23,7 +23,7 @@ function api_log($_data) {
|
|||||||
$val = '*';
|
$val = '*';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$value = json_encode($value);
|
$value = json_encode($value);
|
||||||
}
|
}
|
||||||
$data_var[] = $data . "='" . $value . "'";
|
$data_var[] = $data . "='" . $value . "'";
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ function api_log($_data) {
|
|||||||
'msg' => 'Redis: '.$e
|
'msg' => 'Redis: '.$e
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['query'])) {
|
if (isset($_GET['query'])) {
|
||||||
@ -178,12 +178,12 @@ if (isset($_GET['query'])) {
|
|||||||
// parse post data
|
// parse post data
|
||||||
$post = trim(file_get_contents('php://input'));
|
$post = trim(file_get_contents('php://input'));
|
||||||
if ($post) $post = json_decode($post);
|
if ($post) $post = json_decode($post);
|
||||||
|
|
||||||
// process registration data from authenticator
|
// process registration data from authenticator
|
||||||
try {
|
try {
|
||||||
// decode base64 strings
|
// decode base64 strings
|
||||||
$clientDataJSON = base64_decode($post->clientDataJSON);
|
$clientDataJSON = base64_decode($post->clientDataJSON);
|
||||||
$attestationObject = base64_decode($post->attestationObject);
|
$attestationObject = base64_decode($post->attestationObject);
|
||||||
|
|
||||||
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
|
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
|
||||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
|
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
|
||||||
@ -250,7 +250,7 @@ if (isset($_GET['query'])) {
|
|||||||
default:
|
default:
|
||||||
process_add_return(mailbox('add', 'domain', $attr));
|
process_add_return(mailbox('add', 'domain', $attr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "resource":
|
case "resource":
|
||||||
process_add_return(mailbox('add', 'resource', $attr));
|
process_add_return(mailbox('add', 'resource', $attr));
|
||||||
@ -470,7 +470,7 @@ if (isset($_GET['query'])) {
|
|||||||
// false, if only internal is allowed
|
// false, if only internal is allowed
|
||||||
// null, if internal and cross-platform is allowed
|
// null, if internal and cross-platform is allowed
|
||||||
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
|
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
|
||||||
|
|
||||||
print(json_encode($createArgs));
|
print(json_encode($createArgs));
|
||||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||||
return;
|
return;
|
||||||
@ -533,9 +533,50 @@ if (isset($_GET['query'])) {
|
|||||||
|
|
||||||
case "domain":
|
case "domain":
|
||||||
switch ($object) {
|
switch ($object) {
|
||||||
|
case "datatables":
|
||||||
|
$table = ['domain', 'd'];
|
||||||
|
$primaryKey = 'domain';
|
||||||
|
$columns = [
|
||||||
|
['db' => 'domain', 'dt' => 2],
|
||||||
|
['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"],
|
||||||
|
['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
|
||||||
|
['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
|
||||||
|
['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"],
|
||||||
|
['db' => 'defquota', 'dt' => 7],
|
||||||
|
['db' => 'maxquota', 'dt' => 8],
|
||||||
|
['db' => 'backupmx', 'dt' => 10],
|
||||||
|
['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']],
|
||||||
|
['db' => 'active', 'dt' => 15],
|
||||||
|
];
|
||||||
|
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
|
||||||
|
global $pdo;
|
||||||
|
if($_SESSION['mailcow_cc_role'] === 'admin') {
|
||||||
|
$data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns);
|
||||||
|
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
|
||||||
|
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
|
||||||
|
'INNER JOIN domain_admins as da ON da.domain = d.domain',
|
||||||
|
[
|
||||||
|
'condition' => 'da.active = 1 and da.username = :username',
|
||||||
|
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['data'])) {
|
||||||
|
$domainsData = [];
|
||||||
|
foreach ($data['data'] as $domain) {
|
||||||
|
if ($details = mailbox('get', 'domain_details', $domain[2])) {
|
||||||
|
$domainsData[] = $details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$data['data'] = $domainsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_get_return($data);
|
||||||
|
break;
|
||||||
case "all":
|
case "all":
|
||||||
$tags = null;
|
$tags = null;
|
||||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||||
$tags = explode(',', $_GET['tags']);
|
$tags = explode(',', $_GET['tags']);
|
||||||
|
|
||||||
$domains = mailbox('get', 'domains', null, $tags);
|
$domains = mailbox('get', 'domains', null, $tags);
|
||||||
@ -1021,10 +1062,49 @@ if (isset($_GET['query'])) {
|
|||||||
break;
|
break;
|
||||||
case "mailbox":
|
case "mailbox":
|
||||||
switch ($object) {
|
switch ($object) {
|
||||||
|
case "datatables":
|
||||||
|
$table = ['mailbox', 'm'];
|
||||||
|
$primaryKey = 'username';
|
||||||
|
$columns = [
|
||||||
|
['db' => 'username', 'dt' => 2],
|
||||||
|
['db' => 'quota', 'dt' => 3],
|
||||||
|
['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"],
|
||||||
|
['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"],
|
||||||
|
['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"],
|
||||||
|
['db' => 'messages', 'dt' => 17, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
|
||||||
|
['db' => 'tags', 'dt' => 20, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
|
||||||
|
['db' => 'active', 'dt' => 21]
|
||||||
|
];
|
||||||
|
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
|
||||||
|
global $pdo;
|
||||||
|
if($_SESSION['mailcow_cc_role'] === 'admin') {
|
||||||
|
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)");
|
||||||
|
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
|
||||||
|
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
|
||||||
|
'INNER JOIN domain_admins as da ON da.domain = m.domain',
|
||||||
|
[
|
||||||
|
'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username",
|
||||||
|
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['data'])) {
|
||||||
|
$mailboxData = [];
|
||||||
|
foreach ($data['data'] as $mailbox) {
|
||||||
|
if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) {
|
||||||
|
$mailboxData[] = $details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$data['data'] = $mailboxData;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_get_return($data);
|
||||||
|
break;
|
||||||
case "all":
|
case "all":
|
||||||
case "reduced":
|
case "reduced":
|
||||||
$tags = null;
|
$tags = null;
|
||||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||||
$tags = explode(',', $_GET['tags']);
|
$tags = explode(',', $_GET['tags']);
|
||||||
|
|
||||||
if (empty($extra)) $domains = mailbox('get', 'domains');
|
if (empty($extra)) $domains = mailbox('get', 'domains');
|
||||||
@ -1058,7 +1138,7 @@ if (isset($_GET['query'])) {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$tags = null;
|
$tags = null;
|
||||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||||
$tags = explode(',', $_GET['tags']);
|
$tags = explode(',', $_GET['tags']);
|
||||||
|
|
||||||
if ($tags === null) {
|
if ($tags === null) {
|
||||||
@ -1068,7 +1148,7 @@ if (isset($_GET['query'])) {
|
|||||||
$mailboxes = mailbox('get', 'mailboxes', $object, $tags);
|
$mailboxes = mailbox('get', 'mailboxes', $object, $tags);
|
||||||
if (is_array($mailboxes)) {
|
if (is_array($mailboxes)) {
|
||||||
foreach ($mailboxes as $mailbox) {
|
foreach ($mailboxes as $mailbox) {
|
||||||
if ($details = mailbox('get', 'mailbox_details', $mailbox))
|
if ($details = mailbox('get', 'mailbox_details', $mailbox))
|
||||||
$data[] = $details;
|
$data[] = $details;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1571,15 +1651,15 @@ if (isset($_GET['query'])) {
|
|||||||
'solr_size' => $solr_size,
|
'solr_size' => $solr_size,
|
||||||
'solr_documents' => $solr_documents
|
'solr_documents' => $solr_documents
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case "host":
|
case "host":
|
||||||
if (!$extra){
|
if (!$extra){
|
||||||
$stats = docker("host_stats");
|
$stats = docker("host_stats");
|
||||||
echo json_encode($stats);
|
echo json_encode($stats);
|
||||||
}
|
}
|
||||||
else if ($extra == "ip") {
|
else if ($extra == "ip") {
|
||||||
// get public ips
|
// get public ips
|
||||||
|
|
||||||
$curl = curl_init();
|
$curl = curl_init();
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||||
curl_setopt($curl, CURLOPT_POST, 0);
|
curl_setopt($curl, CURLOPT_POST, 0);
|
||||||
@ -2003,7 +2083,7 @@ if (isset($_GET['query'])) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($_SESSION['mailcow_cc_api'] === true) {
|
if (array_key_exists('mailcow_cc_api', $_SESSION) && $_SESSION['mailcow_cc_api'] === true) {
|
||||||
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
|
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
|
||||||
unset($_SESSION['return']);
|
unset($_SESSION['return']);
|
||||||
}
|
}
|
||||||
|
@ -394,7 +394,9 @@
|
|||||||
"goto_invalid": "Ziel-Adresse %s ist ungültig",
|
"goto_invalid": "Ziel-Adresse %s ist ungültig",
|
||||||
"ham_learn_error": "Ham Lernfehler: %s",
|
"ham_learn_error": "Ham Lernfehler: %s",
|
||||||
"imagick_exception": "Fataler Bildverarbeitungsfehler",
|
"imagick_exception": "Fataler Bildverarbeitungsfehler",
|
||||||
|
"img_dimensions_exceeded": "Grafik überschreitet die maximale Bildgröße",
|
||||||
"img_invalid": "Grafik konnte nicht validiert werden",
|
"img_invalid": "Grafik konnte nicht validiert werden",
|
||||||
|
"img_size_exceeded": "Grafik überschreitet die maximale Dateigröße",
|
||||||
"img_tmp_missing": "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen.",
|
"img_tmp_missing": "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen.",
|
||||||
"invalid_bcc_map_type": "Ungültiger BCC-Map-Typ",
|
"invalid_bcc_map_type": "Ungültiger BCC-Map-Typ",
|
||||||
"invalid_destination": "Ziel-Format \"%s\" ist ungültig",
|
"invalid_destination": "Ziel-Format \"%s\" ist ungültig",
|
||||||
@ -588,10 +590,19 @@
|
|||||||
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
||||||
"domain": "Domain bearbeiten",
|
"domain": "Domain bearbeiten",
|
||||||
"domain_admin": "Domain-Administrator bearbeiten",
|
"domain_admin": "Domain-Administrator bearbeiten",
|
||||||
"domain_footer": "Domain wide footer",
|
"domain_footer": "Domänenweite Fußzeile",
|
||||||
"domain_footer_html": "HTML footer",
|
"domain_footer_html": "Fußzeile im HTML Format",
|
||||||
"domain_footer_info": "Domain wide footer werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.<br>Die folgenden Variablen können für den Footer benutzt werden:",
|
"domain_footer_info": "Domänenweite Footer (Domain wide footer) werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.<br>Die folgenden Variablen können für die Fußzeile benutzt werden:",
|
||||||
"domain_footer_plain": "PLAIN footer",
|
"domain_footer_info_vars": {
|
||||||
|
"auth_user": "{= auth_user =} - Angemeldeter Benutzername vom MTA",
|
||||||
|
"from_user": "{= from_user =} - Absender Teil der E-Mail z.B. für \"moo@mailcow.tld\" wird \"moo\" zurückgeben.",
|
||||||
|
"from_name": "{= from_name =} - Namen des Absenders z.B. für \"Mailcow <moo@mailcow.tld>\", wird \"Mailcow\" zurückgegeben.",
|
||||||
|
"from_addr": "{= from_addr =} - Adresse des Absenders.",
|
||||||
|
"from_domain": "{= from_domain =} - Domain des Absenders",
|
||||||
|
"custom": "{= foo =} - Wenn die Mailbox das benutzerdefinierte Attribut \"foo\" mit dem Wert \"bar\" hat, wird \"bar\" zurückgegeben."
|
||||||
|
},
|
||||||
|
"domain_footer_plain": "Fußzeile im PLAIN Format",
|
||||||
|
"domain_footer_skip_replies": "Ignoriere Footer bei Antwort E-Mails",
|
||||||
"domain_quota": "Domain Speicherplatz gesamt (MiB)",
|
"domain_quota": "Domain Speicherplatz gesamt (MiB)",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
||||||
@ -680,11 +691,7 @@
|
|||||||
"unchanged_if_empty": "Unverändert, wenn leer",
|
"unchanged_if_empty": "Unverändert, wenn leer",
|
||||||
"username": "Benutzername",
|
"username": "Benutzername",
|
||||||
"validate_save": "Validieren und speichern",
|
"validate_save": "Validieren und speichern",
|
||||||
"pushover_sound": "Ton",
|
"pushover_sound": "Ton"
|
||||||
"domain_footer_info_vars": {
|
|
||||||
"auth_user": "{= auth_user =} - Angemeldeter Benutzername vom MTA",
|
|
||||||
"from_user": "{= from_user =} - Von Teil des Benutzers z.B. \"moo@mailcow.tld\" wird \"moo\" zurückgeben."
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
@ -1088,6 +1095,7 @@
|
|||||||
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
|
"verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
"authenticators": "Authentikatoren",
|
||||||
"api_register": "%s verwendet die Yubico-Cloud-API. Ein API-Key für den Yubico-Stick kann <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">hier</a> bezogen werden.",
|
"api_register": "%s verwendet die Yubico-Cloud-API. Ein API-Key für den Yubico-Stick kann <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">hier</a> bezogen werden.",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
"confirm_totp_token": "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens",
|
"confirm_totp_token": "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens",
|
||||||
|
@ -394,7 +394,9 @@
|
|||||||
"goto_invalid": "Goto address %s is invalid",
|
"goto_invalid": "Goto address %s is invalid",
|
||||||
"ham_learn_error": "Ham learn error: %s",
|
"ham_learn_error": "Ham learn error: %s",
|
||||||
"imagick_exception": "Error: Imagick exception while reading image",
|
"imagick_exception": "Error: Imagick exception while reading image",
|
||||||
|
"img_dimensions_exceeded": "Image exceeds the maximum image size",
|
||||||
"img_invalid": "Cannot validate image file",
|
"img_invalid": "Cannot validate image file",
|
||||||
|
"img_size_exceeded": "Image exceeds the maximum file size",
|
||||||
"img_tmp_missing": "Cannot validate image file: Temporary file not found",
|
"img_tmp_missing": "Cannot validate image file: Temporary file not found",
|
||||||
"invalid_bcc_map_type": "Invalid BCC map type",
|
"invalid_bcc_map_type": "Invalid BCC map type",
|
||||||
"invalid_destination": "Destination format \"%s\" is invalid",
|
"invalid_destination": "Destination format \"%s\" is invalid",
|
||||||
@ -600,6 +602,7 @@
|
|||||||
"custom": "{= foo =} - If mailbox has the custom attribute \"foo\" with value \"bar\" it returns \"bar\""
|
"custom": "{= foo =} - If mailbox has the custom attribute \"foo\" with value \"bar\" it returns \"bar\""
|
||||||
},
|
},
|
||||||
"domain_footer_plain": "PLAIN footer",
|
"domain_footer_plain": "PLAIN footer",
|
||||||
|
"domain_footer_skip_replies": "Ignore footer on reply e-mails",
|
||||||
"domain_quota": "Domain quota",
|
"domain_quota": "Domain quota",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
||||||
@ -1099,6 +1102,7 @@
|
|||||||
"verified_yotp_login": "Verified Yubico OTP login"
|
"verified_yotp_login": "Verified Yubico OTP login"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
|
"authenticators": "Authenticators",
|
||||||
"api_register": "%s uses the Yubico Cloud API. Please get an API key for your key <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
|
"api_register": "%s uses the Yubico Cloud API. Please get an API key for your key <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"confirm_totp_token": "Please confirm your changes by entering the generated token",
|
"confirm_totp_token": "Please confirm your changes by entering the generated token",
|
||||||
|
@ -114,7 +114,9 @@
|
|||||||
<li class="logged-in-as nav-item"><a href="#" onclick="logout.submit()" class="nav-link"><b class="username-lia">{{ mailcow_cc_username }} <span class="text-info">({{ dual_login.username }})</span> </b><i class="bi bi-power ms-2"></i></a></li>
|
<li class="logged-in-as nav-item"><a href="#" onclick="logout.submit()" class="nav-link"><b class="username-lia">{{ mailcow_cc_username }} <span class="text-info">({{ dual_login.username }})</span> </b><i class="bi bi-power ms-2"></i></a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not is_master %}
|
{% if not is_master %}
|
||||||
<li class="text-warning slave-info nav-item">[ slave ]</li>
|
<div class="nav-link form-check form-switch my-auto d-flex align-items-center">
|
||||||
|
<li class="slave-info">[ slave ]</li>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
|
@ -305,6 +305,14 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-4">
|
||||||
|
<label class="control-label col-sm-2" for="domain_footer_skip_replies">{{ lang.edit.domain_footer_skip_replies }}:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-check">
|
||||||
|
<label><input type="checkbox" class="form-check-input" value="1" id="domain_footer_skip_replies" name="skip_replies"{% if domain_footer.skip_replies == '1' %} checked{% endif %}></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="domain_footer_html">{{ lang.edit.domain_footer_html }}:</label>
|
<label class="control-label col-sm-2" for="domain_footer_html">{{ lang.edit.domain_footer_html }}:</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
@ -155,7 +155,7 @@
|
|||||||
|
|
||||||
{% if pending_tfa_authmechs["totp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
{% if pending_tfa_authmechs["totp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if pending_tfa_authmechs["totp"] %}active{% endif %}" href="#tfa_tab_totp" data-bs-toggle="tab" id="pending_tfa_tab_totp"><i class="bi bi-clock-history"></i> Time based OTP</a>
|
<a class="nav-link {% if pending_tfa_authmechs["totp"] %}active{% endif %}" href="#tfa_tab_totp" data-bs-toggle="tab" id="pending_tfa_tab_totp"><i class="bi bi-clock-history"></i> Time-based OTP</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -173,7 +173,7 @@
|
|||||||
<form role="form" method="post" id="webauthn_auth_form">
|
<form role="form" method="post" id="webauthn_auth_form">
|
||||||
<legend class="mt-2 mb-2">
|
<legend class="mt-2 mb-2">
|
||||||
<i class="bi bi-shield-fill-check"></i>
|
<i class="bi bi-shield-fill-check"></i>
|
||||||
Authenticators
|
{{ lang.tfa.authenticators }}
|
||||||
<hr />
|
<hr />
|
||||||
</legend>
|
</legend>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
@ -216,7 +216,7 @@
|
|||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
<legend class="mt-2 mb-2">
|
<legend class="mt-2 mb-2">
|
||||||
<i class="bi bi-shield-fill-check"></i>
|
<i class="bi bi-shield-fill-check"></i>
|
||||||
Authenticate
|
{{ lang.tfa.authenticators }}
|
||||||
<hr />
|
<hr />
|
||||||
</legend>
|
</legend>
|
||||||
<div class="collapse show pending-tfa-collapse" id="collapseYubiTFA">
|
<div class="collapse show pending-tfa-collapse" id="collapseYubiTFA">
|
||||||
@ -244,7 +244,7 @@
|
|||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
<legend class="mt-2 mb-2">
|
<legend class="mt-2 mb-2">
|
||||||
<i class="bi bi-shield-fill-check"></i>
|
<i class="bi bi-shield-fill-check"></i>
|
||||||
Authenticators
|
{{ lang.tfa.authenticators }}
|
||||||
<hr />
|
<hr />
|
||||||
</legend>
|
</legend>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
|
@ -2,7 +2,7 @@ version: '2.1'
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
image: mailcow/unbound:1.18
|
image: mailcow/unbound:1.19
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
volumes:
|
volumes:
|
||||||
@ -58,7 +58,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
|
|
||||||
clamd-mailcow:
|
clamd-mailcow:
|
||||||
image: mailcow/clamd:1.63
|
image: mailcow/clamd:1.64
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
@ -77,7 +77,7 @@ services:
|
|||||||
- clamd
|
- clamd
|
||||||
|
|
||||||
rspamd-mailcow:
|
rspamd-mailcow:
|
||||||
image: mailcow/rspamd:1.94
|
image: mailcow/rspamd:1.95
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
@ -107,7 +107,7 @@ services:
|
|||||||
- rspamd
|
- rspamd
|
||||||
|
|
||||||
php-fpm-mailcow:
|
php-fpm-mailcow:
|
||||||
image: mailcow/phpfpm:1.85
|
image: mailcow/phpfpm:1.86
|
||||||
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-mailcow
|
- redis-mailcow
|
||||||
@ -171,7 +171,7 @@ services:
|
|||||||
- phpfpm
|
- phpfpm
|
||||||
|
|
||||||
sogo-mailcow:
|
sogo-mailcow:
|
||||||
image: mailcow/sogo:1.120
|
image: mailcow/sogo:1.121
|
||||||
environment:
|
environment:
|
||||||
- DBNAME=${DBNAME}
|
- DBNAME=${DBNAME}
|
||||||
- DBUSER=${DBUSER}
|
- DBUSER=${DBUSER}
|
||||||
@ -203,7 +203,7 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
ofelia.enabled: "true"
|
ofelia.enabled: "true"
|
||||||
ofelia.job-exec.sogo_sessions.schedule: "@every 1m"
|
ofelia.job-exec.sogo_sessions.schedule: "@every 1m"
|
||||||
ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\""
|
ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool -v expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\""
|
||||||
ofelia.job-exec.sogo_ealarms.schedule: "@every 1m"
|
ofelia.job-exec.sogo_ealarms.schedule: "@every 1m"
|
||||||
ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds || exit 0\""
|
ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds || exit 0\""
|
||||||
ofelia.job-exec.sogo_eautoreply.schedule: "@every 5m"
|
ofelia.job-exec.sogo_eautoreply.schedule: "@every 5m"
|
||||||
@ -218,7 +218,7 @@ services:
|
|||||||
- sogo
|
- sogo
|
||||||
|
|
||||||
dovecot-mailcow:
|
dovecot-mailcow:
|
||||||
image: mailcow/dovecot:1.26
|
image: mailcow/dovecot:1.27
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql-mailcow
|
- mysql-mailcow
|
||||||
dns:
|
dns:
|
||||||
@ -298,7 +298,7 @@ services:
|
|||||||
- dovecot
|
- dovecot
|
||||||
|
|
||||||
postfix-mailcow:
|
postfix-mailcow:
|
||||||
image: mailcow/postfix:1.73
|
image: mailcow/postfix:1.74
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql-mailcow:
|
mysql-mailcow:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
@ -398,7 +398,7 @@ services:
|
|||||||
condition: service_started
|
condition: service_started
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
image: mailcow/acme:1.85
|
image: mailcow/acme:1.86
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
@ -434,7 +434,7 @@ services:
|
|||||||
- acme
|
- acme
|
||||||
|
|
||||||
netfilter-mailcow:
|
netfilter-mailcow:
|
||||||
image: mailcow/netfilter:1.54
|
image: mailcow/netfilter:1.55
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
@ -457,7 +457,7 @@ services:
|
|||||||
- /lib/modules:/lib/modules:ro
|
- /lib/modules:/lib/modules:ro
|
||||||
|
|
||||||
watchdog-mailcow:
|
watchdog-mailcow:
|
||||||
image: mailcow/watchdog:2.00
|
image: mailcow/watchdog:2.01
|
||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
tmpfs:
|
tmpfs:
|
||||||
@ -529,7 +529,7 @@ services:
|
|||||||
- watchdog
|
- watchdog
|
||||||
|
|
||||||
dockerapi-mailcow:
|
dockerapi-mailcow:
|
||||||
image: mailcow/dockerapi:2.06
|
image: mailcow/dockerapi:2.07
|
||||||
security_opt:
|
security_opt:
|
||||||
- label=disable
|
- label=disable
|
||||||
restart: always
|
restart: always
|
||||||
@ -547,8 +547,10 @@ services:
|
|||||||
aliases:
|
aliases:
|
||||||
- dockerapi
|
- dockerapi
|
||||||
|
|
||||||
|
|
||||||
|
##### Will be removed soon #####
|
||||||
solr-mailcow:
|
solr-mailcow:
|
||||||
image: mailcow/solr:1.8.1
|
image: mailcow/solr:1.8.2
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- solr-vol-1:/opt/solr/server/solr/dovecot-fts/data
|
- solr-vol-1:/opt/solr/server/solr/dovecot-fts/data
|
||||||
@ -562,9 +564,10 @@ services:
|
|||||||
mailcow-network:
|
mailcow-network:
|
||||||
aliases:
|
aliases:
|
||||||
- solr
|
- solr
|
||||||
|
################################
|
||||||
|
|
||||||
olefy-mailcow:
|
olefy-mailcow:
|
||||||
image: mailcow/olefy:1.11
|
image: mailcow/olefy:1.12
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
|
@ -34,7 +34,7 @@ if docker compose > /dev/null 2>&1; then
|
|||||||
echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m"
|
echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif docker-compose > /dev/null 2>&1; then
|
elif docker-compose > /dev/null 2>&1; then
|
||||||
@ -47,14 +47,14 @@ elif docker-compose > /dev/null 2>&1; then
|
|||||||
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
PATH=${PATH}:/opt/bin
|
PATH=${PATH}:/opt/bin
|
||||||
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
||||||
|
LOCAL_ARCH=$(uname -m)
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
|
|
||||||
echo
|
echo
|
||||||
@ -148,6 +149,9 @@ else
|
|||||||
echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m"
|
echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
REMOTE_ARCH=$(ssh -o StrictHostKeyChecking=no -i "${REMOTE_SSH_KEY}" ${REMOTE_SSH_HOST} -p ${REMOTE_SSH_PORT} "uname -m")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||||
@ -164,6 +168,17 @@ echo -e "\033[1mFound compose project name ${CMPS_PRJ} for ${MAILCOW_HOSTNAME}\0
|
|||||||
echo -e "\033[1mFound SQL ${SQLIMAGE}\033[0m"
|
echo -e "\033[1mFound SQL ${SQLIMAGE}\033[0m"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
# Print Message if Local Arch and Remote Arch is not the same
|
||||||
|
if [[ $LOCAL_ARCH != $REMOTE_ARCH ]]; then
|
||||||
|
echo
|
||||||
|
echo -e "\e[1;33m!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!\e[0m"
|
||||||
|
echo -e "\e[3;33mDetected Architecture missmatch from source to destination...\e[0m"
|
||||||
|
echo -e "\e[3;33mYour backup is transferred but some volumes might be skipped!\e[0m"
|
||||||
|
echo -e "\e[1;33m!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!\e[0m"
|
||||||
|
echo
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
# Make sure destination exists, rsync can fail under some circumstances
|
# Make sure destination exists, rsync can fail under some circumstances
|
||||||
echo -e "\033[1mPreparing remote...\033[0m"
|
echo -e "\033[1mPreparing remote...\033[0m"
|
||||||
if ! ssh -o StrictHostKeyChecking=no \
|
if ! ssh -o StrictHostKeyChecking=no \
|
||||||
@ -248,8 +263,21 @@ for vol in $(docker volume ls -qf name="${CMPS_PRJ}"); do
|
|||||||
# Cleanup
|
# Cleanup
|
||||||
rm -rf "${SCRIPT_DIR}/../_tmp_mariabackup/"
|
rm -rf "${SCRIPT_DIR}/../_tmp_mariabackup/"
|
||||||
|
|
||||||
else
|
elif [[ "${vol}" =~ "rspamd-vol-1" ]]; then
|
||||||
|
# Exclude rspamd-vol-1 if the Architectures are not the same on source and destination due to compatibility issues.
|
||||||
|
if [[ $LOCAL_ARCH == $REMOTE_ARCH ]]; then
|
||||||
|
echo -e "\033[1mSynchronizing ${vol} from local ${mountpoint}...\033[0m"
|
||||||
|
rsync --delete --info=progress2 -aH -e "ssh -o StrictHostKeyChecking=no \
|
||||||
|
-i \"${REMOTE_SSH_KEY}\" \
|
||||||
|
-p ${REMOTE_SSH_PORT}" \
|
||||||
|
"${mountpoint}/" root@${REMOTE_SSH_HOST}:"${mountpoint}"
|
||||||
|
else
|
||||||
|
echo -e "\e[1;31mSkipping ${vol} from local maschine due to incompatiblity between different architecture...\e[0m"
|
||||||
|
sleep 2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
echo -e "\033[1mSynchronizing ${vol} from local ${mountpoint}...\033[0m"
|
echo -e "\033[1mSynchronizing ${vol} from local ${mountpoint}...\033[0m"
|
||||||
rsync --delete --info=progress2 -aH -e "ssh -o StrictHostKeyChecking=no \
|
rsync --delete --info=progress2 -aH -e "ssh -o StrictHostKeyChecking=no \
|
||||||
-i \"${REMOTE_SSH_KEY}\" \
|
-i \"${REMOTE_SSH_KEY}\" \
|
||||||
|
@ -53,6 +53,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||||||
COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
|
COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
|
||||||
ENV_FILE=${SCRIPT_DIR}/../.env
|
ENV_FILE=${SCRIPT_DIR}/../.env
|
||||||
THREADS=$(echo ${THREADS:-1})
|
THREADS=$(echo ${THREADS:-1})
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
if ! [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then
|
if ! [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then
|
||||||
echo "Thread input is not a number!"
|
echo "Thread input is not a number!"
|
||||||
@ -96,6 +97,7 @@ function backup() {
|
|||||||
mkdir -p "${BACKUP_LOCATION}/mailcow-${DATE}"
|
mkdir -p "${BACKUP_LOCATION}/mailcow-${DATE}"
|
||||||
chmod 755 "${BACKUP_LOCATION}/mailcow-${DATE}"
|
chmod 755 "${BACKUP_LOCATION}/mailcow-${DATE}"
|
||||||
cp "${SCRIPT_DIR}/../mailcow.conf" "${BACKUP_LOCATION}/mailcow-${DATE}"
|
cp "${SCRIPT_DIR}/../mailcow.conf" "${BACKUP_LOCATION}/mailcow-${DATE}"
|
||||||
|
touch "${BACKUP_LOCATION}/mailcow-${DATE}/.$ARCH"
|
||||||
for bin in docker; do
|
for bin in docker; do
|
||||||
if [[ -z $(which ${bin}) ]]; then
|
if [[ -z $(which ${bin}) ]]; then
|
||||||
>&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m"
|
>&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m"
|
||||||
@ -231,12 +233,29 @@ function restore() {
|
|||||||
docker start $(docker ps -aqf name=dovecot-mailcow)
|
docker start $(docker ps -aqf name=dovecot-mailcow)
|
||||||
;;
|
;;
|
||||||
rspamd)
|
rspamd)
|
||||||
docker stop $(docker ps -qf name=rspamd-mailcow)
|
if [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
|
||||||
docker run -it --name mailcow-backup --rm \
|
echo -e "\e[33mCould not find a architecture signature of the loaded backup... Maybe the backup was done before the multiarch update?"
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
sleep 2
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
echo -e "Continuing anyhow. If rspamd is crashing opon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m"
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
sleep 2
|
||||||
docker start $(docker ps -aqf name=rspamd-mailcow)
|
docker stop $(docker ps -qf name=rspamd-mailcow)
|
||||||
|
docker run -it --name mailcow-backup --rm \
|
||||||
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
||||||
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
||||||
|
docker start $(docker ps -aqf name=rspamd-mailcow)
|
||||||
|
elif [[ $ARCH != $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then
|
||||||
|
echo -e "\e[31mThe Architecture of the backed up mailcow OS is different then your restoring mailcow OS..."
|
||||||
|
sleep 2
|
||||||
|
echo -e "Skipping rspamd due to compatibility issues!\e[0m"
|
||||||
|
else
|
||||||
|
docker stop $(docker ps -qf name=rspamd-mailcow)
|
||||||
|
docker run -it --name mailcow-backup --rm \
|
||||||
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
||||||
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
||||||
|
docker start $(docker ps -aqf name=rspamd-mailcow)
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
postfix)
|
postfix)
|
||||||
docker stop $(docker ps -qf name=postfix-mailcow)
|
docker stop $(docker ps -qf name=postfix-mailcow)
|
||||||
@ -360,9 +379,17 @@ elif [[ ${1} == "restore" ]]; then
|
|||||||
FILE_SELECTION[${i}]="redis"
|
FILE_SELECTION[${i}]="redis"
|
||||||
((i++))
|
((i++))
|
||||||
elif [[ ${file} =~ rspamd ]]; then
|
elif [[ ${file} =~ rspamd ]]; then
|
||||||
echo "[ ${i} ] - Rspamd data"
|
if [[ $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
|
||||||
FILE_SELECTION[${i}]="rspamd"
|
echo "[ ${i} ] - Rspamd data (unkown Arch detected, restore with caution!)"
|
||||||
((i++))
|
FILE_SELECTION[${i}]="rspamd"
|
||||||
|
((i++))
|
||||||
|
elif [[ $ARCH != $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then
|
||||||
|
echo -e "\e[31m[ NaN ] - Rspamd data (incompatible Arch, cannot restore it)\e[0m"
|
||||||
|
else
|
||||||
|
echo "[ ${i} ] - Rspamd data"
|
||||||
|
FILE_SELECTION[${i}]="rspamd"
|
||||||
|
((i++))
|
||||||
|
fi
|
||||||
elif [[ ${file} =~ postfix ]]; then
|
elif [[ ${file} =~ postfix ]]; then
|
||||||
echo "[ ${i} ] - Postfix data"
|
echo "[ ${i} ] - Postfix data"
|
||||||
FILE_SELECTION[${i}]="postfix"
|
FILE_SELECTION[${i}]="postfix"
|
||||||
|
10
update.sh
10
update.sh
@ -181,7 +181,7 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
|
|||||||
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif docker-compose > /dev/null 2>&1; then
|
elif docker-compose > /dev/null 2>&1; then
|
||||||
@ -196,14 +196,14 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
|
|||||||
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then
|
|||||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then
|
if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then
|
||||||
# IF it cannot find Standalone in > 2.X, then script stops
|
# IF it cannot find Standalone in > 2.X, then script stops
|
||||||
echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m"
|
||||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
# If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
||||||
@ -236,7 +236,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then
|
|||||||
if ! $COMPOSE_COMMAND > /dev/null 2>&1; then
|
if ! $COMPOSE_COMMAND > /dev/null 2>&1; then
|
||||||
# IF it cannot find Native in > 2.X, then script stops
|
# IF it cannot find Native in > 2.X, then script stops
|
||||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m"
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
# If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly
|
||||||
|
Loading…
x
Reference in New Issue
Block a user