Compare commits
276 Commits
feat/ui-im
...
master
Author | SHA1 | Date |
---|---|---|
|
d6c3c58f42 | |
|
b050cb9864 | |
|
e176724775 | |
|
8f9ed9e0df | |
|
003eecf131 | |
|
180b9fc8d2 | |
|
5d3491c801 | |
|
c45684b986 | |
|
5c886d2f4e | |
|
9f39af46aa | |
|
7cda9f063f | |
|
5e7583c5e6 | |
|
a1fb962215 | |
|
57d849a51b | |
|
3000da6b88 | |
|
db75cbbcb0 | |
|
22acbb6b57 | |
|
31cb0f7db1 | |
|
6d17b9f504 | |
|
0f337971ff | |
|
6cf2775e7e | |
|
dabf9104ed | |
|
952ddb18fd | |
|
34d990a800 | |
|
020cb21b35 | |
|
525364ba65 | |
|
731fabef58 | |
|
c10be77a1b | |
|
a8bc4e3f37 | |
|
815572f200 | |
|
23fc54f2cf | |
|
11407973b1 | |
|
b9867e3fe0 | |
|
3814c3294f | |
|
9c44b5e546 | |
|
cd635ec813 | |
|
03831149f8 | |
|
521120a448 | |
|
ec8d298c36 | |
|
03580cbf39 | |
|
2b009c71c1 | |
|
b903cf3888 | |
|
cf239dd6b2 | |
|
a0723f60d2 | |
|
da8e496430 | |
|
722134e474 | |
|
cb1a11e551 | |
|
8984509f58 | |
|
0f0d43b253 | |
|
0f6956572e | |
|
29892dc694 | |
|
14265f3de8 | |
|
0863bffdd2 | |
|
3b748a30cc | |
|
5619175108 | |
|
6e9c024b3c | |
|
8cd4ae1e34 | |
|
689856b186 | |
|
7b645303d6 | |
|
408381bddb | |
|
380cdab6fc | |
|
03b7a8d639 | |
|
bf6a61fa2d | |
|
1de47072f8 | |
|
c0c46b7cf5 | |
|
42a91af7ac | |
|
6e1ee638ff | |
|
61c8afa088 | |
|
c873a14127 | |
|
06cce79806 | |
|
0927c5df57 | |
|
e691d2c782 | |
|
67510adb9e | |
|
490d553dfc | |
|
70aab7568e | |
|
f82aba3e26 | |
|
f80940efdc | |
|
6f875398c0 | |
|
7a582afbdc | |
|
38cd376228 | |
|
74bcec45f1 | |
|
9700b3251f | |
|
88b8d50cd5 | |
|
55b0191050 | |
|
33c97fb318 | |
|
23d33ad5a8 | |
|
bd6c98047a | |
|
73d6a29ae1 | |
|
173e39c859 | |
|
c0745c5cde | |
|
1a6f93327e | |
|
3c68a53170 | |
|
e38c27ed67 | |
|
8eaf8bbbde | |
|
e015c7dbca | |
|
58452abcdf | |
|
2cbf0da137 | |
|
97a492b891 | |
|
aabcd10539 | |
|
ee607dc3cc | |
|
1265302a8e | |
|
b5acf56e20 | |
|
9752313d24 | |
|
fe4a418af4 | |
|
e5f03e8526 | |
|
fb60c4a150 | |
|
6b82284a41 | |
|
192f67cd41 | |
|
fd203abd47 | |
|
6b65f0fc74 | |
|
856b3b62f2 | |
|
0372a2150d | |
|
f3322c0577 | |
|
c2bcc4e086 | |
|
6e79c48640 | |
|
d7dfa95e1b | |
|
cf1cc24e33 | |
|
6824a5650f | |
|
73570cc8b5 | |
|
959dcb9980 | |
|
8f28666916 | |
|
3eaa5a626c | |
|
8c79056a94 | |
|
ed076dc23e | |
|
be2286c11c | |
|
0e24c3d300 | |
|
e1d8df6580 | |
|
04a08a7d69 | |
|
3c0c8aa01f | |
|
026b278357 | |
|
4121509ceb | |
|
00ac61f0a4 | |
|
4bb0dbb2f7 | |
|
13b6df74af | |
|
5c025bf865 | |
|
20fc9eaf84 | |
|
22a0479fab | |
|
3510d5617d | |
|
236d627fbd | |
|
99739eada0 | |
|
7bfef57894 | |
|
d9dfe15253 | |
|
3fe8aaa719 | |
|
78a8fac6af | |
|
6986e7758f | |
|
b4a9df76b8 | |
|
d9d958356a | |
|
96f954a4e2 | |
|
44585e1c15 | |
|
c737ff4180 | |
|
025279009d | |
|
a9dc13d567 | |
|
c3ed01c9b5 | |
|
bd0b4a521e | |
|
800a0ace71 | |
|
db97869472 | |
|
f681fcf154 | |
|
db1b5956fc | |
|
bdb07061ed | |
|
428b917579 | |
|
469f959e96 | |
|
b68e189d97 | |
|
028ef22878 | |
|
80dacc015a | |
|
0194c39bd5 | |
|
f53ca24bb0 | |
|
ae46a877d3 | |
|
400939faf6 | |
|
fd0205aafd | |
|
e367a8ce24 | |
|
096e2a41e9 | |
|
e010f08143 | |
|
3d2483ca37 | |
|
535dd23509 | |
|
4336a99c6a | |
|
4cd5f93cdf | |
|
67955779b0 | |
|
26c34b484a | |
|
4021613059 | |
|
e891bf8411 | |
|
f7798d1aac | |
|
d11f00261b | |
|
22cd12f37b | |
|
db2fb12837 | |
|
e808e595eb | |
|
ce6742c676 | |
|
cf3dc584d0 | |
|
62f3603588 | |
|
9fd4aa93e9 | |
|
5bc3d93545 | |
|
c28a6b89f0 | |
|
1233613bea | |
|
0206e0886c | |
|
f6d135fbad | |
|
f7da314dcf | |
|
e6ce5e88f7 | |
|
e5e6418be8 | |
|
6507b53bbb | |
|
0f59d4952b | |
|
7225bd2f55 | |
|
deb2b80352 | |
|
ad9dee92be | |
|
f36bc16ca7 | |
|
bda5f0ed4a | |
|
cbe1c97a82 | |
|
81fcbdd104 | |
|
1a9294b58f | |
|
310c01aac2 | |
|
229303c1f8 | |
|
fc075bc6b7 | |
|
d04f0257c2 | |
|
d11d356803 | |
|
c54750ef8b | |
|
510ef5196b | |
|
04e46f9f5b | |
|
6c0a5028c0 | |
|
791bbeeb39 | |
|
a5b8f1b7f7 | |
|
af267ff706 | |
|
46cc022590 | |
|
f77c65411d | |
|
1052e13af8 | |
|
11e1502b12 | |
|
02afc45a15 | |
|
3e1cfe0d08 | |
|
d20df7d73e | |
|
a8c61daeaf | |
|
1a4f11209a | |
|
04403aaf70 | |
|
7f0dd7d0d7 | |
|
cd29ad883e | |
|
e1cd719a17 | |
|
15bb331a7d | |
|
6f3179bb8d | |
|
29e5b87207 | |
|
4403bc2d18 | |
|
63e92e0897 | |
|
aa4d8b1f47 | |
|
9054ca18be | |
|
38291d123f | |
|
ca64ff2c0b | |
|
dc85f49961 | |
|
5dca4dac81 | |
|
df8775d4c9 | |
|
2bc663dcd5 | |
|
1071bb8230 | |
|
e437810eca | |
|
e8fd34d31f | |
|
6aebb8352e | |
|
d684e0efc0 | |
|
64ac6a8891 | |
|
72e8180c6b | |
|
d62c275004 | |
|
aa7f562761 | |
|
a1f033e4c1 | |
|
58ddc31db6 | |
|
5bf62481d5 | |
|
6ff3f3f044 | |
|
640f535e99 | |
|
05d1a974eb | |
|
99e38d81b1 | |
|
ed7b384e24 | |
|
5439ea1010 | |
|
b719982504 | |
|
8281d3fa55 | |
|
9ba65a572e | |
|
afddcf7f3b | |
|
294569f5c9 | |
|
ef6452cf55 | |
|
9af40eba10 | |
|
1b3a13ca19 | |
|
71cc607de6 | |
|
5c57df4669 | |
|
1606658cb1 | |
|
54ba66733e | |
|
f6847e6f8c |
|
@ -10,7 +10,7 @@ jobs:
|
||||||
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
|
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
|
||||||
steps:
|
steps:
|
||||||
- name: Send message
|
- name: Send message
|
||||||
uses: thollander/actions-comment-pull-request@v2.3.1
|
uses: thollander/actions-comment-pull-request@v2.4.0
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
|
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
|
||||||
message: |
|
message: |
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||||
uses: actions/stale@v7.0.0
|
uses: actions/stale@v8.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
name: mailcow Integration Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master", "staging" ]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
integration_tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Setup Ansible
|
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install python3 python3-pip git
|
|
||||||
sudo pip3 install ansible
|
|
||||||
- name: Prepair Test Environment
|
|
||||||
run: |
|
|
||||||
git clone https://github.com/mailcow/mailcow-integration-tests.git --branch $(curl -sL https://api.github.com/repos/mailcow/mailcow-integration-tests/releases/latest | jq -r '.tag_name') --single-branch .
|
|
||||||
./fork_check.sh
|
|
||||||
./ci.sh
|
|
||||||
./ci-pip-requirements.sh
|
|
||||||
env:
|
|
||||||
VAULT_PW: ${{ secrets.MAILCOW_TESTS_VAULT_PW }}
|
|
||||||
VAULT_FILE: ${{ secrets.MAILCOW_TESTS_VAULT_FILE }}
|
|
||||||
- name: Start Integration Test Server
|
|
||||||
run: |
|
|
||||||
./fork_check.sh
|
|
||||||
ansible-playbook mailcow-start-server.yml --diff
|
|
||||||
env:
|
|
||||||
PY_COLORS: '1'
|
|
||||||
ANSIBLE_FORCE_COLOR: '1'
|
|
||||||
ANSIBLE_HOST_KEY_CHECKING: 'false'
|
|
||||||
- name: Setup Integration Test Server
|
|
||||||
run: |
|
|
||||||
./fork_check.sh
|
|
||||||
sleep 30
|
|
||||||
ansible-playbook mailcow-setup-server.yml --private-key id_ssh_rsa --diff
|
|
||||||
env:
|
|
||||||
PY_COLORS: '1'
|
|
||||||
ANSIBLE_FORCE_COLOR: '1'
|
|
||||||
ANSIBLE_HOST_KEY_CHECKING: 'false'
|
|
||||||
- name: Run Integration Tests
|
|
||||||
run: |
|
|
||||||
./fork_check.sh
|
|
||||||
ansible-playbook mailcow-integration-tests.yml --private-key id_ssh_rsa --diff
|
|
||||||
env:
|
|
||||||
PY_COLORS: '1'
|
|
||||||
ANSIBLE_FORCE_COLOR: '1'
|
|
||||||
ANSIBLE_HOST_KEY_CHECKING: 'false'
|
|
||||||
- name: Delete Integration Test Server
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
./fork_check.sh
|
|
||||||
ansible-playbook mailcow-delete-server.yml --diff
|
|
||||||
env:
|
|
||||||
PY_COLORS: '1'
|
|
||||||
ANSIBLE_FORCE_COLOR: '1'
|
|
||||||
ANSIBLE_HOST_KEY_CHECKING: 'false'
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run the Action
|
- name: Run the Action
|
||||||
uses: devops-infra/action-pull-request@v0.5.3
|
uses: devops-infra/action-pull-request@v0.5.5
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
|
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: data/Dockerfiles/backup/Dockerfile
|
file: data/Dockerfiles/backup/Dockerfile
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
name: "Tweet trigger release"
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tweet:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: "Get Release Tag"
|
|
||||||
run: |
|
|
||||||
RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name')
|
|
||||||
- name: Tweet-trigger-publish-release
|
|
||||||
uses: mugi111/tweet-trigger-release@v1.2
|
|
||||||
with:
|
|
||||||
consumer_key: ${{ secrets.CONSUMER_KEY }}
|
|
||||||
consumer_secret: ${{ secrets.CONSUMER_SECRET }}
|
|
||||||
access_token_key: ${{ secrets.ACCESS_TOKEN_KEY }}
|
|
||||||
access_token_secret: ${{ secrets.ACCESS_TOKEN_SECRET }}
|
|
||||||
tweet_body: 'A new mailcow update has just been released! Checkout the GitHub Page for changelog and more informations: https://github.com/mailcow/mailcow-dockerized/releases/latest'
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: Update postscreen_access.cidr
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Monthly
|
||||||
|
- cron: "0 0 1 * *"
|
||||||
|
workflow_dispatch: # Allow to run workflow manually
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Update-postscreen_access_cidr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Generate postscreen_access.cidr
|
||||||
|
run: |
|
||||||
|
bash helper-scripts/update_postscreen_whitelist.sh
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||||
|
commit-message: update postscreen_access.cidr
|
||||||
|
committer: milkmaker <milkmaker@mailcow.de>
|
||||||
|
author: milkmaker <milkmaker@mailcow.de>
|
||||||
|
signoff: false
|
||||||
|
branch: update/postscreen_access.cidr
|
||||||
|
base: staging
|
||||||
|
delete-branch: true
|
||||||
|
add-paths: |
|
||||||
|
data/conf/postfix/postscreen_access.cidr
|
||||||
|
title: '[Postfix] update postscreen_access.cidr'
|
||||||
|
body: |
|
||||||
|
This PR updates the postscreen_access.cidr using GitHub Actions and [helper-scripts/update_postscreen_whitelist.sh](https://github.com/mailcow/mailcow-dockerized/blob/master/helper-scripts/update_postscreen_whitelist.sh)
|
|
@ -36,6 +36,8 @@ data/conf/postfix/extra.cf
|
||||||
data/conf/postfix/sni.map
|
data/conf/postfix/sni.map
|
||||||
data/conf/postfix/sni.map.db
|
data/conf/postfix/sni.map.db
|
||||||
data/conf/postfix/sql
|
data/conf/postfix/sql
|
||||||
|
data/conf/postfix/dns_blocklists.cf
|
||||||
|
data/conf/postfix/dnsbl_reply.map
|
||||||
data/conf/rspamd/custom/*
|
data/conf/rspamd/custom/*
|
||||||
data/conf/rspamd/local.d/*
|
data/conf/rspamd/local.d/*
|
||||||
data/conf/rspamd/override.d/*
|
data/conf/rspamd/override.d/*
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# mailcow: dockerized - 🐮 + 🐋 = 💕
|
# mailcow: dockerized - 🐮 + 🐋 = 💕
|
||||||
|
|
||||||
[](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml)
|
|
||||||
[](https://translate.mailcow.email/engage/mailcow-dockerized/)
|
[](https://translate.mailcow.email/engage/mailcow-dockerized/)
|
||||||
[](https://twitter.com/mailcow_email)
|
[](https://twitter.com/mailcow_email)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
RUN apk upgrade --no-cache \
|
RUN apk upgrade --no-cache \
|
||||||
&& apk add --update --no-cache \
|
&& apk add --update --no-cache \
|
||||||
|
|
|
@ -213,11 +213,13 @@ while true; do
|
||||||
done
|
done
|
||||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
||||||
|
|
||||||
|
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||||
# Start IP detection
|
# Start IP detection
|
||||||
log_f "Detecting IP addresses..."
|
log_f "Detecting IP addresses..."
|
||||||
IPV4=$(get_ipv4)
|
IPV4=$(get_ipv4)
|
||||||
IPV6=$(get_ipv6)
|
IPV6=$(get_ipv6)
|
||||||
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
|
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
|
||||||
|
fi
|
||||||
|
|
||||||
#########################################
|
#########################################
|
||||||
# IP and webroot challenge verification #
|
# IP and webroot challenge verification #
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM clamav/clamav:1.0_base
|
FROM clamav/clamav:1.0.1-1_base
|
||||||
|
|
||||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -13,9 +13,13 @@ RUN apk add --update --no-cache python3 \
|
||||||
fastapi \
|
fastapi \
|
||||||
uvicorn \
|
uvicorn \
|
||||||
aiodocker \
|
aiodocker \
|
||||||
redis
|
docker \
|
||||||
|
aioredis
|
||||||
|
RUN mkdir /app/modules
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /app/
|
COPY docker-entrypoint.sh /app/
|
||||||
COPY dockerapi.py /app/
|
COPY main.py /app/main.py
|
||||||
|
COPY modules/ /app/modules/
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
|
||||||
|
CMD exec python main.py
|
|
@ -6,4 +6,4 @@
|
||||||
-subj /CN=dockerapi/O=mailcow \
|
-subj /CN=dockerapi/O=mailcow \
|
||||||
-addext subjectAltName=DNS:dockerapi`
|
-addext subjectAltName=DNS:dockerapi`
|
||||||
|
|
||||||
`uvicorn --host 0.0.0.0 --port 443 --ssl-certfile=/app/dockerapi_cert.pem --ssl-keyfile=/app/dockerapi_key.pem dockerapi:app`
|
exec "$@"
|
||||||
|
|
|
@ -1,623 +0,0 @@
|
||||||
from fastapi import FastAPI, Response, Request
|
|
||||||
import aiodocker
|
|
||||||
import psutil
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import redis
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
containerIds_to_update = []
|
|
||||||
host_stats_isUpdating = False
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/host/stats")
|
|
||||||
async def get_host_update_stats():
|
|
||||||
global host_stats_isUpdating
|
|
||||||
|
|
||||||
if host_stats_isUpdating == False:
|
|
||||||
print("start host stats task")
|
|
||||||
asyncio.create_task(get_host_stats())
|
|
||||||
host_stats_isUpdating = True
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if redis_client.exists('host_stats'):
|
|
||||||
break
|
|
||||||
print("wait for host_stats results")
|
|
||||||
await asyncio.sleep(1.5)
|
|
||||||
|
|
||||||
|
|
||||||
print("host stats pulled")
|
|
||||||
stats = json.loads(redis_client.get('host_stats'))
|
|
||||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
@app.get("/containers/{container_id}/json")
|
|
||||||
async def get_container(container_id : str):
|
|
||||||
if container_id and container_id.isalnum():
|
|
||||||
try:
|
|
||||||
for container in (await async_docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
container_info = await container.show()
|
|
||||||
return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "no container found"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
except Exception as e:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "no or invalid id defined"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
@app.get("/containers/json")
|
|
||||||
async def get_containers():
|
|
||||||
containers = {}
|
|
||||||
try:
|
|
||||||
for container in (await async_docker_client.containers.list()):
|
|
||||||
container_info = await container.show()
|
|
||||||
containers.update({container_info['Id']: container_info})
|
|
||||||
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
|
||||||
except Exception as e:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
@app.post("/containers/{container_id}/{post_action}")
|
|
||||||
async def post_containers(container_id : str, post_action : str, request: Request):
|
|
||||||
try :
|
|
||||||
request_json = await request.json()
|
|
||||||
except Exception as err:
|
|
||||||
request_json = {}
|
|
||||||
|
|
||||||
if container_id and container_id.isalnum() and post_action:
|
|
||||||
try:
|
|
||||||
"""Dispatch container_post api call"""
|
|
||||||
if post_action == 'exec':
|
|
||||||
if not request_json or not 'cmd' in request_json:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "cmd is missing"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
if not request_json or not 'task' in request_json:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "task is missing"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
|
|
||||||
else:
|
|
||||||
api_call_method_name = '__'.join(['container_post', str(post_action) ])
|
|
||||||
|
|
||||||
docker_utils = DockerUtils(async_docker_client)
|
|
||||||
api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
|
|
||||||
|
|
||||||
|
|
||||||
print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
|
|
||||||
return await api_call_method(container_id, request_json)
|
|
||||||
except Exception as e:
|
|
||||||
print("error - container_post: %s" % str(e))
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "invalid container id or missing action"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
@app.post("/container/{container_id}/stats/update")
|
|
||||||
async def post_container_update_stats(container_id : str):
|
|
||||||
global containerIds_to_update
|
|
||||||
|
|
||||||
# start update task for container if no task is running
|
|
||||||
if container_id not in containerIds_to_update:
|
|
||||||
asyncio.create_task(get_container_stats(container_id))
|
|
||||||
containerIds_to_update.append(container_id)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if redis_client.exists(container_id + '_stats'):
|
|
||||||
break
|
|
||||||
await asyncio.sleep(1.5)
|
|
||||||
|
|
||||||
stats = json.loads(redis_client.get(container_id + '_stats'))
|
|
||||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DockerUtils:
|
|
||||||
def __init__(self, docker_client):
|
|
||||||
self.docker_client = docker_client
|
|
||||||
|
|
||||||
# api call: container_post - post_action: stop
|
|
||||||
async def container_post__stop(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
await container.stop()
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'command completed successfully'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
# api call: container_post - post_action: start
|
|
||||||
async def container_post__start(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
await container.start()
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'command completed successfully'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: restart
|
|
||||||
async def container_post__restart(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
await container.restart()
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'command completed successfully'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: top
|
|
||||||
async def container_post__top(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
ps_exec = await container.exec("ps")
|
|
||||||
async with ps_exec.start(detach=False) as stream:
|
|
||||||
ps_return = await stream.read_out()
|
|
||||||
|
|
||||||
exec_details = await ps_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': ps_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'danger',
|
|
||||||
'msg': ''
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: delete
|
|
||||||
async def container_post__exec__mailq__delete(self, container_id, request_json):
|
|
||||||
if 'items' in request_json:
|
|
||||||
r = re.compile("^[0-9a-fA-F]+$")
|
|
||||||
filtered_qids = filter(r.match, request_json['items'])
|
|
||||||
if filtered_qids:
|
|
||||||
flagged_qids = ['-d %s' % i for i in filtered_qids]
|
|
||||||
sanitized_string = str(' '.join(flagged_qids))
|
|
||||||
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
|
||||||
return await exec_run_handler('generic', postsuper_r_exec)
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: hold
|
|
||||||
async def container_post__exec__mailq__hold(self, container_id, request_json):
|
|
||||||
if 'items' in request_json:
|
|
||||||
r = re.compile("^[0-9a-fA-F]+$")
|
|
||||||
filtered_qids = filter(r.match, request_json['items'])
|
|
||||||
if filtered_qids:
|
|
||||||
flagged_qids = ['-h %s' % i for i in filtered_qids]
|
|
||||||
sanitized_string = str(' '.join(flagged_qids))
|
|
||||||
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
|
||||||
return await exec_run_handler('generic', postsuper_r_exec)
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: cat
|
|
||||||
async def container_post__exec__mailq__cat(self, container_id, request_json):
|
|
||||||
if 'items' in request_json:
|
|
||||||
r = re.compile("^[0-9a-fA-F]+$")
|
|
||||||
filtered_qids = filter(r.match, request_json['items'])
|
|
||||||
if filtered_qids:
|
|
||||||
sanitized_string = str(' '.join(filtered_qids))
|
|
||||||
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
|
|
||||||
return await exec_run_handler('utf8_text_only', postcat_exec)
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: unhold
|
|
||||||
async def container_post__exec__mailq__unhold(self, container_id, request_json):
|
|
||||||
if 'items' in request_json:
|
|
||||||
r = re.compile("^[0-9a-fA-F]+$")
|
|
||||||
filtered_qids = filter(r.match, request_json['items'])
|
|
||||||
if filtered_qids:
|
|
||||||
flagged_qids = ['-H %s' % i for i in filtered_qids]
|
|
||||||
sanitized_string = str(' '.join(flagged_qids))
|
|
||||||
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
|
||||||
return await exec_run_handler('generic', postsuper_r_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: deliver
|
|
||||||
async def container_post__exec__mailq__deliver(self, container_id, request_json):
|
|
||||||
if 'items' in request_json:
|
|
||||||
r = re.compile("^[0-9a-fA-F]+$")
|
|
||||||
filtered_qids = filter(r.match, request_json['items'])
|
|
||||||
if filtered_qids:
|
|
||||||
flagged_qids = ['-i %s' % i for i in filtered_qids]
|
|
||||||
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
for i in flagged_qids:
|
|
||||||
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
|
||||||
async with postsuper_r_exec.start(detach=False) as stream:
|
|
||||||
postsuper_r_return = await stream.read_out()
|
|
||||||
# todo: check each exit code
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'Scheduled immediate delivery'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: list
|
|
||||||
async def container_post__exec__mailq__list(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
|
|
||||||
return await exec_run_handler('utf8_text_only', mailq_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: flush
|
|
||||||
async def container_post__exec__mailq__flush(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
|
|
||||||
return await exec_run_handler('generic', postsuper_r_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
|
|
||||||
async def container_post__exec__mailq__super_delete(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
|
|
||||||
return await exec_run_handler('generic', postsuper_r_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
|
|
||||||
async def container_post__exec__system__fts_rescan(self, container_id, request_json):
|
|
||||||
if 'username' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
|
||||||
async with rescan_exec.start(detach=False) as stream:
|
|
||||||
rescan_return = await stream.read_out()
|
|
||||||
|
|
||||||
exec_details = await rescan_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'fts_rescan: rescan triggered'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'warning',
|
|
||||||
'msg': 'fts_rescan error'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
if 'all' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
|
||||||
async with rescan_exec.start(detach=False) as stream:
|
|
||||||
rescan_return = await stream.read_out()
|
|
||||||
|
|
||||||
exec_details = await rescan_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'fts_rescan: rescan triggered'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'warning',
|
|
||||||
'msg': 'fts_rescan error'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: system - task: df
|
|
||||||
async def container_post__exec__system__df(self, container_id, request_json):
|
|
||||||
if 'dir' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
|
||||||
async with df_exec.start(detach=False) as stream:
|
|
||||||
df_return = await stream.read_out()
|
|
||||||
|
|
||||||
print(df_return)
|
|
||||||
print(await df_exec.inspect())
|
|
||||||
exec_details = await df_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
return df_return.data.decode('utf-8').rstrip()
|
|
||||||
else:
|
|
||||||
return "0,0,0,0,0,0"
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
|
|
||||||
async def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
|
||||||
async with sql_exec.start(detach=False) as stream:
|
|
||||||
sql_return = await stream.read_out()
|
|
||||||
|
|
||||||
exec_details = await sql_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
matched = False
|
|
||||||
for line in sql_return.data.decode('utf-8').split("\n"):
|
|
||||||
if 'is already upgraded to' in line:
|
|
||||||
matched = True
|
|
||||||
if matched:
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'mysql_upgrade: already upgraded',
|
|
||||||
'text': sql_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
await container.restart()
|
|
||||||
res = {
|
|
||||||
'type': 'warning',
|
|
||||||
'msg': 'mysql_upgrade: upgrade was applied',
|
|
||||||
'text': sql_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'error',
|
|
||||||
'msg': 'mysql_upgrade: error running command',
|
|
||||||
'text': sql_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
|
|
||||||
async def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
|
|
||||||
async with sql_exec.start(detach=False) as stream:
|
|
||||||
sql_return = await stream.read_out()
|
|
||||||
|
|
||||||
exec_details = await sql_exec.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
res = {
|
|
||||||
'type': 'info',
|
|
||||||
'msg': 'mysql_tzinfo_to_sql: command completed successfully',
|
|
||||||
'text': sql_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'error',
|
|
||||||
'msg': 'mysql_tzinfo_to_sql: error running command',
|
|
||||||
'text': sql_return.data.decode('utf-8')
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
|
|
||||||
async def container_post__exec__reload__dovecot(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
|
|
||||||
return await exec_run_handler('generic', reload_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: reload - task: postfix
|
|
||||||
async def container_post__exec__reload__postfix(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
|
|
||||||
return await exec_run_handler('generic', reload_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: reload - task: nginx
|
|
||||||
async def container_post__exec__reload__nginx(self, container_id, request_json):
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
|
|
||||||
return await exec_run_handler('generic', reload_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: sieve - task: list
|
|
||||||
async def container_post__exec__sieve__list(self, container_id, request_json):
|
|
||||||
if 'username' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
|
|
||||||
return await exec_run_handler('utf8_text_only', sieve_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: sieve - task: print
|
|
||||||
async def container_post__exec__sieve__print(self, container_id, request_json):
|
|
||||||
if 'username' in request_json and 'script_name' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
|
|
||||||
sieve_exec = await container.exec(cmd)
|
|
||||||
return await exec_run_handler('utf8_text_only', sieve_exec)
|
|
||||||
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
|
|
||||||
async def container_post__exec__maildir__cleanup(self, container_id, request_json):
|
|
||||||
if 'maildir' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
sane_name = re.sub(r'\W+', '', request_json['maildir'])
|
|
||||||
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
|
|
||||||
maildir_cleanup_exec = await container.exec(cmd, user='vmail')
|
|
||||||
return await exec_run_handler('generic', maildir_cleanup_exec)
|
|
||||||
|
|
||||||
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
|
|
||||||
async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
|
|
||||||
if 'raw' in request_json:
|
|
||||||
for container in (await self.docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
|
|
||||||
cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
|
|
||||||
rspamd_password_exec = await container.exec(cmd, user='_rspamd')
|
|
||||||
async with rspamd_password_exec.start(detach=False) as stream:
|
|
||||||
rspamd_password_return = await stream.read_out()
|
|
||||||
|
|
||||||
matched = False
|
|
||||||
if "OK" in rspamd_password_return.data.decode('utf-8'):
|
|
||||||
matched = True
|
|
||||||
await container.restart()
|
|
||||||
|
|
||||||
if matched:
|
|
||||||
res = {
|
|
||||||
'type': 'success',
|
|
||||||
'msg': 'command completed successfully'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
'type': 'danger',
|
|
||||||
'msg': 'command did not complete'
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def exec_run_handler(type, exec_obj):
|
|
||||||
async with exec_obj.start(detach=False) as stream:
|
|
||||||
exec_return = await stream.read_out()
|
|
||||||
|
|
||||||
if exec_return == None:
|
|
||||||
exec_return = ""
|
|
||||||
else:
|
|
||||||
exec_return = exec_return.data.decode('utf-8')
|
|
||||||
|
|
||||||
if type == 'generic':
|
|
||||||
exec_details = await exec_obj.inspect()
|
|
||||||
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
|
|
||||||
res = {
|
|
||||||
"type": "success",
|
|
||||||
"msg": "command completed successfully"
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
"type": "success",
|
|
||||||
"msg": "'command failed: " + exec_return
|
|
||||||
}
|
|
||||||
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
|
||||||
if type == 'utf8_text_only':
|
|
||||||
return Response(content=exec_return, media_type="text/plain")
|
|
||||||
|
|
||||||
async def get_host_stats(wait=5):
|
|
||||||
global host_stats_isUpdating
|
|
||||||
|
|
||||||
try:
|
|
||||||
system_time = datetime.now()
|
|
||||||
host_stats = {
|
|
||||||
"cpu": {
|
|
||||||
"cores": psutil.cpu_count(),
|
|
||||||
"usage": psutil.cpu_percent()
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"total": psutil.virtual_memory().total,
|
|
||||||
"usage": psutil.virtual_memory().percent,
|
|
||||||
"swap": psutil.swap_memory()
|
|
||||||
},
|
|
||||||
"uptime": time.time() - psutil.boot_time(),
|
|
||||||
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S")
|
|
||||||
}
|
|
||||||
|
|
||||||
redis_client.set('host_stats', json.dumps(host_stats), ex=10)
|
|
||||||
except Exception as e:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
print(json.dumps(res, indent=4))
|
|
||||||
|
|
||||||
await asyncio.sleep(wait)
|
|
||||||
host_stats_isUpdating = False
|
|
||||||
|
|
||||||
|
|
||||||
async def get_container_stats(container_id, wait=5, stop=False):
|
|
||||||
global containerIds_to_update
|
|
||||||
|
|
||||||
if container_id and container_id.isalnum():
|
|
||||||
try:
|
|
||||||
for container in (await async_docker_client.containers.list()):
|
|
||||||
if container._id == container_id:
|
|
||||||
res = await container.stats(stream=False)
|
|
||||||
|
|
||||||
if redis_client.exists(container_id + '_stats'):
|
|
||||||
stats = json.loads(redis_client.get(container_id + '_stats'))
|
|
||||||
else:
|
|
||||||
stats = []
|
|
||||||
stats.append(res[0])
|
|
||||||
if len(stats) > 3:
|
|
||||||
del stats[0]
|
|
||||||
redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
|
||||||
except Exception as e:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": str(e)
|
|
||||||
}
|
|
||||||
print(json.dumps(res, indent=4))
|
|
||||||
else:
|
|
||||||
res = {
|
|
||||||
"type": "danger",
|
|
||||||
"msg": "no or invalid id defined"
|
|
||||||
}
|
|
||||||
print(json.dumps(res, indent=4))
|
|
||||||
|
|
||||||
await asyncio.sleep(wait)
|
|
||||||
if stop == True:
|
|
||||||
# update task was called second time, stop
|
|
||||||
containerIds_to_update.remove(container_id)
|
|
||||||
else:
|
|
||||||
# call update task a second time
|
|
||||||
await get_container_stats(container_id, wait=0, stop=True)
|
|
||||||
|
|
||||||
|
|
||||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
|
||||||
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
|
|
||||||
else:
|
|
||||||
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
|
|
||||||
|
|
||||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import uvicorn
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import async_timeout
|
||||||
|
import asyncio
|
||||||
|
import aioredis
|
||||||
|
import aiodocker
|
||||||
|
import docker
|
||||||
|
import logging
|
||||||
|
from logging.config import dictConfig
|
||||||
|
from fastapi import FastAPI, Response, Request
|
||||||
|
from modules.DockerApi import DockerApi
|
||||||
|
|
||||||
|
dockerapi = None
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Define Routes
|
||||||
|
@app.get("/host/stats")
|
||||||
|
async def get_host_update_stats():
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
if dockerapi.host_stats_isUpdating == False:
|
||||||
|
asyncio.create_task(dockerapi.get_host_stats())
|
||||||
|
dockerapi.host_stats_isUpdating = True
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if await dockerapi.redis_client.exists('host_stats'):
|
||||||
|
break
|
||||||
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
|
stats = json.loads(await dockerapi.redis_client.get('host_stats'))
|
||||||
|
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
@app.get("/containers/{container_id}/json")
|
||||||
|
async def get_container(container_id : str):
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
if container_id and container_id.isalnum():
|
||||||
|
try:
|
||||||
|
for container in (await dockerapi.async_docker_client.containers.list()):
|
||||||
|
if container._id == container_id:
|
||||||
|
container_info = await container.show()
|
||||||
|
return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "no container found"
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
except Exception as e:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": str(e)
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "no or invalid id defined"
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
@app.get("/containers/json")
|
||||||
|
async def get_containers():
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
containers = {}
|
||||||
|
try:
|
||||||
|
for container in (await dockerapi.async_docker_client.containers.list()):
|
||||||
|
container_info = await container.show()
|
||||||
|
containers.update({container_info['Id']: container_info})
|
||||||
|
return Response(content=json.dumps(containers, indent=4), media_type="application/json")
|
||||||
|
except Exception as e:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": str(e)
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
@app.post("/containers/{container_id}/{post_action}")
|
||||||
|
async def post_containers(container_id : str, post_action : str, request: Request):
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
try :
|
||||||
|
request_json = await request.json()
|
||||||
|
except Exception as err:
|
||||||
|
request_json = {}
|
||||||
|
|
||||||
|
if container_id and container_id.isalnum() and post_action:
|
||||||
|
try:
|
||||||
|
"""Dispatch container_post api call"""
|
||||||
|
if post_action == 'exec':
|
||||||
|
if not request_json or not 'cmd' in request_json:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "cmd is missing"
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
if not request_json or not 'task' in request_json:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "task is missing"
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
|
||||||
|
else:
|
||||||
|
api_call_method_name = '__'.join(['container_post', str(post_action) ])
|
||||||
|
|
||||||
|
api_call_method = getattr(dockerapi, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
|
||||||
|
|
||||||
|
dockerapi.logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
|
||||||
|
return api_call_method(request_json, container_id=container_id)
|
||||||
|
except Exception as e:
|
||||||
|
dockerapi.logger.error("error - container_post: %s" % str(e))
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": str(e)
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
else:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "invalid container id or missing action"
|
||||||
|
}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
@app.post("/container/{container_id}/stats/update")
|
||||||
|
async def post_container_update_stats(container_id : str):
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
# start update task for container if no task is running
|
||||||
|
if container_id not in dockerapi.containerIds_to_update:
|
||||||
|
asyncio.create_task(dockerapi.get_container_stats(container_id))
|
||||||
|
dockerapi.containerIds_to_update.append(container_id)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if await dockerapi.redis_client.exists(container_id + '_stats'):
|
||||||
|
break
|
||||||
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
|
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
||||||
|
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
|
||||||
|
async def handle_pubsub_messages(channel: aioredis.client.PubSub):
|
||||||
|
global dockerapi
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(1):
|
||||||
|
message = await channel.get_message(ignore_subscribe_messages=True)
|
||||||
|
if message is not None:
|
||||||
|
# Parse message
|
||||||
|
data_json = json.loads(message['data'].decode('utf-8'))
|
||||||
|
dockerapi.logger.info(f"PubSub Received - {json.dumps(data_json)}")
|
||||||
|
|
||||||
|
# Handle api_call
|
||||||
|
if 'api_call' in data_json:
|
||||||
|
# api_call: container_post
|
||||||
|
if data_json['api_call'] == "container_post":
|
||||||
|
if 'post_action' in data_json and 'container_name' in data_json:
|
||||||
|
try:
|
||||||
|
"""Dispatch container_post api call"""
|
||||||
|
request_json = {}
|
||||||
|
if data_json['post_action'] == 'exec':
|
||||||
|
if 'request' in data_json:
|
||||||
|
request_json = data_json['request']
|
||||||
|
if 'cmd' in request_json:
|
||||||
|
if 'task' in request_json:
|
||||||
|
api_call_method_name = '__'.join(['container_post', str(data_json['post_action']), str(request_json['cmd']), str(request_json['task']) ])
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("api call: task missing")
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("api call: cmd missing")
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("api call: request missing")
|
||||||
|
else:
|
||||||
|
api_call_method_name = '__'.join(['container_post', str(data_json['post_action'])])
|
||||||
|
|
||||||
|
if api_call_method_name:
|
||||||
|
api_call_method = getattr(dockerapi, api_call_method_name)
|
||||||
|
if api_call_method:
|
||||||
|
dockerapi.logger.info("api call: %s, container_name: %s" % (api_call_method_name, data_json['container_name']))
|
||||||
|
api_call_method(request_json, container_name=data_json['container_name'])
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("api call not found: %s, container_name: %s" % (api_call_method_name, data_json['container_name']))
|
||||||
|
except Exception as e:
|
||||||
|
dockerapi.logger.error("container_post: %s" % str(e))
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("api call: missing container_name, post_action or request")
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
|
||||||
|
else:
|
||||||
|
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
|
||||||
|
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=443,
|
||||||
|
ssl_certfile="/app/dockerapi_cert.pem",
|
||||||
|
ssl_keyfile="/app/dockerapi_key.pem",
|
||||||
|
log_level="info",
|
||||||
|
loop="none"
|
||||||
|
)
|
|
@ -0,0 +1,487 @@
|
||||||
|
import psutil
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
import platform
|
||||||
|
from datetime import datetime
|
||||||
|
from fastapi import FastAPI, Response, Request
|
||||||
|
|
||||||
|
class DockerApi:
|
||||||
|
def __init__(self, redis_client, sync_docker_client, async_docker_client, logger):
|
||||||
|
self.redis_client = redis_client
|
||||||
|
self.sync_docker_client = sync_docker_client
|
||||||
|
self.async_docker_client = async_docker_client
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
self.host_stats_isUpdating = False
|
||||||
|
self.containerIds_to_update = []
|
||||||
|
|
||||||
|
# api call: container_post - post_action: stop
|
||||||
|
def container_post__stop(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||||
|
container.stop()
|
||||||
|
|
||||||
|
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: start
|
||||||
|
def container_post__start(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||||
|
container.start()
|
||||||
|
|
||||||
|
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: restart
|
||||||
|
def container_post__restart(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||||
|
container.restart()
|
||||||
|
|
||||||
|
res = { 'type': 'success', 'msg': 'command completed successfully'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: top
|
||||||
|
def container_post__top(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||||
|
res = { 'type': 'success', 'msg': container.top()}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: stats
|
||||||
|
def container_post__stats(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(all=True, filters=filters):
|
||||||
|
for stat in container.stats(decode=True, stream=True):
|
||||||
|
res = { 'type': 'success', 'msg': stat}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: delete
|
||||||
|
def container_post__exec__mailq__delete(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'items' in request_json:
|
||||||
|
r = re.compile("^[0-9a-fA-F]+$")
|
||||||
|
filtered_qids = filter(r.match, request_json['items'])
|
||||||
|
if filtered_qids:
|
||||||
|
flagged_qids = ['-d %s' % i for i in filtered_qids]
|
||||||
|
sanitized_string = str(' '.join(flagged_qids))
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||||
|
return self.exec_run_handler('generic', postsuper_r)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: hold
|
||||||
|
def container_post__exec__mailq__hold(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'items' in request_json:
|
||||||
|
r = re.compile("^[0-9a-fA-F]+$")
|
||||||
|
filtered_qids = filter(r.match, request_json['items'])
|
||||||
|
if filtered_qids:
|
||||||
|
flagged_qids = ['-h %s' % i for i in filtered_qids]
|
||||||
|
sanitized_string = str(' '.join(flagged_qids))
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||||
|
return self.exec_run_handler('generic', postsuper_r)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: cat
|
||||||
|
def container_post__exec__mailq__cat(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'items' in request_json:
|
||||||
|
r = re.compile("^[0-9a-fA-F]+$")
|
||||||
|
filtered_qids = filter(r.match, request_json['items'])
|
||||||
|
if filtered_qids:
|
||||||
|
sanitized_string = str(' '.join(filtered_qids))
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
|
||||||
|
if not postcat_return:
|
||||||
|
postcat_return = 'err: invalid'
|
||||||
|
return self.exec_run_handler('utf8_text_only', postcat_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: unhold
|
||||||
|
def container_post__exec__mailq__unhold(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'items' in request_json:
|
||||||
|
r = re.compile("^[0-9a-fA-F]+$")
|
||||||
|
filtered_qids = filter(r.match, request_json['items'])
|
||||||
|
if filtered_qids:
|
||||||
|
flagged_qids = ['-H %s' % i for i in filtered_qids]
|
||||||
|
sanitized_string = str(' '.join(flagged_qids))
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
|
||||||
|
return self.exec_run_handler('generic', postsuper_r)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: deliver
|
||||||
|
def container_post__exec__mailq__deliver(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'items' in request_json:
|
||||||
|
r = re.compile("^[0-9a-fA-F]+$")
|
||||||
|
filtered_qids = filter(r.match, request_json['items'])
|
||||||
|
if filtered_qids:
|
||||||
|
flagged_qids = ['-i %s' % i for i in filtered_qids]
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
for i in flagged_qids:
|
||||||
|
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
|
||||||
|
# todo: check each exit code
|
||||||
|
res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: list
|
||||||
|
def container_post__exec__mailq__list(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
|
||||||
|
return self.exec_run_handler('utf8_text_only', mailq_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: flush
|
||||||
|
def container_post__exec__mailq__flush(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
|
||||||
|
return self.exec_run_handler('generic', postqueue_r)
|
||||||
|
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
|
||||||
|
def container_post__exec__mailq__super_delete(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
|
||||||
|
return self.exec_run_handler('generic', postsuper_r)
|
||||||
|
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
|
||||||
|
def container_post__exec__system__fts_rescan(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'username' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
|
||||||
|
if rescan_return.exit_code == 0:
|
||||||
|
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
if 'all' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
|
||||||
|
if rescan_return.exit_code == 0:
|
||||||
|
res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = { 'type': 'warning', 'msg': 'fts_rescan error'}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: exec - cmd: system - task: df
|
||||||
|
def container_post__exec__system__df(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'dir' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
|
||||||
|
if df_return.exit_code == 0:
|
||||||
|
return df_return.output.decode('utf-8').rstrip()
|
||||||
|
else:
|
||||||
|
return "0,0,0,0,0,0"
|
||||||
|
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
|
||||||
|
def container_post__exec__system__mysql_upgrade(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
|
||||||
|
if sql_return.exit_code == 0:
|
||||||
|
matched = False
|
||||||
|
for line in sql_return.output.decode('utf-8').split("\n"):
|
||||||
|
if 'is already upgraded to' in line:
|
||||||
|
matched = True
|
||||||
|
if matched:
|
||||||
|
res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
container.restart()
|
||||||
|
res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
|
||||||
|
def container_post__exec__system__mysql_tzinfo_to_sql(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
|
||||||
|
if sql_return.exit_code == 0:
|
||||||
|
res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')}
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
|
||||||
|
def container_post__exec__reload__dovecot(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
|
||||||
|
return self.exec_run_handler('generic', reload_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: reload - task: postfix
|
||||||
|
def container_post__exec__reload__postfix(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
|
||||||
|
return self.exec_run_handler('generic', reload_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: reload - task: nginx
|
||||||
|
def container_post__exec__reload__nginx(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
|
||||||
|
return self.exec_run_handler('generic', reload_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: sieve - task: list
|
||||||
|
def container_post__exec__sieve__list(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'username' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
|
||||||
|
return self.exec_run_handler('utf8_text_only', sieve_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: sieve - task: print
|
||||||
|
def container_post__exec__sieve__print(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'username' in request_json and 'script_name' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
|
||||||
|
sieve_return = container.exec_run(cmd)
|
||||||
|
return self.exec_run_handler('utf8_text_only', sieve_return)
|
||||||
|
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
|
||||||
|
def container_post__exec__maildir__cleanup(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'maildir' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
sane_name = re.sub(r'\W+', '', request_json['maildir'])
|
||||||
|
vmail_name = request_json['maildir'].replace("'", "'\\''")
|
||||||
|
cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"
|
||||||
|
index_name = request_json['maildir'].split("/")
|
||||||
|
if len(index_name) > 1:
|
||||||
|
index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
|
||||||
|
cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi"
|
||||||
|
cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
|
||||||
|
else:
|
||||||
|
cmd = ["/bin/bash", "-c", cmd_vmail]
|
||||||
|
maildir_cleanup = container.exec_run(cmd, user='vmail')
|
||||||
|
return self.exec_run_handler('generic', maildir_cleanup)
|
||||||
|
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
|
||||||
|
def container_post__exec__rspamd__worker_password(self, request_json, **kwargs):
|
||||||
|
if 'container_id' in kwargs:
|
||||||
|
filters = {"id": kwargs['container_id']}
|
||||||
|
elif 'container_name' in kwargs:
|
||||||
|
filters = {"name": kwargs['container_name']}
|
||||||
|
|
||||||
|
if 'raw' in request_json:
|
||||||
|
for container in self.sync_docker_client.containers.list(filters=filters):
|
||||||
|
cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
|
||||||
|
cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd")
|
||||||
|
|
||||||
|
matched = False
|
||||||
|
for line in cmd_response.split("\n"):
|
||||||
|
if '$2$' in line:
|
||||||
|
hash = line.strip()
|
||||||
|
hash_out = re.search('\$2\$.+$', hash).group(0)
|
||||||
|
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||||
|
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
|
||||||
|
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
|
||||||
|
cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd")
|
||||||
|
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
|
||||||
|
container.restart()
|
||||||
|
matched = True
|
||||||
|
if matched:
|
||||||
|
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||||
|
self.logger.info('success changing Rspamd password')
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
self.logger.error('failed changing Rspamd password')
|
||||||
|
res = { 'type': 'danger', 'msg': 'command did not complete' }
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
|
||||||
|
# Collect host stats
|
||||||
|
async def get_host_stats(self, wait=5):
|
||||||
|
try:
|
||||||
|
system_time = datetime.now()
|
||||||
|
host_stats = {
|
||||||
|
"cpu": {
|
||||||
|
"cores": psutil.cpu_count(),
|
||||||
|
"usage": psutil.cpu_percent()
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"total": psutil.virtual_memory().total,
|
||||||
|
"usage": psutil.virtual_memory().percent,
|
||||||
|
"swap": psutil.swap_memory()
|
||||||
|
},
|
||||||
|
"uptime": time.time() - psutil.boot_time(),
|
||||||
|
"system_time": system_time.strftime("%d.%m.%Y %H:%M:%S"),
|
||||||
|
"architecture": platform.machine()
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.redis_client.set('host_stats', json.dumps(host_stats), ex=10)
|
||||||
|
except Exception as e:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
await asyncio.sleep(wait)
|
||||||
|
self.host_stats_isUpdating = False
|
||||||
|
# Collect container stats
|
||||||
|
async def get_container_stats(self, container_id, wait=5, stop=False):
|
||||||
|
if container_id and container_id.isalnum():
|
||||||
|
try:
|
||||||
|
for container in (await self.async_docker_client.containers.list()):
|
||||||
|
if container._id == container_id:
|
||||||
|
res = await container.stats(stream=False)
|
||||||
|
|
||||||
|
if await self.redis_client.exists(container_id + '_stats'):
|
||||||
|
stats = json.loads(await self.redis_client.get(container_id + '_stats'))
|
||||||
|
else:
|
||||||
|
stats = []
|
||||||
|
stats.append(res[0])
|
||||||
|
if len(stats) > 3:
|
||||||
|
del stats[0]
|
||||||
|
await self.redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
|
||||||
|
except Exception as e:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": str(e)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
res = {
|
||||||
|
"type": "danger",
|
||||||
|
"msg": "no or invalid id defined"
|
||||||
|
}
|
||||||
|
|
||||||
|
await asyncio.sleep(wait)
|
||||||
|
if stop == True:
|
||||||
|
# update task was called second time, stop
|
||||||
|
self.containerIds_to_update.remove(container_id)
|
||||||
|
else:
|
||||||
|
# call update task a second time
|
||||||
|
await self.get_container_stats(container_id, wait=0, stop=True)
|
||||||
|
|
||||||
|
def exec_cmd_container(self, container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
|
||||||
|
def recv_socket_data(c_socket, timeout):
|
||||||
|
c_socket.setblocking(0)
|
||||||
|
total_data=[]
|
||||||
|
data=''
|
||||||
|
begin=time.time()
|
||||||
|
while True:
|
||||||
|
if total_data and time.time()-begin > timeout:
|
||||||
|
break
|
||||||
|
elif time.time()-begin > timeout*2:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
data = c_socket.recv(8192)
|
||||||
|
if data:
|
||||||
|
total_data.append(data.decode('utf-8'))
|
||||||
|
#change the beginning time for measurement
|
||||||
|
begin=time.time()
|
||||||
|
else:
|
||||||
|
#sleep for sometime to indicate a gap
|
||||||
|
time.sleep(0.1)
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return ''.join(total_data)
|
||||||
|
|
||||||
|
try :
|
||||||
|
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
|
||||||
|
if not cmd.endswith("\n"):
|
||||||
|
cmd = cmd + "\n"
|
||||||
|
socket.send(cmd.encode('utf-8'))
|
||||||
|
data = recv_socket_data(socket, timeout)
|
||||||
|
socket.close()
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("error - exec_cmd_container: %s" % str(e))
|
||||||
|
traceback.print_exc(file=sys.stdout)
|
||||||
|
|
||||||
|
def exec_run_handler(self, type, output):
|
||||||
|
if type == 'generic':
|
||||||
|
if output.exit_code == 0:
|
||||||
|
res = { 'type': 'success', 'msg': 'command completed successfully' }
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
else:
|
||||||
|
res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
|
||||||
|
return Response(content=json.dumps(res, indent=4), media_type="application/json")
|
||||||
|
if type == 'utf8_text_only':
|
||||||
|
return Response(content=output.output.decode('utf-8'), media_type="text/plain")
|
|
@ -1,5 +1,5 @@
|
||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced
|
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced
|
||||||
|
@ -21,6 +21,7 @@ RUN groupadd -g 5000 vmail \
|
||||||
&& touch /etc/default/locale \
|
&& touch /etc/default/locale \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get -y --no-install-recommends install \
|
&& apt-get -y --no-install-recommends install \
|
||||||
|
build-essential \
|
||||||
apt-transport-https \
|
apt-transport-https \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
cpanminus \
|
cpanminus \
|
||||||
|
@ -61,6 +62,7 @@ RUN groupadd -g 5000 vmail \
|
||||||
libproc-processtable-perl \
|
libproc-processtable-perl \
|
||||||
libreadonly-perl \
|
libreadonly-perl \
|
||||||
libregexp-common-perl \
|
libregexp-common-perl \
|
||||||
|
libssl-dev \
|
||||||
libsys-meminfo-perl \
|
libsys-meminfo-perl \
|
||||||
libterm-readkey-perl \
|
libterm-readkey-perl \
|
||||||
libtest-deep-perl \
|
libtest-deep-perl \
|
||||||
|
@ -110,6 +112,8 @@ RUN groupadd -g 5000 vmail \
|
||||||
&& apt-get autoclean \
|
&& apt-get autoclean \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& rm -rf /tmp/* /var/tmp/* /root/.cache/
|
&& rm -rf /tmp/* /var/tmp/* /root/.cache/
|
||||||
|
# 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
|
||||||
|
|
|
@ -159,7 +159,7 @@ function auth_password_verify(req, pass)
|
||||||
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
|
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
|
||||||
cur:close()
|
cur:close()
|
||||||
con:close()
|
con:close()
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||||
end
|
end
|
||||||
row = cur:fetch (row, "a")
|
row = cur:fetch (row, "a")
|
||||||
end
|
end
|
||||||
|
@ -180,13 +180,13 @@ function auth_password_verify(req, pass)
|
||||||
if tostring(req.real_rip) == "__IPV4_SOGO__" then
|
if tostring(req.real_rip) == "__IPV4_SOGO__" then
|
||||||
cur:close()
|
cur:close()
|
||||||
con:close()
|
con:close()
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||||
elseif row.has_prot_access == "1" then
|
elseif row.has_prot_access == "1" then
|
||||||
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
||||||
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
|
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
|
||||||
cur:close()
|
cur:close()
|
||||||
con:close()
|
con:close()
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
row = cur:fetch (row, "a")
|
row = cur:fetch (row, "a")
|
||||||
|
|
|
@ -8492,6 +8492,7 @@ sub xoauth2
|
||||||
require HTML::Entities ;
|
require HTML::Entities ;
|
||||||
require JSON ;
|
require JSON ;
|
||||||
require JSON::WebToken::Crypt::RSA ;
|
require JSON::WebToken::Crypt::RSA ;
|
||||||
|
require Crypt::OpenSSL::PKCS12;
|
||||||
require Crypt::OpenSSL::RSA ;
|
require Crypt::OpenSSL::RSA ;
|
||||||
require Encode::Byte ;
|
require Encode::Byte ;
|
||||||
require IO::Socket::SSL ;
|
require IO::Socket::SSL ;
|
||||||
|
@ -8532,8 +8533,9 @@ sub xoauth2
|
||||||
|
|
||||||
$sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
|
$sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n");
|
||||||
|
|
||||||
# Get private key from p12 file (would be better in perl...)
|
# Get private key from p12 file
|
||||||
$key = `openssl pkcs12 -in "$keyfile" -nodes -nocerts -passin pass:$keypass -nomacver`;
|
my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile);
|
||||||
|
$key = $pkcs12->private_key($keypass);
|
||||||
|
|
||||||
$sync->{ debug } and myprint( "Private key:\n$key\n");
|
$sync->{ debug } and myprint( "Private key:\n$key\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ENV XTABLES_LIBDIR /usr/lib/xtables
|
ENV XTABLES_LIBDIR /usr/lib/xtables
|
||||||
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
||||||
|
|
|
@ -64,28 +64,40 @@ def refreshF2boptions():
|
||||||
global f2boptions
|
global f2boptions
|
||||||
global quit_now
|
global quit_now
|
||||||
global exit_code
|
global exit_code
|
||||||
|
|
||||||
|
f2boptions = {}
|
||||||
|
|
||||||
if not r.get('F2B_OPTIONS'):
|
if not r.get('F2B_OPTIONS'):
|
||||||
f2boptions = {}
|
f2boptions['ban_time'] = r.get('F2B_BAN_TIME')
|
||||||
f2boptions['ban_time'] = int
|
f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME')
|
||||||
f2boptions['max_attempts'] = int
|
f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT')
|
||||||
f2boptions['retry_window'] = int
|
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS')
|
||||||
f2boptions['netban_ipv4'] = int
|
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW')
|
||||||
f2boptions['netban_ipv6'] = int
|
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4')
|
||||||
f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800
|
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6')
|
||||||
f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10
|
|
||||||
f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600
|
|
||||||
f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32
|
|
||||||
f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128
|
|
||||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
f2boptions = {}
|
|
||||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print('Error loading F2B options: F2B_OPTIONS is not json')
|
print('Error loading F2B options: F2B_OPTIONS is not json')
|
||||||
quit_now = True
|
quit_now = True
|
||||||
exit_code = 2
|
exit_code = 2
|
||||||
|
|
||||||
|
verifyF2boptions(f2boptions)
|
||||||
|
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||||
|
|
||||||
|
def verifyF2boptions(f2boptions):
|
||||||
|
verifyF2boption(f2boptions,'ban_time', 1800)
|
||||||
|
verifyF2boption(f2boptions,'max_ban_time', 10000)
|
||||||
|
verifyF2boption(f2boptions,'ban_time_increment', True)
|
||||||
|
verifyF2boption(f2boptions,'max_attempts', 10)
|
||||||
|
verifyF2boption(f2boptions,'retry_window', 600)
|
||||||
|
verifyF2boption(f2boptions,'netban_ipv4', 32)
|
||||||
|
verifyF2boption(f2boptions,'netban_ipv6', 128)
|
||||||
|
|
||||||
|
def verifyF2boption(f2boptions, f2boption, f2bdefault):
|
||||||
|
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
|
||||||
|
|
||||||
def refreshF2bregex():
|
def refreshF2bregex():
|
||||||
global f2bregex
|
global f2bregex
|
||||||
global quit_now
|
global quit_now
|
||||||
|
@ -147,6 +159,7 @@ def ban(address):
|
||||||
global lock
|
global lock
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
BAN_TIME = int(f2boptions['ban_time'])
|
BAN_TIME = int(f2boptions['ban_time'])
|
||||||
|
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||||
NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
|
NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
|
||||||
|
@ -174,20 +187,16 @@ def ban(address):
|
||||||
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
||||||
net = str(net)
|
net = str(net)
|
||||||
|
|
||||||
if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
|
if not net in bans:
|
||||||
bans[net] = { 'attempts': 0 }
|
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
||||||
active_window = RETRY_WINDOW
|
|
||||||
else:
|
|
||||||
active_window = time.time() - bans[net]['last_attempt']
|
|
||||||
|
|
||||||
bans[net]['attempts'] += 1
|
bans[net]['attempts'] += 1
|
||||||
bans[net]['last_attempt'] = time.time()
|
bans[net]['last_attempt'] = time.time()
|
||||||
|
|
||||||
active_window = time.time() - bans[net]['last_attempt']
|
|
||||||
|
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||||
cur_time = int(round(time.time()))
|
cur_time = int(round(time.time()))
|
||||||
logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60))
|
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||||
|
logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
|
||||||
if type(ip) is ipaddress.IPv4Address:
|
if type(ip) is ipaddress.IPv4Address:
|
||||||
with lock:
|
with lock:
|
||||||
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
|
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW')
|
||||||
|
@ -206,7 +215,7 @@ def ban(address):
|
||||||
rule.target = target
|
rule.target = target
|
||||||
if rule not in chain.rules:
|
if rule not in chain.rules:
|
||||||
chain.insert_rule(rule)
|
chain.insert_rule(rule)
|
||||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME)
|
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||||
else:
|
else:
|
||||||
logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||||
|
|
||||||
|
@ -238,7 +247,8 @@ def unban(net):
|
||||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||||
if net in bans:
|
if net in bans:
|
||||||
del bans[net]
|
bans[net]['attempts'] = 0
|
||||||
|
bans[net]['ban_counter'] += 1
|
||||||
|
|
||||||
def permBan(net, unban=False):
|
def permBan(net, unban=False):
|
||||||
global lock
|
global lock
|
||||||
|
@ -332,7 +342,7 @@ def watch():
|
||||||
logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data']))
|
logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data']))
|
||||||
ban(addr)
|
ban(addr)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logWarn('Error reading log line from pubsub')
|
logWarn('Error reading log line from pubsub: %s' % ex)
|
||||||
quit_now = True
|
quit_now = True
|
||||||
exit_code = 2
|
exit_code = 2
|
||||||
|
|
||||||
|
@ -359,21 +369,30 @@ def snat4(snat_target):
|
||||||
chain = iptc.Chain(table, 'POSTROUTING')
|
chain = iptc.Chain(table, 'POSTROUTING')
|
||||||
table.autocommit = False
|
table.autocommit = False
|
||||||
new_rule = get_snat4_rule()
|
new_rule = get_snat4_rule()
|
||||||
for position, rule in enumerate(chain.rules):
|
|
||||||
match = all((
|
if not chain.rules:
|
||||||
new_rule.get_src() == rule.get_src(),
|
# if there are no rules in the chain, insert the new rule directly
|
||||||
new_rule.get_dst() == rule.get_dst(),
|
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
|
||||||
new_rule.target.parameters == rule.target.parameters,
|
chain.insert_rule(new_rule)
|
||||||
new_rule.target.name == rule.target.name
|
else:
|
||||||
))
|
for position, rule in enumerate(chain.rules):
|
||||||
if position == 0:
|
if not hasattr(rule.target, 'parameter'):
|
||||||
if not match:
|
continue
|
||||||
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
|
match = all((
|
||||||
chain.insert_rule(new_rule)
|
new_rule.get_src() == rule.get_src(),
|
||||||
else:
|
new_rule.get_dst() == rule.get_dst(),
|
||||||
if match:
|
new_rule.target.parameters == rule.target.parameters,
|
||||||
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
|
new_rule.target.name == rule.target.name
|
||||||
chain.delete_rule(rule)
|
))
|
||||||
|
if position == 0:
|
||||||
|
if not match:
|
||||||
|
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
|
||||||
|
chain.insert_rule(new_rule)
|
||||||
|
else:
|
||||||
|
if match:
|
||||||
|
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
|
||||||
|
chain.delete_rule(rule)
|
||||||
|
|
||||||
table.commit()
|
table.commit()
|
||||||
table.autocommit = True
|
table.autocommit = True
|
||||||
except:
|
except:
|
||||||
|
@ -418,6 +437,8 @@ def autopurge():
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
BAN_TIME = int(f2boptions['ban_time'])
|
BAN_TIME = int(f2boptions['ban_time'])
|
||||||
|
MAX_BAN_TIME = int(f2boptions['max_ban_time'])
|
||||||
|
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||||
if QUEUE_UNBAN:
|
if QUEUE_UNBAN:
|
||||||
|
@ -425,7 +446,9 @@ def autopurge():
|
||||||
unban(str(net))
|
unban(str(net))
|
||||||
for net in bans.copy():
|
for net in bans.copy():
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||||
if time.time() - bans[net]['last_attempt'] > BAN_TIME:
|
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||||
|
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
|
||||||
|
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME:
|
||||||
unban(net)
|
unban(net)
|
||||||
|
|
||||||
def isIpNetwork(address):
|
def isIpNetwork(address):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM php:8.1-fpm-alpine3.17
|
FROM php:8.2-fpm-alpine3.17
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
|
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
|
||||||
ARG APCU_PECL_VERSION=5.1.22
|
ARG APCU_PECL_VERSION=5.1.22
|
||||||
|
@ -12,7 +12,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
|
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
|
||||||
ARG REDIS_PECL_VERSION=5.3.7
|
ARG REDIS_PECL_VERSION=5.3.7
|
||||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
|
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
|
||||||
ARG COMPOSER_VERSION=2.5.1
|
ARG COMPOSER_VERSION=2.5.5
|
||||||
|
|
||||||
RUN apk add -U --no-cache autoconf \
|
RUN apk add -U --no-cache autoconf \
|
||||||
aspell-dev \
|
aspell-dev \
|
||||||
|
@ -52,6 +52,7 @@ RUN apk add -U --no-cache autoconf \
|
||||||
libxpm-dev \
|
libxpm-dev \
|
||||||
libzip \
|
libzip \
|
||||||
libzip-dev \
|
libzip-dev \
|
||||||
|
linux-headers \
|
||||||
make \
|
make \
|
||||||
mysql-client \
|
mysql-client \
|
||||||
openldap-dev \
|
openldap-dev \
|
||||||
|
@ -75,7 +76,7 @@ RUN apk add -U --no-cache autoconf \
|
||||||
--with-webp \
|
--with-webp \
|
||||||
--with-xpm \
|
--with-xpm \
|
||||||
--with-avif \
|
--with-avif \
|
||||||
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \
|
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \
|
||||||
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \
|
&& docker-php-ext-configure imap --with-imap --with-imap-ssl \
|
||||||
&& docker-php-ext-install -j 4 imap \
|
&& docker-php-ext-install -j 4 imap \
|
||||||
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
|
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
|
||||||
|
@ -99,6 +100,7 @@ RUN apk add -U --no-cache autoconf \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxpm-dev \
|
libxpm-dev \
|
||||||
libzip-dev \
|
libzip-dev \
|
||||||
|
linux-headers \
|
||||||
make \
|
make \
|
||||||
openldap-dev \
|
openldap-dev \
|
||||||
pcre-dev \
|
pcre-dev \
|
||||||
|
|
|
@ -172,6 +172,24 @@ BEGIN
|
||||||
END;
|
END;
|
||||||
//
|
//
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
DROP EVENT IF EXISTS clean_sasl_log;
|
||||||
|
DELIMITER //
|
||||||
|
CREATE EVENT clean_sasl_log
|
||||||
|
ON SCHEDULE EVERY 1 DAY DO
|
||||||
|
BEGIN
|
||||||
|
DELETE sasl_log.* FROM sasl_log
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT username, service, MAX(datetime) AS lastdate
|
||||||
|
FROM sasl_log
|
||||||
|
GROUP BY username, service
|
||||||
|
) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service
|
||||||
|
WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate;
|
||||||
|
DELETE FROM sasl_log
|
||||||
|
WHERE username NOT IN (SELECT username FROM mailbox) AND
|
||||||
|
datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
|
||||||
|
END;
|
||||||
|
//
|
||||||
|
DELIMITER ;
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ENV LC_ALL C
|
ENV LC_ALL C
|
||||||
|
@ -17,10 +17,10 @@ RUN groupadd -g 102 postfix \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
dirmngr \
|
dirmngr \
|
||||||
dnsutils \
|
dnsutils \
|
||||||
gnupg \
|
gnupg \
|
||||||
libsasl2-modules \
|
libsasl2-modules \
|
||||||
mariadb-client \
|
mariadb-client \
|
||||||
perl \
|
perl \
|
||||||
postfix \
|
postfix \
|
||||||
postfix-mysql \
|
postfix-mysql \
|
||||||
|
@ -32,7 +32,7 @@ RUN groupadd -g 102 postfix \
|
||||||
syslog-ng \
|
syslog-ng \
|
||||||
syslog-ng-core \
|
syslog-ng-core \
|
||||||
syslog-ng-mod-redis \
|
syslog-ng-mod-redis \
|
||||||
tzdata \
|
tzdata \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& touch /etc/default/locale \
|
&& touch /etc/default/locale \
|
||||||
&& printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \
|
&& printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \
|
||||||
|
|
|
@ -393,12 +393,101 @@ query = SELECT goto FROM spamalias
|
||||||
AND validity >= UNIX_TIMESTAMP()
|
AND validity >= UNIX_TIMESTAMP()
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sed -i '/User overrides/q' /opt/postfix/conf/main.cf
|
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
||||||
|
cat <<EOF > /opt/postfix/conf/dns_blocklists.cf
|
||||||
|
# This file can be edited.
|
||||||
|
# Delete this file and restart postfix container to revert any changes.
|
||||||
|
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
||||||
|
hostkarma.junkemailfilter.com=127.0.0.1*-2
|
||||||
|
list.dnswl.org=127.0.[0..255].0*-2
|
||||||
|
list.dnswl.org=127.0.[0..255].1*-4
|
||||||
|
list.dnswl.org=127.0.[0..255].2*-6
|
||||||
|
list.dnswl.org=127.0.[0..255].3*-8
|
||||||
|
ix.dnsbl.manitu.net*2
|
||||||
|
bl.spamcop.net*2
|
||||||
|
bl.suomispam.net*2
|
||||||
|
hostkarma.junkemailfilter.com=127.0.0.2*3
|
||||||
|
hostkarma.junkemailfilter.com=127.0.0.4*2
|
||||||
|
hostkarma.junkemailfilter.com=127.0.1.2*1
|
||||||
|
backscatter.spameatingmonkey.net*2
|
||||||
|
bl.ipv6.spameatingmonkey.net*2
|
||||||
|
bl.spameatingmonkey.net*2
|
||||||
|
b.barracudacentral.org=127.0.0.2*7
|
||||||
|
bl.mailspike.net=127.0.0.2*5
|
||||||
|
bl.mailspike.net=127.0.0.[10;11;12]*4
|
||||||
|
dnsbl.sorbs.net=127.0.0.10*8
|
||||||
|
dnsbl.sorbs.net=127.0.0.5*6
|
||||||
|
dnsbl.sorbs.net=127.0.0.7*3
|
||||||
|
dnsbl.sorbs.net=127.0.0.8*2
|
||||||
|
dnsbl.sorbs.net=127.0.0.6*2
|
||||||
|
dnsbl.sorbs.net=127.0.0.9*2
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
|
||||||
|
|
||||||
|
if [ ! -z "$DNSBL_CONFIG" ]; then
|
||||||
|
echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m"
|
||||||
|
if [ -n "$SPAMHAUS_DQS_KEY" ]; then
|
||||||
|
echo -e "\e[32mDetected SPAMHAUS_DQS_KEY variable from mailcow.conf...\e[0m"
|
||||||
|
echo -e "\e[33mUsing DQS Blocklists from Spamhaus!\e[0m"
|
||||||
|
SPAMHAUS_DNSBL_CONFIG=$(cat <<EOF
|
||||||
|
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.[4..7]*6
|
||||||
|
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.[10;11]*8
|
||||||
|
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.3*4
|
||||||
|
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net=127.0.0.2*3
|
||||||
|
postscreen_dnsbl_reply_map = texthash:/opt/postfix/conf/dnsbl_reply.map
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > /opt/postfix/conf/dnsbl_reply.map
|
||||||
|
# Autogenerated by mailcow, using Spamhaus DQS reply domains
|
||||||
|
${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net sbl.spamhaus.org
|
||||||
|
${SPAMHAUS_DQS_KEY}.xbl.dq.spamhaus.net xbl.spamhaus.org
|
||||||
|
${SPAMHAUS_DQS_KEY}.pbl.dq.spamhaus.net pbl.spamhaus.org
|
||||||
|
${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net zen.spamhaus.org
|
||||||
|
${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net dbl.spamhaus.org
|
||||||
|
${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net zrd.spamhaus.org
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
else
|
||||||
|
if [ -f "/opt/postfix/conf/dnsbl_reply.map" ]; then
|
||||||
|
rm /opt/postfix/conf/dnsbl_reply.map
|
||||||
|
fi
|
||||||
|
response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
|
||||||
|
if [ "$response" -eq 503 ]; then
|
||||||
|
echo -e "\e[31mThe AS of your IP is listed as a banned AS from Spamhaus!\e[0m"
|
||||||
|
echo -e "\e[33mNo SPAMHAUS_DQS_KEY found... Skipping Spamhaus blocklists entirely!\e[0m"
|
||||||
|
SPAMHAUS_DNSBL_CONFIG=""
|
||||||
|
elif [ "$response" -eq 200 ]; then
|
||||||
|
echo -e "\e[32mThe AS of your IP is NOT listed as a banned AS from Spamhaus!\e[0m"
|
||||||
|
echo -e "\e[33mUsing the open Spamhaus blocklists.\e[0m"
|
||||||
|
SPAMHAUS_DNSBL_CONFIG=$(cat <<EOF
|
||||||
|
zen.spamhaus.org=127.0.0.[10;11]*8
|
||||||
|
zen.spamhaus.org=127.0.0.[4..7]*6
|
||||||
|
zen.spamhaus.org=127.0.0.3*4
|
||||||
|
zen.spamhaus.org=127.0.0.2*3
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
else
|
||||||
|
echo -e "\e[31mWe couldn't determine your AS... (maybe DNS/Network issue?) Response Code: $response\e[0m"
|
||||||
|
echo -e "\e[33mDeactivating Spamhaus DNS Blocklists to be on the safe site!\e[0m"
|
||||||
|
SPAMHAUS_DNSBL_CONFIG=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reset main.cf
|
||||||
|
sed -i '/Overrides/q' /opt/postfix/conf/main.cf
|
||||||
echo >> /opt/postfix/conf/main.cf
|
echo >> /opt/postfix/conf/main.cf
|
||||||
|
# Append postscreen dnsbl sites to main.cf
|
||||||
|
if [ ! -z "$DNSBL_CONFIG" ]; then
|
||||||
|
echo -e "${DNSBL_CONFIG}\n${SPAMHAUS_DNSBL_CONFIG}" >> /opt/postfix/conf/main.cf
|
||||||
|
fi
|
||||||
|
# Append user overrides
|
||||||
|
echo -e "\n# User Overrides" >> /opt/postfix/conf/main.cf
|
||||||
touch /opt/postfix/conf/extra.cf
|
touch /opt/postfix/conf/extra.cf
|
||||||
sed -i '/myhostname/d' /opt/postfix/conf/extra.cf
|
sed -i '/myhostname/d' /opt/postfix/conf/extra.cf
|
||||||
echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf
|
echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf
|
||||||
|
|
||||||
cat /opt/postfix/conf/extra.cf >> /opt/postfix/conf/main.cf
|
cat /opt/postfix/conf/extra.cf >> /opt/postfix/conf/main.cf
|
||||||
|
|
||||||
if [ ! -f /opt/postfix/conf/custom_transport.pcre ]; then
|
if [ ! -f /opt/postfix/conf/custom_transport.pcre ]; then
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "Andre Peters <andre.peters@tinc.gmbh>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG CODENAME=bullseye
|
ARG CODENAME=bullseye
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM debian:bullseye-slim
|
FROM debian:bullseye-slim
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company 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://packages.sogo.nu/nightly/5/debian/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
FROM alpine:3.17
|
FROM alpine:3.17
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
curl \
|
curl \
|
||||||
|
|
|
@ -24,7 +24,7 @@ server {
|
||||||
add_header X-Download-Options "noopen" always;
|
add_header X-Download-Options "noopen" always;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||||
add_header X-Robots-Tag "none" always;
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
fastcgi_hide_header X-Powered-By;
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
|
|
@ -24,6 +24,11 @@ mail_plugins = </etc/dovecot/mail_plugins
|
||||||
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
|
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
|
||||||
mail_attachment_dir = /var/attachments
|
mail_attachment_dir = /var/attachments
|
||||||
mail_attachment_min_size = 128k
|
mail_attachment_min_size = 128k
|
||||||
|
# Significantly speeds up very large mailboxes, but is only safe to enable if
|
||||||
|
# you do not manually modify the files in the `cur` directories in
|
||||||
|
# mailcowdockerized_vmail-vol-1.
|
||||||
|
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
|
||||||
|
maildir_very_dirty_syncs = yes
|
||||||
|
|
||||||
# Dovecot 2.2
|
# Dovecot 2.2
|
||||||
#ssl_protocols = !SSLv3
|
#ssl_protocols = !SSLv3
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
error_page 403 /_rspamderror.php;
|
error_page 401 /_rspamderror.php;
|
||||||
}
|
}
|
||||||
proxy_pass http://rspamd:11334/;
|
proxy_pass http://rspamd:11334/;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
|
@ -40,34 +40,6 @@ postscreen_blacklist_action = drop
|
||||||
postscreen_cache_cleanup_interval = 24h
|
postscreen_cache_cleanup_interval = 24h
|
||||||
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
||||||
postscreen_dnsbl_action = enforce
|
postscreen_dnsbl_action = enforce
|
||||||
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
|
||||||
hostkarma.junkemailfilter.com=127.0.0.1*-2
|
|
||||||
list.dnswl.org=127.0.[0..255].0*-2
|
|
||||||
list.dnswl.org=127.0.[0..255].1*-4
|
|
||||||
list.dnswl.org=127.0.[0..255].2*-6
|
|
||||||
list.dnswl.org=127.0.[0..255].3*-8
|
|
||||||
ix.dnsbl.manitu.net*2
|
|
||||||
bl.spamcop.net*2
|
|
||||||
bl.suomispam.net*2
|
|
||||||
hostkarma.junkemailfilter.com=127.0.0.2*3
|
|
||||||
hostkarma.junkemailfilter.com=127.0.0.4*2
|
|
||||||
hostkarma.junkemailfilter.com=127.0.1.2*1
|
|
||||||
backscatter.spameatingmonkey.net*2
|
|
||||||
bl.ipv6.spameatingmonkey.net*2
|
|
||||||
bl.spameatingmonkey.net*2
|
|
||||||
b.barracudacentral.org=127.0.0.2*7
|
|
||||||
bl.mailspike.net=127.0.0.2*5
|
|
||||||
bl.mailspike.net=127.0.0.[10;11;12]*4
|
|
||||||
dnsbl.sorbs.net=127.0.0.10*8
|
|
||||||
dnsbl.sorbs.net=127.0.0.5*6
|
|
||||||
dnsbl.sorbs.net=127.0.0.7*3
|
|
||||||
dnsbl.sorbs.net=127.0.0.8*2
|
|
||||||
dnsbl.sorbs.net=127.0.0.6*2
|
|
||||||
dnsbl.sorbs.net=127.0.0.9*2
|
|
||||||
zen.spamhaus.org=127.0.0.[10;11]*8
|
|
||||||
zen.spamhaus.org=127.0.0.[4..7]*6
|
|
||||||
zen.spamhaus.org=127.0.0.3*4
|
|
||||||
zen.spamhaus.org=127.0.0.2*3
|
|
||||||
postscreen_dnsbl_threshold = 6
|
postscreen_dnsbl_threshold = 6
|
||||||
postscreen_dnsbl_ttl = 5m
|
postscreen_dnsbl_ttl = 5m
|
||||||
postscreen_greet_action = enforce
|
postscreen_greet_action = enforce
|
||||||
|
@ -197,4 +169,4 @@ smtps_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||||
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
|
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
|
||||||
|
|
||||||
# DO NOT EDIT ANYTHING BELOW #
|
# DO NOT EDIT ANYTHING BELOW #
|
||||||
# User overrides #
|
# Overrides #
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
# Whitelist generated by Postwhite v3.4 on Mon 21 Mar 2022 06:50:26 PM CET
|
# Whitelist generated by Postwhite v3.4 on Mon Jul 31 10:06:06 UTC 2023
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 1898 total rules
|
# 2043 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::/48 permit
|
2a01:111:f403:8000::/50 permit
|
||||||
2a01:4180:4050:0400::/64 permit
|
2a01:111:f403::/49 permit
|
||||||
2a01:4180:4050:0800::/64 permit
|
2a01:111:f403:c000::/51 permit
|
||||||
2a01:4180:4051:0400::/64 permit
|
2a01:111:f403:f000::/52 permit
|
||||||
2a01:4180:4051:0800::/64 permit
|
|
||||||
2a02:a60:0:5::/64 permit
|
2a02:a60:0:5::/64 permit
|
||||||
2c0f:fb50:4000::/36 permit
|
2c0f:fb50:4000::/36 permit
|
||||||
|
2.207.151.53 permit
|
||||||
|
3.14.230.16 permit
|
||||||
|
3.70.123.177 permit
|
||||||
|
3.93.157.0/24 permit
|
||||||
|
3.129.120.190 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
|
||||||
8.25.196.0/23 permit
|
8.25.196.0/23 permit
|
||||||
|
@ -19,41 +24,53 @@
|
||||||
13.70.32.43 permit
|
13.70.32.43 permit
|
||||||
13.72.50.45 permit
|
13.72.50.45 permit
|
||||||
13.74.143.28 permit
|
13.74.143.28 permit
|
||||||
13.77.161.179 permit
|
|
||||||
13.78.233.182 permit
|
13.78.233.182 permit
|
||||||
13.92.31.129 permit
|
13.92.31.129 permit
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
|
13.110.209.0/24 permit
|
||||||
13.110.216.0/22 permit
|
13.110.216.0/22 permit
|
||||||
13.110.224.0/20 permit
|
13.110.224.0/20 permit
|
||||||
13.111.0.0/16 permit
|
13.111.0.0/16 permit
|
||||||
17.41.0.0/16 permit
|
15.200.21.50 permit
|
||||||
|
15.200.44.248 permit
|
||||||
|
15.200.201.185 permit
|
||||||
17.57.155.0/24 permit
|
17.57.155.0/24 permit
|
||||||
17.57.156.0/24 permit
|
17.57.156.0/24 permit
|
||||||
17.58.0.0/16 permit
|
17.58.0.0/16 permit
|
||||||
17.110.0.0/15 permit
|
18.156.89.250 permit
|
||||||
17.142.0.0/15 permit
|
18.157.243.190 permit
|
||||||
17.162.0.0/15 permit
|
|
||||||
17.164.0.0/16 permit
|
|
||||||
17.171.37.0/24 permit
|
|
||||||
17.172.0.0/16 permit
|
|
||||||
17.179.168.0/23 permit
|
|
||||||
18.194.95.56 permit
|
18.194.95.56 permit
|
||||||
18.198.96.88 permit
|
18.198.96.88 permit
|
||||||
20.47.149.138 permit
|
18.208.124.128/25 permit
|
||||||
20.48.0.0/12 permit
|
18.216.232.154 permit
|
||||||
|
18.234.1.244 permit
|
||||||
|
18.236.40.242 permit
|
||||||
|
20.51.6.32/30 permit
|
||||||
20.52.52.2 permit
|
20.52.52.2 permit
|
||||||
20.52.128.133 permit
|
20.52.128.133 permit
|
||||||
|
20.59.80.4/30 permit
|
||||||
20.63.210.192/28 permit
|
20.63.210.192/28 permit
|
||||||
20.64.0.0/10 permit
|
20.69.8.108/30 permit
|
||||||
|
20.70.246.20 permit
|
||||||
|
20.76.201.171 permit
|
||||||
|
20.83.222.104/30 permit
|
||||||
|
20.88.157.184/30 permit
|
||||||
20.94.180.64/28 permit
|
20.94.180.64/28 permit
|
||||||
|
20.97.34.220/30 permit
|
||||||
|
20.98.148.156/30 permit
|
||||||
|
20.98.194.68/30 permit
|
||||||
|
20.105.209.76/30 permit
|
||||||
|
20.107.239.64/30 permit
|
||||||
|
20.112.250.133 permit
|
||||||
|
20.118.139.208/30 permit
|
||||||
20.185.213.160/27 permit
|
20.185.213.160/27 permit
|
||||||
20.185.213.224/27 permit
|
20.185.213.224/27 permit
|
||||||
20.185.214.0/27 permit
|
20.185.214.0/27 permit
|
||||||
20.185.214.2 permit
|
20.185.214.2 permit
|
||||||
20.185.214.32/27 permit
|
20.185.214.32/27 permit
|
||||||
20.185.214.64/27 permit
|
20.185.214.64/27 permit
|
||||||
20.192.0.0/10 permit
|
20.231.239.246 permit
|
||||||
23.100.85.1 permit
|
20.236.44.162 permit
|
||||||
23.103.224.0/19 permit
|
23.103.224.0/19 permit
|
||||||
23.249.208.0/20 permit
|
23.249.208.0/20 permit
|
||||||
23.251.224.0/19 permit
|
23.251.224.0/19 permit
|
||||||
|
@ -78,46 +95,38 @@
|
||||||
27.123.206.56/29 permit
|
27.123.206.56/29 permit
|
||||||
27.123.206.76/30 permit
|
27.123.206.76/30 permit
|
||||||
27.123.206.80/28 permit
|
27.123.206.80/28 permit
|
||||||
34.194.25.167 permit
|
31.25.48.222 permit
|
||||||
34.194.144.120 permit
|
34.195.217.107 permit
|
||||||
|
34.202.239.6 permit
|
||||||
34.212.163.75 permit
|
34.212.163.75 permit
|
||||||
|
34.215.104.144 permit
|
||||||
34.225.212.172 permit
|
34.225.212.172 permit
|
||||||
34.247.168.44 permit
|
34.247.168.44 permit
|
||||||
|
35.161.32.253 permit
|
||||||
|
35.167.93.243 permit
|
||||||
35.176.132.251 permit
|
35.176.132.251 permit
|
||||||
35.190.247.0/24 permit
|
35.190.247.0/24 permit
|
||||||
35.191.0.0/16 permit
|
35.191.0.0/16 permit
|
||||||
37.188.97.188 permit
|
|
||||||
37.218.248.47 permit
|
37.218.248.47 permit
|
||||||
37.218.249.47 permit
|
37.218.249.47 permit
|
||||||
37.218.251.62 permit
|
37.218.251.62 permit
|
||||||
39.156.163.64/29 permit
|
39.156.163.64/29 permit
|
||||||
40.71.187.0/24 permit
|
40.71.187.0/24 permit
|
||||||
40.76.4.15 permit
|
|
||||||
40.77.102.222 permit
|
|
||||||
40.92.0.0/15 permit
|
40.92.0.0/15 permit
|
||||||
40.97.116.82 permit
|
|
||||||
40.97.128.194 permit
|
|
||||||
40.97.148.226 permit
|
|
||||||
40.97.153.146 permit
|
|
||||||
40.97.156.114 permit
|
|
||||||
40.97.160.2 permit
|
|
||||||
40.97.161.50 permit
|
|
||||||
40.97.164.146 permit
|
|
||||||
40.107.0.0/16 permit
|
40.107.0.0/16 permit
|
||||||
40.112.65.63 permit
|
40.112.65.63 permit
|
||||||
40.112.72.205 permit
|
|
||||||
40.113.200.201 permit
|
|
||||||
40.117.80.0/24 permit
|
40.117.80.0/24 permit
|
||||||
40.121.71.46 permit
|
|
||||||
41.74.192.0/22 permit
|
41.74.192.0/22 permit
|
||||||
41.74.196.0/22 permit
|
41.74.196.0/22 permit
|
||||||
41.74.200.0/23 permit
|
41.74.200.0/23 permit
|
||||||
41.74.204.0/23 permit
|
41.74.204.0/23 permit
|
||||||
41.74.206.0/24 permit
|
41.74.206.0/24 permit
|
||||||
42.159.163.81 permit
|
|
||||||
42.159.163.82 permit
|
|
||||||
42.159.163.83 permit
|
|
||||||
43.228.184.0/22 permit
|
43.228.184.0/22 permit
|
||||||
|
44.206.138.57 permit
|
||||||
|
44.209.42.157 permit
|
||||||
|
44.236.56.93 permit
|
||||||
|
44.238.220.251 permit
|
||||||
|
46.19.168.0/23 permit
|
||||||
46.226.48.0/21 permit
|
46.226.48.0/21 permit
|
||||||
46.228.36.37 permit
|
46.228.36.37 permit
|
||||||
46.228.36.38/31 permit
|
46.228.36.38/31 permit
|
||||||
|
@ -167,6 +176,8 @@
|
||||||
46.243.88.175 permit
|
46.243.88.175 permit
|
||||||
46.243.88.176 permit
|
46.243.88.176 permit
|
||||||
46.243.88.177 permit
|
46.243.88.177 permit
|
||||||
|
46.243.95.179 permit
|
||||||
|
46.243.95.180 permit
|
||||||
50.18.45.249 permit
|
50.18.45.249 permit
|
||||||
50.18.121.236 permit
|
50.18.121.236 permit
|
||||||
50.18.121.248 permit
|
50.18.121.248 permit
|
||||||
|
@ -178,11 +189,6 @@
|
||||||
50.31.32.0/19 permit
|
50.31.32.0/19 permit
|
||||||
50.31.156.96/27 permit
|
50.31.156.96/27 permit
|
||||||
50.31.205.0/24 permit
|
50.31.205.0/24 permit
|
||||||
51.4.71.62 permit
|
|
||||||
51.4.72.0/24 permit
|
|
||||||
51.4.80.0/27 permit
|
|
||||||
51.5.72.0/24 permit
|
|
||||||
51.5.80.0/27 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
|
||||||
|
@ -191,17 +197,28 @@
|
||||||
52.5.230.59 permit
|
52.5.230.59 permit
|
||||||
52.27.5.72 permit
|
52.27.5.72 permit
|
||||||
52.27.28.47 permit
|
52.27.28.47 permit
|
||||||
52.33.191.91 permit
|
52.28.63.81 permit
|
||||||
52.36.138.31 permit
|
52.36.138.31 permit
|
||||||
52.37.142.146 permit
|
52.37.142.146 permit
|
||||||
52.38.191.253 permit
|
52.58.216.183 permit
|
||||||
52.41.64.145 permit
|
52.59.143.3 permit
|
||||||
52.60.41.5 permit
|
52.60.41.5 permit
|
||||||
52.60.115.116 permit
|
52.60.115.116 permit
|
||||||
|
52.61.91.9 permit
|
||||||
|
52.71.0.205 permit
|
||||||
52.82.172.0/22 permit
|
52.82.172.0/22 permit
|
||||||
52.94.124.0/28 permit
|
52.94.124.0/28 permit
|
||||||
52.95.48.152/29 permit
|
52.95.48.152/29 permit
|
||||||
52.95.49.88/29 permit
|
52.95.49.88/29 permit
|
||||||
|
52.96.91.34 permit
|
||||||
|
52.96.111.82 permit
|
||||||
|
52.96.172.98 permit
|
||||||
|
52.96.214.50 permit
|
||||||
|
52.96.222.194 permit
|
||||||
|
52.96.222.226 permit
|
||||||
|
52.96.223.2 permit
|
||||||
|
52.96.228.130 permit
|
||||||
|
52.96.229.242 permit
|
||||||
52.100.0.0/14 permit
|
52.100.0.0/14 permit
|
||||||
52.119.213.144/28 permit
|
52.119.213.144/28 permit
|
||||||
52.160.39.140 permit
|
52.160.39.140 permit
|
||||||
|
@ -214,23 +231,29 @@
|
||||||
52.222.73.83 permit
|
52.222.73.83 permit
|
||||||
52.222.73.120 permit
|
52.222.73.120 permit
|
||||||
52.222.75.85 permit
|
52.222.75.85 permit
|
||||||
|
52.222.89.228 permit
|
||||||
52.234.172.96/28 permit
|
52.234.172.96/28 permit
|
||||||
52.236.28.240/28 permit
|
52.236.28.240/28 permit
|
||||||
52.237.141.173 permit
|
|
||||||
52.244.206.214 permit
|
52.244.206.214 permit
|
||||||
52.247.53.144 permit
|
52.247.53.144 permit
|
||||||
52.250.107.196 permit
|
52.250.107.196 permit
|
||||||
52.250.126.174 permit
|
52.250.126.174 permit
|
||||||
52.251.55.143 permit
|
|
||||||
54.90.148.255 permit
|
54.90.148.255 permit
|
||||||
54.156.255.69 permit
|
|
||||||
54.172.97.247 permit
|
54.172.97.247 permit
|
||||||
|
54.174.52.0/24 permit
|
||||||
|
54.174.53.128/30 permit
|
||||||
|
54.174.57.0/24 permit
|
||||||
|
54.174.59.0/24 permit
|
||||||
|
54.174.60.0/23 permit
|
||||||
|
54.174.63.0/24 permit
|
||||||
54.186.193.102 permit
|
54.186.193.102 permit
|
||||||
54.191.223.5 permit
|
54.191.223.56 permit
|
||||||
54.194.61.95 permit
|
54.194.61.95 permit
|
||||||
54.195.113.45 permit
|
54.195.113.45 permit
|
||||||
|
54.213.20.246 permit
|
||||||
54.214.39.184 permit
|
54.214.39.184 permit
|
||||||
54.216.77.168 permit
|
54.216.77.168 permit
|
||||||
|
54.221.227.204 permit
|
||||||
54.240.0.0/18 permit
|
54.240.0.0/18 permit
|
||||||
54.240.64.0/19 permit
|
54.240.64.0/19 permit
|
||||||
54.240.96.0/19 permit
|
54.240.96.0/19 permit
|
||||||
|
@ -238,7 +261,9 @@
|
||||||
54.244.54.130 permit
|
54.244.54.130 permit
|
||||||
54.244.242.0/24 permit
|
54.244.242.0/24 permit
|
||||||
54.246.232.180 permit
|
54.246.232.180 permit
|
||||||
|
54.255.61.23 permit
|
||||||
62.13.128.0/24 permit
|
62.13.128.0/24 permit
|
||||||
|
62.13.128.150 permit
|
||||||
62.13.129.128/25 permit
|
62.13.129.128/25 permit
|
||||||
62.13.136.0/22 permit
|
62.13.136.0/22 permit
|
||||||
62.13.140.0/22 permit
|
62.13.140.0/22 permit
|
||||||
|
@ -249,22 +274,29 @@
|
||||||
62.17.146.128/26 permit
|
62.17.146.128/26 permit
|
||||||
62.140.7.0/24 permit
|
62.140.7.0/24 permit
|
||||||
62.140.10.21 permit
|
62.140.10.21 permit
|
||||||
|
62.179.121.0/24 permit
|
||||||
|
62.201.172.0/27 permit
|
||||||
|
62.201.172.32/27 permit
|
||||||
|
62.253.227.114 permit
|
||||||
63.32.13.159 permit
|
63.32.13.159 permit
|
||||||
63.80.14.0/23 permit
|
63.80.14.0/23 permit
|
||||||
|
63.111.28.137 permit
|
||||||
63.128.21.0/24 permit
|
63.128.21.0/24 permit
|
||||||
63.143.57.128/25 permit
|
63.143.57.128/25 permit
|
||||||
63.143.59.128/25 permit
|
63.143.59.128/25 permit
|
||||||
64.18.0.0/20 permit
|
64.18.0.0/20 permit
|
||||||
64.20.241.45 permit
|
64.20.241.45 permit
|
||||||
64.34.47.128/27 permit
|
64.69.212.0/24 permit
|
||||||
64.34.57.192/26 permit
|
|
||||||
64.71.149.160/28 permit
|
64.71.149.160/28 permit
|
||||||
64.79.155.0/24 permit
|
64.79.155.0/24 permit
|
||||||
|
64.79.155.192 permit
|
||||||
|
64.79.155.193 permit
|
||||||
|
64.79.155.205 permit
|
||||||
|
64.79.155.206 permit
|
||||||
64.89.44.85 permit
|
64.89.44.85 permit
|
||||||
64.89.45.80 permit
|
64.89.45.80 permit
|
||||||
64.89.45.194 permit
|
64.89.45.194 permit
|
||||||
64.89.45.196 permit
|
64.89.45.196 permit
|
||||||
64.95.144.196 permit
|
|
||||||
64.127.115.252 permit
|
64.127.115.252 permit
|
||||||
64.132.88.0/23 permit
|
64.132.88.0/23 permit
|
||||||
64.132.92.0/24 permit
|
64.132.92.0/24 permit
|
||||||
|
@ -290,6 +322,7 @@
|
||||||
64.207.219.71 permit
|
64.207.219.71 permit
|
||||||
64.207.219.72 permit
|
64.207.219.72 permit
|
||||||
64.207.219.73 permit
|
64.207.219.73 permit
|
||||||
|
64.207.219.75 permit
|
||||||
64.207.219.77 permit
|
64.207.219.77 permit
|
||||||
64.207.219.78 permit
|
64.207.219.78 permit
|
||||||
64.207.219.79 permit
|
64.207.219.79 permit
|
||||||
|
@ -300,9 +333,6 @@
|
||||||
64.207.219.142 permit
|
64.207.219.142 permit
|
||||||
64.207.219.143 permit
|
64.207.219.143 permit
|
||||||
64.233.160.0/19 permit
|
64.233.160.0/19 permit
|
||||||
65.38.115.76 permit
|
|
||||||
65.38.115.84 permit
|
|
||||||
65.39.215.0/24 permit
|
|
||||||
65.52.80.137 permit
|
65.52.80.137 permit
|
||||||
65.54.51.64/26 permit
|
65.54.51.64/26 permit
|
||||||
65.54.61.64/26 permit
|
65.54.61.64/26 permit
|
||||||
|
@ -342,6 +372,10 @@
|
||||||
66.111.4.225 permit
|
66.111.4.225 permit
|
||||||
66.111.4.229 permit
|
66.111.4.229 permit
|
||||||
66.111.4.230 permit
|
66.111.4.230 permit
|
||||||
|
66.119.150.192/26 permit
|
||||||
|
66.135.202.0/27 permit
|
||||||
|
66.135.215.0/24 permit
|
||||||
|
66.135.222.1 permit
|
||||||
66.162.193.226/31 permit
|
66.162.193.226/31 permit
|
||||||
66.163.184.0/21 permit
|
66.163.184.0/21 permit
|
||||||
66.163.184.0/24 permit
|
66.163.184.0/24 permit
|
||||||
|
@ -373,7 +407,8 @@
|
||||||
66.196.81.234 permit
|
66.196.81.234 permit
|
||||||
66.211.168.230/31 permit
|
66.211.168.230/31 permit
|
||||||
66.211.170.86/31 permit
|
66.211.170.86/31 permit
|
||||||
66.211.170.88/30 permit
|
66.211.170.88/29 permit
|
||||||
|
66.211.184.0/23 permit
|
||||||
66.218.74.64/30 permit
|
66.218.74.64/30 permit
|
||||||
66.218.74.68/31 permit
|
66.218.74.68/31 permit
|
||||||
66.218.75.112/30 permit
|
66.218.75.112/30 permit
|
||||||
|
@ -445,6 +480,8 @@
|
||||||
68.142.230.72/30 permit
|
68.142.230.72/30 permit
|
||||||
68.142.230.76/31 permit
|
68.142.230.76/31 permit
|
||||||
68.142.230.78 permit
|
68.142.230.78 permit
|
||||||
|
68.232.140.138 permit
|
||||||
|
68.232.157.143 permit
|
||||||
68.232.192.0/20 permit
|
68.232.192.0/20 permit
|
||||||
69.63.178.128/25 permit
|
69.63.178.128/25 permit
|
||||||
69.63.181.0/24 permit
|
69.63.181.0/24 permit
|
||||||
|
@ -452,6 +489,10 @@
|
||||||
69.65.42.195 permit
|
69.65.42.195 permit
|
||||||
69.65.49.192/29 permit
|
69.65.49.192/29 permit
|
||||||
69.72.32.0/20 permit
|
69.72.32.0/20 permit
|
||||||
|
69.72.40.93 permit
|
||||||
|
69.72.40.94/31 permit
|
||||||
|
69.72.40.96/30 permit
|
||||||
|
69.72.47.205 permit
|
||||||
69.147.84.227 permit
|
69.147.84.227 permit
|
||||||
69.162.98.0/24 permit
|
69.162.98.0/24 permit
|
||||||
69.169.224.0/20 permit
|
69.169.224.0/20 permit
|
||||||
|
@ -460,7 +501,7 @@
|
||||||
70.37.151.128/25 permit
|
70.37.151.128/25 permit
|
||||||
70.42.149.0/24 permit
|
70.42.149.0/24 permit
|
||||||
70.42.149.35 permit
|
70.42.149.35 permit
|
||||||
72.3.185.0/24 permit
|
72.3.237.64/28 permit
|
||||||
72.14.192.0/18 permit
|
72.14.192.0/18 permit
|
||||||
72.21.192.0/19 permit
|
72.21.192.0/19 permit
|
||||||
72.21.217.142 permit
|
72.21.217.142 permit
|
||||||
|
@ -522,15 +563,11 @@
|
||||||
72.30.239.228/31 permit
|
72.30.239.228/31 permit
|
||||||
72.30.239.244/30 permit
|
72.30.239.244/30 permit
|
||||||
72.30.239.248/31 permit
|
72.30.239.248/31 permit
|
||||||
72.32.154.0/24 permit
|
|
||||||
72.32.217.0/24 permit
|
|
||||||
72.32.243.0/24 permit
|
|
||||||
72.34.168.76 permit
|
72.34.168.76 permit
|
||||||
72.34.168.80 permit
|
72.34.168.80 permit
|
||||||
72.34.168.85 permit
|
72.34.168.85 permit
|
||||||
72.34.168.86 permit
|
72.34.168.86 permit
|
||||||
72.52.72.32/28 permit
|
72.52.72.32/28 permit
|
||||||
72.52.72.36 permit
|
|
||||||
74.6.128.0/21 permit
|
74.6.128.0/21 permit
|
||||||
74.6.128.0/24 permit
|
74.6.128.0/24 permit
|
||||||
74.6.129.0/24 permit
|
74.6.129.0/24 permit
|
||||||
|
@ -558,8 +595,11 @@
|
||||||
74.112.67.243 permit
|
74.112.67.243 permit
|
||||||
74.125.0.0/16 permit
|
74.125.0.0/16 permit
|
||||||
74.202.227.40 permit
|
74.202.227.40 permit
|
||||||
|
74.208.4.192/26 permit
|
||||||
|
74.208.5.64/26 permit
|
||||||
|
74.208.122.0/26 permit
|
||||||
74.209.250.0/24 permit
|
74.209.250.0/24 permit
|
||||||
74.209.250.12 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
|
||||||
77.238.176.0/24 permit
|
77.238.176.0/24 permit
|
||||||
|
@ -583,7 +623,13 @@
|
||||||
77.238.189.146/31 permit
|
77.238.189.146/31 permit
|
||||||
77.238.189.148/30 permit
|
77.238.189.148/30 permit
|
||||||
81.223.46.0/27 permit
|
81.223.46.0/27 permit
|
||||||
84.16.77.1 permit
|
82.165.159.0/24 permit
|
||||||
|
82.165.159.0/26 permit
|
||||||
|
82.165.229.31 permit
|
||||||
|
82.165.229.130 permit
|
||||||
|
82.165.230.21 permit
|
||||||
|
82.165.230.22 permit
|
||||||
|
84.116.36.0/24 permit
|
||||||
85.158.136.0/21 permit
|
85.158.136.0/21 permit
|
||||||
86.61.88.25 permit
|
86.61.88.25 permit
|
||||||
87.198.219.130 permit
|
87.198.219.130 permit
|
||||||
|
@ -624,11 +670,11 @@
|
||||||
87.248.117.201 permit
|
87.248.117.201 permit
|
||||||
87.248.117.202 permit
|
87.248.117.202 permit
|
||||||
87.248.117.205 permit
|
87.248.117.205 permit
|
||||||
87.252.219.254 permit
|
|
||||||
87.253.232.0/21 permit
|
87.253.232.0/21 permit
|
||||||
89.22.108.0/24 permit
|
89.22.108.0/24 permit
|
||||||
|
91.194.248.0/23 permit
|
||||||
|
91.211.240.0/22 permit
|
||||||
91.220.42.0/24 permit
|
91.220.42.0/24 permit
|
||||||
94.236.119.0/26 permit
|
|
||||||
94.245.112.0/27 permit
|
94.245.112.0/27 permit
|
||||||
94.245.112.10/31 permit
|
94.245.112.10/31 permit
|
||||||
95.131.104.0/21 permit
|
95.131.104.0/21 permit
|
||||||
|
@ -638,6 +684,7 @@
|
||||||
96.43.148.64/28 permit
|
96.43.148.64/28 permit
|
||||||
96.43.148.64/31 permit
|
96.43.148.64/31 permit
|
||||||
96.43.151.64/28 permit
|
96.43.151.64/28 permit
|
||||||
|
98.97.248.0/21 permit
|
||||||
98.136.44.181 permit
|
98.136.44.181 permit
|
||||||
98.136.44.182/31 permit
|
98.136.44.182/31 permit
|
||||||
98.136.44.184 permit
|
98.136.44.184 permit
|
||||||
|
@ -1142,23 +1189,21 @@
|
||||||
98.139.245.212/31 permit
|
98.139.245.212/31 permit
|
||||||
99.78.197.208/28 permit
|
99.78.197.208/28 permit
|
||||||
103.2.140.0/22 permit
|
103.2.140.0/22 permit
|
||||||
103.9.8.121 permit
|
|
||||||
103.9.8.122 permit
|
|
||||||
103.9.8.123 permit
|
|
||||||
103.9.96.0/22 permit
|
103.9.96.0/22 permit
|
||||||
103.13.69.0/24 permit
|
103.13.69.0/24 permit
|
||||||
|
103.28.42.0/24 permit
|
||||||
103.47.204.0/22 permit
|
103.47.204.0/22 permit
|
||||||
103.96.21.0/24 permit
|
103.96.21.0/24 permit
|
||||||
|
103.96.22.0/24 permit
|
||||||
103.96.23.0/24 permit
|
103.96.23.0/24 permit
|
||||||
103.151.192.0/23 permit
|
103.151.192.0/23 permit
|
||||||
103.237.104.0/22 permit
|
103.168.172.128/27 permit
|
||||||
104.43.243.237 permit
|
104.43.243.237 permit
|
||||||
|
104.44.112.128/25 permit
|
||||||
104.47.0.0/17 permit
|
104.47.0.0/17 permit
|
||||||
104.130.96.0/28 permit
|
104.130.96.0/28 permit
|
||||||
104.130.122.0/23 permit
|
104.130.122.0/23 permit
|
||||||
104.214.25.77 permit
|
104.214.25.77 permit
|
||||||
104.215.148.63 permit
|
|
||||||
104.215.186.3 permit
|
|
||||||
104.245.209.192/26 permit
|
104.245.209.192/26 permit
|
||||||
106.10.144.64/27 permit
|
106.10.144.64/27 permit
|
||||||
106.10.144.100/31 permit
|
106.10.144.100/31 permit
|
||||||
|
@ -1320,6 +1365,8 @@
|
||||||
117.120.16.0/21 permit
|
117.120.16.0/21 permit
|
||||||
119.42.242.52/31 permit
|
119.42.242.52/31 permit
|
||||||
119.42.242.156 permit
|
119.42.242.156 permit
|
||||||
|
121.244.91.48 permit
|
||||||
|
122.15.156.182 permit
|
||||||
123.126.78.64/29 permit
|
123.126.78.64/29 permit
|
||||||
124.47.150.0/24 permit
|
124.47.150.0/24 permit
|
||||||
124.47.189.0/24 permit
|
124.47.189.0/24 permit
|
||||||
|
@ -1335,20 +1382,35 @@
|
||||||
128.127.70.0/26 permit
|
128.127.70.0/26 permit
|
||||||
128.245.0.0/20 permit
|
128.245.0.0/20 permit
|
||||||
128.245.64.0/20 permit
|
128.245.64.0/20 permit
|
||||||
|
128.245.176.0/20 permit
|
||||||
|
128.245.242.0/24 permit
|
||||||
|
128.245.242.16 permit
|
||||||
|
128.245.242.17 permit
|
||||||
|
128.245.242.18 permit
|
||||||
|
128.245.243.0/24 permit
|
||||||
|
128.245.244.0/24 permit
|
||||||
|
128.245.245.0/24 permit
|
||||||
|
128.245.246.0/24 permit
|
||||||
|
128.245.247.0/24 permit
|
||||||
129.41.77.70 permit
|
129.41.77.70 permit
|
||||||
129.41.169.249 permit
|
129.41.169.249 permit
|
||||||
|
129.80.5.164 permit
|
||||||
|
129.80.67.121 permit
|
||||||
|
129.146.88.28 permit
|
||||||
|
129.146.147.105 permit
|
||||||
129.146.236.58 permit
|
129.146.236.58 permit
|
||||||
|
129.151.67.221 permit
|
||||||
|
129.153.62.216 permit
|
||||||
|
129.153.104.71 permit
|
||||||
|
129.153.168.146 permit
|
||||||
|
129.153.190.200 permit
|
||||||
129.153.194.228 permit
|
129.153.194.228 permit
|
||||||
129.159.87.137 permit
|
129.159.87.137 permit
|
||||||
|
129.213.195.191 permit
|
||||||
130.61.9.72 permit
|
130.61.9.72 permit
|
||||||
130.211.0.0/22 permit
|
130.211.0.0/22 permit
|
||||||
130.248.172.0/24 permit
|
|
||||||
130.248.173.0/24 permit
|
|
||||||
131.107.0.0/16 permit
|
|
||||||
131.253.30.0/24 permit
|
131.253.30.0/24 permit
|
||||||
131.253.121.0/26 permit
|
131.253.121.0/26 permit
|
||||||
131.253.121.20 permit
|
|
||||||
131.253.121.52 permit
|
|
||||||
132.145.13.209 permit
|
132.145.13.209 permit
|
||||||
132.226.26.225 permit
|
132.226.26.225 permit
|
||||||
132.226.49.32 permit
|
132.226.49.32 permit
|
||||||
|
@ -1358,9 +1420,13 @@
|
||||||
134.170.141.64/26 permit
|
134.170.141.64/26 permit
|
||||||
134.170.143.0/24 permit
|
134.170.143.0/24 permit
|
||||||
134.170.174.0/24 permit
|
134.170.174.0/24 permit
|
||||||
135.84.80.192/26 permit
|
135.84.80.0/24 permit
|
||||||
|
135.84.81.0/24 permit
|
||||||
135.84.82.0/24 permit
|
135.84.82.0/24 permit
|
||||||
|
135.84.83.0/24 permit
|
||||||
135.84.216.0/22 permit
|
135.84.216.0/22 permit
|
||||||
|
136.143.160.0/24 permit
|
||||||
|
136.143.161.0/24 permit
|
||||||
136.143.182.0/23 permit
|
136.143.182.0/23 permit
|
||||||
136.143.184.0/24 permit
|
136.143.184.0/24 permit
|
||||||
136.143.188.0/24 permit
|
136.143.188.0/24 permit
|
||||||
|
@ -1369,34 +1435,53 @@
|
||||||
136.147.176.0/20 permit
|
136.147.176.0/20 permit
|
||||||
136.147.176.0/24 permit
|
136.147.176.0/24 permit
|
||||||
136.147.182.0/24 permit
|
136.147.182.0/24 permit
|
||||||
|
136.179.50.206 permit
|
||||||
138.91.172.26 permit
|
138.91.172.26 permit
|
||||||
139.60.152.0/22 permit
|
139.60.152.0/22 permit
|
||||||
139.178.64.159 permit
|
139.138.35.44 permit
|
||||||
139.178.64.195 permit
|
139.138.46.121 permit
|
||||||
|
139.138.46.176 permit
|
||||||
|
139.138.46.219 permit
|
||||||
|
139.138.57.55 permit
|
||||||
|
139.138.58.119 permit
|
||||||
|
139.180.17.0/24 permit
|
||||||
|
141.148.159.229 permit
|
||||||
141.193.32.0/23 permit
|
141.193.32.0/23 permit
|
||||||
143.55.224.0/21 permit
|
143.55.224.0/21 permit
|
||||||
143.55.232.0/22 permit
|
143.55.232.0/22 permit
|
||||||
143.55.236.0/22 permit
|
143.55.236.0/22 permit
|
||||||
|
143.244.80.0/20 permit
|
||||||
|
144.24.6.140 permit
|
||||||
|
144.34.8.247 permit
|
||||||
|
144.34.9.247 permit
|
||||||
|
144.34.32.247 permit
|
||||||
|
144.34.33.247 permit
|
||||||
144.178.36.0/24 permit
|
144.178.36.0/24 permit
|
||||||
144.178.38.0/24 permit
|
144.178.38.0/24 permit
|
||||||
|
145.253.228.160/29 permit
|
||||||
|
145.253.239.128/29 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
|
||||||
146.20.215.0/24 permit
|
146.20.215.0/24 permit
|
||||||
|
146.20.215.182 permit
|
||||||
|
146.88.28.0/24 permit
|
||||||
146.101.78.0/24 permit
|
146.101.78.0/24 permit
|
||||||
147.75.65.173 permit
|
147.28.36.0/24 permit
|
||||||
147.75.65.174 permit
|
|
||||||
147.75.98.190 permit
|
|
||||||
147.160.158.0/24 permit
|
147.160.158.0/24 permit
|
||||||
147.243.1.47 permit
|
147.243.1.47 permit
|
||||||
147.243.1.48 permit
|
147.243.1.48 permit
|
||||||
147.243.1.153 permit
|
147.243.1.153 permit
|
||||||
147.243.128.24 permit
|
147.243.128.24 permit
|
||||||
147.243.128.26 permit
|
147.243.128.26 permit
|
||||||
148.105.0.14 permit
|
148.105.0.0/16 permit
|
||||||
148.105.8.0/21 permit
|
148.105.8.0/21 permit
|
||||||
149.72.0.0/16 permit
|
149.72.0.0/16 permit
|
||||||
|
149.97.173.180 permit
|
||||||
|
150.230.98.160 permit
|
||||||
152.67.105.195 permit
|
152.67.105.195 permit
|
||||||
|
152.69.200.236 permit
|
||||||
|
155.248.208.51 permit
|
||||||
157.55.0.192/26 permit
|
157.55.0.192/26 permit
|
||||||
157.55.1.128/26 permit
|
157.55.1.128/26 permit
|
||||||
157.55.2.0/25 permit
|
157.55.2.0/25 permit
|
||||||
|
@ -1412,32 +1497,43 @@
|
||||||
157.56.232.0/21 permit
|
157.56.232.0/21 permit
|
||||||
157.56.240.0/20 permit
|
157.56.240.0/20 permit
|
||||||
157.56.248.0/21 permit
|
157.56.248.0/21 permit
|
||||||
|
157.58.30.128/25 permit
|
||||||
157.58.196.96/29 permit
|
157.58.196.96/29 permit
|
||||||
157.58.249.3 permit
|
157.58.249.3 permit
|
||||||
157.151.208.65 permit
|
157.151.208.65 permit
|
||||||
157.255.1.64/29 permit
|
157.255.1.64/29 permit
|
||||||
|
158.101.211.207 permit
|
||||||
|
158.120.80.0/21 permit
|
||||||
|
158.247.16.0/20 permit
|
||||||
159.92.157.0/24 permit
|
159.92.157.0/24 permit
|
||||||
|
159.92.157.16 permit
|
||||||
|
159.92.157.17 permit
|
||||||
|
159.92.157.18 permit
|
||||||
159.92.158.0/24 permit
|
159.92.158.0/24 permit
|
||||||
159.92.159.0/24 permit
|
159.92.159.0/24 permit
|
||||||
159.92.160.0/24 permit
|
159.92.160.0/24 permit
|
||||||
159.92.161.0/24 permit
|
159.92.161.0/24 permit
|
||||||
159.92.162.0/24 permit
|
159.92.162.0/24 permit
|
||||||
|
159.112.240.0/20 permit
|
||||||
|
159.112.242.162 permit
|
||||||
159.135.132.128/25 permit
|
159.135.132.128/25 permit
|
||||||
159.135.140.80/29 permit
|
159.135.140.80/29 permit
|
||||||
159.135.224.0/20 permit
|
159.135.224.0/20 permit
|
||||||
|
159.135.228.10 permit
|
||||||
159.183.0.0/16 permit
|
159.183.0.0/16 permit
|
||||||
|
160.1.62.192 permit
|
||||||
161.38.192.0/20 permit
|
161.38.192.0/20 permit
|
||||||
161.38.204.0/22 permit
|
161.38.204.0/22 permit
|
||||||
161.71.32.0/19 permit
|
161.71.32.0/19 permit
|
||||||
161.71.64.0/20 permit
|
161.71.64.0/20 permit
|
||||||
162.208.119.181 permit
|
|
||||||
162.247.216.0/22 permit
|
162.247.216.0/22 permit
|
||||||
|
163.47.180.0/22 permit
|
||||||
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
|
||||||
|
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
|
||||||
166.78.69.146 permit
|
|
||||||
166.78.69.169 permit
|
166.78.69.169 permit
|
||||||
166.78.69.170 permit
|
166.78.69.170 permit
|
||||||
166.78.71.131 permit
|
166.78.71.131 permit
|
||||||
|
@ -1457,10 +1553,13 @@
|
||||||
167.216.129.210 permit
|
167.216.129.210 permit
|
||||||
167.216.131.180 permit
|
167.216.131.180 permit
|
||||||
167.220.67.232/29 permit
|
167.220.67.232/29 permit
|
||||||
167.220.67.238 permit
|
|
||||||
168.138.5.36 permit
|
168.138.5.36 permit
|
||||||
|
168.138.73.51 permit
|
||||||
168.245.0.0/17 permit
|
168.245.0.0/17 permit
|
||||||
|
169.148.129.0/24 permit
|
||||||
|
169.148.131.0/24 permit
|
||||||
170.10.68.0/22 permit
|
170.10.68.0/22 permit
|
||||||
|
170.10.128.0/24 permit
|
||||||
170.10.129.0/24 permit
|
170.10.129.0/24 permit
|
||||||
170.10.133.0/24 permit
|
170.10.133.0/24 permit
|
||||||
172.217.0.0/19 permit
|
172.217.0.0/19 permit
|
||||||
|
@ -1475,10 +1574,8 @@
|
||||||
173.194.0.0/16 permit
|
173.194.0.0/16 permit
|
||||||
173.203.79.182 permit
|
173.203.79.182 permit
|
||||||
173.203.81.39 permit
|
173.203.81.39 permit
|
||||||
173.224.160.128/25 permit
|
|
||||||
173.224.160.188 permit
|
|
||||||
173.224.161.128/25 permit
|
173.224.161.128/25 permit
|
||||||
173.228.155.0/24 permit
|
173.224.165.0/26 permit
|
||||||
174.36.84.8/29 permit
|
174.36.84.8/29 permit
|
||||||
174.36.84.16/29 permit
|
174.36.84.16/29 permit
|
||||||
174.36.84.32/29 permit
|
174.36.84.32/29 permit
|
||||||
|
@ -1491,6 +1588,7 @@
|
||||||
174.36.114.152/29 permit
|
174.36.114.152/29 permit
|
||||||
174.37.67.28/30 permit
|
174.37.67.28/30 permit
|
||||||
174.129.203.189 permit
|
174.129.203.189 permit
|
||||||
|
175.41.215.51 permit
|
||||||
176.32.105.0/24 permit
|
176.32.105.0/24 permit
|
||||||
176.32.127.0/24 permit
|
176.32.127.0/24 permit
|
||||||
178.236.10.128/26 permit
|
178.236.10.128/26 permit
|
||||||
|
@ -1498,8 +1596,9 @@
|
||||||
182.50.76.0/22 permit
|
182.50.76.0/22 permit
|
||||||
182.50.78.64/28 permit
|
182.50.78.64/28 permit
|
||||||
183.240.219.64/29 permit
|
183.240.219.64/29 permit
|
||||||
|
185.4.120.0/23 permit
|
||||||
|
185.4.122.0/24 permit
|
||||||
185.12.80.0/22 permit
|
185.12.80.0/22 permit
|
||||||
185.28.196.0/22 permit
|
|
||||||
185.58.84.93 permit
|
185.58.84.93 permit
|
||||||
185.58.85.0/24 permit
|
185.58.85.0/24 permit
|
||||||
185.58.86.0/24 permit
|
185.58.86.0/24 permit
|
||||||
|
@ -1509,9 +1608,13 @@
|
||||||
185.80.93.204 permit
|
185.80.93.204 permit
|
||||||
185.80.93.227 permit
|
185.80.93.227 permit
|
||||||
185.80.95.31 permit
|
185.80.95.31 permit
|
||||||
|
185.90.20.0/22 permit
|
||||||
185.189.236.0/22 permit
|
185.189.236.0/22 permit
|
||||||
185.211.120.0/22 permit
|
185.211.120.0/22 permit
|
||||||
185.250.236.0/22 permit
|
185.250.236.0/22 permit
|
||||||
|
185.250.239.148 permit
|
||||||
|
185.250.239.168 permit
|
||||||
|
185.250.239.190 permit
|
||||||
188.125.68.132 permit
|
188.125.68.132 permit
|
||||||
188.125.68.152/31 permit
|
188.125.68.152/31 permit
|
||||||
188.125.68.156 permit
|
188.125.68.156 permit
|
||||||
|
@ -1563,7 +1666,7 @@
|
||||||
188.125.85.238 permit
|
188.125.85.238 permit
|
||||||
188.172.128.0/20 permit
|
188.172.128.0/20 permit
|
||||||
192.0.64.0/18 permit
|
192.0.64.0/18 permit
|
||||||
192.28.128.0/18 permit
|
192.18.139.154 permit
|
||||||
192.30.252.0/22 permit
|
192.30.252.0/22 permit
|
||||||
192.64.236.0/24 permit
|
192.64.236.0/24 permit
|
||||||
192.64.237.0/24 permit
|
192.64.237.0/24 permit
|
||||||
|
@ -1579,16 +1682,21 @@
|
||||||
192.254.113.10 permit
|
192.254.113.10 permit
|
||||||
192.254.113.101 permit
|
192.254.113.101 permit
|
||||||
192.254.114.176 permit
|
192.254.114.176 permit
|
||||||
192.254.118.63 permit
|
|
||||||
193.7.206.0/25 permit
|
193.7.206.0/25 permit
|
||||||
193.7.207.0/25 permit
|
193.7.207.0/25 permit
|
||||||
193.109.254.0/23 permit
|
193.109.254.0/23 permit
|
||||||
193.122.128.100 permit
|
193.122.128.100 permit
|
||||||
|
194.64.234.128/27 permit
|
||||||
194.64.234.129 permit
|
194.64.234.129 permit
|
||||||
194.104.109.0/24 permit
|
194.104.109.0/24 permit
|
||||||
|
194.104.110.21 permit
|
||||||
|
194.104.110.240/28 permit
|
||||||
194.104.111.0/24 permit
|
194.104.111.0/24 permit
|
||||||
194.106.220.0/23 permit
|
194.106.220.0/23 permit
|
||||||
|
194.113.24.0/22 permit
|
||||||
194.154.193.192/27 permit
|
194.154.193.192/27 permit
|
||||||
|
195.4.92.0/23 permit
|
||||||
|
195.54.172.0/23 permit
|
||||||
195.130.217.0/24 permit
|
195.130.217.0/24 permit
|
||||||
195.234.109.226 permit
|
195.234.109.226 permit
|
||||||
195.245.230.0/23 permit
|
195.245.230.0/23 permit
|
||||||
|
@ -1605,19 +1713,23 @@
|
||||||
198.37.144.0/20 permit
|
198.37.144.0/20 permit
|
||||||
198.37.152.186 permit
|
198.37.152.186 permit
|
||||||
198.61.254.0/23 permit
|
198.61.254.0/23 permit
|
||||||
|
198.61.254.21 permit
|
||||||
198.61.254.231 permit
|
198.61.254.231 permit
|
||||||
198.74.56.28 permit
|
|
||||||
198.178.234.57 permit
|
198.178.234.57 permit
|
||||||
|
198.244.48.0/20 permit
|
||||||
|
198.244.60.0/22 permit
|
||||||
198.245.80.0/20 permit
|
198.245.80.0/20 permit
|
||||||
198.245.81.0/24 permit
|
198.245.81.0/24 permit
|
||||||
199.15.176.173 permit
|
199.15.176.173 permit
|
||||||
199.15.212.0/22 permit
|
|
||||||
199.15.213.187 permit
|
199.15.213.187 permit
|
||||||
199.15.226.37 permit
|
199.15.226.37 permit
|
||||||
199.16.156.0/22 permit
|
199.16.156.0/22 permit
|
||||||
199.33.145.1 permit
|
199.33.145.1 permit
|
||||||
199.33.145.32 permit
|
199.33.145.32 permit
|
||||||
199.59.148.0/22 permit
|
199.59.148.0/22 permit
|
||||||
|
199.67.84.0/24 permit
|
||||||
|
199.67.86.0/24 permit
|
||||||
|
199.67.88.0/24 permit
|
||||||
199.101.161.130 permit
|
199.101.161.130 permit
|
||||||
199.101.162.0/25 permit
|
199.101.162.0/25 permit
|
||||||
199.122.120.0/21 permit
|
199.122.120.0/21 permit
|
||||||
|
@ -1630,8 +1742,10 @@
|
||||||
202.177.148.110 permit
|
202.177.148.110 permit
|
||||||
203.31.36.0/22 permit
|
203.31.36.0/22 permit
|
||||||
203.32.4.25 permit
|
203.32.4.25 permit
|
||||||
|
203.55.21.0/24 permit
|
||||||
203.81.17.0/24 permit
|
203.81.17.0/24 permit
|
||||||
203.122.32.250 permit
|
203.122.32.250 permit
|
||||||
|
203.145.57.160/27 permit
|
||||||
203.188.194.32 permit
|
203.188.194.32 permit
|
||||||
203.188.194.151 permit
|
203.188.194.151 permit
|
||||||
203.188.194.203 permit
|
203.188.194.203 permit
|
||||||
|
@ -1666,28 +1780,31 @@
|
||||||
203.209.230.76/31 permit
|
203.209.230.76/31 permit
|
||||||
204.11.168.0/21 permit
|
204.11.168.0/21 permit
|
||||||
204.13.11.48/29 permit
|
204.13.11.48/29 permit
|
||||||
|
204.13.11.48/30 permit
|
||||||
204.14.232.0/21 permit
|
204.14.232.0/21 permit
|
||||||
204.14.232.64/28 permit
|
204.14.232.64/28 permit
|
||||||
204.14.234.64/28 permit
|
204.14.234.64/28 permit
|
||||||
204.29.186.0/23 permit
|
204.29.186.0/23 permit
|
||||||
|
204.75.142.0/24 permit
|
||||||
204.79.197.212 permit
|
204.79.197.212 permit
|
||||||
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.141.32.0/23 permit
|
204.141.32.0/23 permit
|
||||||
204.141.42.0/23 permit
|
204.141.42.0/23 permit
|
||||||
204.153.121.0/24 permit
|
|
||||||
204.232.168.0/24 permit
|
204.232.168.0/24 permit
|
||||||
205.139.110.0/24 permit
|
205.139.110.0/24 permit
|
||||||
205.201.128.0/20 permit
|
205.201.128.0/20 permit
|
||||||
205.201.131.128/25 permit
|
205.201.131.128/25 permit
|
||||||
205.201.134.128/25 permit
|
205.201.134.128/25 permit
|
||||||
205.201.136.0/23 permit
|
205.201.136.0/23 permit
|
||||||
|
205.201.137.229 permit
|
||||||
205.201.139.0/24 permit
|
205.201.139.0/24 permit
|
||||||
205.207.104.0/22 permit
|
205.207.104.0/22 permit
|
||||||
205.207.104.108 permit
|
|
||||||
205.220.167.17 permit
|
205.220.167.17 permit
|
||||||
|
205.220.167.98 permit
|
||||||
205.220.179.17 permit
|
205.220.179.17 permit
|
||||||
|
205.220.179.98 permit
|
||||||
205.251.233.32 permit
|
205.251.233.32 permit
|
||||||
205.251.233.36 permit
|
205.251.233.36 permit
|
||||||
206.25.247.143 permit
|
206.25.247.143 permit
|
||||||
|
@ -1723,6 +1840,7 @@
|
||||||
207.211.31.0/25 permit
|
207.211.31.0/25 permit
|
||||||
207.211.41.113 permit
|
207.211.41.113 permit
|
||||||
207.218.90.0/24 permit
|
207.218.90.0/24 permit
|
||||||
|
207.218.90.122 permit
|
||||||
207.250.68.0/24 permit
|
207.250.68.0/24 permit
|
||||||
208.40.232.70 permit
|
208.40.232.70 permit
|
||||||
208.43.21.28/30 permit
|
208.43.21.28/30 permit
|
||||||
|
@ -1758,8 +1876,10 @@
|
||||||
208.71.42.212/31 permit
|
208.71.42.212/31 permit
|
||||||
208.71.42.214 permit
|
208.71.42.214 permit
|
||||||
208.72.249.240/29 permit
|
208.72.249.240/29 permit
|
||||||
|
208.74.204.0/22 permit
|
||||||
208.74.204.9 permit
|
208.74.204.9 permit
|
||||||
208.75.120.0/22 permit
|
208.75.120.0/22 permit
|
||||||
|
208.75.121.246 permit
|
||||||
208.75.122.246 permit
|
208.75.122.246 permit
|
||||||
208.82.237.96/29 permit
|
208.82.237.96/29 permit
|
||||||
208.82.237.104/31 permit
|
208.82.237.104/31 permit
|
||||||
|
@ -1773,14 +1893,13 @@
|
||||||
209.46.117.168 permit
|
209.46.117.168 permit
|
||||||
209.46.117.179 permit
|
209.46.117.179 permit
|
||||||
209.61.151.0/24 permit
|
209.61.151.0/24 permit
|
||||||
|
209.61.151.236 permit
|
||||||
|
209.61.151.249 permit
|
||||||
|
209.61.151.251 permit
|
||||||
209.67.98.46 permit
|
209.67.98.46 permit
|
||||||
209.67.98.59 permit
|
209.67.98.59 permit
|
||||||
209.85.128.0/17 permit
|
209.85.128.0/17 permit
|
||||||
212.4.136.0/26 permit
|
212.4.136.0/26 permit
|
||||||
212.25.240.80 permit
|
|
||||||
212.25.240.83 permit
|
|
||||||
212.25.240.84/31 permit
|
|
||||||
212.25.240.88 permit
|
|
||||||
212.82.96.0/24 permit
|
212.82.96.0/24 permit
|
||||||
212.82.96.32/27 permit
|
212.82.96.32/27 permit
|
||||||
212.82.96.64/29 permit
|
212.82.96.64/29 permit
|
||||||
|
@ -1821,6 +1940,12 @@
|
||||||
212.82.111.228/31 permit
|
212.82.111.228/31 permit
|
||||||
212.82.111.230 permit
|
212.82.111.230 permit
|
||||||
212.123.28.40 permit
|
212.123.28.40 permit
|
||||||
|
212.227.15.0/24 permit
|
||||||
|
212.227.15.0/25 permit
|
||||||
|
212.227.17.0/27 permit
|
||||||
|
212.227.126.128/25 permit
|
||||||
|
213.46.255.0/24 permit
|
||||||
|
213.165.64.0/23 permit
|
||||||
213.167.75.0/25 permit
|
213.167.75.0/25 permit
|
||||||
213.167.81.0/25 permit
|
213.167.81.0/25 permit
|
||||||
213.199.128.139 permit
|
213.199.128.139 permit
|
||||||
|
@ -1861,6 +1986,10 @@
|
||||||
216.46.168.0/24 permit
|
216.46.168.0/24 permit
|
||||||
216.58.192.0/19 permit
|
216.58.192.0/19 permit
|
||||||
216.66.217.240/29 permit
|
216.66.217.240/29 permit
|
||||||
|
216.71.138.33 permit
|
||||||
|
216.71.152.207 permit
|
||||||
|
216.71.154.29 permit
|
||||||
|
216.71.155.89 permit
|
||||||
216.74.162.13 permit
|
216.74.162.13 permit
|
||||||
216.74.162.14 permit
|
216.74.162.14 permit
|
||||||
216.82.240.0/20 permit
|
216.82.240.0/20 permit
|
||||||
|
@ -1870,33 +1999,49 @@
|
||||||
216.109.114.0/24 permit
|
216.109.114.0/24 permit
|
||||||
216.109.114.32/27 permit
|
216.109.114.32/27 permit
|
||||||
216.109.114.64/29 permit
|
216.109.114.64/29 permit
|
||||||
|
216.113.160.0/24 permit
|
||||||
|
216.113.172.0/25 permit
|
||||||
|
216.113.175.0/24 permit
|
||||||
216.128.126.97 permit
|
216.128.126.97 permit
|
||||||
216.136.162.65 permit
|
216.136.162.65 permit
|
||||||
216.136.162.120/29 permit
|
216.136.162.120/29 permit
|
||||||
216.136.168.80/28 permit
|
216.136.168.80/28 permit
|
||||||
|
216.145.217.0/24 permit
|
||||||
|
216.145.221.0/24 permit
|
||||||
216.198.0.0/18 permit
|
216.198.0.0/18 permit
|
||||||
216.203.30.55 permit
|
216.203.30.55 permit
|
||||||
216.203.33.178/31 permit
|
216.203.33.178/31 permit
|
||||||
216.205.24.0/24 permit
|
216.205.24.0/24 permit
|
||||||
216.239.32.0/19 permit
|
216.239.32.0/19 permit
|
||||||
|
217.72.192.64/26 permit
|
||||||
|
217.72.192.248/29 permit
|
||||||
|
217.72.207.0/27 permit
|
||||||
217.77.141.52 permit
|
217.77.141.52 permit
|
||||||
217.77.141.59 permit
|
217.77.141.59 permit
|
||||||
|
217.175.194.0/24 permit
|
||||||
222.73.195.64/29 permit
|
222.73.195.64/29 permit
|
||||||
223.165.113.0/24 permit
|
223.165.113.0/24 permit
|
||||||
223.165.115.0/24 permit
|
223.165.115.0/24 permit
|
||||||
223.165.118.0/23 permit
|
223.165.118.0/23 permit
|
||||||
223.165.120.0/23 permit
|
223.165.120.0/23 permit
|
||||||
|
2001:0868:0100:0600::/64 permit
|
||||||
2001:4860:4000::/36 permit
|
2001:4860:4000::/36 permit
|
||||||
|
2001:748:100:40::2:0/112 permit
|
||||||
2404:6800:4000::/36 permit
|
2404:6800:4000::/36 permit
|
||||||
|
2603:1010:3:3::5b permit
|
||||||
|
2603:1020:201:10::10f permit
|
||||||
|
2603:1030:20e:3::23c permit
|
||||||
|
2603:1030:b:3::152 permit
|
||||||
|
2603:1030:c02:8::14 permit
|
||||||
2607:f8b0:4000::/36 permit
|
2607:f8b0:4000::/36 permit
|
||||||
2620:109:c003:104::215 permit
|
|
||||||
2620:109:c003:104::/64 permit
|
2620:109:c003:104::/64 permit
|
||||||
2620:109:c006:104::215 permit
|
2620:109:c003:104::215 permit
|
||||||
2620:109:c006:104::/64 permit
|
2620:109:c006:104::/64 permit
|
||||||
|
2620:109:c006:104::215 permit
|
||||||
2620:109:c00d:104::/64 permit
|
2620:109:c00d:104::/64 permit
|
||||||
2620:10d:c090:450::120 permit
|
2620:10d:c090:450::120 permit
|
||||||
2620:10d:c091:450::16 permit
|
2620:10d:c091:400::8:1 permit
|
||||||
2620:119:50c0:207::215 permit
|
|
||||||
2620:119:50c0:207::/64 permit
|
2620:119:50c0:207::/64 permit
|
||||||
|
2620:119:50c0:207::215 permit
|
||||||
2800:3f0:4000::/36 permit
|
2800:3f0:4000::/36 permit
|
||||||
194.25.134.0/24 permit # t-online.de
|
194.25.134.0/24 permit # t-online.de
|
||||||
|
|
|
@ -27,4 +27,5 @@
|
||||||
#197518 2 #Rackmarkt SL, Spain
|
#197518 2 #Rackmarkt SL, Spain
|
||||||
#197695 2 #Domain names registrar REG.RU Ltd, Russia
|
#197695 2 #Domain names registrar REG.RU Ltd, Russia
|
||||||
#198068 2 #P.A.G.M. OU, Estonia
|
#198068 2 #P.A.G.M. OU, Estonia
|
||||||
#201942 5 #Soltia Consulting SL, Spain
|
#201942 5 #Soltia Consulting SL, Spain
|
||||||
|
#213373 4 #IP Connect Inc
|
|
@ -8,7 +8,7 @@ VIRUS_FOUND {
|
||||||
}
|
}
|
||||||
# Bad policy from free mail providers
|
# Bad policy from free mail providers
|
||||||
FREEMAIL_POLICY_FAILURE {
|
FREEMAIL_POLICY_FAILURE {
|
||||||
expression = "-g+:policies & !DMARC_POLICY_ALLOW & !MAILLIST & ( FREEMAIL_ENVFROM | FREEMAIL_FROM ) & !WHITELISTED_FWD_HOST";
|
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
|
||||||
score = 16.0;
|
score = 16.0;
|
||||||
}
|
}
|
||||||
# Applies to freemail with undisclosed recipients
|
# Applies to freemail with undisclosed recipients
|
||||||
|
@ -68,3 +68,39 @@ WL_FWD_HOST {
|
||||||
ENCRYPTED_CHAT {
|
ENCRYPTED_CHAT {
|
||||||
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
|
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CLAMD_SPAM_FOUND {
|
||||||
|
expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE";
|
||||||
|
description = "Probably Spam, Securite Spam Flag set through ClamAV";
|
||||||
|
score = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMD_BAD_PDF {
|
||||||
|
expression = "CLAM_SECI_PDF & !MAILCOW_WHITE";
|
||||||
|
description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV";
|
||||||
|
score = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMD_BAD_JPG {
|
||||||
|
expression = "CLAM_SECI_JPG & !MAILCOW_WHITE";
|
||||||
|
description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV";
|
||||||
|
score = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMD_ASCII_MALWARE {
|
||||||
|
expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE";
|
||||||
|
description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV";
|
||||||
|
score = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMD_HTML_MALWARE {
|
||||||
|
expression = "CLAM_SECI_HTML & !MAILCOW_WHITE";
|
||||||
|
description = "HTML malware found, Securite HTML malware Flag set through ClamAV";
|
||||||
|
score = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLAMD_JS_MALWARE {
|
||||||
|
expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
|
||||||
|
description = "JS malware found, Securite JS malware Flag set through ClamAV";
|
||||||
|
score = 8;
|
||||||
|
}
|
|
@ -159,8 +159,8 @@ BAZAAR_ABUSE_CH {
|
||||||
}
|
}
|
||||||
|
|
||||||
URLHAUS_ABUSE_CH {
|
URLHAUS_ABUSE_CH {
|
||||||
type = "url";
|
type = "selector";
|
||||||
filter = "full";
|
selector = "urls";
|
||||||
map = "https://urlhaus.abuse.ch/downloads/text_online/";
|
map = "https://urlhaus.abuse.ch/downloads/text_online/";
|
||||||
score = 10.0;
|
score = 10.0;
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ BAD_SUBJECT_00 {
|
||||||
type = "header";
|
type = "header";
|
||||||
header = "subject";
|
header = "subject";
|
||||||
regexp = true;
|
regexp = true;
|
||||||
map = "http://nullnull.org/bad-subject-regex.txt";
|
map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
|
||||||
score = 6.0;
|
score = 6.0;
|
||||||
symbols_set = ["BAD_SUBJECT_00"];
|
symbols_set = ["BAD_SUBJECT_00"];
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,6 +340,10 @@ rspamd_config:register_symbol({
|
||||||
if not bcc_dest then
|
if not bcc_dest then
|
||||||
return -- stop
|
return -- stop
|
||||||
end
|
end
|
||||||
|
-- dot stuff content before sending
|
||||||
|
local email_content = tostring(task:get_content())
|
||||||
|
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||||
|
-- send mail
|
||||||
lua_smtp.sendmail({
|
lua_smtp.sendmail({
|
||||||
task = task,
|
task = task,
|
||||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||||
|
@ -347,8 +351,8 @@ rspamd_config:register_symbol({
|
||||||
from = task:get_from(stp)[1].addr,
|
from = task:get_from(stp)[1].addr,
|
||||||
recipients = bcc_dest,
|
recipients = bcc_dest,
|
||||||
helo = 'bcc',
|
helo = 'bcc',
|
||||||
timeout = 10,
|
timeout = 20,
|
||||||
}, task:get_content(), sendmail_cb)
|
}, email_content, sendmail_cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- determine from
|
-- determine from
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
SOGoFirstDayOfWeek = "1";
|
SOGoFirstDayOfWeek = "1";
|
||||||
|
|
||||||
SOGoSieveFolderEncoding = "UTF-8";
|
SOGoSieveFolderEncoding = "UTF-8";
|
||||||
SOGoPasswordChangeEnabled = YES;
|
SOGoPasswordChangeEnabled = NO;
|
||||||
SOGoSentFolderName = "Sent";
|
SOGoSentFolderName = "Sent";
|
||||||
SOGoMailShowSubscribedFoldersOnly = NO;
|
SOGoMailShowSubscribedFoldersOnly = NO;
|
||||||
NGImap4ConnectionStringSeparator = "/";
|
NGImap4ConnectionStringSeparator = "/";
|
||||||
|
|
|
@ -80,6 +80,11 @@ foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cors settings
|
||||||
|
$cors_settings = cors('get');
|
||||||
|
$cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']);
|
||||||
|
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
|
||||||
|
|
||||||
$template = 'admin.twig';
|
$template = 'admin.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'tfa_data' => $tfa_data,
|
'tfa_data' => $tfa_data,
|
||||||
|
@ -106,6 +111,7 @@ $template_data = [
|
||||||
'ip_check' => customize('get', 'ip_check'),
|
'ip_check' => customize('get', 'ip_check'),
|
||||||
'password_complexity' => password_complexity('get'),
|
'password_complexity' => password_complexity('get'),
|
||||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||||
|
'cors_settings' => $cors_settings,
|
||||||
'lang_admin' => json_encode($lang['admin']),
|
'lang_admin' => json_encode($lang['admin']),
|
||||||
'lang_datatables' => json_encode($lang['datatables'])
|
'lang_datatables' => json_encode($lang['datatables'])
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
openapi: 3.0.0
|
openapi: 3.1.0
|
||||||
info:
|
info:
|
||||||
description: >-
|
description: >-
|
||||||
mailcow is complete e-mailing solution with advanced antispam, antivirus,
|
mailcow is complete e-mailing solution with advanced antispam, antivirus,
|
||||||
|
@ -699,6 +699,38 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
summary: Create Domain Admin user
|
summary: Create Domain Admin user
|
||||||
|
/api/v1/add/sso/domain-admin:
|
||||||
|
post:
|
||||||
|
responses:
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/Unauthorized"
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
examples:
|
||||||
|
response:
|
||||||
|
value:
|
||||||
|
token: "591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC"
|
||||||
|
description: OK
|
||||||
|
headers: { }
|
||||||
|
tags:
|
||||||
|
- Single Sign-On
|
||||||
|
description: >-
|
||||||
|
Using this endpoint you can issue a token for Domain Admin user. This token can be used for
|
||||||
|
autologin Domain Admin user by using query_string var sso_token={token}. Token expiration time is 30s
|
||||||
|
operationId: Issue Domain Admin SSO token
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
example:
|
||||||
|
username: testadmin
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
description: the username for the admin user
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
summary: Issue Domain Admin SSO token
|
||||||
/api/v1/edit/da-acl:
|
/api/v1/edit/da-acl:
|
||||||
post:
|
post:
|
||||||
responses:
|
responses:
|
||||||
|
@ -1999,7 +2031,7 @@ paths:
|
||||||
- domain.tld
|
- domain.tld
|
||||||
- domain2.tld
|
- domain2.tld
|
||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
@ -2993,7 +3025,7 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
log:
|
log:
|
||||||
|
@ -3144,8 +3176,10 @@ paths:
|
||||||
example:
|
example:
|
||||||
attr:
|
attr:
|
||||||
ban_time: "86400"
|
ban_time: "86400"
|
||||||
|
ban_time_increment: "1"
|
||||||
blacklist: "10.100.6.5/32,10.100.8.4/32"
|
blacklist: "10.100.6.5/32,10.100.8.4/32"
|
||||||
max_attempts: "5"
|
max_attempts: "5"
|
||||||
|
max_ban_time: "86400"
|
||||||
netban_ipv4: "24"
|
netban_ipv4: "24"
|
||||||
netban_ipv6: "64"
|
netban_ipv6: "64"
|
||||||
retry_window: "600"
|
retry_window: "600"
|
||||||
|
@ -3159,11 +3193,17 @@ paths:
|
||||||
description: the backlisted ips or hostnames separated by comma
|
description: the backlisted ips or hostnames separated by comma
|
||||||
type: string
|
type: string
|
||||||
ban_time:
|
ban_time:
|
||||||
description: the time a ip should be banned
|
description: the time an ip should be banned
|
||||||
type: number
|
type: number
|
||||||
|
ban_time_increment:
|
||||||
|
description: if the time of the ban should increase each time
|
||||||
|
type: boolean
|
||||||
max_attempts:
|
max_attempts:
|
||||||
description: the maximum numbe of wrong logins before a ip is banned
|
description: the maximum numbe of wrong logins before a ip is banned
|
||||||
type: number
|
type: number
|
||||||
|
max_ban_time:
|
||||||
|
description: the maximum time an ip should be banned
|
||||||
|
type: number
|
||||||
netban_ipv4:
|
netban_ipv4:
|
||||||
description: the networks mask to ban for ipv4
|
description: the networks mask to ban for ipv4
|
||||||
type: number
|
type: number
|
||||||
|
@ -4081,10 +4121,12 @@ paths:
|
||||||
response:
|
response:
|
||||||
value:
|
value:
|
||||||
ban_time: 604800
|
ban_time: 604800
|
||||||
|
ban_time_increment: 1
|
||||||
blacklist: |-
|
blacklist: |-
|
||||||
45.82.153.37/32
|
45.82.153.37/32
|
||||||
92.118.38.52/32
|
92.118.38.52/32
|
||||||
max_attempts: 1
|
max_attempts: 1
|
||||||
|
max_ban_time: 604800
|
||||||
netban_ipv4: 32
|
netban_ipv4: 32
|
||||||
netban_ipv6: 128
|
netban_ipv6: 128
|
||||||
perm_bans:
|
perm_bans:
|
||||||
|
@ -5560,6 +5602,50 @@ paths:
|
||||||
description: You can list all mailboxes existing in system for a specific domain.
|
description: You can list all mailboxes existing in system for a specific domain.
|
||||||
operationId: Get mailboxes of a domain
|
operationId: Get mailboxes of a domain
|
||||||
summary: Get mailboxes of a domain
|
summary: Get mailboxes of a domain
|
||||||
|
/api/v1/edit/cors:
|
||||||
|
post:
|
||||||
|
responses:
|
||||||
|
"401":
|
||||||
|
$ref: "#/components/responses/Unauthorized"
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
examples:
|
||||||
|
response:
|
||||||
|
value:
|
||||||
|
- type: "success"
|
||||||
|
log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}]
|
||||||
|
msg: "cors_headers_edited"
|
||||||
|
description: OK
|
||||||
|
headers: { }
|
||||||
|
tags:
|
||||||
|
- Cross-Origin Resource Sharing (CORS)
|
||||||
|
description: >-
|
||||||
|
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
|
||||||
|
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
|
||||||
|
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
|
||||||
|
operationId: Edit Cross-Origin Resource Sharing (CORS) settings
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
example:
|
||||||
|
attr:
|
||||||
|
allowed_origins: ["*", "mail.mailcow.tld"]
|
||||||
|
allowed_methods: ["POST", "GET", "DELETE", "PUT"]
|
||||||
|
properties:
|
||||||
|
attr:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
allowed_origins:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
allowed_methods:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
summary: Edit Cross-Origin Resource Sharing (CORS) settings
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
- name: Domains
|
- name: Domains
|
||||||
|
@ -5586,6 +5672,8 @@ tags:
|
||||||
description: Manage DKIM keys
|
description: Manage DKIM keys
|
||||||
- name: Domain admin
|
- name: Domain admin
|
||||||
description: Create or udpdate domain admin users
|
description: Create or udpdate domain admin users
|
||||||
|
- name: Single Sign-On
|
||||||
|
description: Issue tokens for users
|
||||||
- name: Address Rewriting
|
- name: Address Rewriting
|
||||||
description: Create BCC maps or recipient maps
|
description: Create BCC maps or recipient maps
|
||||||
- name: Outgoing TLS Policy Map Overrides
|
- name: Outgoing TLS Policy Map Overrides
|
||||||
|
@ -5602,3 +5690,5 @@ tags:
|
||||||
description: Get the status of your cow
|
description: Get the status of your cow
|
||||||
- name: Ratelimits
|
- name: Ratelimits
|
||||||
description: Edit domain ratelimits
|
description: Edit domain ratelimits
|
||||||
|
- name: Cross-Origin Resource Sharing (CORS)
|
||||||
|
description: Manage Cross-Origin Resource Sharing (CORS) settings
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
window.ui = SwaggerUIBundle({
|
||||||
urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
|
urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
|
@ -15,5 +15,4 @@ window.onload = function() {
|
||||||
});
|
});
|
||||||
// End Swagger UI call region
|
// End Swagger UI call region
|
||||||
|
|
||||||
window.ui = ui;
|
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -342,6 +342,10 @@ div.dataTables_wrapper div.dt-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dataTables_wrapper span.sorting-value {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
div.dataTables_scrollHead table.dataTable {
|
div.dataTables_scrollHead table.dataTable {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,4 +77,22 @@ li .dtr-data {
|
||||||
table.dataTable>tbody>tr.child span.dtr-title {
|
table.dataTable>tbody>tr.child span.dtr-title {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.dataTables_wrapper div.dataTables_filter {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
div.dataTables_wrapper div.dataTables_length {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.dataTables_paginate, .dataTables_length, .dataTables_filter {
|
||||||
|
margin: 10px 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.dt-text-right {
|
||||||
|
text-align: end !important;
|
||||||
|
}
|
||||||
|
th.dt-text-right {
|
||||||
|
text-align: end !important;
|
||||||
|
}
|
||||||
|
|
|
@ -370,14 +370,3 @@ button[aria-expanded='true'] > .caret {
|
||||||
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
||||||
background-color: #f0f0f0 !important;
|
background-color: #f0f0f0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
div.dataTables_wrapper div.dataTables_filter {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
div.dataTables_wrapper div.dataTables_length {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.dataTables_paginate, .dataTables_length, .dataTables_filter {
|
|
||||||
margin: 10px 0!important;
|
|
||||||
}
|
|
|
@ -203,6 +203,9 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.senders-mw220 {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 350px) {
|
@media (max-width: 350px) {
|
||||||
|
|
|
@ -66,4 +66,6 @@ table tbody tr td input[type="checkbox"] {
|
||||||
padding: .2em .4em .3em !important;
|
padding: .2em .4em .3em !important;
|
||||||
background-color: #ececec!important;
|
background-color: #ececec!important;
|
||||||
}
|
}
|
||||||
|
.badge.bg-info .bi {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
|
@ -1,102 +1,104 @@
|
||||||
.pagination a {
|
.pagination a {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel.panel-default {
|
.panel.panel-default {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow-x: scroll !important;
|
overflow-x: scroll !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-add-item {
|
.footer-add-item {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #F5F5F5;
|
background: #F5F5F5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 1920px) {
|
@media (min-width: 1920px) {
|
||||||
.container {
|
.container {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mass-actions-quarantine {
|
.mass-actions-quarantine {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputMissingAttr {
|
.inputMissingAttr {
|
||||||
border-color: #FF4136;
|
border-color: #FF4136;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal#qidDetailModal p {
|
.modal#qidDetailModal p {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
span#qid_detail_score {
|
span#qid_detail_score {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.rspamd-symbol {
|
span.rspamd-symbol {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 2px 6px 2px 0;
|
margin: 2px 6px 2px 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 0 7px;
|
padding: 0 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.rspamd-symbol.positive {
|
span.rspamd-symbol.positive {
|
||||||
background: #4CAF50;
|
background: #4CAF50;
|
||||||
border: 1px solid #4CAF50;
|
border: 1px solid #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.rspamd-symbol.negative {
|
span.rspamd-symbol.negative {
|
||||||
background: #ff4136;
|
background: #ff4136;
|
||||||
border: 1px solid #ff4136;
|
border: 1px solid #ff4136;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.rspamd-symbol.neutral {
|
span.rspamd-symbol.neutral {
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
color: #333;
|
color: #333;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.rspamd-symbol span.score {
|
span.rspamd-symbol span.score {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.mail-address-item {
|
span.mail-address-item {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 2px 6px 2px 0;
|
margin: 2px 6px 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tbody tr {
|
table tbody tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
table tbody tr td input[type="checkbox"] {
|
table tbody tr td input[type="checkbox"] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.label-rspamd-action {
|
.label-rspamd-action {
|
||||||
font-size:110%;
|
font-size:110%;
|
||||||
margin:20px;
|
margin:20px;
|
||||||
}
|
}
|
||||||
|
.senders-mw220 {
|
||||||
|
max-width: 220px;
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ legend {
|
||||||
background-color: #7a7a7a !important;
|
background-color: #7a7a7a !important;
|
||||||
border-color: #5c5c5c !important;
|
border-color: #5c5c5c !important;
|
||||||
}
|
}
|
||||||
|
.btn-dark {
|
||||||
|
color: #000 !important;;
|
||||||
|
background-color: #f6f6f6 !important;;
|
||||||
|
border-color: #ddd !important;;
|
||||||
|
}
|
||||||
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
.btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
|
||||||
border-color: #7a7a7a !important;
|
border-color: #7a7a7a !important;
|
||||||
}
|
}
|
||||||
|
@ -299,22 +304,22 @@ a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
|
||||||
background-color: #7a7a7a !important;
|
background-color: #7a7a7a !important;
|
||||||
}
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
|
||||||
background-color: #7a7a7a !important;
|
background-color: #7a7a7a !important;
|
||||||
border: 1.5px solid #5c5c5c !important;
|
border: 1.5px solid #5c5c5c !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
|
||||||
background-color: #949494;
|
background-color: #949494;
|
||||||
}
|
}
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
|
||||||
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
}
|
}
|
||||||
|
@ -327,7 +332,7 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
|
||||||
}
|
}
|
||||||
.btn.btn-outline-secondary {
|
.btn.btn-outline-secondary {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
border-color: #7a7a7a !important;
|
border-color: #7a7a7a !important;
|
||||||
}
|
}
|
||||||
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
|
||||||
background-color: #9b9b9b !important;
|
background-color: #9b9b9b !important;
|
||||||
|
|
|
@ -49,7 +49,9 @@ function bcc($_action, $_data = null, $_attr = null) {
|
||||||
}
|
}
|
||||||
elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) {
|
elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) {
|
||||||
$mailbox = mailbox('get', 'mailbox_details', $local_dest);
|
$mailbox = mailbox('get', 'mailbox_details', $local_dest);
|
||||||
if ($mailbox === false && array_key_exists($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) {
|
$shared_aliases = mailbox('get', 'shared_aliases');
|
||||||
|
$direct_aliases = mailbox('get', 'direct_aliases');
|
||||||
|
if ($mailbox === false && in_array($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_data, $_attr),
|
||||||
|
|
|
@ -192,5 +192,16 @@ function docker($action, $service_name = null, $attr1 = null, $attr2 = null, $ex
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
case 'broadcast':
|
||||||
|
$request = array(
|
||||||
|
"api_call" => "container_post",
|
||||||
|
"container_name" => $service_name,
|
||||||
|
"post_action" => $attr1,
|
||||||
|
"request" => $attr2
|
||||||
|
);
|
||||||
|
|
||||||
|
$redis->publish("MC_CHANNEL", json_encode($request));
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,407 +1,468 @@
|
||||||
<?php
|
<?php
|
||||||
function domain_admin($_action, $_data = null) {
|
function domain_admin($_action, $_data = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $lang;
|
global $lang;
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
|
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
|
||||||
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
|
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
|
||||||
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
|
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
|
||||||
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
|
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
|
||||||
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
|
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
|
||||||
switch ($_action) {
|
switch ($_action) {
|
||||||
case 'add':
|
case 'add':
|
||||||
$username = strtolower(trim($_data['username']));
|
$username = strtolower(trim($_data['username']));
|
||||||
$password = $_data['password'];
|
$password = $_data['password'];
|
||||||
$password2 = $_data['password2'];
|
$password2 = $_data['password2'];
|
||||||
$domains = (array)$_data['domains'];
|
$domains = (array)$_data['domains'];
|
||||||
$active = intval($_data['active']);
|
$active = intval($_data['active']);
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (empty($domains)) {
|
if (empty($domains)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'domain_invalid'
|
'msg' => 'domain_invalid'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') {
|
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('username_invalid', $username)
|
'msg' => array('username_invalid', $username)
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
|
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
|
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
|
||||||
WHERE `username` = :username");
|
WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|
||||||
foreach ($num_results as $num_results_each) {
|
foreach ($num_results as $num_results_each) {
|
||||||
if ($num_results_each != 0) {
|
if ($num_results_each != 0) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('object_exists', htmlspecialchars($username))
|
'msg' => array('object_exists', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (password_check($password, $password2) !== true) {
|
if (password_check($password, $password2) !== true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
$password_hashed = hash_password($password);
|
||||||
$valid_domains = 0;
|
$valid_domains = 0;
|
||||||
foreach ($domains as $domain) {
|
foreach ($domains as $domain) {
|
||||||
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
|
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_invalid', htmlspecialchars($domain))
|
'msg' => array('domain_invalid', htmlspecialchars($domain))
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$valid_domains++;
|
$valid_domains++;
|
||||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||||
VALUES (:username, :domain, :created, :active)");
|
VALUES (:username, :domain, :created, :active)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':created' => date('Y-m-d H:i:s'),
|
':created' => date('Y-m-d H:i:s'),
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if ($valid_domains != 0) {
|
if ($valid_domains != 0) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
|
||||||
VALUES (:username, :password_hashed, '0', :active)");
|
VALUES (:username, :password_hashed, '0', :active)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
|
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_admin_added', htmlspecialchars($username))
|
'msg' => array('domain_admin_added', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'edit':
|
case 'edit':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Administrator
|
// Administrator
|
||||||
if ($_SESSION['mailcow_cc_role'] == "admin") {
|
if ($_SESSION['mailcow_cc_role'] == "admin") {
|
||||||
if (!is_array($_data['username'])) {
|
if (!is_array($_data['username'])) {
|
||||||
$usernames = array();
|
$usernames = array();
|
||||||
$usernames[] = $_data['username'];
|
$usernames[] = $_data['username'];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$usernames = $_data['username'];
|
$usernames = $_data['username'];
|
||||||
}
|
}
|
||||||
foreach ($usernames as $username) {
|
foreach ($usernames as $username) {
|
||||||
$is_now = domain_admin('details', $username);
|
$is_now = domain_admin('details', $username);
|
||||||
$domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null;
|
$domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null;
|
||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
|
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
|
||||||
$username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username'];
|
$username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username'];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$password = $_data['password'];
|
$password = $_data['password'];
|
||||||
$password2 = $_data['password2'];
|
$password2 = $_data['password2'];
|
||||||
if (!empty($domains)) {
|
if (!empty($domains)) {
|
||||||
foreach ($domains as $domain) {
|
foreach ($domains as $domain) {
|
||||||
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
|
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_invalid', htmlspecialchars($domain))
|
'msg' => array('domain_invalid', htmlspecialchars($domain))
|
||||||
);
|
);
|
||||||
continue 2;
|
continue 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
|
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('username_invalid', $username_new)
|
'msg' => array('username_invalid', $username_new)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ($username_new != $username) {
|
if ($username_new != $username) {
|
||||||
if (!empty(domain_admin('details', $username_new)['username'])) {
|
if (!empty(domain_admin('details', $username_new)['username'])) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('username_invalid', $username_new)
|
'msg' => array('username_invalid', $username_new)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
if (!empty($domains)) {
|
if (!empty($domains)) {
|
||||||
foreach ($domains as $domain) {
|
foreach ($domains as $domain) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
|
||||||
VALUES (:username_new, :domain, :created, :active)");
|
VALUES (:username_new, :domain, :created, :active)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':created' => date('Y-m-d H:i:s'),
|
':created' => date('Y-m-d H:i:s'),
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($password)) {
|
if (!empty($password)) {
|
||||||
if (password_check($password, $password2) !== true) {
|
if (password_check($password, $password2) !== true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
$password_hashed = hash_password($password);
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
if (isset($_data['disable_tfa'])) {
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username_new' => $username_new,
|
':username_new' => $username_new,
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
if (isset($_data['disable_tfa'])) {
|
if (isset($_data['disable_tfa'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
|
||||||
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_admin_modified', htmlspecialchars($username))
|
'msg' => array('domain_admin_modified', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Domain administrator
|
// Domain administrator
|
||||||
// Can only edit itself
|
// Can only edit itself
|
||||||
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||||
$username = $_SESSION['mailcow_cc_username'];
|
$username = $_SESSION['mailcow_cc_username'];
|
||||||
$password_old = $_data['user_old_pass'];
|
$password_old = $_data['user_old_pass'];
|
||||||
$password_new = $_data['user_new_pass'];
|
$password_new = $_data['user_new_pass'];
|
||||||
$password_new2 = $_data['user_new_pass2'];
|
$password_new2 = $_data['user_new_pass2'];
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
WHERE `username` = :user");
|
WHERE `username` = :user");
|
||||||
$stmt->execute(array(':user' => $username));
|
$stmt->execute(array(':user' => $username));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (!verify_hash($row['password'], $password_old)) {
|
if (!verify_hash($row['password'], $password_old)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (password_check($password_new, $password_new2) !== true) {
|
if (password_check($password_new, $password_new2) !== true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password_new);
|
$password_hashed = hash_password($password_new);
|
||||||
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
|
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_admin_modified', htmlspecialchars($username))
|
'msg' => array('domain_admin_modified', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$usernames = (array)$_data['username'];
|
$usernames = (array)$_data['username'];
|
||||||
foreach ($usernames as $username) {
|
foreach ($usernames as $username) {
|
||||||
if (empty(domain_admin('details', $username))) {
|
if (empty(domain_admin('details', $username))) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('username_invalid', $username)
|
'msg' => array('username_invalid', $username)
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => array('domain_admin_removed', htmlspecialchars($username))
|
'msg' => array('domain_admin_removed', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'get':
|
case 'get':
|
||||||
$domainadmins = array();
|
$domainadmins = array();
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'access_denied'
|
'msg' => 'access_denied'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->query("SELECT DISTINCT
|
$stmt = $pdo->query("SELECT DISTINCT
|
||||||
`username`
|
`username`
|
||||||
FROM `domain_admins`
|
FROM `domain_admins`
|
||||||
WHERE `username` IN (
|
WHERE `username` IN (
|
||||||
SELECT `username` FROM `admin`
|
SELECT `username` FROM `admin`
|
||||||
WHERE `superadmin`!='1'
|
WHERE `superadmin`!='1'
|
||||||
)");
|
)");
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while ($row = array_shift($rows)) {
|
while ($row = array_shift($rows)) {
|
||||||
$domainadmins[] = $row['username'];
|
$domainadmins[] = $row['username'];
|
||||||
}
|
}
|
||||||
return $domainadmins;
|
return $domainadmins;
|
||||||
break;
|
break;
|
||||||
case 'details':
|
case 'details':
|
||||||
$domainadmindata = array();
|
$domainadmindata = array();
|
||||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) {
|
if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
|
elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) {
|
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT
|
$stmt = $pdo->prepare("SELECT
|
||||||
`tfa`.`active` AS `tfa_active`,
|
`tfa`.`active` AS `tfa_active`,
|
||||||
`domain_admins`.`username`,
|
`domain_admins`.`username`,
|
||||||
`domain_admins`.`created`,
|
`domain_admins`.`created`,
|
||||||
`domain_admins`.`active` AS `active`
|
`domain_admins`.`active` AS `active`
|
||||||
FROM `domain_admins`
|
FROM `domain_admins`
|
||||||
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
|
||||||
WHERE `domain_admins`.`username`= :domain_admin");
|
WHERE `domain_admins`.`username`= :domain_admin");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain_admin' => $_data
|
':domain_admin' => $_data
|
||||||
));
|
));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (empty($row)) {
|
if (empty($row)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$domainadmindata['username'] = $row['username'];
|
$domainadmindata['username'] = $row['username'];
|
||||||
$domainadmindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active'];
|
$domainadmindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active'];
|
||||||
$domainadmindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active'];
|
$domainadmindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active'];
|
||||||
$domainadmindata['active'] = $row['active'];
|
$domainadmindata['active'] = $row['active'];
|
||||||
$domainadmindata['active_int'] = $row['active'];
|
$domainadmindata['active_int'] = $row['active'];
|
||||||
$domainadmindata['created'] = $row['created'];
|
$domainadmindata['created'] = $row['created'];
|
||||||
// GET SELECTED
|
// GET SELECTED
|
||||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||||
WHERE `domain` IN (
|
WHERE `domain` IN (
|
||||||
SELECT `domain` FROM `domain_admins`
|
SELECT `domain` FROM `domain_admins`
|
||||||
WHERE `username`= :domain_admin)");
|
WHERE `username`= :domain_admin)");
|
||||||
$stmt->execute(array(':domain_admin' => $_data));
|
$stmt->execute(array(':domain_admin' => $_data));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while($row = array_shift($rows)) {
|
while($row = array_shift($rows)) {
|
||||||
$domainadmindata['selected_domains'][] = $row['domain'];
|
$domainadmindata['selected_domains'][] = $row['domain'];
|
||||||
}
|
}
|
||||||
// GET UNSELECTED
|
// GET UNSELECTED
|
||||||
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
|
||||||
WHERE `domain` NOT IN (
|
WHERE `domain` NOT IN (
|
||||||
SELECT `domain` FROM `domain_admins`
|
SELECT `domain` FROM `domain_admins`
|
||||||
WHERE `username`= :domain_admin)");
|
WHERE `username`= :domain_admin)");
|
||||||
$stmt->execute(array(':domain_admin' => $_data));
|
$stmt->execute(array(':domain_admin' => $_data));
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
while($row = array_shift($rows)) {
|
while($row = array_shift($rows)) {
|
||||||
$domainadmindata['unselected_domains'][] = $row['domain'];
|
$domainadmindata['unselected_domains'][] = $row['domain'];
|
||||||
}
|
}
|
||||||
if (!isset($domainadmindata['unselected_domains'])) {
|
if (!isset($domainadmindata['unselected_domains'])) {
|
||||||
$domainadmindata['unselected_domains'] = "";
|
$domainadmindata['unselected_domains'] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $domainadmindata;
|
return $domainadmindata;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function domain_admin_sso($_action, $_data) {
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
switch ($_action) {
|
||||||
|
case 'check':
|
||||||
|
$token = $_data;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `da_sso` AS `t1` JOIN `admin` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL '30' SECOND) AND `t2`.`active` = 1 AND `t2`.`superadmin` = 0;");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token)
|
||||||
|
));
|
||||||
|
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return empty($return['username']) ? false : $return['username'];
|
||||||
|
case 'issue':
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $_data['username'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
|
||||||
|
WHERE `username` = :username");
|
||||||
|
$stmt->execute(array(':username' => $username));
|
||||||
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
|
||||||
|
if ($num_results < 1) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_data),
|
||||||
|
'msg' => array('object_doesnt_exist', htmlspecialchars($username))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = implode('-', array(
|
||||||
|
strtoupper(bin2hex(random_bytes(3))),
|
||||||
|
strtoupper(bin2hex(random_bytes(3))),
|
||||||
|
strtoupper(bin2hex(random_bytes(3))),
|
||||||
|
strtoupper(bin2hex(random_bytes(3))),
|
||||||
|
strtoupper(bin2hex(random_bytes(3)))
|
||||||
|
));
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `da_sso` (`username`, `token`)
|
||||||
|
VALUES (:username, :token)");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
':token' => $token
|
||||||
|
));
|
||||||
|
|
||||||
|
// perform cleanup
|
||||||
|
$pdo->query("DELETE FROM `da_sso` WHERE created < DATE_SUB(NOW(), INTERVAL '30' SECOND);");
|
||||||
|
|
||||||
|
return ['token' => $token];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) {
|
||||||
$is_now = fail2ban('get');
|
$is_now = fail2ban('get');
|
||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
$ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
|
$ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
|
||||||
|
$ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0;
|
||||||
$max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']);
|
$max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']);
|
||||||
|
$max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']);
|
||||||
$retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
|
$retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
|
||||||
$netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
|
$netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
|
||||||
$netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
|
$netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
|
||||||
|
@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) {
|
||||||
}
|
}
|
||||||
$f2b_options = array();
|
$f2b_options = array();
|
||||||
$f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
|
$f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time;
|
||||||
|
$f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false;
|
||||||
|
$f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time;
|
||||||
$f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
|
$f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
|
||||||
$f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
|
$f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
|
||||||
$f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
|
$f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
|
||||||
|
|
|
@ -526,8 +526,9 @@ function logger($_data = false) {
|
||||||
':remote' => get_remote_ip()
|
':remote' => get_remote_ip()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (PDOException $e) {
|
||||||
// Do nothing
|
# handle the exception here, as the exception handler function results in a white page
|
||||||
|
error_log($e->getMessage(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1015,20 +1016,58 @@ function formatBytes($size, $precision = 2) {
|
||||||
}
|
}
|
||||||
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
|
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
|
||||||
}
|
}
|
||||||
function update_sogo_static_view() {
|
function update_sogo_static_view($mailbox = null) {
|
||||||
if (getenv('SKIP_SOGO') == "y") {
|
if (getenv('SKIP_SOGO') == "y") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $lang;
|
global $lang;
|
||||||
$stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
|
|
||||||
WHERE TABLE_NAME = 'sogo_view'");
|
$mailbox_exists = false;
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
if ($mailbox !== null) {
|
||||||
if ($num_results != 0) {
|
// Check if the mailbox exists
|
||||||
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
|
$stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
|
||||||
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
|
$stmt->execute(array(':mailbox' => $mailbox));
|
||||||
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row){
|
||||||
|
$mailbox_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$query = "REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
|
||||||
|
SELECT
|
||||||
|
mailbox.username,
|
||||||
|
mailbox.domain,
|
||||||
|
mailbox.username,
|
||||||
|
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0',
|
||||||
|
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
|
||||||
|
'{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
|
||||||
|
mailbox.name,
|
||||||
|
mailbox.username,
|
||||||
|
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
|
||||||
|
IFNULL(gda.ad_alias, ''),
|
||||||
|
IFNULL(external_acl.send_as_acl, ''),
|
||||||
|
mailbox.kind,
|
||||||
|
mailbox.multiple_bookings
|
||||||
|
FROM
|
||||||
|
mailbox
|
||||||
|
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
|
||||||
|
LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
|
||||||
|
LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
|
||||||
|
WHERE
|
||||||
|
mailbox.active = '1'";
|
||||||
|
|
||||||
|
if ($mailbox_exists) {
|
||||||
|
$query .= " AND mailbox.username = :mailbox";
|
||||||
|
$stmt = $pdo->prepare($query);
|
||||||
|
$stmt->execute(array(':mailbox' => $mailbox));
|
||||||
|
} else {
|
||||||
|
$query .= " GROUP BY mailbox.username";
|
||||||
|
$stmt = $pdo->query($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
|
||||||
|
|
||||||
flush_memcached();
|
flush_memcached();
|
||||||
}
|
}
|
||||||
function edit_user_account($_data) {
|
function edit_user_account($_data) {
|
||||||
|
@ -1739,7 +1778,7 @@ function verify_tfa_login($username, $_data) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
'msg' => array('webauthn_verification_failed', 'authenticator not found')
|
'msg' => array('webauthn_authenticator_failed')
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1748,11 +1787,20 @@ function verify_tfa_login($username, $_data) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
'msg' => array('webauthn_verification_failed', 'publicKey not found')
|
'msg' => array('webauthn_publickey_failed')
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
|
'msg' => array('webauthn_username_failed')
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
|
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
|
||||||
}
|
}
|
||||||
|
@ -1784,21 +1832,12 @@ function verify_tfa_login($username, $_data) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
'log' => array(__FUNCTION__, $username, '*'),
|
||||||
'msg' => array('webauthn_verification_failed', 'could not determine user role')
|
'msg' => array('webauthn_role_failed')
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $username, '*'),
|
|
||||||
'msg' => array('webauthn_verification_failed', 'user who requests does not match with sql entry')
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
|
$_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
|
||||||
$_SESSION['tfa_id'] = $process_webauthn['id'];
|
$_SESSION['tfa_id'] = $process_webauthn['id'];
|
||||||
$_SESSION['authReq'] = null;
|
$_SESSION['authReq'] = null;
|
||||||
|
@ -2093,6 +2132,120 @@ function rspamd_ui($action, $data = null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function cors($action, $data = null) {
|
||||||
|
global $redis;
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case "edit":
|
||||||
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
|
||||||
|
$allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
|
||||||
|
foreach ($allowed_origins as $origin) {
|
||||||
|
if (!filter_var($origin, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) && $origin != '*') {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => 'cors_invalid_origin'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed_methods = isset($data['allowed_methods']) ? $data['allowed_methods'] : array('GET', 'POST', 'PUT', 'DELETE');
|
||||||
|
$allowed_methods = !is_array($allowed_methods) ? array_map('trim', preg_split( "/( |,|;|\n)/", $allowed_methods)) : $allowed_methods;
|
||||||
|
$available_methods = array('GET', 'POST', 'PUT', 'DELETE');
|
||||||
|
foreach ($allowed_methods as $method) {
|
||||||
|
if (!in_array($method, $available_methods)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => 'cors_invalid_method'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$redis->hMSet('CORS_SETTINGS', array(
|
||||||
|
'allowed_origins' => implode(', ', $allowed_origins),
|
||||||
|
'allowed_methods' => implode(', ', $allowed_methods)
|
||||||
|
));
|
||||||
|
} catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => 'cors_headers_edited'
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case "get":
|
||||||
|
try {
|
||||||
|
$cors_settings = $redis->hMGet('CORS_SETTINGS', array('allowed_origins', 'allowed_methods'));
|
||||||
|
} catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $action, $data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cors_settings = !$cors_settings ? array('allowed_origins' => $_SERVER['SERVER_NAME'], 'allowed_methods' => 'GET, POST, PUT, DELETE') : $cors_settings;
|
||||||
|
$cors_settings['allowed_origins'] = empty($cors_settings['allowed_origins']) ? $_SERVER['SERVER_NAME'] : $cors_settings['allowed_origins'];
|
||||||
|
$cors_settings['allowed_methods'] = empty($cors_settings['allowed_methods']) ? 'GET, POST, PUT, DELETE, OPTION' : $cors_settings['allowed_methods'];
|
||||||
|
|
||||||
|
return $cors_settings;
|
||||||
|
break;
|
||||||
|
case "set_headers":
|
||||||
|
$cors_settings = cors('get');
|
||||||
|
// check if requested origin is in allowed origins
|
||||||
|
$allowed_origins = explode(', ', $cors_settings['allowed_origins']);
|
||||||
|
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
||||||
|
if (in_array('*', $allowed_origins)){
|
||||||
|
$cors_settings['allowed_origins'] = '*';
|
||||||
|
} else if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||||
|
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
|
||||||
|
}
|
||||||
|
// always allow OPTIONS for preflight request
|
||||||
|
$cors_settings["allowed_methods"] = empty($cors_settings["allowed_methods"]) ? 'OPTIONS' : $cors_settings["allowed_methods"] . ', ' . 'OPTIONS';
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: ' . $cors_settings['allowed_origins']);
|
||||||
|
header('Access-Control-Allow-Methods: '. $cors_settings['allowed_methods']);
|
||||||
|
header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
|
||||||
|
|
||||||
|
// Access-Control settings requested, this is just a preflight request
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' &&
|
||||||
|
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
|
||||||
|
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
|
||||||
|
|
||||||
|
$allowed_methods = explode(', ', $cors_settings["allowed_methods"]);
|
||||||
|
if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true))
|
||||||
|
// method allowed send 200 OK
|
||||||
|
http_response_code(200);
|
||||||
|
else
|
||||||
|
// method not allowed send 405 METHOD NOT ALLOWED
|
||||||
|
http_response_code(405);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function get_logs($application, $lines = false) {
|
function get_logs($application, $lines = false) {
|
||||||
if ($lines === false) {
|
if ($lines === false) {
|
||||||
|
|
|
@ -1264,11 +1264,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_sogo_static_view($username);
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('mailbox_added', htmlspecialchars($username))
|
'msg' => array('mailbox_added', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
break;
|
break;
|
||||||
case 'resource':
|
case 'resource':
|
||||||
$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);
|
||||||
|
@ -3130,7 +3132,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('mailbox_modified', $username)
|
'msg' => array('mailbox_modified', $username)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
update_sogo_static_view($username);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
break;
|
break;
|
||||||
case 'mailbox_templates':
|
case 'mailbox_templates':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
@ -3960,6 +3965,39 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
}
|
}
|
||||||
return $aliasdomaindata;
|
return $aliasdomaindata;
|
||||||
break;
|
break;
|
||||||
|
case 'shared_aliases':
|
||||||
|
$shared_aliases = array();
|
||||||
|
$stmt = $pdo->query("SELECT `address` FROM `alias`
|
||||||
|
WHERE `goto` REGEXP ','
|
||||||
|
AND `address` NOT LIKE '@%'
|
||||||
|
AND `goto` != `address`");
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$domain = explode("@", $row['address'])[1];
|
||||||
|
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||||
|
$shared_aliases[] = $row['address'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $shared_aliases;
|
||||||
|
break;
|
||||||
|
case 'direct_aliases':
|
||||||
|
$direct_aliases = array();
|
||||||
|
$stmt = $pdo->query("SELECT `address` FROM `alias`
|
||||||
|
WHERE `goto` NOT LIKE '%,%'
|
||||||
|
AND `address` NOT LIKE '@%'
|
||||||
|
AND `goto` != `address`");
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
while($row = array_shift($rows)) {
|
||||||
|
$domain = explode("@", $row['address'])[1];
|
||||||
|
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||||
|
$direct_aliases[] = $row['address'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $direct_aliases;
|
||||||
|
break;
|
||||||
case 'domains':
|
case 'domains':
|
||||||
$domains = array();
|
$domains = array();
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
|
||||||
|
@ -4892,13 +4930,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) {
|
if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) {
|
||||||
$maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part'];
|
$maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part'];
|
||||||
$exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir);
|
$exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir);
|
||||||
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
|
|
||||||
if ($maildir_gc['type'] != 'success') {
|
if (getenv("CLUSTERMODE") == "replication") {
|
||||||
$_SESSION['return'][] = array(
|
// broadcast to each dovecot container
|
||||||
'type' => 'warning',
|
docker('broadcast', 'dovecot-mailcow', 'exec', $exec_fields);
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
} else {
|
||||||
'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg']
|
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
|
||||||
);
|
if ($maildir_gc['type'] != 'success') {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'warning',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg']
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -4951,9 +4995,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as OR `send_as` = :send_as");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':logged_in_as' => $username,
|
||||||
|
':send_as' => $username
|
||||||
));
|
));
|
||||||
// fk, better safe than sorry
|
// fk, better safe than sorry
|
||||||
$stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username");
|
$stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username");
|
||||||
|
@ -5053,12 +5098,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_sogo_static_view($username);
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
'msg' => array('mailbox_removed', htmlspecialchars($username))
|
'msg' => array('mailbox_removed', htmlspecialchars($username))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
break;
|
break;
|
||||||
case 'mailbox_templates':
|
case 'mailbox_templates':
|
||||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||||
|
@ -5264,7 +5312,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) {
|
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
|
||||||
update_sogo_static_view();
|
update_sogo_static_view();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,140 +1,140 @@
|
||||||
<?php
|
<?php
|
||||||
// Start session
|
// Start session
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
ini_set("session.cookie_httponly", 1);
|
ini_set("session.cookie_httponly", 1);
|
||||||
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
|
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
|
||||||
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
|
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
ini_set("session.cookie_secure", 1);
|
ini_set("session.cookie_secure", 1);
|
||||||
}
|
}
|
||||||
$IS_HTTPS = true;
|
$IS_HTTPS = true;
|
||||||
}
|
}
|
||||||
elseif (isset($_SERVER['HTTPS'])) {
|
elseif (isset($_SERVER['HTTPS'])) {
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
ini_set("session.cookie_secure", 1);
|
ini_set("session.cookie_secure", 1);
|
||||||
}
|
}
|
||||||
$IS_HTTPS = true;
|
$IS_HTTPS = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$IS_HTTPS = false;
|
$IS_HTTPS = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($_SESSION['CSRF']['TOKEN'])) {
|
if (!isset($_SESSION['CSRF']['TOKEN'])) {
|
||||||
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
|
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set session UA
|
// Set session UA
|
||||||
if (!isset($_SESSION['SESS_REMOTE_UA'])) {
|
if (!isset($_SESSION['SESS_REMOTE_UA'])) {
|
||||||
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep session active
|
// Keep session active
|
||||||
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
|
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
|
||||||
session_unset();
|
session_unset();
|
||||||
session_destroy();
|
session_destroy();
|
||||||
}
|
}
|
||||||
$_SESSION['LAST_ACTIVITY'] = time();
|
$_SESSION['LAST_ACTIVITY'] = time();
|
||||||
|
|
||||||
// API
|
// API
|
||||||
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
|
||||||
$stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
|
$stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
|
':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
|
||||||
));
|
));
|
||||||
$api_return = $stmt->fetch(PDO::FETCH_ASSOC);
|
$api_return = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (!empty($api_return['api_key'])) {
|
if (!empty($api_return['api_key'])) {
|
||||||
$skip_ip_check = ($api_return['skip_ip_check'] == 1);
|
$skip_ip_check = ($api_return['skip_ip_check'] == 1);
|
||||||
$remote = get_remote_ip(false);
|
$remote = get_remote_ip(false);
|
||||||
$allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
|
$allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
|
||||||
if ($skip_ip_check === true || ip_acl($remote, $allow_from)) {
|
if ($skip_ip_check === true || ip_acl($remote, $allow_from)) {
|
||||||
$_SESSION['mailcow_cc_username'] = 'API';
|
$_SESSION['mailcow_cc_username'] = 'API';
|
||||||
$_SESSION['mailcow_cc_role'] = 'admin';
|
$_SESSION['mailcow_cc_role'] = 'admin';
|
||||||
$_SESSION['mailcow_cc_api'] = true;
|
$_SESSION['mailcow_cc_api'] = true;
|
||||||
if ($api_return['access'] == 'rw') {
|
if ($api_return['access'] == 'rw') {
|
||||||
$_SESSION['mailcow_cc_api_access'] = 'rw';
|
$_SESSION['mailcow_cc_api_access'] = 'rw';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$_SESSION['mailcow_cc_api_access'] = 'ro';
|
$_SESSION['mailcow_cc_api_access'] = 'ro';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(array(
|
echo json_encode(array(
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR']
|
'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR']
|
||||||
));
|
));
|
||||||
unset($_POST);
|
unset($_POST);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
|
||||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(array(
|
echo json_encode(array(
|
||||||
'type' => 'error',
|
'type' => 'error',
|
||||||
'msg' => 'authentication failed'
|
'msg' => 'authentication failed'
|
||||||
));
|
));
|
||||||
unset($_POST);
|
unset($_POST);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle logouts
|
// Handle logouts
|
||||||
if (isset($_POST["logout"])) {
|
if (isset($_POST["logout"])) {
|
||||||
if (isset($_SESSION["dual-login"])) {
|
if (isset($_SESSION["dual-login"])) {
|
||||||
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
|
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
|
||||||
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
|
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
|
||||||
unset($_SESSION["dual-login"]);
|
unset($_SESSION["dual-login"]);
|
||||||
header("Location: /mailbox");
|
header("Location: /mailbox");
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
session_unset();
|
session_unset();
|
||||||
session_destroy();
|
session_destroy();
|
||||||
session_write_close();
|
session_write_close();
|
||||||
header("Location: /");
|
header("Location: /");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check session
|
// Check session
|
||||||
function session_check() {
|
function session_check() {
|
||||||
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
|
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
|
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'warning',
|
'type' => 'warning',
|
||||||
'msg' => 'session_ua'
|
'msg' => 'session_ua'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!empty($_POST)) {
|
if (!empty($_POST)) {
|
||||||
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
|
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'warning',
|
'type' => 'warning',
|
||||||
'msg' => 'session_token'
|
'msg' => 'session_token'
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
unset($_POST['csrf_token']);
|
unset($_POST['csrf_token']);
|
||||||
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
|
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
|
||||||
$_SESSION['CSRF']['TIME'] = time();
|
$_SESSION['CSRF']['TIME'] = time();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
|
if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
|
||||||
$_POST = array();
|
$_POST = array();
|
||||||
$_FILES = array();
|
$_FILES = array();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
// SSO Domain Admin
|
||||||
|
if (!empty($_GET['sso_token'])) {
|
||||||
|
$username = domain_admin_sso('check', $_GET['sso_token']);
|
||||||
|
|
||||||
|
if ($username !== false) {
|
||||||
|
$_SESSION['mailcow_cc_username'] = $username;
|
||||||
|
$_SESSION['mailcow_cc_role'] = 'domainadmin';
|
||||||
|
header('Location: /mailbox');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_POST["verify_tfa_login"])) {
|
if (isset($_POST["verify_tfa_login"])) {
|
||||||
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
|
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
|
||||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||||
|
@ -6,7 +17,7 @@ if (isset($_POST["verify_tfa_login"])) {
|
||||||
unset($_SESSION['pending_mailcow_cc_username']);
|
unset($_SESSION['pending_mailcow_cc_username']);
|
||||||
unset($_SESSION['pending_mailcow_cc_role']);
|
unset($_SESSION['pending_mailcow_cc_role']);
|
||||||
unset($_SESSION['pending_tfa_methods']);
|
unset($_SESSION['pending_tfa_methods']);
|
||||||
|
|
||||||
header("Location: /user");
|
header("Location: /user");
|
||||||
} else {
|
} else {
|
||||||
unset($_SESSION['pending_mailcow_cc_username']);
|
unset($_SESSION['pending_mailcow_cc_username']);
|
||||||
|
@ -34,7 +45,7 @@ if (isset($_POST["quick_delete"])) {
|
||||||
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||||
$login_user = strtolower(trim($_POST["login_user"]));
|
$login_user = strtolower(trim($_POST["login_user"]));
|
||||||
$as = check_login($login_user, $_POST["pass_user"]);
|
$as = check_login($login_user, $_POST["pass_user"]);
|
||||||
|
|
||||||
if ($as == "admin") {
|
if ($as == "admin") {
|
||||||
$_SESSION['mailcow_cc_username'] = $login_user;
|
$_SESSION['mailcow_cc_username'] = $login_user;
|
||||||
$_SESSION['mailcow_cc_role'] = "admin";
|
$_SESSION['mailcow_cc_role'] = "admin";
|
||||||
|
@ -52,7 +63,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||||
unset($_SESSION['index_query_string']);
|
unset($_SESSION['index_query_string']);
|
||||||
if (in_array('mobileconfig', $http_parameters)) {
|
if (in_array('mobileconfig', $http_parameters)) {
|
||||||
if (in_array('only_email', $http_parameters)) {
|
if (in_array('only_email', $http_parameters)) {
|
||||||
header("Location: /mobileconfig.php?email_only");
|
header("Location: /mobileconfig.php?only_email");
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
header("Location: /mobileconfig.php");
|
header("Location: /mobileconfig.php");
|
||||||
|
|
|
@ -124,7 +124,7 @@ $MAILCOW_APPS = array(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Rows until pagination begins
|
// Rows until pagination begins
|
||||||
$PAGINATION_SIZE = 20;
|
$PAGINATION_SIZE = 25;
|
||||||
|
|
||||||
// Default number of rows/lines to display (log table)
|
// Default number of rows/lines to display (log table)
|
||||||
$LOG_LINES = 1000;
|
$LOG_LINES = 1000;
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
const LOCALE = undefined;
|
||||||
|
const DATETIME_FORMAT = {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
};
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// mailcow alert box generator
|
// mailcow alert box generator
|
||||||
window.mailcow_alert_box = function(message, type) {
|
window.mailcow_alert_box = function(message, type) {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,3 @@
|
||||||
const LOCALE = undefined;
|
|
||||||
const DATETIME_FORMAT = {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit"
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Parse seconds ago to date
|
// Parse seconds ago to date
|
||||||
// Get "now" timestamp
|
// Get "now" timestamp
|
||||||
|
@ -34,7 +24,7 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// set update loop container list
|
// set update loop container list
|
||||||
containersToUpdate = {}
|
containersToUpdate = {};
|
||||||
// set default ChartJs Font Color
|
// set default ChartJs Font Color
|
||||||
Chart.defaults.color = '#999';
|
Chart.defaults.color = '#999';
|
||||||
// create host cpu and mem charts
|
// create host cpu and mem charts
|
||||||
|
@ -43,15 +33,14 @@ $(document).ready(function() {
|
||||||
if (mailcow_info.branch === "master"){
|
if (mailcow_info.branch === "master"){
|
||||||
check_update(mailcow_info.version_tag, mailcow_info.project_url);
|
check_update(mailcow_info.version_tag, mailcow_info.project_url);
|
||||||
}
|
}
|
||||||
$("#maiclow_version").click(function(){
|
$("#mailcow_version").click(function(){
|
||||||
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" ||
|
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
|
||||||
mailcow_info.branch !== "master")
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
|
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
|
||||||
})
|
})
|
||||||
// get public ips
|
// get public ips
|
||||||
$("#host_show_ip").click(function(){
|
$("#host_show_ip").click(function(){
|
||||||
$("#host_show_ip").find(".text").addClass("d-none");
|
$("#host_show_ip").find(".text").addClass("d-none");
|
||||||
$("#host_show_ip").find(".spinner-border").removeClass("d-none");
|
$("#host_show_ip").find(".spinner-border").removeClass("d-none");
|
||||||
|
|
||||||
|
@ -76,7 +65,7 @@ $(document).ready(function() {
|
||||||
$("#host_ipv6").addClass("d-block");
|
$("#host_ipv6").addClass("d-block");
|
||||||
}).catch(function(error){
|
}).catch(function(error){
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
||||||
$("#host_ipv6").removeClass("d-none");
|
$("#host_ipv6").removeClass("d-none");
|
||||||
$("#host_ipv6").addClass("d-block");
|
$("#host_ipv6").addClass("d-block");
|
||||||
$("#host_ipv6").addClass("text-danger");
|
$("#host_ipv6").addClass("text-danger");
|
||||||
|
@ -119,10 +108,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#autodiscover_log').DataTable({
|
var table = $('#autodiscover_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -188,10 +178,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#postfix_log').DataTable({
|
var table = $('#postfix_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -242,10 +233,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#watchdog_log').DataTable({
|
var table = $('#watchdog_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -300,10 +292,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#api_log').DataTable({
|
var table = $('#api_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -352,7 +345,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-api-logs', '#api_log');
|
hideTableExpandCollapseBtn('#tab-api-logs', '#api_log');
|
||||||
});
|
});
|
||||||
|
@ -365,10 +358,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#rl_log').DataTable({
|
var table = $('#rl_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -455,7 +449,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log');
|
hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log');
|
||||||
});
|
});
|
||||||
|
@ -468,10 +462,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#ui_logs').DataTable({
|
var table = $('#ui_logs').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -538,7 +533,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log');
|
hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log');
|
||||||
});
|
});
|
||||||
|
@ -551,10 +546,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#sasl_logs').DataTable({
|
var table = $('#sasl_logs').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -598,7 +594,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs');
|
hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs');
|
||||||
});
|
});
|
||||||
|
@ -611,10 +607,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#acme_log').DataTable({
|
var table = $('#acme_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -647,7 +644,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log');
|
hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log');
|
||||||
});
|
});
|
||||||
|
@ -660,10 +657,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#netfilter_log').DataTable({
|
var table = $('#netfilter_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -701,7 +699,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log');
|
hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log');
|
||||||
});
|
});
|
||||||
|
@ -714,10 +712,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#sogo_log').DataTable({
|
var table = $('#sogo_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -755,7 +754,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log');
|
hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log');
|
||||||
});
|
});
|
||||||
|
@ -768,10 +767,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#dovecot_log').DataTable({
|
var table = $('#dovecot_log').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -819,13 +819,10 @@ jQuery(function($){
|
||||||
url: '/api/v1/get/rspamd/actions',
|
url: '/api/v1/get/rspamd/actions',
|
||||||
async: true,
|
async: true,
|
||||||
success: function(data){
|
success: function(data){
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
var total = 0;
|
var total = 0;
|
||||||
$(data).map(function(){total += this[1];});
|
$(data).map(function(){total += this[1];});
|
||||||
var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';}));
|
var labels = $.makeArray($(data).map(function(){return this[0] + ' ' + Math.round(this[1]/total * 100) + '%';}));
|
||||||
var values = $.makeArray($(data).map(function(){return this[1];}));
|
var values = $.makeArray($(data).map(function(){return this[1];}));
|
||||||
console.log(values);
|
|
||||||
|
|
||||||
var graphdata = {
|
var graphdata = {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
|
@ -883,10 +880,11 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = $('#rspamd_history').DataTable({
|
var table = $('#rspamd_history').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
|
pageLength: log_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>>" +
|
||||||
"tr" +
|
"tr" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
@ -940,12 +938,15 @@ jQuery(function($){
|
||||||
title: 'Score',
|
title: 'Score',
|
||||||
data: 'score',
|
data: 'score',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
|
class: 'text-nowrap',
|
||||||
createdCell: function(td, cellData) {
|
createdCell: function(td, cellData) {
|
||||||
$(td).attr({
|
$(td).attr({
|
||||||
"data-order": cellData.sortBy,
|
"data-order": cellData.sortBy,
|
||||||
"data-sort": cellData.sortBy
|
"data-sort": cellData.sortBy
|
||||||
});
|
});
|
||||||
$(td).html(cellData.value);
|
},
|
||||||
|
render: function (data) {
|
||||||
|
return data.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -968,7 +969,9 @@ jQuery(function($){
|
||||||
"data-order": cellData.sortBy,
|
"data-order": cellData.sortBy,
|
||||||
"data-sort": cellData.sortBy
|
"data-sort": cellData.sortBy
|
||||||
});
|
});
|
||||||
$(td).html(cellData.value);
|
},
|
||||||
|
render: function (data) {
|
||||||
|
return data.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -983,7 +986,7 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history');
|
hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history');
|
||||||
});
|
});
|
||||||
|
@ -998,31 +1001,31 @@ jQuery(function($){
|
||||||
item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
|
item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
|
||||||
}
|
}
|
||||||
item.symbols = Object.keys(item.symbols).sort(function (a, b) {
|
item.symbols = Object.keys(item.symbols).sort(function (a, b) {
|
||||||
if (item.symbols[a].score === 0) return 1
|
if (item.symbols[a].score === 0) return 1;
|
||||||
if (item.symbols[b].score === 0) return -1
|
if (item.symbols[b].score === 0) return -1;
|
||||||
if (item.symbols[b].score < 0 && item.symbols[a].score < 0) {
|
if (item.symbols[b].score < 0 && item.symbols[a].score < 0) {
|
||||||
return item.symbols[a].score - item.symbols[b].score
|
return item.symbols[a].score - item.symbols[b].score;
|
||||||
}
|
}
|
||||||
if (item.symbols[b].score > 0 && item.symbols[a].score > 0) {
|
if (item.symbols[b].score > 0 && item.symbols[a].score > 0) {
|
||||||
return item.symbols[b].score - item.symbols[a].score
|
return item.symbols[b].score - item.symbols[a].score;
|
||||||
}
|
}
|
||||||
return item.symbols[b].score - item.symbols[a].score
|
return item.symbols[b].score - item.symbols[a].score;
|
||||||
}).map(function(key) {
|
}).map(function(key) {
|
||||||
var sym = item.symbols[key];
|
var sym = item.symbols[key];
|
||||||
if (sym.score < 0) {
|
if (sym.score < 0) {
|
||||||
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
|
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)';
|
||||||
}
|
}
|
||||||
else if (sym.score === 0) {
|
else if (sym.score === 0) {
|
||||||
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)'
|
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
|
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)';
|
||||||
}
|
}
|
||||||
var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
|
var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
|
||||||
if (sym.options) {
|
if (sym.options) {
|
||||||
str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
|
str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
|
||||||
}
|
}
|
||||||
return str
|
return str;
|
||||||
}).join('<br>\n');
|
}).join('<br>\n');
|
||||||
item.subject = escapeHtml(item.subject);
|
item.subject = escapeHtml(item.subject);
|
||||||
var scan_time = item.time_real.toFixed(3);
|
var scan_time = item.time_real.toFixed(3);
|
||||||
|
@ -1155,14 +1158,14 @@ jQuery(function($){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return data
|
return data;
|
||||||
};
|
};
|
||||||
$('.add_log_lines').on('click', function (e) {
|
$('.add_log_lines').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var log_table= $(this).data("table")
|
var log_table= $(this).data("table");
|
||||||
var new_nrows = $(this).data("nrows")
|
var new_nrows = $(this).data("nrows");
|
||||||
var post_process = $(this).data("post-process")
|
var post_process = $(this).data("post-process");
|
||||||
var log_url = $(this).data("log-url")
|
var log_url = $(this).data("log-url");
|
||||||
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
|
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
|
||||||
console.log("no data-table or data-nrows or log_url or data-post-process attr found");
|
console.log("no data-table or data-nrows or log_url or data-post-process attr found");
|
||||||
return;
|
return;
|
||||||
|
@ -1170,7 +1173,7 @@ jQuery(function($){
|
||||||
|
|
||||||
if (table = $('#' + log_table).DataTable()) {
|
if (table = $('#' + log_table).DataTable()) {
|
||||||
var heading = $('#' + log_table).closest('.card').find('.card-header');
|
var heading = $('#' + log_table).closest('.card').find('.card-header');
|
||||||
var load_rows = (table.page.len() + 1) + '-' + (table.page.len() + new_nrows)
|
var load_rows = (table.data().count() + 1) + '-' + (table.data().count() + new_nrows)
|
||||||
|
|
||||||
$.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
|
$.get('/api/v1/get/logs/' + log_url + '/' + load_rows).then(function(data){
|
||||||
if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
|
if (data.length === undefined) { mailcow_alert_box(lang.no_new_rows, "info"); return; }
|
||||||
|
@ -1184,9 +1187,9 @@ jQuery(function($){
|
||||||
})
|
})
|
||||||
function hideTableExpandCollapseBtn(tab, table){
|
function hideTableExpandCollapseBtn(tab, table){
|
||||||
if ($(table).hasClass('collapsed'))
|
if ($(table).hasClass('collapsed'))
|
||||||
$(tab).find(".table_collapse_option").show();
|
$(tab).find(".table_collapse_option").show();
|
||||||
else
|
else
|
||||||
$(tab).find(".table_collapse_option").hide();
|
$(tab).find(".table_collapse_option").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect element visibility changes
|
// detect element visibility changes
|
||||||
|
@ -1220,7 +1223,6 @@ jQuery(function($){
|
||||||
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
|
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// start polling host stats if tab is active
|
// start polling host stats if tab is active
|
||||||
onVisible("[id^=tab-containers]", () => update_stats());
|
onVisible("[id^=tab-containers]", () => update_stats());
|
||||||
// start polling container stats if collapse is active
|
// start polling container stats if collapse is active
|
||||||
|
@ -1292,6 +1294,12 @@ function update_stats(timeout=5){
|
||||||
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
|
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
|
||||||
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
|
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
|
||||||
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
|
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
|
||||||
|
if (data.architecture == "aarch64"){
|
||||||
|
$("#host_architecture").html('<span data-bs-toggle="tooltip" data-bs-placement="top" title="' + lang_debug.wip +'">' + data.architecture + ' ⚠️</span>');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#host_architecture").html(data.architecture);
|
||||||
|
}
|
||||||
|
|
||||||
// update cpu and mem chart
|
// update cpu and mem chart
|
||||||
var cpu_chart = Chart.getChart("host_cpu_chart");
|
var cpu_chart = Chart.getChart("host_cpu_chart");
|
||||||
|
@ -1303,9 +1311,9 @@ function update_stats(timeout=5){
|
||||||
if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift();
|
if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift();
|
||||||
|
|
||||||
cpu_chart.data.datasets[0].data.push(data.cpu.usage);
|
cpu_chart.data.datasets[0].data.push(data.cpu.usage);
|
||||||
if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift();
|
if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift();
|
||||||
mem_chart.data.datasets[0].data.push(data.memory.usage);
|
mem_chart.data.datasets[0].data.push(data.memory.usage);
|
||||||
if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift();
|
if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift();
|
||||||
|
|
||||||
cpu_chart.update();
|
cpu_chart.update();
|
||||||
mem_chart.update();
|
mem_chart.update();
|
||||||
|
@ -1464,23 +1472,23 @@ function createReadWriteChart(chart_id, read_lable, write_lable){
|
||||||
};
|
};
|
||||||
var optionsNet = {
|
var optionsNet = {
|
||||||
interaction: {
|
interaction: {
|
||||||
mode: 'index'
|
mode: 'index'
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
min: 0,
|
min: 0,
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function(i, index, ticks) {
|
callback: function(i, index, ticks) {
|
||||||
return formatBytes(i);
|
return formatBytes(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1528,13 +1536,13 @@ function createHostCpuAndMemChart(){
|
||||||
};
|
};
|
||||||
var optionsCpu = {
|
var optionsCpu = {
|
||||||
interaction: {
|
interaction: {
|
||||||
mode: 'index'
|
mode: 'index'
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
min: 0,
|
min: 0,
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function(i, index, ticks) {
|
callback: function(i, index, ticks) {
|
||||||
|
@ -1544,7 +1552,7 @@ function createHostCpuAndMemChart(){
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1566,13 +1574,13 @@ function createHostCpuAndMemChart(){
|
||||||
};
|
};
|
||||||
var optionsMem = {
|
var optionsMem = {
|
||||||
interaction: {
|
interaction: {
|
||||||
mode: 'index'
|
mode: 'index'
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
min: 0,
|
min: 0,
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function(i, index, ticks) {
|
callback: function(i, index, ticks) {
|
||||||
|
@ -1582,7 +1590,7 @@ function createHostCpuAndMemChart(){
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
grid: {
|
grid: {
|
||||||
display: false
|
display: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1678,22 +1686,22 @@ function parseGithubMarkdownLinks(inputText) {
|
||||||
|
|
||||||
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
|
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
|
||||||
replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
|
replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
|
||||||
if (matched.includes('github.com')){
|
if (matched.includes('github.com')){
|
||||||
// return short link if it's github link
|
// return short link if it's github link
|
||||||
last_uri_path = matched.split('/');
|
last_uri_path = matched.split('/');
|
||||||
last_uri_path = last_uri_path[last_uri_path.length - 1];
|
last_uri_path = last_uri_path[last_uri_path.length - 1];
|
||||||
|
|
||||||
// adjust Full Changelog link to match last git version and new git version, if link is a compare link
|
// adjust Full Changelog link to match last git version and new git version, if link is a compare link
|
||||||
if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
|
if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
|
||||||
matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag);
|
matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag);
|
||||||
last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
|
last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
|
return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
|
||||||
};
|
};
|
||||||
|
|
||||||
// if it's not a github link, return complete link
|
// if it's not a github link, return complete link
|
||||||
return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
|
return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
|
||||||
});
|
});
|
||||||
|
|
||||||
return replacedText;
|
return replacedText;
|
||||||
|
|
|
@ -1,220 +1,222 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
|
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
|
||||||
$("#pushover_delete").click(function() { return confirm(lang.delete_ays); });
|
$("#pushover_delete").click(function() { return confirm(lang.delete_ays); });
|
||||||
$(".goto_checkbox").click(function( event ) {
|
$(".goto_checkbox").click(function( event ) {
|
||||||
$("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false);
|
$("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false);
|
||||||
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
|
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
|
||||||
$('#textarea_alias_goto').prop('disabled', true);
|
$('#textarea_alias_goto').prop('disabled', true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$("#textarea_alias_goto").removeAttr('disabled');
|
$("#textarea_alias_goto").removeAttr('disabled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#disable_sender_check").click(function( event ) {
|
$("#disable_sender_check").click(function( event ) {
|
||||||
if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) {
|
if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) {
|
||||||
$('#editSelectSenderACL').prop('disabled', true);
|
$('#editSelectSenderACL').prop('disabled', true);
|
||||||
$('#editSelectSenderACL').selectpicker('refresh');
|
$('#editSelectSenderACL').selectpicker('refresh');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('#editSelectSenderACL').prop('disabled', false);
|
$('#editSelectSenderACL').prop('disabled', false);
|
||||||
$('#editSelectSenderACL').selectpicker('refresh');
|
$('#editSelectSenderACL').selectpicker('refresh');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
|
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
|
||||||
$('#textarea_alias_goto').prop('disabled', true);
|
$('#textarea_alias_goto').prop('disabled', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#mailbox-password-warning-close").click(function( event ) {
|
$("#mailbox-password-warning-close").click(function( event ) {
|
||||||
$('#mailbox-passwd-hidden-info').addClass('hidden');
|
$('#mailbox-passwd-hidden-info').addClass('hidden');
|
||||||
$('#mailbox-passwd-form-groups').removeClass('hidden');
|
$('#mailbox-passwd-form-groups').removeClass('hidden');
|
||||||
});
|
});
|
||||||
// Sender ACL
|
// Sender ACL
|
||||||
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
|
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
|
||||||
$("#sender_acl_disabled").show();
|
$("#sender_acl_disabled").show();
|
||||||
}
|
}
|
||||||
$('#editSelectSenderACL').change(function() {
|
$('#editSelectSenderACL').change(function() {
|
||||||
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
|
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
|
||||||
$("#sender_acl_disabled").show();
|
$("#sender_acl_disabled").show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$("#sender_acl_disabled").hide();
|
$("#sender_acl_disabled").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Resources
|
// Resources
|
||||||
if ($("#editSelectMultipleBookings").val() == "custom") {
|
if ($("#editSelectMultipleBookings").val() == "custom") {
|
||||||
$("#multiple_bookings_custom_div").show();
|
$("#multiple_bookings_custom_div").show();
|
||||||
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
|
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
|
||||||
}
|
}
|
||||||
$("#editSelectMultipleBookings").change(function() {
|
$("#editSelectMultipleBookings").change(function() {
|
||||||
$('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val());
|
$('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val());
|
||||||
if ($('input[name=multiple_bookings]').val() == "custom") {
|
if ($('input[name=multiple_bookings]').val() == "custom") {
|
||||||
$("#multiple_bookings_custom_div").show();
|
$("#multiple_bookings_custom_div").show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$("#multiple_bookings_custom_div").hide();
|
$("#multiple_bookings_custom_div").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
|
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
|
||||||
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
|
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
|
||||||
});
|
});
|
||||||
|
|
||||||
// load tags
|
// load tags
|
||||||
if ($('#tags').length){
|
if ($('#tags').length){
|
||||||
var tagsEl = $('#tags').parent().find('.tag-values')[0];
|
var tagsEl = $('#tags').parent().find('.tag-values')[0];
|
||||||
console.log($(tagsEl).val())
|
console.log($(tagsEl).val())
|
||||||
var tags = JSON.parse($(tagsEl).val());
|
var tags = JSON.parse($(tagsEl).val());
|
||||||
$(tagsEl).val("");
|
$(tagsEl).val("");
|
||||||
|
|
||||||
for (var i = 0; i < tags.length; i++)
|
for (var i = 0; i < tags.length; i++)
|
||||||
addTag($('#tags'), tags[i]);
|
addTag($('#tags'), tags[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
jQuery(function($){
|
jQuery(function($){
|
||||||
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
|
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
|
||||||
function validateEmail(email) {
|
function validateEmail(email) {
|
||||||
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
return re.test(email);
|
return re.test(email);
|
||||||
}
|
}
|
||||||
function draw_wl_policy_domain_table() {
|
function draw_wl_policy_domain_table() {
|
||||||
$('#wl_policy_domain_table').DataTable({
|
$('#wl_policy_domain_table').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
pageLength: pagination_size,
|
||||||
"tr" +
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
"tr" +
|
||||||
language: lang_datatables,
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
ajax: {
|
language: lang_datatables,
|
||||||
type: "GET",
|
ajax: {
|
||||||
url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
|
type: "GET",
|
||||||
dataSrc: function(data){
|
url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
|
||||||
$.each(data, function (i, item) {
|
dataSrc: function(data){
|
||||||
if (!validateEmail(item.object)) {
|
$.each(data, function (i, item) {
|
||||||
item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
|
if (!validateEmail(item.object)) {
|
||||||
}
|
item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||||
else {
|
}
|
||||||
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
|
else {
|
||||||
}
|
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||||
});
|
}
|
||||||
|
});
|
||||||
return data;
|
|
||||||
}
|
return data;
|
||||||
},
|
}
|
||||||
columns: [
|
},
|
||||||
{
|
columns: [
|
||||||
// placeholder, so checkbox will not block child row toggle
|
{
|
||||||
title: '',
|
// placeholder, so checkbox will not block child row toggle
|
||||||
data: null,
|
title: '',
|
||||||
searchable: false,
|
data: null,
|
||||||
orderable: false,
|
searchable: false,
|
||||||
defaultContent: ''
|
orderable: false,
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: '',
|
{
|
||||||
data: 'chkbox',
|
title: '',
|
||||||
searchable: false,
|
data: 'chkbox',
|
||||||
orderable: false,
|
searchable: false,
|
||||||
defaultContent: ''
|
orderable: false,
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: 'ID',
|
{
|
||||||
data: 'prefid',
|
title: 'ID',
|
||||||
defaultContent: ''
|
data: 'prefid',
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: lang_user.spamfilter_table_rule,
|
{
|
||||||
data: 'value',
|
title: lang_user.spamfilter_table_rule,
|
||||||
defaultContent: ''
|
data: 'value',
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: 'Scope',
|
{
|
||||||
data: 'object',
|
title: 'Scope',
|
||||||
defaultContent: ''
|
data: 'object',
|
||||||
}
|
defaultContent: ''
|
||||||
]
|
}
|
||||||
});
|
]
|
||||||
}
|
});
|
||||||
function draw_bl_policy_domain_table() {
|
}
|
||||||
$('#bl_policy_domain_table').DataTable({
|
function draw_bl_policy_domain_table() {
|
||||||
responsive: true,
|
$('#bl_policy_domain_table').DataTable({
|
||||||
processing: true,
|
responsive: true,
|
||||||
serverSide: false,
|
processing: true,
|
||||||
stateSave: true,
|
serverSide: false,
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
stateSave: true,
|
||||||
"tr" +
|
pageLength: pagination_size,
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
language: lang_datatables,
|
"tr" +
|
||||||
ajax: {
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
type: "GET",
|
language: lang_datatables,
|
||||||
url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
|
ajax: {
|
||||||
dataSrc: function(data){
|
type: "GET",
|
||||||
$.each(data, function (i, item) {
|
url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
|
||||||
if (!validateEmail(item.object)) {
|
dataSrc: function(data){
|
||||||
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
|
$.each(data, function (i, item) {
|
||||||
}
|
if (!validateEmail(item.object)) {
|
||||||
else {
|
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
|
||||||
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
|
}
|
||||||
}
|
else {
|
||||||
});
|
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
|
||||||
|
}
|
||||||
return data;
|
});
|
||||||
}
|
|
||||||
},
|
return data;
|
||||||
columns: [
|
}
|
||||||
{
|
},
|
||||||
// placeholder, so checkbox will not block child row toggle
|
columns: [
|
||||||
title: '',
|
{
|
||||||
data: null,
|
// placeholder, so checkbox will not block child row toggle
|
||||||
searchable: false,
|
title: '',
|
||||||
orderable: false,
|
data: null,
|
||||||
defaultContent: ''
|
searchable: false,
|
||||||
},
|
orderable: false,
|
||||||
{
|
defaultContent: ''
|
||||||
title: '',
|
},
|
||||||
data: 'chkbox',
|
{
|
||||||
searchable: false,
|
title: '',
|
||||||
orderable: false,
|
data: 'chkbox',
|
||||||
defaultContent: ''
|
searchable: false,
|
||||||
},
|
orderable: false,
|
||||||
{
|
defaultContent: ''
|
||||||
title: 'ID',
|
},
|
||||||
data: 'prefid',
|
{
|
||||||
defaultContent: ''
|
title: 'ID',
|
||||||
},
|
data: 'prefid',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang_user.spamfilter_table_rule,
|
},
|
||||||
data: 'value',
|
{
|
||||||
defaultContent: ''
|
title: lang_user.spamfilter_table_rule,
|
||||||
},
|
data: 'value',
|
||||||
{
|
defaultContent: ''
|
||||||
title: 'Scope',
|
},
|
||||||
data: 'object',
|
{
|
||||||
defaultContent: ''
|
title: 'Scope',
|
||||||
}
|
data: 'object',
|
||||||
]
|
defaultContent: ''
|
||||||
});
|
}
|
||||||
}
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
// detect element visibility changes
|
|
||||||
function onVisible(element, callback) {
|
|
||||||
$(document).ready(function() {
|
// detect element visibility changes
|
||||||
element_object = document.querySelector(element);
|
function onVisible(element, callback) {
|
||||||
if (element_object === null) return;
|
$(document).ready(function() {
|
||||||
|
element_object = document.querySelector(element);
|
||||||
new IntersectionObserver((entries, observer) => {
|
if (element_object === null) return;
|
||||||
entries.forEach(entry => {
|
|
||||||
if(entry.intersectionRatio > 0) {
|
new IntersectionObserver((entries, observer) => {
|
||||||
callback(element_object);
|
entries.forEach(entry => {
|
||||||
observer.disconnect();
|
if(entry.intersectionRatio > 0) {
|
||||||
}
|
callback(element_object);
|
||||||
});
|
observer.disconnect();
|
||||||
}).observe(element_object);
|
}
|
||||||
});
|
});
|
||||||
}
|
}).observe(element_object);
|
||||||
// Draw Table if tab is active
|
});
|
||||||
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
|
}
|
||||||
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table());
|
// Draw Table if tab is active
|
||||||
});
|
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
|
||||||
|
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table());
|
||||||
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,71 +1,71 @@
|
||||||
jQuery(function($){
|
jQuery(function($){
|
||||||
var qitem = $('legend').data('hash');
|
var qitem = $('legend').data('hash');
|
||||||
var qError = $("#qid_error");
|
var qError = $("#qid_error");
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/inc/ajax/qitem_details.php',
|
url: '/inc/ajax/qitem_details.php',
|
||||||
data: { hash: qitem },
|
data: { hash: qitem },
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(data){
|
success: function(data){
|
||||||
$('[data-id="qitems_single"]').each(function(index) {
|
$('[data-id="qitems_single"]').each(function(index) {
|
||||||
$(this).attr("data-item", qitem);
|
$(this).attr("data-item", qitem);
|
||||||
});
|
});
|
||||||
$('#qid_detail_subj').text(data.subject);
|
$('#qid_detail_subj').text(data.subject);
|
||||||
$('#qid_detail_hfrom').text(data.header_from);
|
$('#qid_detail_hfrom').text(data.header_from);
|
||||||
$('#qid_detail_efrom').text(data.env_from);
|
$('#qid_detail_efrom').text(data.env_from);
|
||||||
$('#qid_detail_score').html('');
|
$('#qid_detail_score').html('');
|
||||||
$('#qid_detail_symbols').html('');
|
$('#qid_detail_symbols').html('');
|
||||||
$('#qid_detail_recipients').html('');
|
$('#qid_detail_recipients').html('');
|
||||||
$('#qid_detail_fuzzy').html('');
|
$('#qid_detail_fuzzy').html('');
|
||||||
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
|
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
|
||||||
$.each(data.fuzzy_hashes, function (index, value) {
|
$.each(data.fuzzy_hashes, function (index, value) {
|
||||||
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
|
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$('#qid_detail_fuzzy').append('-');
|
$('#qid_detail_fuzzy').append('-');
|
||||||
}
|
}
|
||||||
if (typeof data.symbols !== 'undefined') {
|
if (typeof data.symbols !== 'undefined') {
|
||||||
data.symbols.sort(function (a, b) {
|
data.symbols.sort(function (a, b) {
|
||||||
if (a.score === 0) return 1
|
if (a.score === 0) return 1;
|
||||||
if (b.score === 0) return -1
|
if (b.score === 0) return -1;
|
||||||
if (b.score < 0 && a.score < 0) {
|
if (b.score < 0 && a.score < 0) {
|
||||||
return a.score - b.score
|
return a.score - b.score;
|
||||||
}
|
}
|
||||||
if (b.score > 0 && a.score > 0) {
|
if (b.score > 0 && a.score > 0) {
|
||||||
return b.score - a.score
|
return b.score - a.score;
|
||||||
}
|
}
|
||||||
return b.score - a.score
|
return b.score - a.score;
|
||||||
})
|
})
|
||||||
$.each(data.symbols, function (index, value) {
|
$.each(data.symbols, function (index, value) {
|
||||||
var highlightClass = ''
|
var highlightClass = '';
|
||||||
if (value.score > 0) highlightClass = 'negative'
|
if (value.score > 0) highlightClass = 'negative';
|
||||||
else if (value.score < 0) highlightClass = 'positive'
|
else if (value.score < 0) highlightClass = 'positive';
|
||||||
else highlightClass = 'neutral'
|
else highlightClass = 'neutral';
|
||||||
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
|
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
|
||||||
});
|
});
|
||||||
$('[data-bs-toggle="tooltip"]').tooltip()
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||||
}
|
}
|
||||||
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
|
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
|
||||||
if (data.action === "add header") {
|
if (data.action === "add header") {
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
|
||||||
} else if (data.action === "reject") {
|
} else if (data.action === "reject") {
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
|
||||||
} else if (data.action === "rewrite subject") {
|
} else if (data.action === "rewrite subject") {
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof data.recipients !== 'undefined') {
|
if (typeof data.recipients !== 'undefined') {
|
||||||
$.each(data.recipients, function(index, value) {
|
$.each(data.recipients, function(index, value) {
|
||||||
var elem = $('<span class="mail-address-item"></span>');
|
var elem = $('<span class="mail-address-item"></span>');
|
||||||
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
|
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
|
||||||
$('#qid_detail_recipients').append(elem);
|
$('#qid_detail_recipients').append(elem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(data){
|
error: function(data){
|
||||||
if (typeof data.error !== 'undefined') {
|
if (typeof data.error !== 'undefined') {
|
||||||
qError.text("Error loading quarantine item");
|
qError.text("Error loading quarantine item");
|
||||||
qError.show();
|
qError.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,286 +1,297 @@
|
||||||
// Base64 functions
|
// Base64 functions
|
||||||
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
|
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
|
||||||
|
|
||||||
jQuery(function($){
|
jQuery(function($){
|
||||||
acl_data = JSON.parse(acl);
|
acl_data = JSON.parse(acl);
|
||||||
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
|
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
|
||||||
var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};
|
var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};
|
||||||
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
|
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
|
||||||
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
|
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
|
||||||
$(".refresh_table").on('click', function(e) {
|
$(".refresh_table").on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var table_name = $(this).data('table');
|
var table_name = $(this).data('table');
|
||||||
$('#' + table_name).DataTable().ajax.reload();
|
$('#' + table_name).DataTable().ajax.reload();
|
||||||
});
|
});
|
||||||
function draw_quarantine_table() {
|
function draw_quarantine_table() {
|
||||||
var table = $('#quarantinetable').DataTable({
|
var table = $('#quarantinetable').DataTable({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
processing: true,
|
processing: true,
|
||||||
serverSide: false,
|
serverSide: false,
|
||||||
stateSave: true,
|
stateSave: true,
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
pageLength: pagination_size,
|
||||||
"tr" +
|
order: [[2, 'desc']],
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
lengthMenu: [
|
||||||
language: lang_datatables,
|
[10, 25, 50, 100, -1],
|
||||||
initComplete: function(){
|
[10, 25, 50, 100, 'all']
|
||||||
hideTableExpandCollapseBtn('#quarantinetable');
|
],
|
||||||
},
|
pagingType: 'first_last_numbers',
|
||||||
ajax: {
|
aColumns: [
|
||||||
type: "GET",
|
{ sWidth: '8.25%' },
|
||||||
url: "/api/v1/get/quarantine/all",
|
{ sClass: 'classDataTable' }
|
||||||
dataSrc: function(data){
|
],
|
||||||
$.each(data, function (i, item) {
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
if (item.subject === null) {
|
"tr" +
|
||||||
item.subject = '';
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
} else {
|
language: lang_datatables,
|
||||||
item.subject = escapeHtml(item.subject);
|
initComplete: function(){
|
||||||
}
|
hideTableExpandCollapseBtn('#quarantinetable');
|
||||||
if (item.score === null) {
|
},
|
||||||
item.score = '-';
|
ajax: {
|
||||||
}
|
type: "GET",
|
||||||
if (item.virus_flag > 0) {
|
url: "/api/v1/get/quarantine/all",
|
||||||
item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
|
dataSrc: function(data){
|
||||||
} else {
|
$.each(data, function (i, item) {
|
||||||
item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
|
if (item.subject === null) {
|
||||||
}
|
item.subject = '';
|
||||||
if (item.action === "reject") {
|
} else {
|
||||||
item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
|
item.subject = escapeHtml(item.subject);
|
||||||
} else if (item.action === "add header") {
|
}
|
||||||
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
|
if (item.score === null) {
|
||||||
} else if (item.action === "rewrite subject") {
|
item.score = '-';
|
||||||
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
|
}
|
||||||
}
|
if (item.virus_flag > 0) {
|
||||||
if(item.notified > 0) {
|
item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
|
||||||
item.notified = '✔';
|
} else {
|
||||||
} else {
|
item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
|
||||||
item.notified = '✖';
|
}
|
||||||
}
|
if (item.action === "reject") {
|
||||||
if (acl_data.login_as === 1) {
|
item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
|
||||||
item.action = '<div class="btn-group">' +
|
} else if (item.action === "add header") {
|
||||||
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
|
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
|
||||||
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
} else if (item.action === "rewrite subject") {
|
||||||
'</div>';
|
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
|
||||||
}
|
}
|
||||||
else {
|
if(item.notified > 0) {
|
||||||
item.action = '<div class="btn-group">' +
|
item.notified = '✔';
|
||||||
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-file-earmark-text"></i> ' + lang.show_item + '</a>' +
|
} else {
|
||||||
'</div>';
|
item.notified = '✖';
|
||||||
}
|
}
|
||||||
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
|
if (acl_data.login_as === 1) {
|
||||||
});
|
item.action = '<div class="btn-group">' +
|
||||||
|
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
|
||||||
return data;
|
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||||
}
|
'</div>';
|
||||||
},
|
}
|
||||||
columns: [
|
else {
|
||||||
{
|
item.action = '<div class="btn-group">' +
|
||||||
// placeholder, so checkbox will not block child row toggle
|
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><i class="bi bi-file-earmark-text"></i> ' + lang.show_item + '</a>' +
|
||||||
title: '',
|
'</div>';
|
||||||
data: null,
|
}
|
||||||
searchable: false,
|
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
|
||||||
orderable: false,
|
});
|
||||||
defaultContent: ''
|
|
||||||
},
|
return data;
|
||||||
{
|
}
|
||||||
title: '',
|
},
|
||||||
data: 'chkbox',
|
columns: [
|
||||||
searchable: false,
|
{
|
||||||
orderable: false,
|
// placeholder, so checkbox will not block child row toggle
|
||||||
defaultContent: ''
|
title: '',
|
||||||
},
|
data: null,
|
||||||
{
|
searchable: false,
|
||||||
title: 'ID',
|
orderable: false,
|
||||||
data: 'id',
|
defaultContent: ''
|
||||||
defaultContent: ''
|
},
|
||||||
},
|
{
|
||||||
{
|
title: '',
|
||||||
title: lang.qid,
|
data: 'chkbox',
|
||||||
data: 'qid',
|
searchable: false,
|
||||||
defaultContent: ''
|
orderable: false,
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: lang.sender,
|
{
|
||||||
data: 'sender',
|
title: 'ID',
|
||||||
defaultContent: ''
|
data: 'id',
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: lang.subj,
|
{
|
||||||
data: 'subject',
|
title: lang.qid,
|
||||||
defaultContent: ''
|
data: 'qid',
|
||||||
},
|
defaultContent: ''
|
||||||
{
|
},
|
||||||
title: lang.rspamd_result,
|
{
|
||||||
data: 'rspamdaction',
|
title: lang.sender,
|
||||||
defaultContent: ''
|
data: 'sender',
|
||||||
},
|
className: 'senders-mw220',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang.rcpt,
|
},
|
||||||
data: 'rcpt',
|
{
|
||||||
defaultContent: ''
|
title: lang.subj,
|
||||||
},
|
data: 'subject',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang.danger,
|
},
|
||||||
data: 'virus',
|
{
|
||||||
defaultContent: ''
|
title: lang.rspamd_result,
|
||||||
},
|
data: 'rspamdaction',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang.spam_score,
|
},
|
||||||
data: 'score',
|
{
|
||||||
defaultContent: ''
|
title: lang.rcpt,
|
||||||
},
|
data: 'rcpt',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang.notified,
|
},
|
||||||
data: 'notified',
|
{
|
||||||
defaultContent: ''
|
title: lang.danger,
|
||||||
},
|
data: 'virus',
|
||||||
{
|
defaultContent: ''
|
||||||
title: lang.received,
|
},
|
||||||
data: 'created',
|
{
|
||||||
defaultContent: '',
|
title: lang.spam_score,
|
||||||
createdCell: function(td, cellData) {
|
data: 'score',
|
||||||
$(td).attr({
|
defaultContent: ''
|
||||||
"data-order": cellData,
|
},
|
||||||
"data-sort": cellData
|
{
|
||||||
});
|
title: lang.notified,
|
||||||
|
data: 'notified',
|
||||||
var date = new Date(cellData ? cellData * 1000 : 0);
|
defaultContent: ''
|
||||||
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
},
|
||||||
$(td).html(dateString);
|
{
|
||||||
}
|
title: lang.received,
|
||||||
},
|
data: 'created',
|
||||||
{
|
defaultContent: '',
|
||||||
title: lang.action,
|
createdCell: function(td, cellData) {
|
||||||
data: 'action',
|
$(td).attr({
|
||||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
"data-order": cellData,
|
||||||
defaultContent: ''
|
"data-sort": cellData
|
||||||
},
|
});
|
||||||
]
|
|
||||||
});
|
var date = new Date(cellData ? cellData * 1000 : 0);
|
||||||
|
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||||
table.on('responsive-resize', function (e, datatable, columns){
|
$(td).html(dateString);
|
||||||
hideTableExpandCollapseBtn('#quarantinetable');
|
}
|
||||||
});
|
},
|
||||||
}
|
{
|
||||||
|
title: lang.action,
|
||||||
$('body').on('click', '.show_qid_info', function (e) {
|
data: 'action',
|
||||||
e.preventDefault();
|
className: 'dt-text-right dt-sm-head-hidden',
|
||||||
var qitem = $(this).attr('data-item');
|
defaultContent: ''
|
||||||
var qError = $("#qid_error");
|
},
|
||||||
|
]
|
||||||
$('#qidDetailModal').modal('show');
|
});
|
||||||
qError.hide();
|
|
||||||
|
table.on('responsive-resize', function (e, datatable, columns){
|
||||||
$.ajax({
|
hideTableExpandCollapseBtn('#quarantinetable');
|
||||||
url: '/inc/ajax/qitem_details.php',
|
});
|
||||||
data: { id: qitem },
|
}
|
||||||
dataType: 'json',
|
|
||||||
success: function(data){
|
$('body').on('click', '.show_qid_info', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
$('[data-id="qitems_single"]').each(function(index) {
|
var qitem = $(this).attr('data-item');
|
||||||
$(this).attr("data-item", qitem);
|
var qError = $("#qid_error");
|
||||||
});
|
|
||||||
|
$('#qidDetailModal').modal('show');
|
||||||
$("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')");
|
qError.hide();
|
||||||
$("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')");
|
|
||||||
$("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')");
|
$.ajax({
|
||||||
|
url: '/inc/ajax/qitem_details.php',
|
||||||
$('#qid_detail_subj').text(data.subject);
|
data: { id: qitem },
|
||||||
$('#qid_detail_hfrom').text(data.header_from);
|
dataType: 'json',
|
||||||
$('#qid_detail_efrom').text(data.env_from);
|
success: function(data){
|
||||||
$('#qid_detail_score').html('');
|
|
||||||
$('#qid_detail_recipients').html('');
|
$('[data-id="qitems_single"]').each(function(index) {
|
||||||
$('#qid_detail_symbols').html('');
|
$(this).attr("data-item", qitem);
|
||||||
$('#qid_detail_fuzzy').html('');
|
});
|
||||||
if (typeof data.symbols !== 'undefined') {
|
|
||||||
data.symbols.sort(function (a, b) {
|
$("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')");
|
||||||
if (a.score === 0) return 1
|
$("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')");
|
||||||
if (b.score === 0) return -1
|
$("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')");
|
||||||
if (b.score < 0 && a.score < 0) {
|
|
||||||
return a.score - b.score
|
$('#qid_detail_subj').text(data.subject);
|
||||||
}
|
$('#qid_detail_hfrom').text(data.header_from);
|
||||||
if (b.score > 0 && a.score > 0) {
|
$('#qid_detail_efrom').text(data.env_from);
|
||||||
return b.score - a.score
|
$('#qid_detail_score').html('');
|
||||||
}
|
$('#qid_detail_recipients').html('');
|
||||||
return b.score - a.score
|
$('#qid_detail_symbols').html('');
|
||||||
})
|
$('#qid_detail_fuzzy').html('');
|
||||||
$.each(data.symbols, function (index, value) {
|
if (typeof data.symbols !== 'undefined') {
|
||||||
var highlightClass = ''
|
data.symbols.sort(function (a, b) {
|
||||||
if (value.score > 0) highlightClass = 'negative'
|
if (a.score === 0) return 1;
|
||||||
else if (value.score < 0) highlightClass = 'positive'
|
if (b.score === 0) return -1;
|
||||||
else highlightClass = 'neutral'
|
if (b.score < 0 && a.score < 0) {
|
||||||
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
|
return a.score - b.score;
|
||||||
});
|
}
|
||||||
$('[data-bs-toggle="tooltip"]').tooltip()
|
if (b.score > 0 && a.score > 0) {
|
||||||
}
|
return b.score - a.score;
|
||||||
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
|
}
|
||||||
$.each(data.fuzzy_hashes, function (index, value) {
|
return b.score - a.score;
|
||||||
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
|
})
|
||||||
});
|
$.each(data.symbols, function (index, value) {
|
||||||
} else {
|
var highlightClass = '';
|
||||||
$('#qid_detail_fuzzy').append('-');
|
if (value.score > 0) highlightClass = 'negative';
|
||||||
}
|
else if (value.score < 0) highlightClass = 'positive';
|
||||||
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
|
else highlightClass = 'neutral';
|
||||||
if (data.action == "add header") {
|
$('#qid_detail_symbols').append('<span data-bs-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
|
});
|
||||||
} else if (data.action == "reject") {
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
|
}
|
||||||
} else if (data.action == "rewrite subject") {
|
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
|
||||||
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
|
$.each(data.fuzzy_hashes, function (index, value) {
|
||||||
}
|
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
|
||||||
}
|
});
|
||||||
if (typeof data.recipients !== 'undefined') {
|
} else {
|
||||||
$.each(data.recipients, function(index, value) {
|
$('#qid_detail_fuzzy').append('-');
|
||||||
var elem = $('<span class="mail-address-item"></span>');
|
}
|
||||||
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
|
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
|
||||||
$('#qid_detail_recipients').append(elem);
|
if (data.action == "add header") {
|
||||||
});
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
|
||||||
}
|
} else if (data.action == "reject") {
|
||||||
$('#qid_detail_text').text(data.text_plain);
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
|
||||||
$('#qid_detail_text_from_html').text(data.text_html);
|
} else if (data.action == "rewrite subject") {
|
||||||
var qAtts = $("#qid_detail_atts");
|
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>');
|
||||||
if (typeof data.attachments !== 'undefined') {
|
}
|
||||||
qAtts.text('');
|
}
|
||||||
$.each(data.attachments, function(index, value) {
|
if (typeof data.recipients !== 'undefined') {
|
||||||
qAtts.append(
|
$.each(data.recipients, function(index, value) {
|
||||||
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
|
var elem = $('<span class="mail-address-item"></span>');
|
||||||
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
|
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
|
||||||
);
|
$('#qid_detail_recipients').append(elem);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
$('#qid_detail_text').text(data.text_plain);
|
||||||
qAtts.text('-');
|
$('#qid_detail_text_from_html').text(data.text_html);
|
||||||
}
|
var qAtts = $("#qid_detail_atts");
|
||||||
},
|
if (typeof data.attachments !== 'undefined') {
|
||||||
error: function(data){
|
qAtts.text('');
|
||||||
if (typeof data.error !== 'undefined') {
|
$.each(data.attachments, function(index, value) {
|
||||||
$('#qid_detail_subj').text('-');
|
qAtts.append(
|
||||||
$('#qid_detail_hfrom').text('-');
|
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
|
||||||
$('#qid_detail_efrom').text('-');
|
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
|
||||||
$('#qid_detail_score').html('-');
|
);
|
||||||
$('#qid_detail_recipients').html('-');
|
});
|
||||||
$('#qid_detail_symbols').html('-');
|
}
|
||||||
$('#qid_detail_fuzzy').html('-');
|
else {
|
||||||
$('#qid_detail_text').text('-');
|
qAtts.text('-');
|
||||||
$('#qid_detail_text_from_html').text('-');
|
}
|
||||||
qError.text("Error loading quarantine item");
|
},
|
||||||
qError.show();
|
error: function(data){
|
||||||
}
|
if (typeof data.error !== 'undefined') {
|
||||||
}
|
$('#qid_detail_subj').text('-');
|
||||||
});
|
$('#qid_detail_hfrom').text('-');
|
||||||
});
|
$('#qid_detail_efrom').text('-');
|
||||||
|
$('#qid_detail_score').html('-');
|
||||||
$('body').on('click', 'span.footable-toggle', function () {
|
$('#qid_detail_recipients').html('-');
|
||||||
event.stopPropagation();
|
$('#qid_detail_symbols').html('-');
|
||||||
})
|
$('#qid_detail_fuzzy').html('-');
|
||||||
|
$('#qid_detail_text').text('-');
|
||||||
// Initial table drawings
|
$('#qid_detail_text_from_html').text('-');
|
||||||
draw_quarantine_table();
|
qError.text("Error loading quarantine item");
|
||||||
|
qError.show();
|
||||||
|
}
|
||||||
function hideTableExpandCollapseBtn(table){
|
}
|
||||||
if ($(table).hasClass('collapsed'))
|
});
|
||||||
$(".table_collapse_option").show();
|
});
|
||||||
else
|
|
||||||
$(".table_collapse_option").hide();
|
$('body').on('click', 'span.footable-toggle', function () {
|
||||||
}
|
event.stopPropagation();
|
||||||
});
|
})
|
||||||
|
|
||||||
|
// Initial table drawings
|
||||||
|
draw_quarantine_table();
|
||||||
|
|
||||||
|
function hideTableExpandCollapseBtn(table){
|
||||||
|
if ($(table).hasClass('collapsed'))
|
||||||
|
$(".table_collapse_option").show();
|
||||||
|
else
|
||||||
|
$(".table_collapse_option").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,128 +1,128 @@
|
||||||
jQuery(function($){
|
jQuery(function($){
|
||||||
|
|
||||||
$(".refresh_table").on('click', function(e) {
|
$(".refresh_table").on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var table_name = $(this).data('table');
|
var table_name = $(this).data('table');
|
||||||
$('#' + table_name).DataTable().ajax.reload();
|
$('#' + table_name).DataTable().ajax.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
|
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
|
||||||
|
|
||||||
// Queue item
|
// Queue item
|
||||||
$('#showQueuedMsg').on('show.bs.modal', function (e) {
|
$('#showQueuedMsg').on('show.bs.modal', function (e) {
|
||||||
$('#queue_msg_content').text(lang.loading);
|
$('#queue_msg_content').text(lang.loading);
|
||||||
button = $(e.relatedTarget)
|
button = $(e.relatedTarget)
|
||||||
if (button != null) {
|
if (button != null) {
|
||||||
$('#queue_id').text(button.data('queue-id'));
|
$('#queue_id').text(button.data('queue-id'));
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: '/api/v1/get/postcat/' + button.data('queue-id'),
|
|
||||||
dataType: 'text',
|
|
||||||
complete: function (data) {
|
|
||||||
console.log(data);
|
|
||||||
$('#queue_msg_content').text(data.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
function draw_queue() {
|
|
||||||
// just recalc width if instance already exists
|
|
||||||
if ($.fn.DataTable.isDataTable('#queuetable') ) {
|
|
||||||
$('#queuetable').DataTable().columns.adjust().responsive.recalc();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/v1/get/postcat/' + button.data('queue-id'),
|
||||||
|
dataType: 'text',
|
||||||
|
complete: function (data) {
|
||||||
|
$('#queue_msg_content').text(data.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
$('#queuetable').DataTable({
|
function draw_queue() {
|
||||||
responsive: true,
|
// just recalc width if instance already exists
|
||||||
processing: true,
|
if ($.fn.DataTable.isDataTable('#queuetable') ) {
|
||||||
serverSide: false,
|
$('#queuetable').DataTable().columns.adjust().responsive.recalc();
|
||||||
stateSave: true,
|
return;
|
||||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
}
|
||||||
"tr" +
|
|
||||||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
$('#queuetable').DataTable({
|
||||||
language: lang_datatables,
|
responsive: true,
|
||||||
ajax: {
|
processing: true,
|
||||||
type: "GET",
|
serverSide: false,
|
||||||
url: "/api/v1/get/mailq/all",
|
stateSave: true,
|
||||||
dataSrc: function(data){
|
pageLength: pagination_size,
|
||||||
$.each(data, function (i, item) {
|
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||||
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
"tr" +
|
||||||
rcpts = $.map(item.recipients, function(i) {
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
return escapeHtml(i);
|
language: lang_datatables,
|
||||||
});
|
ajax: {
|
||||||
item.recipients = rcpts.join('<hr style="margin:1px!important">');
|
type: "GET",
|
||||||
item.action = '<div class="btn-group">' +
|
url: "/api/v1/get/mailq/all",
|
||||||
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.queue_show_message + '</a>' +
|
dataSrc: function(data){
|
||||||
|
$.each(data, function (i, item) {
|
||||||
|
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
|
||||||
|
rcpts = $.map(item.recipients, function(i) {
|
||||||
|
return escapeHtml(i);
|
||||||
|
});
|
||||||
|
item.recipients = rcpts.join('<hr style="margin:1px!important">');
|
||||||
|
item.action = '<div class="btn-group">' +
|
||||||
|
'<a href="#" data-bs-toggle="modal" data-bs-target="#showQueuedMsg" data-queue-id="' + encodeURI(item.queue_id) + '" class="btn btn-xs btn-secondary">' + lang.show_message + '</a>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
// placeholder, so checkbox will not block child row toggle
|
// placeholder, so checkbox will not block child row toggle
|
||||||
title: '',
|
title: '',
|
||||||
data: null,
|
data: null,
|
||||||
searchable: false,
|
searchable: false,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
data: 'chkbox',
|
data: 'chkbox',
|
||||||
searchable: false,
|
searchable: false,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'QID',
|
title: 'QID',
|
||||||
data: 'queue_id',
|
data: 'queue_id',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Queue',
|
title: 'Queue',
|
||||||
data: 'queue_name',
|
data: 'queue_name',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.arrival_time,
|
title: lang_admin.arrival_time,
|
||||||
data: 'arrival_time',
|
data: 'arrival_time',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
render: function (data, type){
|
render: function (data, type){
|
||||||
var date = new Date(data ? data * 1000 : 0);
|
var date = new Date(data ? data * 1000 : 0);
|
||||||
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
return date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.message_size,
|
title: lang_admin.message_size,
|
||||||
data: 'message_size',
|
data: 'message_size',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
render: function (data, type){
|
render: function (data, type){
|
||||||
return humanFileSize(data);
|
return humanFileSize(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.sender,
|
title: lang_admin.sender,
|
||||||
data: 'sender',
|
data: 'sender',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.recipients,
|
title: lang_admin.recipients,
|
||||||
data: 'recipients',
|
data: 'recipients',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang_admin.action,
|
title: lang_admin.action,
|
||||||
data: 'action',
|
data: 'action',
|
||||||
className: 'text-md-end dt-sm-head-hidden dt-body-right',
|
className: 'dt-sm-head-hidden dt-text-right',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_queue();
|
draw_queue();
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,9 +2,9 @@
|
||||||
/*
|
/*
|
||||||
see /api
|
see /api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||||
|
cors("set_headers");
|
||||||
|
header('Content-Type: application/json');
|
||||||
error_reporting(0);
|
error_reporting(0);
|
||||||
|
|
||||||
function api_log($_data) {
|
function api_log($_data) {
|
||||||
|
@ -288,6 +288,18 @@ if (isset($_GET['query'])) {
|
||||||
case "domain-admin":
|
case "domain-admin":
|
||||||
process_add_return(domain_admin('add', $attr));
|
process_add_return(domain_admin('add', $attr));
|
||||||
break;
|
break;
|
||||||
|
case "sso":
|
||||||
|
switch ($object) {
|
||||||
|
case "domain-admin":
|
||||||
|
$data = domain_admin_sso('issue', $attr);
|
||||||
|
if($data) {
|
||||||
|
echo json_encode($data);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
process_add_return($data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "admin":
|
case "admin":
|
||||||
process_add_return(admin('add', $attr));
|
process_add_return(admin('add', $attr));
|
||||||
break;
|
break;
|
||||||
|
@ -1934,6 +1946,9 @@ if (isset($_GET['query'])) {
|
||||||
process_edit_return(edit_user_account($attr));
|
process_edit_return(edit_user_account($attr));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "cors":
|
||||||
|
process_edit_return(cors('edit', $attr));
|
||||||
|
break;
|
||||||
// return no route found if no case is matched
|
// return no route found if no case is matched
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
|
|
@ -105,7 +105,8 @@
|
||||||
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
||||||
"username": "Uživatelské jméno",
|
"username": "Uživatelské jméno",
|
||||||
"validate": "Ověřit",
|
"validate": "Ověřit",
|
||||||
"validation_success": "Úspěšně ověřeno"
|
"validation_success": "Úspěšně ověřeno",
|
||||||
|
"tags": "Štítky"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Přístupy",
|
"access": "Přístupy",
|
||||||
|
@ -333,7 +334,11 @@
|
||||||
"username": "Uživatelské jméno",
|
"username": "Uživatelské jméno",
|
||||||
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
||||||
"verify": "Ověřit",
|
"verify": "Ověřit",
|
||||||
"yes": "✓"
|
"yes": "✓",
|
||||||
|
"f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
|
||||||
|
"f2b_max_ban_time": "Maximální délka banu (s)",
|
||||||
|
"ip_check": "Kontrola IP",
|
||||||
|
"ip_check_disabled": "Kontrola IP je vypnuta. Můžete ji zapnout v <br> <strong>System > Nastavení > Options > Přizpůsobení</strong>"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
||||||
|
@ -536,7 +541,7 @@
|
||||||
"inactive": "Neaktivní",
|
"inactive": "Neaktivní",
|
||||||
"kind": "Druh",
|
"kind": "Druh",
|
||||||
"last_modified": "Naposledy změněn",
|
"last_modified": "Naposledy změněn",
|
||||||
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
|
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*\\.google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
|
||||||
"mailbox": "Úprava mailové schránky",
|
"mailbox": "Úprava mailové schránky",
|
||||||
"mailbox_quota_def": "Výchozí kvóta schránky",
|
"mailbox_quota_def": "Výchozí kvóta schránky",
|
||||||
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",
|
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",
|
||||||
|
@ -650,7 +655,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Přihlášení zpožděno o %s sekund.",
|
"delayed": "Přihlášení zpožděno o %s sekund.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Přihlásit",
|
"login": "Přihlásit",
|
||||||
"mobileconfig_info": "Ke stažení profilového souboru se přihlaste jako uživatel schránky.",
|
"mobileconfig_info": "Ke stažení profilového souboru se přihlaste jako uživatel schránky.",
|
||||||
"other_logins": "Přihlášení klíčem",
|
"other_logins": "Přihlášení klíčem",
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"alias_domains": "Tilføj kældenavn domæner",
|
"alias_domains": "Tilføj domænealias",
|
||||||
"app_passwds": "Administrer app-adgangskoder",
|
"app_passwds": "Administrer app-adgangskoder",
|
||||||
"bcc_maps": "BCC kort",
|
"bcc_maps": "BCC kort",
|
||||||
"delimiter_action": "Afgrænsning handling",
|
"delimiter_action": "Afgrænsning handling",
|
||||||
"eas_reset": "Nulstil EAS endheder",
|
"eas_reset": "Nulstil EAS enheder",
|
||||||
"extend_sender_acl": "Tillad at udvide afsenderens ACL med eksterne adresser",
|
"extend_sender_acl": "Tillad at udvide afsenderens ACL med eksterne adresser",
|
||||||
"filters": "Filtre",
|
"filters": "Filtre",
|
||||||
"login_as": "Login som mailboks bruger",
|
"login_as": "Login som mailboks bruger",
|
||||||
"prohibited": "Forbudt af ACL",
|
"prohibited": "Nægtet af ACL",
|
||||||
"protocol_access": "Ændre protokol adgang",
|
"protocol_access": "Skift protokol adgang",
|
||||||
"pushover": "Pushover",
|
"pushover": "Pushover",
|
||||||
"quarantine": "Karantæneaktioner",
|
"quarantine": "Karantænehandlinger",
|
||||||
"quarantine_attachments": "Karantæne vedhæftede filer",
|
"quarantine_attachments": "Karantænevedhæftede filer",
|
||||||
"quarantine_notification": "Skift karantænemeddelelser",
|
"quarantine_notification": "Skift karantænemeddelelser",
|
||||||
"ratelimit": "Satsgrænse",
|
"ratelimit": "Satsgrænse",
|
||||||
"recipient_maps": "Modtagerkort",
|
"recipient_maps": "Modtagerkort",
|
||||||
|
@ -20,12 +20,15 @@
|
||||||
"sogo_access": "Tillad styring af SOGo-adgang",
|
"sogo_access": "Tillad styring af SOGo-adgang",
|
||||||
"sogo_profile_reset": "Nulstil SOGo-profil",
|
"sogo_profile_reset": "Nulstil SOGo-profil",
|
||||||
"spam_alias": "Midlertidige aliasser",
|
"spam_alias": "Midlertidige aliasser",
|
||||||
"spam_policy": "Sortliste / hvidliste",
|
"spam_policy": "Sortliste/hvidliste",
|
||||||
"spam_score": "Spam-score",
|
"spam_score": "Spam-score",
|
||||||
"syncjobs": "Synkroniser job",
|
"syncjobs": "Synkroniserings job",
|
||||||
"tls_policy": "TLS politik",
|
"tls_policy": "TLS politik",
|
||||||
"unlimited_quota": "Ubegrænset quote for mailbokse",
|
"unlimited_quota": "Ubegrænset plads for mailbokse",
|
||||||
"domain_desc": "Skift domæne beskrivelse"
|
"domain_desc": "Skift domæne beskrivelse",
|
||||||
|
"domain_relayhost": "Skift relæ host for et domæne",
|
||||||
|
"mailbox_relayhost": "Skift relæ-host for en postkasse",
|
||||||
|
"quarantine_category": "Skift kategorien for karantænemeddelelse"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"activate_filter_warn": "Alle andre filtre deaktiveres, når aktiv er markeret.",
|
"activate_filter_warn": "Alle andre filtre deaktiveres, når aktiv er markeret.",
|
||||||
|
@ -33,7 +36,7 @@
|
||||||
"add": "Tilføj",
|
"add": "Tilføj",
|
||||||
"add_domain_only": "Tilføj kun domæne",
|
"add_domain_only": "Tilføj kun domæne",
|
||||||
"add_domain_restart": "Tilføj domæne og genstart SOGo",
|
"add_domain_restart": "Tilføj domæne og genstart SOGo",
|
||||||
"alias_address": "Alias adresse (r)",
|
"alias_address": "Alias adresse(r)",
|
||||||
"alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>",
|
"alias_address_info": "<small>Fuld e-mail-adresse eller @ eksempel.com for at fange alle beskeder til et domæne (kommasepareret). <b> kun mailcow-domæner</b>.</small>",
|
||||||
"alias_domain": "Alias-domæne",
|
"alias_domain": "Alias-domæne",
|
||||||
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
|
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
|
||||||
|
@ -59,7 +62,7 @@
|
||||||
"gal": "Global adresseliste",
|
"gal": "Global adresseliste",
|
||||||
"gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! <b> Genstart SOGo for at anvende ændringer. </b>",
|
"gal_info": "GAL indeholder alle objekter i et domæne og kan ikke redigeres af nogen bruger. Information om ledig / optaget i SOGo mangler, hvis deaktiveret! <b> Genstart SOGo for at anvende ændringer. </b>",
|
||||||
"generate": "generere",
|
"generate": "generere",
|
||||||
"goto_ham": "Lær som <span class=\"text-success\"><b>ham</b></span>",
|
"goto_ham": "Lær som <span class=\"text-success\"><b>ønsket</b></span>",
|
||||||
"goto_null": "Kassér e-mail i stilhed",
|
"goto_null": "Kassér e-mail i stilhed",
|
||||||
"goto_spam": "Lær som <span class=\"text-danger\"><b>spam</b></span>",
|
"goto_spam": "Lær som <span class=\"text-danger\"><b>spam</b></span>",
|
||||||
"hostname": "Vært",
|
"hostname": "Vært",
|
||||||
|
@ -80,7 +83,7 @@
|
||||||
"private_comment": "Privat kommentar",
|
"private_comment": "Privat kommentar",
|
||||||
"public_comment": "Offentlig kommentar",
|
"public_comment": "Offentlig kommentar",
|
||||||
"quota_mb": "Kvota (Mb)",
|
"quota_mb": "Kvota (Mb)",
|
||||||
"relay_all": "Send alle modtagere videre",
|
"relay_all": "Besvar alle modtager",
|
||||||
"relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
|
"relay_all_info": "↪ Hvis du vælger <b> ikke </b> at videresende alle modtagere, skal du tilføje et (\"blind\") postkasse til hver enkelt modtager, der skal videresendes.",
|
||||||
"relay_domain": "Send dette domæne videre",
|
"relay_domain": "Send dette domæne videre",
|
||||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.",
|
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> Du kan definere transportkort til en tilpasset destination for dette domæne. Hvis ikke indstillet, foretages der et MX-opslag.",
|
||||||
|
@ -101,7 +104,10 @@
|
||||||
"timeout2": "Timeout for forbindelse til lokal vært",
|
"timeout2": "Timeout for forbindelse til lokal vært",
|
||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"validate": "Bekræft",
|
"validate": "Bekræft",
|
||||||
"validation_success": "Valideret med succes"
|
"validation_success": "Valideret med succes",
|
||||||
|
"bcc_dest_format": "BCC-destination skal være en enkelt gyldig e-mail-adresse.<br>Hvis du har brug for at sende en kopi til flere adresser, kan du oprette et alias og bruge det her.",
|
||||||
|
"app_passwd_protocols": "Tilladte protokoller for app adgangskode",
|
||||||
|
"tags": "Tag's"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Adgang",
|
"access": "Adgang",
|
||||||
|
@ -308,7 +314,10 @@
|
||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"validate_license_now": "Valider GUID mod licensserver",
|
"validate_license_now": "Valider GUID mod licensserver",
|
||||||
"verify": "Verificere",
|
"verify": "Verificere",
|
||||||
"yes": "✓"
|
"yes": "✓",
|
||||||
|
"ip_check_opt_in": "Opt-In for brug af tredjepartstjeneste <strong>ipv4.mailcow.email</strong> og <strong>ipv6.mailcow.email</strong> til at finde eksterne IP-adresser.",
|
||||||
|
"queue_unban": "unban",
|
||||||
|
"admins": "Administratorer"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Adgang nægtet eller ugyldig formular data",
|
"access_denied": "Adgang nægtet eller ugyldig formular data",
|
||||||
|
@ -425,7 +434,8 @@
|
||||||
"username_invalid": "Brugernavn %s kan ikke bruges",
|
"username_invalid": "Brugernavn %s kan ikke bruges",
|
||||||
"validity_missing": "Tildel venligst en gyldighedsperiode",
|
"validity_missing": "Tildel venligst en gyldighedsperiode",
|
||||||
"value_missing": "Angiv alle værdier",
|
"value_missing": "Angiv alle værdier",
|
||||||
"yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s"
|
"yotp_verification_failed": "Yubico OTP verifikationen mislykkedes: %s",
|
||||||
|
"webauthn_publickey_failed": "Der er ikke gemt nogen offentlig nøgle for den valgte autentifikator"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"chart_this_server": "Diagram (denne server)",
|
"chart_this_server": "Diagram (denne server)",
|
||||||
|
@ -442,7 +452,8 @@
|
||||||
"solr_status": "Solr-status",
|
"solr_status": "Solr-status",
|
||||||
"started_on": "Startede den",
|
"started_on": "Startede den",
|
||||||
"static_logs": "Statiske logfiler",
|
"static_logs": "Statiske logfiler",
|
||||||
"system_containers": "System og Beholdere"
|
"system_containers": "System og Beholdere",
|
||||||
|
"error_show_ip": "Kunne ikke finde de offentlige IP-adresser"
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.",
|
"cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.",
|
||||||
|
@ -553,7 +564,11 @@
|
||||||
"title": "Rediger objekt",
|
"title": "Rediger objekt",
|
||||||
"unchanged_if_empty": "Lad være tomt, hvis uændret",
|
"unchanged_if_empty": "Lad være tomt, hvis uændret",
|
||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"validate_save": "Valider og gem"
|
"validate_save": "Valider og gem",
|
||||||
|
"admin": "Rediger administrator",
|
||||||
|
"lookup_mx": "Destination er et regulært udtryk, der matcher MX-navnet (<code>.*google\\.dk</code> for at dirigere al e-mail, der er målrettet til en MX, der ender på google.dk, over dette hop)",
|
||||||
|
"mailbox_relayhost_info": "Anvendt på postkassen og kun direkte aliasser, og overskriver et domæne relæ-host.",
|
||||||
|
"quota_warning_bcc": "Kvoteadvarsel BCC"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Afbestille",
|
"cancel": "Afbestille",
|
||||||
|
@ -571,7 +586,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"administration": "Konfiguration og detailer",
|
"administration": "Konfiguration og detailer",
|
||||||
"apps": "Apps",
|
"apps": "Apps",
|
||||||
"debug": "Systemoplysninger",
|
"debug": "Information",
|
||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"mailcow_config": "Konfiguration",
|
"mailcow_config": "Konfiguration",
|
||||||
"quarantine": "Karantæne",
|
"quarantine": "Karantæne",
|
||||||
|
@ -586,7 +601,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Login blev forsinket med% s sekunder.",
|
"delayed": "Login blev forsinket med% s sekunder.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"mobileconfig_info": "Log ind som postkassebruger for at downloade den anmodede Apple-forbindelsesprofil.",
|
"mobileconfig_info": "Log ind som postkassebruger for at downloade den anmodede Apple-forbindelsesprofil.",
|
||||||
"other_logins": "Nøgle login",
|
"other_logins": "Nøgle login",
|
||||||
|
@ -739,7 +754,10 @@
|
||||||
"username": "Brugernavn",
|
"username": "Brugernavn",
|
||||||
"waiting": "Venter",
|
"waiting": "Venter",
|
||||||
"weekly": "Ugentlig",
|
"weekly": "Ugentlig",
|
||||||
"yes": "✓"
|
"yes": "✓",
|
||||||
|
"goto_ham": "Lær som <b>ønsket</b>",
|
||||||
|
"catch_all": "Fang-alt",
|
||||||
|
"open_logs": "Åben logfiler"
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"access_denied": "Log ind som mailboks ejer for at give adgang via OAuth2.",
|
"access_denied": "Log ind som mailboks ejer for at give adgang via OAuth2.",
|
||||||
|
@ -1030,7 +1048,7 @@
|
||||||
"spamfilter_table_empty": "Intet data at vise",
|
"spamfilter_table_empty": "Intet data at vise",
|
||||||
"spamfilter_table_remove": "slet",
|
"spamfilter_table_remove": "slet",
|
||||||
"spamfilter_table_rule": "Regl",
|
"spamfilter_table_rule": "Regl",
|
||||||
"spamfilter_wl": "Hvisliste",
|
"spamfilter_wl": "Hvidliste",
|
||||||
"spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.",
|
"spamfilter_wl_desc": "Hvidlistede e-mail-adresser til <b>aldrig</b> at klassificeres som spam. Wildcards kan bruges. Et filter anvendes kun på direkte aliaser (aliaser med en enkelt målpostkasse) eksklusive catch-aliaser og selve en postkasse.",
|
||||||
"spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe",
|
"spamfilter_yellow": "Gul: denne besked kan være spam, vil blive tagget som spam og flyttes til din junk-mappe",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
@ -1066,5 +1084,11 @@
|
||||||
"quota_exceeded_scope": "Domænekvote overskredet: Kun ubegrænsede postkasser kan oprettes i dette domæneomfang.",
|
"quota_exceeded_scope": "Domænekvote overskredet: Kun ubegrænsede postkasser kan oprettes i dette domæneomfang.",
|
||||||
"session_token": "Form nøgle ugyldig: Nøgle passer ikke",
|
"session_token": "Form nøgle ugyldig: Nøgle passer ikke",
|
||||||
"session_ua": "Form nøgle ugyldig: Bruger-Agent gyldighedskontrols fejl"
|
"session_ua": "Form nøgle ugyldig: Bruger-Agent gyldighedskontrols fejl"
|
||||||
|
},
|
||||||
|
"datatables": {
|
||||||
|
"lengthMenu": "Vis _MENU_ poster",
|
||||||
|
"paginate": {
|
||||||
|
"first": "Først"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,7 @@
|
||||||
"change_logo": "Logo ändern",
|
"change_logo": "Logo ändern",
|
||||||
"configuration": "Konfiguration",
|
"configuration": "Konfiguration",
|
||||||
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
||||||
|
"cors_settings": "CORS Einstellungen",
|
||||||
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
|
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
|
||||||
"customer_id": "Kunde",
|
"customer_id": "Kunde",
|
||||||
"customize": "UI-Anpassung",
|
"customize": "UI-Anpassung",
|
||||||
|
@ -175,10 +176,12 @@
|
||||||
"empty": "Keine Einträge vorhanden",
|
"empty": "Keine Einträge vorhanden",
|
||||||
"excludes": "Diese Empfänger ausschließen",
|
"excludes": "Diese Empfänger ausschließen",
|
||||||
"f2b_ban_time": "Bannzeit in Sekunden",
|
"f2b_ban_time": "Bannzeit in Sekunden",
|
||||||
|
"f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann",
|
||||||
"f2b_blacklist": "Blacklist für Netzwerke und Hosts",
|
"f2b_blacklist": "Blacklist für Netzwerke und Hosts",
|
||||||
"f2b_filter": "Regex-Filter",
|
"f2b_filter": "Regex-Filter",
|
||||||
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
|
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
|
||||||
"f2b_max_attempts": "Max. Versuche",
|
"f2b_max_attempts": "Max. Versuche",
|
||||||
|
"f2b_max_ban_time": "Maximale Bannzeit in Sekunden",
|
||||||
"f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)",
|
"f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)",
|
||||||
"f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
|
"f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)",
|
||||||
"f2b_parameters": "Fail2ban-Parameter",
|
"f2b_parameters": "Fail2ban-Parameter",
|
||||||
|
@ -214,7 +217,7 @@
|
||||||
"loading": "Bitte warten...",
|
"loading": "Bitte warten...",
|
||||||
"login_time": "Zeit",
|
"login_time": "Zeit",
|
||||||
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
|
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
|
||||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||||
"main_name": "\"mailcow UI\" Name",
|
"main_name": "\"mailcow UI\" Name",
|
||||||
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
|
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
|
||||||
"message": "Nachricht",
|
"message": "Nachricht",
|
||||||
|
@ -339,7 +342,8 @@
|
||||||
"oauth2_add_client": "Füge OAuth2 Client hinzu",
|
"oauth2_add_client": "Füge OAuth2 Client hinzu",
|
||||||
"api_read_only": "Schreibgeschützter Zugriff",
|
"api_read_only": "Schreibgeschützter Zugriff",
|
||||||
"api_read_write": "Lese-Schreib-Zugriff",
|
"api_read_write": "Lese-Schreib-Zugriff",
|
||||||
"oauth2_apps": "OAuth2 Apps"
|
"oauth2_apps": "OAuth2 Apps",
|
||||||
|
"queue_unban": "entsperren"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
||||||
|
@ -355,6 +359,8 @@
|
||||||
"bcc_exists": "Ein BCC-Map-Eintrag %s existiert bereits als Typ %s",
|
"bcc_exists": "Ein BCC-Map-Eintrag %s existiert bereits als Typ %s",
|
||||||
"bcc_must_be_email": "BCC-Ziel %s ist keine gültige E-Mail-Adresse",
|
"bcc_must_be_email": "BCC-Ziel %s ist keine gültige E-Mail-Adresse",
|
||||||
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
|
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
|
||||||
|
"cors_invalid_method": "Allow-Methods enthält eine ungültige Methode",
|
||||||
|
"cors_invalid_origin": "Allow-Origins enthält eine ungültige Origin",
|
||||||
"defquota_empty": "Standard-Quota darf nicht 0 sein",
|
"defquota_empty": "Standard-Quota darf nicht 0 sein",
|
||||||
"demo_mode_enabled": "Demo Mode ist aktiviert",
|
"demo_mode_enabled": "Demo Mode ist aktiviert",
|
||||||
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
|
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
|
||||||
|
@ -366,7 +372,7 @@
|
||||||
"domain_not_empty": "Domain %s ist nicht leer",
|
"domain_not_empty": "Domain %s ist nicht leer",
|
||||||
"domain_not_found": "Domain %s nicht gefunden",
|
"domain_not_found": "Domain %s nicht gefunden",
|
||||||
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
||||||
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen",
|
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
|
||||||
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
|
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
|
||||||
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
|
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
|
||||||
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
|
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
|
||||||
|
@ -454,17 +460,23 @@
|
||||||
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
|
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
|
||||||
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
|
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
|
||||||
"webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s",
|
"webauthn_verification_failed": "WebAuthn-Verifizierung fehlgeschlagen: %s",
|
||||||
|
"webauthn_authenticator_failed": "Der ausgewählte Authenticator wurde nicht gefunden",
|
||||||
|
"webauthn_publickey_failed": "Zu dem ausgewählten Authenticator wurde kein Publickey hinterlegt",
|
||||||
|
"webauthn_username_failed": "Der ausgewählte Authenticator gehört zu einem anderen Konto",
|
||||||
"unknown": "Ein unbekannter Fehler trat auf",
|
"unknown": "Ein unbekannter Fehler trat auf",
|
||||||
"unknown_tfa_method": "Unbekannte TFA-Methode",
|
"unknown_tfa_method": "Unbekannte TFA-Methode",
|
||||||
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
|
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
|
||||||
"username_invalid": "Benutzername %s kann nicht verwendet werden",
|
"username_invalid": "Benutzername %s kann nicht verwendet werden",
|
||||||
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
|
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
|
||||||
"value_missing": "Bitte alle Felder ausfüllen",
|
"value_missing": "Bitte alle Felder ausfüllen",
|
||||||
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s"
|
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s",
|
||||||
|
"template_exists": "Vorlage %s existiert bereits",
|
||||||
|
"template_id_invalid": "Vorlagen-ID %s ungültig",
|
||||||
|
"template_name_invalid": "Name der Vorlage ungültig"
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"collapse_all": "Alle Einklappen",
|
"collapse_all": "Alle Einklappen",
|
||||||
"decimal": "",
|
"decimal": ",",
|
||||||
"emptyTable": "Keine Daten in der Tabelle vorhanden",
|
"emptyTable": "Keine Daten in der Tabelle vorhanden",
|
||||||
"expand_all": "Alle Ausklappen",
|
"expand_all": "Alle Ausklappen",
|
||||||
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
|
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
|
||||||
|
@ -489,6 +501,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
|
"architecture": "Architektur",
|
||||||
"chart_this_server": "Chart (dieser Server)",
|
"chart_this_server": "Chart (dieser Server)",
|
||||||
"containers_info": "Container-Information",
|
"containers_info": "Container-Information",
|
||||||
"container_running": "Läuft",
|
"container_running": "Läuft",
|
||||||
|
@ -498,7 +511,7 @@
|
||||||
"current_time": "Systemzeit",
|
"current_time": "Systemzeit",
|
||||||
"disk_usage": "Festplattennutzung",
|
"disk_usage": "Festplattennutzung",
|
||||||
"docs": "Dokumente",
|
"docs": "Dokumente",
|
||||||
"error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen",
|
"error_show_ip": "Konnte die öffentlichen IP Adressen nicht auflösen",
|
||||||
"external_logs": "Externe Logs",
|
"external_logs": "Externe Logs",
|
||||||
"history_all_servers": "History (alle Server)",
|
"history_all_servers": "History (alle Server)",
|
||||||
"in_memory_logs": "In-memory Logs",
|
"in_memory_logs": "In-memory Logs",
|
||||||
|
@ -525,7 +538,8 @@
|
||||||
"update_available": "Es ist ein Update verfügbar",
|
"update_available": "Es ist ein Update verfügbar",
|
||||||
"no_update_available": "Das System ist auf aktuellem Stand",
|
"no_update_available": "Das System ist auf aktuellem Stand",
|
||||||
"update_failed": "Es konnte nicht nach einem Update gesucht werden",
|
"update_failed": "Es konnte nicht nach einem Update gesucht werden",
|
||||||
"username": "Benutzername"
|
"username": "Benutzername",
|
||||||
|
"wip": "Aktuell noch in Arbeit"
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.",
|
"cname_from_a": "Wert abgeleitet von A/AAAA-Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt.",
|
||||||
|
@ -584,7 +598,7 @@
|
||||||
"inactive": "Inaktiv",
|
"inactive": "Inaktiv",
|
||||||
"kind": "Art",
|
"kind": "Art",
|
||||||
"last_modified": "Zuletzt geändert",
|
"last_modified": "Zuletzt geändert",
|
||||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||||
"mailbox": "Mailbox bearbeiten",
|
"mailbox": "Mailbox bearbeiten",
|
||||||
"mailbox_quota_def": "Standard-Quota einer Mailbox",
|
"mailbox_quota_def": "Standard-Quota einer Mailbox",
|
||||||
"mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",
|
"mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",
|
||||||
|
@ -651,7 +665,8 @@
|
||||||
"title": "Objekt bearbeiten",
|
"title": "Objekt bearbeiten",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
|
@ -692,7 +707,8 @@
|
||||||
"quarantine": "Quarantäne",
|
"quarantine": "Quarantäne",
|
||||||
"restart_netfilter": "Netfilter neustarten",
|
"restart_netfilter": "Netfilter neustarten",
|
||||||
"restart_sogo": "SOGo neustarten",
|
"restart_sogo": "SOGo neustarten",
|
||||||
"user_settings": "Benutzereinstellungen"
|
"user_settings": "Benutzereinstellungen",
|
||||||
|
"mailcow_system": "System"
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung",
|
"awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung",
|
||||||
|
@ -701,7 +717,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert.",
|
"delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
|
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
|
||||||
"other_logins": "Key Login",
|
"other_logins": "Key Login",
|
||||||
|
@ -985,6 +1001,7 @@
|
||||||
"bcc_deleted": "BCC-Map-Einträge gelöscht: %s",
|
"bcc_deleted": "BCC-Map-Einträge gelöscht: %s",
|
||||||
"bcc_edited": "BCC-Map-Eintrag %s wurde geändert",
|
"bcc_edited": "BCC-Map-Eintrag %s wurde geändert",
|
||||||
"bcc_saved": "BCC- Map-Eintrag wurde gespeichert",
|
"bcc_saved": "BCC- Map-Eintrag wurde gespeichert",
|
||||||
|
"cors_headers_edited": "CORS Einstellungen wurden erfolgreich gespeichert",
|
||||||
"db_init_complete": "Datenbankinitialisierung abgeschlossen",
|
"db_init_complete": "Datenbankinitialisierung abgeschlossen",
|
||||||
"delete_filter": "Filter-ID %s wurde gelöscht",
|
"delete_filter": "Filter-ID %s wurde gelöscht",
|
||||||
"delete_filters": "Filter gelöscht: %s",
|
"delete_filters": "Filter gelöscht: %s",
|
||||||
|
@ -1236,7 +1253,8 @@
|
||||||
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
|
||||||
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
||||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort"
|
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Falscher Benutzername oder Passwort",
|
||||||
|
"pushover_sound": "Ton"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",
|
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",
|
||||||
|
|
|
@ -133,6 +133,8 @@
|
||||||
"admins": "Administrators",
|
"admins": "Administrators",
|
||||||
"admins_ldap": "LDAP Administrators",
|
"admins_ldap": "LDAP Administrators",
|
||||||
"advanced_settings": "Advanced settings",
|
"advanced_settings": "Advanced settings",
|
||||||
|
"allowed_methods": "Access-Control-Allow-Methods",
|
||||||
|
"allowed_origins": "Access-Control-Allow-Origin",
|
||||||
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
"api_allow_from": "Allow API access from these IPs/CIDR network notations",
|
||||||
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
|
||||||
"api_key": "API key",
|
"api_key": "API key",
|
||||||
|
@ -149,6 +151,7 @@
|
||||||
"change_logo": "Change logo",
|
"change_logo": "Change logo",
|
||||||
"configuration": "Configuration",
|
"configuration": "Configuration",
|
||||||
"convert_html_to_text": "Convert HTML to plain text",
|
"convert_html_to_text": "Convert HTML to plain text",
|
||||||
|
"cors_settings": "CORS Settings",
|
||||||
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
||||||
"customer_id": "Customer ID",
|
"customer_id": "Customer ID",
|
||||||
"customize": "Customize",
|
"customize": "Customize",
|
||||||
|
@ -177,10 +180,12 @@
|
||||||
"empty": "No results",
|
"empty": "No results",
|
||||||
"excludes": "Excludes these recipients",
|
"excludes": "Excludes these recipients",
|
||||||
"f2b_ban_time": "Ban time (s)",
|
"f2b_ban_time": "Ban time (s)",
|
||||||
|
"f2b_ban_time_increment": "Ban time is incremented with each ban",
|
||||||
"f2b_blacklist": "Blacklisted networks/hosts",
|
"f2b_blacklist": "Blacklisted networks/hosts",
|
||||||
"f2b_filter": "Regex filters",
|
"f2b_filter": "Regex filters",
|
||||||
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
|
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
|
||||||
"f2b_max_attempts": "Max. attempts",
|
"f2b_max_attempts": "Max. attempts",
|
||||||
|
"f2b_max_ban_time": "Max. ban time (s)",
|
||||||
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
||||||
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
||||||
"f2b_parameters": "Fail2ban parameters",
|
"f2b_parameters": "Fail2ban parameters",
|
||||||
|
@ -216,7 +221,7 @@
|
||||||
"loading": "Please wait...",
|
"loading": "Please wait...",
|
||||||
"login_time": "Login time",
|
"login_time": "Login time",
|
||||||
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
|
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
|
||||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||||
"main_name": "\"mailcow UI\" name",
|
"main_name": "\"mailcow UI\" name",
|
||||||
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
|
@ -356,6 +361,8 @@
|
||||||
"bcc_exists": "A BCC map %s exists for type %s",
|
"bcc_exists": "A BCC map %s exists for type %s",
|
||||||
"bcc_must_be_email": "BCC destination %s is not a valid email address",
|
"bcc_must_be_email": "BCC destination %s is not a valid email address",
|
||||||
"comment_too_long": "Comment too long, max 160 chars allowed",
|
"comment_too_long": "Comment too long, max 160 chars allowed",
|
||||||
|
"cors_invalid_method": "Invalid Allow-Method specified",
|
||||||
|
"cors_invalid_origin": "Invalid Allow-Origin specified",
|
||||||
"defquota_empty": "Default quota per mailbox must not be 0.",
|
"defquota_empty": "Default quota per mailbox must not be 0.",
|
||||||
"demo_mode_enabled": "Demo Mode is enabled",
|
"demo_mode_enabled": "Demo Mode is enabled",
|
||||||
"description_invalid": "Resource description for %s is invalid",
|
"description_invalid": "Resource description for %s is invalid",
|
||||||
|
@ -458,6 +465,9 @@
|
||||||
"totp_verification_failed": "TOTP verification failed",
|
"totp_verification_failed": "TOTP verification failed",
|
||||||
"transport_dest_exists": "Transport destination \"%s\" exists",
|
"transport_dest_exists": "Transport destination \"%s\" exists",
|
||||||
"webauthn_verification_failed": "WebAuthn verification failed: %s",
|
"webauthn_verification_failed": "WebAuthn verification failed: %s",
|
||||||
|
"webauthn_authenticator_failed": "The selected authenticator was not found",
|
||||||
|
"webauthn_publickey_failed": "No public key was stored for the selected authenticator",
|
||||||
|
"webauthn_username_failed": "The selected authenticator belongs to another account",
|
||||||
"unknown": "An unknown error occurred",
|
"unknown": "An unknown error occurred",
|
||||||
"unknown_tfa_method": "Unknown TFA method",
|
"unknown_tfa_method": "Unknown TFA method",
|
||||||
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
|
||||||
|
@ -468,7 +478,7 @@
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"collapse_all": "Collapse All",
|
"collapse_all": "Collapse All",
|
||||||
"decimal": "",
|
"decimal": ".",
|
||||||
"emptyTable": "No data available in table",
|
"emptyTable": "No data available in table",
|
||||||
"expand_all": "Expand All",
|
"expand_all": "Expand All",
|
||||||
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
|
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
|
||||||
|
@ -493,6 +503,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
|
"architecture": "Architecture",
|
||||||
"chart_this_server": "Chart (this server)",
|
"chart_this_server": "Chart (this server)",
|
||||||
"containers_info": "Container information",
|
"containers_info": "Container information",
|
||||||
"container_running": "Running",
|
"container_running": "Running",
|
||||||
|
@ -529,7 +540,8 @@
|
||||||
"update_available": "There is an update available",
|
"update_available": "There is an update available",
|
||||||
"no_update_available": "The System is on the latest version",
|
"no_update_available": "The System is on the latest version",
|
||||||
"update_failed": "Could not check for an Update",
|
"update_failed": "Could not check for an Update",
|
||||||
"username": "Username"
|
"username": "Username",
|
||||||
|
"wip": "Currently Work in Progress"
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
|
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
|
||||||
|
@ -588,7 +600,7 @@
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"kind": "Kind",
|
"kind": "Kind",
|
||||||
"last_modified": "Last modified",
|
"last_modified": "Last modified",
|
||||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||||
"mailbox": "Edit mailbox",
|
"mailbox": "Edit mailbox",
|
||||||
"mailbox_quota_def": "Default mailbox quota",
|
"mailbox_quota_def": "Default mailbox quota",
|
||||||
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
||||||
|
@ -707,7 +719,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Login was delayed by %s seconds.",
|
"delayed": "Login was delayed by %s seconds.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
||||||
"other_logins": "Key login",
|
"other_logins": "Key login",
|
||||||
|
@ -998,6 +1010,7 @@
|
||||||
"bcc_deleted": "BCC map entries deleted: %s",
|
"bcc_deleted": "BCC map entries deleted: %s",
|
||||||
"bcc_edited": "BCC map entry %s edited",
|
"bcc_edited": "BCC map entry %s edited",
|
||||||
"bcc_saved": "BCC map entry saved",
|
"bcc_saved": "BCC map entry saved",
|
||||||
|
"cors_headers_edited": "CORS settings have been saved",
|
||||||
"db_init_complete": "Database initialization completed",
|
"db_init_complete": "Database initialization completed",
|
||||||
"delete_filter": "Deleted filters ID %s",
|
"delete_filter": "Deleted filters ID %s",
|
||||||
"delete_filters": "Deleted filters: %s",
|
"delete_filters": "Deleted filters: %s",
|
||||||
|
|
|
@ -141,9 +141,11 @@
|
||||||
"empty": "Sin resultados",
|
"empty": "Sin resultados",
|
||||||
"excludes": "Excluye a estos destinatarios",
|
"excludes": "Excluye a estos destinatarios",
|
||||||
"f2b_ban_time": "Tiempo de restricción (s)",
|
"f2b_ban_time": "Tiempo de restricción (s)",
|
||||||
|
"f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción",
|
||||||
"f2b_blacklist": "Redes y hosts en lista negra",
|
"f2b_blacklist": "Redes y hosts en lista negra",
|
||||||
"f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
|
"f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
|
||||||
"f2b_max_attempts": "Max num. de intentos",
|
"f2b_max_attempts": "Max num. de intentos",
|
||||||
|
"f2b_max_ban_time": "Max tiempo de restricción (s)",
|
||||||
"f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)",
|
"f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)",
|
||||||
"f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
|
"f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
|
||||||
"f2b_parameters": "Parametros Fail2ban",
|
"f2b_parameters": "Parametros Fail2ban",
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"spam_policy": "Liste Noire/Liste Blanche",
|
"spam_policy": "Liste Noire/Liste Blanche",
|
||||||
"spam_score": "Score SPAM",
|
"spam_score": "Score SPAM",
|
||||||
"syncjobs": "Tâches de synchronisation",
|
"syncjobs": "Tâches de synchronisation",
|
||||||
"tls_policy": "Police TLS",
|
"tls_policy": "Politique TLS",
|
||||||
"unlimited_quota": "Quota illimité pour les boites de courriel",
|
"unlimited_quota": "Quota illimité pour les boites de courriel",
|
||||||
"domain_desc": "Modifier la description du domaine",
|
"domain_desc": "Modifier la description du domaine",
|
||||||
"domain_relayhost": "Changer le relais pour un domaine",
|
"domain_relayhost": "Changer le relais pour un domaine",
|
||||||
|
@ -106,7 +106,8 @@
|
||||||
"validate": "Valider",
|
"validate": "Valider",
|
||||||
"validation_success": "Validation réussie",
|
"validation_success": "Validation réussie",
|
||||||
"bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
|
"bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
|
||||||
"tags": "Etiquettes"
|
"tags": "Etiquettes",
|
||||||
|
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Accès",
|
"access": "Accès",
|
||||||
|
@ -171,11 +172,13 @@
|
||||||
"edit": "Editer",
|
"edit": "Editer",
|
||||||
"empty": "Aucun résultat",
|
"empty": "Aucun résultat",
|
||||||
"excludes": "Exclure ces destinataires",
|
"excludes": "Exclure ces destinataires",
|
||||||
"f2b_ban_time": "Durée du bannissement(s)",
|
"f2b_ban_time": "Durée du bannissement (s)",
|
||||||
|
"f2b_ban_time_increment": "Durée du bannissement est augmentée à chaque bannissement",
|
||||||
"f2b_blacklist": "Réseaux/Domaines sur Liste Noire",
|
"f2b_blacklist": "Réseaux/Domaines sur Liste Noire",
|
||||||
"f2b_filter": "Filtre(s) Regex",
|
"f2b_filter": "Filtre(s) Regex",
|
||||||
"f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
|
"f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. <b>L'application des mises à jour de liste prendra quelques secondes.</b>",
|
||||||
"f2b_max_attempts": "Nb max. de tentatives",
|
"f2b_max_attempts": "Nb max. de tentatives",
|
||||||
|
"f2b_max_ban_time": "Max. durée du bannissement (s)",
|
||||||
"f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)",
|
"f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)",
|
||||||
"f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)",
|
"f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)",
|
||||||
"f2b_parameters": "Paramètres Fail2ban",
|
"f2b_parameters": "Paramètres Fail2ban",
|
||||||
|
@ -321,7 +324,9 @@
|
||||||
"admins": "Administrateurs",
|
"admins": "Administrateurs",
|
||||||
"api_read_only": "Accès lecture-seule",
|
"api_read_only": "Accès lecture-seule",
|
||||||
"password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
|
"password_policy_lowerupper": "Doit contenir des caractères minuscules et majuscules",
|
||||||
"password_policy_numbers": "Doit contenir au moins un chiffre"
|
"password_policy_numbers": "Doit contenir au moins un chiffre",
|
||||||
|
"ip_check": "Vérification IP",
|
||||||
|
"ip_check_disabled": "La vérification IP est désactivée. Vous pouvez l'activer sous<br> <strong>Système > Configuration > Options > Personnaliser</strong>"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||||
|
@ -440,7 +445,12 @@
|
||||||
"username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé",
|
"username_invalid": "Le nom d'utilisateur %s ne peut pas être utilisé",
|
||||||
"validity_missing": "Veuillez attribuer une période de validité",
|
"validity_missing": "Veuillez attribuer une période de validité",
|
||||||
"value_missing": "Veuillez fournir toutes les valeurs",
|
"value_missing": "Veuillez fournir toutes les valeurs",
|
||||||
"yotp_verification_failed": "La vérification Yubico OTP a échoué : %s"
|
"yotp_verification_failed": "La vérification Yubico OTP a échoué : %s",
|
||||||
|
"webauthn_authenticator_failed": "L'authentificateur selectionné est introuvable",
|
||||||
|
"demo_mode_enabled": "Le mode de démonstration est activé",
|
||||||
|
"template_exists": "La template %s existe déja",
|
||||||
|
"template_id_invalid": "Le numéro de template %s est invalide",
|
||||||
|
"template_name_invalid": "Le nom de la template est invalide"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"chart_this_server": "Graphique (ce serveur)",
|
"chart_this_server": "Graphique (ce serveur)",
|
||||||
|
@ -578,7 +588,7 @@
|
||||||
"unchanged_if_empty": "Si non modifié, laisser en blanc",
|
"unchanged_if_empty": "Si non modifié, laisser en blanc",
|
||||||
"username": "Nom d'utilisateur",
|
"username": "Nom d'utilisateur",
|
||||||
"validate_save": "Valider et sauver",
|
"validate_save": "Valider et sauver",
|
||||||
"lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut).",
|
"lookup_mx": "La destination est une expression régulière qui doit correspondre avec le nom du MX (<code>.*\\.google\\.com</code> pour acheminer tout le courrier destiné à un MX se terminant par google.com via ce saut)",
|
||||||
"mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine."
|
"mailbox_relayhost_info": "S'applique uniquement à la boîte aux lettres et aux alias directs, remplace le relayhost du domaine."
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
|
@ -612,7 +622,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "La connexion a été retardée de %s secondes.",
|
"delayed": "La connexion a été retardée de %s secondes.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"mobileconfig_info": "Veuillez vous connecter en tant qu’utilisateur de la boîte pour télécharger le profil de connexion Apple demandé.",
|
"mobileconfig_info": "Veuillez vous connecter en tant qu’utilisateur de la boîte pour télécharger le profil de connexion Apple demandé.",
|
||||||
"other_logins": "Clé d'authentification",
|
"other_logins": "Clé d'authentification",
|
||||||
|
@ -1081,9 +1091,12 @@
|
||||||
"username": "Nom d'utilisateur",
|
"username": "Nom d'utilisateur",
|
||||||
"verify": "Vérification",
|
"verify": "Vérification",
|
||||||
"waiting": "En attente",
|
"waiting": "En attente",
|
||||||
"week": "Semaine",
|
"week": "semaine",
|
||||||
"weekly": "Hebdomadaire",
|
"weekly": "Hebdomadaire",
|
||||||
"weeks": "semaines"
|
"weeks": "semaines",
|
||||||
|
"months": "mois",
|
||||||
|
"year": "année",
|
||||||
|
"years": "années"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||||
|
|
|
@ -175,10 +175,12 @@
|
||||||
"empty": "Nessun risultato",
|
"empty": "Nessun risultato",
|
||||||
"excludes": "Esclude questi destinatari",
|
"excludes": "Esclude questi destinatari",
|
||||||
"f2b_ban_time": "Tempo di blocco (s)",
|
"f2b_ban_time": "Tempo di blocco (s)",
|
||||||
|
"f2b_ban_time_increment": "Tempo di blocco aumenta ad ogni blocco",
|
||||||
"f2b_blacklist": "Host/reti in blacklist",
|
"f2b_blacklist": "Host/reti in blacklist",
|
||||||
"f2b_filter": "Filtri Regex",
|
"f2b_filter": "Filtri Regex",
|
||||||
"f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
|
"f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. <b>L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.</b>",
|
||||||
"f2b_max_attempts": "Tentativi massimi",
|
"f2b_max_attempts": "Tentativi massimi",
|
||||||
|
"f2b_max_ban_time": "Tempo massimo di blocco (s)",
|
||||||
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
|
||||||
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
|
||||||
"f2b_parameters": "Parametri Fail2ban",
|
"f2b_parameters": "Parametri Fail2ban",
|
||||||
|
@ -211,7 +213,7 @@
|
||||||
"loading": "Caricamento in corso...",
|
"loading": "Caricamento in corso...",
|
||||||
"login_time": "Ora di accesso",
|
"login_time": "Ora di accesso",
|
||||||
"logo_info": "La tua immagine verrà ridimensionata a 40px di altezza, quando verrà usata nella barra di navigazione in alto, ed ad una larghezza massima di 250px nella schermata iniziale. È altamente consigliato l'utilizzo di un'immagine modulabile.",
|
"logo_info": "La tua immagine verrà ridimensionata a 40px di altezza, quando verrà usata nella barra di navigazione in alto, ed ad una larghezza massima di 250px nella schermata iniziale. È altamente consigliato l'utilizzo di un'immagine modulabile.",
|
||||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||||
"main_name": "Nome \"mailcow UI\"",
|
"main_name": "Nome \"mailcow UI\"",
|
||||||
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
|
||||||
"message": "Messaggio",
|
"message": "Messaggio",
|
||||||
|
@ -552,7 +554,7 @@
|
||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
"inactive": "Inattivo",
|
"inactive": "Inattivo",
|
||||||
"kind": "Genere",
|
"kind": "Genere",
|
||||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||||
"mailbox": "Modifica casella di posta",
|
"mailbox": "Modifica casella di posta",
|
||||||
"mailbox_quota_def": "Default mailbox quota",
|
"mailbox_quota_def": "Default mailbox quota",
|
||||||
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
|
||||||
|
@ -674,7 +676,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "L'accesso è stato ritardato di %s secondi.",
|
"delayed": "L'accesso è stato ritardato di %s secondi.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
|
||||||
"other_logins": "Key login",
|
"other_logins": "Key login",
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"bcc_maps": "BCC kartes",
|
"bcc_maps": "BCC kartes",
|
||||||
"filters": "Filtri",
|
"filters": "Filtri",
|
||||||
"recipient_maps": "Saņēmēja kartes",
|
"recipient_maps": "Saņēmēja kartes",
|
||||||
"syncjobs": "Sinhronizācijas uzdevumi"
|
"syncjobs": "Sinhronizācijas uzdevumi",
|
||||||
|
"spam_score": "Mēstules novērtējums"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.",
|
"activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.",
|
||||||
|
@ -104,10 +105,10 @@
|
||||||
"host": "Hosts",
|
"host": "Hosts",
|
||||||
"import": "Importēt",
|
"import": "Importēt",
|
||||||
"import_private_key": "Importēt privātu atslēgu",
|
"import_private_key": "Importēt privātu atslēgu",
|
||||||
"in_use_by": "Tiek lietots ar",
|
"in_use_by": "Izmanto",
|
||||||
"inactive": "Neaktīvs",
|
"inactive": "Neaktīvs",
|
||||||
"link": "Saite",
|
"link": "Saite",
|
||||||
"loading": "Lūdzu uzgaidiet...",
|
"loading": "Lūgums uzgaidīt...",
|
||||||
"logo_info": "Jūsu attēls augšējā navigācijas joslā tiks palielināts līdz 40 pikseļiem un maks. sākumlapas platums par 250 pikseļi. Ir ļoti ieteicama pielāgojama grafikaYour image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. Ir ļoti ieteicama pielāgojamā grafika",
|
"logo_info": "Jūsu attēls augšējā navigācijas joslā tiks palielināts līdz 40 pikseļiem un maks. sākumlapas platums par 250 pikseļi. Ir ļoti ieteicama pielāgojama grafikaYour image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. Ir ļoti ieteicama pielāgojamā grafika",
|
||||||
"main_name": "\"mailcow UI\" nosaukums",
|
"main_name": "\"mailcow UI\" nosaukums",
|
||||||
"merged_vars_hint": "Pelēkās rindas tika apvienotas <code>vars.(local.)inc.php</code> un nevar tikt modificētas.",
|
"merged_vars_hint": "Pelēkās rindas tika apvienotas <code>vars.(local.)inc.php</code> un nevar tikt modificētas.",
|
||||||
|
@ -144,7 +145,10 @@
|
||||||
"ui_texts": "UI etiķetes un teksti",
|
"ui_texts": "UI etiķetes un teksti",
|
||||||
"unchanged_if_empty": "Ja nav veiktas izmaiņas, atstājiet tukšu",
|
"unchanged_if_empty": "Ja nav veiktas izmaiņas, atstājiet tukšu",
|
||||||
"upload": "Augšupielādēt",
|
"upload": "Augšupielādēt",
|
||||||
"username": "Lietotājvārds"
|
"username": "Lietotājvārds",
|
||||||
|
"generate": "izveidot",
|
||||||
|
"message": "Ziņojums",
|
||||||
|
"last_applied": "Pēdējoreiz pielietots"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Piekļuve liegta, vai nepareizi dati",
|
"access_denied": "Piekļuve liegta, vai nepareizi dati",
|
||||||
|
@ -170,7 +174,7 @@
|
||||||
"is_alias": "%s jau ir zināms alias",
|
"is_alias": "%s jau ir zināms alias",
|
||||||
"is_alias_or_mailbox": "%s jau ir zināms alias, pastkastes vai alias addrese izvērsta no alias domēna.",
|
"is_alias_or_mailbox": "%s jau ir zināms alias, pastkastes vai alias addrese izvērsta no alias domēna.",
|
||||||
"is_spam_alias": "%s ir jau zināms spam alias",
|
"is_spam_alias": "%s ir jau zināms spam alias",
|
||||||
"last_key": "Pēdējā atslēga nevar būt dzēsta",
|
"last_key": "Pēdējo atslēgu nevar izdzēst, tā vietā jāatspējo divpakāpju pārbaude.",
|
||||||
"login_failed": "Ielogošanās neveiksmīga",
|
"login_failed": "Ielogošanās neveiksmīga",
|
||||||
"mailbox_invalid": "Pastkastes vārds ir nederīgs",
|
"mailbox_invalid": "Pastkastes vārds ir nederīgs",
|
||||||
"mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)",
|
"mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)",
|
||||||
|
@ -262,7 +266,8 @@
|
||||||
"title": "Labot priekšmetu",
|
"title": "Labot priekšmetu",
|
||||||
"unchanged_if_empty": "Ja neizmainīts atstājiet tukšu",
|
"unchanged_if_empty": "Ja neizmainīts atstājiet tukšu",
|
||||||
"username": "Lietotājvārds",
|
"username": "Lietotājvārds",
|
||||||
"validate_save": "Apstiprināt un saglabāt"
|
"validate_save": "Apstiprināt un saglabāt",
|
||||||
|
"last_modified": "Pēdējoreiz mainīts"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"cancel": "Atcelt",
|
"cancel": "Atcelt",
|
||||||
|
@ -314,21 +319,21 @@
|
||||||
"bcc_destinations": "BCC galamērķi/s",
|
"bcc_destinations": "BCC galamērķi/s",
|
||||||
"bcc_info": "BCC kartes tiek izmantotas, lai klusu pārsūtītu visu ziņojumu kopijas uz citu adresi. Saņēmēja kartes tipa ieraksts tiek izmantots, kad vietējais galamērķis darbojas kā pasta adresāts. Sūtītāja kartes atbilst vienam un tam pašam principam. <br/>\r\n Vietējais galamērķis netiks informēts par piegādes neveiksmi. ",
|
"bcc_info": "BCC kartes tiek izmantotas, lai klusu pārsūtītu visu ziņojumu kopijas uz citu adresi. Saņēmēja kartes tipa ieraksts tiek izmantots, kad vietējais galamērķis darbojas kā pasta adresāts. Sūtītāja kartes atbilst vienam un tam pašam principam. <br/>\r\n Vietējais galamērķis netiks informēts par piegādes neveiksmi. ",
|
||||||
"bcc_local_dest": "Vietējais galamērķis",
|
"bcc_local_dest": "Vietējais galamērķis",
|
||||||
"bcc_map_type": "BCC tips",
|
"bcc_map_type": "BCC veids",
|
||||||
"bcc_maps": "BCC kartes",
|
"bcc_maps": "BCC kartes",
|
||||||
"bcc_rcpt_map": "saņēmēja karte",
|
"bcc_rcpt_map": "saņēmēja karte",
|
||||||
"bcc_sender_map": "Sūtītāja karte",
|
"bcc_sender_map": "Sūtītāja karte",
|
||||||
"bcc_to_rcpt": "Pārslēdzieties uz adresāta kartes tipu",
|
"bcc_to_rcpt": "Pārslēdzieties uz adresāta kartes tipu",
|
||||||
"bcc_to_sender": "Pārslēgties uz sūtītāja kartes tipu",
|
"bcc_to_sender": "Pārslēgties uz sūtītāja kartes tipu",
|
||||||
"bcc_type": "BCC tips",
|
"bcc_type": "BCC tips",
|
||||||
"deactivate": "Deaktivizēt",
|
"deactivate": "Deaktivēt",
|
||||||
"description": "Apraksts",
|
"description": "Apraksts",
|
||||||
"dkim_key_length": "DKIM atslēgas garums (bits)",
|
"dkim_key_length": "DKIM atslēgas garums (bits)",
|
||||||
"domain": "Domēns",
|
"domain": "Domēns",
|
||||||
"domain_admins": "Domēna administratori",
|
"domain_admins": "Domēna administratori",
|
||||||
"domain_aliases": "Domēna aliases",
|
"domain_aliases": "Domēna aliases",
|
||||||
"domain_quota": "Kvota",
|
"domain_quota": "Kvota",
|
||||||
"domain_quota_total": "Kopējā domēna kvota",
|
"domain_quota_total": "Kopējais domēna ierobežojums",
|
||||||
"domains": "Domēns",
|
"domains": "Domēns",
|
||||||
"edit": "Labot",
|
"edit": "Labot",
|
||||||
"empty": "Nav rezultātu",
|
"empty": "Nav rezultātu",
|
||||||
|
@ -341,7 +346,7 @@
|
||||||
"inactive": "Neaktīvs",
|
"inactive": "Neaktīvs",
|
||||||
"kind": "Veids",
|
"kind": "Veids",
|
||||||
"last_run": "Pēdējā norise",
|
"last_run": "Pēdējā norise",
|
||||||
"last_run_reset": "Nākamais grafiks",
|
"last_run_reset": "Ievietot sarakstā kā nākamo",
|
||||||
"mailbox_quota": "Maks. pastkastes izmērs",
|
"mailbox_quota": "Maks. pastkastes izmērs",
|
||||||
"mailboxes": "Pastkaste",
|
"mailboxes": "Pastkaste",
|
||||||
"max_aliases": "Maks. iespejamās aliases",
|
"max_aliases": "Maks. iespejamās aliases",
|
||||||
|
@ -374,7 +379,13 @@
|
||||||
"tls_enforce_out": "Piespiest TLS izejošajiem",
|
"tls_enforce_out": "Piespiest TLS izejošajiem",
|
||||||
"toggle_all": "Pārslēgt visu",
|
"toggle_all": "Pārslēgt visu",
|
||||||
"username": "Lietotājvārds",
|
"username": "Lietotājvārds",
|
||||||
"waiting": "Gaidīšana"
|
"waiting": "Gaidīšana",
|
||||||
|
"last_modified": "Pēdējoreiz mainīts",
|
||||||
|
"booking_0_short": "Vienmēŗ bezmaksas",
|
||||||
|
"daily": "Ik dienu",
|
||||||
|
"hourly": "Ik stundu",
|
||||||
|
"last_mail_login": "Pēdējā pieteikšanās pastkastē",
|
||||||
|
"mailbox": "Pastkaste"
|
||||||
},
|
},
|
||||||
"quarantine": {
|
"quarantine": {
|
||||||
"action": "Darbības",
|
"action": "Darbības",
|
||||||
|
@ -547,5 +558,14 @@
|
||||||
"waiting": "Waiting",
|
"waiting": "Waiting",
|
||||||
"week": "Nedēļa",
|
"week": "Nedēļa",
|
||||||
"weeks": "Nedēļas"
|
"weeks": "Nedēļas"
|
||||||
|
},
|
||||||
|
"datatables": {
|
||||||
|
"paginate": {
|
||||||
|
"first": "Pirmā",
|
||||||
|
"last": "Pēdējā"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"last_modified": "Pēdējoreiz mainīts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,10 +168,12 @@
|
||||||
"empty": "Geen resultaten",
|
"empty": "Geen resultaten",
|
||||||
"excludes": "Exclusief",
|
"excludes": "Exclusief",
|
||||||
"f2b_ban_time": "Verbanningstijd (s)",
|
"f2b_ban_time": "Verbanningstijd (s)",
|
||||||
|
"f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning",
|
||||||
"f2b_blacklist": "Netwerken/hosts op de blacklist",
|
"f2b_blacklist": "Netwerken/hosts op de blacklist",
|
||||||
"f2b_filter": "Regex-filters",
|
"f2b_filter": "Regex-filters",
|
||||||
"f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
|
"f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. <b>Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.</b>",
|
||||||
"f2b_max_attempts": "Maximaal aantal pogingen",
|
"f2b_max_attempts": "Maximaal aantal pogingen",
|
||||||
|
"f2b_max_ban_time": "Maximaal verbanningstijd (s)",
|
||||||
"f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)",
|
"f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)",
|
||||||
"f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)",
|
"f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)",
|
||||||
"f2b_parameters": "Fail2ban",
|
"f2b_parameters": "Fail2ban",
|
||||||
|
@ -598,7 +600,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Aanmelding vertraagd met %s seconden.",
|
"delayed": "Aanmelding vertraagd met %s seconden.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Aanmelden",
|
"login": "Aanmelden",
|
||||||
"mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.",
|
"mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.",
|
||||||
"other_logins": "Meld aan met key",
|
"other_logins": "Meld aan met key",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"sogo_profile_reset": "Usuń profil SOGo (webmail)",
|
"sogo_profile_reset": "Usuń profil SOGo (webmail)",
|
||||||
"syncjobs": "Polecenie synchronizacji"
|
"syncjobs": "Polecenie synchronizacji",
|
||||||
|
"alias_domains": "Dodaj aliasy domen"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"active": "Aktywny",
|
"active": "Aktywny",
|
||||||
|
|
|
@ -539,7 +539,7 @@
|
||||||
"inactive": "Inactiv",
|
"inactive": "Inactiv",
|
||||||
"kind": "Fel",
|
"kind": "Fel",
|
||||||
"last_modified": "Ultima modificare",
|
"last_modified": "Ultima modificare",
|
||||||
"lookup_mx": "Destinația este o expresie regulată care potrivită cu numele MX (<code>.*google\\.com</code> pentru a direcționa toate e-mailurile vizate către un MX care se termină în google.com peste acest hop)",
|
"lookup_mx": "Destinația este o expresie regulată care potrivită cu numele MX (<code>.*\\.google\\.com</code> pentru a direcționa toate e-mailurile vizate către un MX care se termină în google.com peste acest hop)",
|
||||||
"mailbox": "Editează căsuța poștală",
|
"mailbox": "Editează căsuța poștală",
|
||||||
"mailbox_quota_def": "Cota implicită a căsuței poștale",
|
"mailbox_quota_def": "Cota implicită a căsuței poștale",
|
||||||
"mailbox_relayhost_info": "Aplicat numai căsuței poștale și aliasurilor directe, suprascrie un transport dependent de domeniu.",
|
"mailbox_relayhost_info": "Aplicat numai căsuței poștale și aliasurilor directe, suprascrie un transport dependent de domeniu.",
|
||||||
|
@ -656,7 +656,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Conectarea a fost întârziată cu %s secunde.",
|
"delayed": "Conectarea a fost întârziată cu %s secunde.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Autentificare",
|
"login": "Autentificare",
|
||||||
"mobileconfig_info": "Autentificați-vă cu adresa de email pentru a descărca profilul de conexiune Apple.",
|
"mobileconfig_info": "Autentificați-vă cu adresa de email pentru a descărca profilul de conexiune Apple.",
|
||||||
"other_logins": "Autentificare cu cheie",
|
"other_logins": "Autentificare cu cheie",
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
"add_domain_only": "Только добавить домен",
|
"add_domain_only": "Только добавить домен",
|
||||||
"add_domain_restart": "Добавить домен и перезапустить SOGo",
|
"add_domain_restart": "Добавить домен и перезапустить SOGo",
|
||||||
"alias_address": "Псевдоним/ы",
|
"alias_address": "Псевдоним/ы",
|
||||||
"alias_address_info": "<small>Укажите почтовые адреса разделенные запятыми или, если хотите пересылать все сообщения для домена владельцам псевдонима то: <code>@example.com</code>. <b>Только домены mailcow разрешены</b>.</small>",
|
"alias_address_info": "<small>Адрес(а) электронной почты (через запятую) или @example.com (для перехвата всех писем для домена). <b>только домены mailcow</b>.</small>",
|
||||||
"alias_domain": "Псевдоним домена",
|
"alias_domain": "Псевдоним домена",
|
||||||
"alias_domain_info": "<small>Действительные имена доменов, раздёленные запятыми.</small>",
|
"alias_domain_info": "<small>Действительные имена доменов, раздёленные запятыми.</small>",
|
||||||
"app_name": "Название приложения",
|
"app_name": "Название приложения",
|
||||||
|
@ -335,7 +335,10 @@
|
||||||
"username": "Имя пользователя",
|
"username": "Имя пользователя",
|
||||||
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
|
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
|
||||||
"verify": "Проверить",
|
"verify": "Проверить",
|
||||||
"yes": "✓"
|
"yes": "✓",
|
||||||
|
"queue_unban": "разблокировать",
|
||||||
|
"f2b_ban_time_increment": "Время бана увеличивается с каждым баном",
|
||||||
|
"f2b_max_ban_time": "Максимальное время блокировки"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Доступ запрещён, или указаны неверные данные",
|
"access_denied": "Доступ запрещён, или указаны неверные данные",
|
||||||
|
@ -654,7 +657,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Вход был отложен на %s секунд.",
|
"delayed": "Вход был отложен на %s секунд.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Войти",
|
"login": "Войти",
|
||||||
"mobileconfig_info": "Пожалуйста, войдите в систему как пользователь почтового аккаунта для загрузки профиля подключения Apple.",
|
"mobileconfig_info": "Пожалуйста, войдите в систему как пользователь почтового аккаунта для загрузки профиля подключения Apple.",
|
||||||
"other_logins": "Вход с помощью ключа",
|
"other_logins": "Вход с помощью ключа",
|
||||||
|
|
|
@ -106,7 +106,8 @@
|
||||||
"username": "Používateľské meno",
|
"username": "Používateľské meno",
|
||||||
"validate": "Overiť",
|
"validate": "Overiť",
|
||||||
"validation_success": "Úspešne overené",
|
"validation_success": "Úspešne overené",
|
||||||
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie"
|
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie",
|
||||||
|
"tags": "Štítky"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Prístup",
|
"access": "Prístup",
|
||||||
|
@ -212,7 +213,7 @@
|
||||||
"loading": "Čakajte prosím ...",
|
"loading": "Čakajte prosím ...",
|
||||||
"login_time": "Čas prihlásenia",
|
"login_time": "Čas prihlásenia",
|
||||||
"logo_info": "Váš obrázok bude upravený na výšku 40px pre vrchný navigačný riadok a na maximálnu šírku 250px pre úvodnú stránku. Odporúča sa škálovateľná grafika.",
|
"logo_info": "Váš obrázok bude upravený na výšku 40px pre vrchný navigačný riadok a na maximálnu šírku 250px pre úvodnú stránku. Odporúča sa škálovateľná grafika.",
|
||||||
"lookup_mx": "Cieľ je regulárny výraz ktorý sa porovnáva s MX záznamom (<code>.*google\\.com</code> smeruje všetku poštu určenú pre MX ktoré sú cieľom pre google.com cez tento skok)",
|
"lookup_mx": "Cieľ je regulárny výraz ktorý sa porovnáva s MX záznamom (<code>.*\\.google\\.com</code> smeruje všetku poštu určenú pre MX ktoré sú cieľom pre google.com cez tento skok)",
|
||||||
"main_name": "\"mailcow UI\" názov",
|
"main_name": "\"mailcow UI\" názov",
|
||||||
"merged_vars_hint": "Sivé riadky boli načítané z <code>vars.(local.)inc.php</code> a nemôžu byť modifikované cez UI.",
|
"merged_vars_hint": "Sivé riadky boli načítané z <code>vars.(local.)inc.php</code> a nemôžu byť modifikované cez UI.",
|
||||||
"message": "Správa",
|
"message": "Správa",
|
||||||
|
@ -538,7 +539,7 @@
|
||||||
"inactive": "Neaktívny",
|
"inactive": "Neaktívny",
|
||||||
"kind": "Druh",
|
"kind": "Druh",
|
||||||
"last_modified": "Naposledy upravené",
|
"last_modified": "Naposledy upravené",
|
||||||
"lookup_mx": "Cieľ je regulárny výraz ktorý sa zhoduje s MX záznamom (<code>.*google\\.com</code> smeruje všetku poštu na MX ktoré sú cieľom pre google.com cez tento skok)",
|
"lookup_mx": "Cieľ je regulárny výraz ktorý sa zhoduje s MX záznamom (<code>.*\\.google\\.com</code> smeruje všetku poštu na MX ktoré sú cieľom pre google.com cez tento skok)",
|
||||||
"mailbox": "Upraviť mailovú schránku",
|
"mailbox": "Upraviť mailovú schránku",
|
||||||
"mailbox_quota_def": "Predvolená veľkosť mailovej schránky",
|
"mailbox_quota_def": "Predvolená veľkosť mailovej schránky",
|
||||||
"mailbox_relayhost_info": "Aplikované len na používateľské schránky a priame aliasy, prepisuje doménového preposielateľa.",
|
"mailbox_relayhost_info": "Aplikované len na používateľské schránky a priame aliasy, prepisuje doménového preposielateľa.",
|
||||||
|
@ -656,7 +657,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Prihlásenie bolo oneskorené o %s sekúnd.",
|
"delayed": "Prihlásenie bolo oneskorené o %s sekúnd.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Prihlásenie",
|
"login": "Prihlásenie",
|
||||||
"mobileconfig_info": "Prosím, prihláste sa ako mailový používateľ pre stiahnutie požadovaného Apple profilu.",
|
"mobileconfig_info": "Prosím, prihláste sa ako mailový používateľ pre stiahnutie požadovaného Apple profilu.",
|
||||||
"other_logins": "Prihlásenie kľúčom",
|
"other_logins": "Prihlásenie kľúčom",
|
||||||
|
|
|
@ -618,7 +618,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "Av säkerhetsskäl har inloggning inaktiverats i %s sekunder.",
|
"delayed": "Av säkerhetsskäl har inloggning inaktiverats i %s sekunder.",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Logga in",
|
"login": "Logga in",
|
||||||
"mobileconfig_info": "Logga in som en användare av brevlåda för att ladda ner den begärda Apple-anslutningsprofilen.",
|
"mobileconfig_info": "Logga in som en användare av brevlåda för att ladda ner den begärda Apple-anslutningsprofilen.",
|
||||||
"other_logins": "Loggain med nyckel",
|
"other_logins": "Loggain med nyckel",
|
||||||
|
|
|
@ -656,7 +656,7 @@
|
||||||
"awaiting_tfa_confirmation": "В очікуванні підтвердження TFA"
|
"awaiting_tfa_confirmation": "В очікуванні підтвердження TFA"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "Увійти",
|
"login": "Увійти",
|
||||||
"other_logins": "Вхід за допомогою ключа",
|
"other_logins": "Вхід за допомогою ключа",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
|
|
|
@ -213,7 +213,7 @@
|
||||||
"loading": "请等待...",
|
"loading": "请等待...",
|
||||||
"login_time": "登录时间",
|
"login_time": "登录时间",
|
||||||
"logo_info": "你的图片将会在顶部导航栏被缩放为 40px 高,在起始页被缩放为最大 250px 高。强烈推荐使用能较好适应缩放的图片。",
|
"logo_info": "你的图片将会在顶部导航栏被缩放为 40px 高,在起始页被缩放为最大 250px 高。强烈推荐使用能较好适应缩放的图片。",
|
||||||
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
|
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*\\.google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
|
||||||
"main_name": "Mailcow UI 的名称",
|
"main_name": "Mailcow UI 的名称",
|
||||||
"merged_vars_hint": "灰色行来自 <code>vars.(local.)inc.php</code> 文件并且无法修改。",
|
"merged_vars_hint": "灰色行来自 <code>vars.(local.)inc.php</code> 文件并且无法修改。",
|
||||||
"message": "消息",
|
"message": "消息",
|
||||||
|
@ -544,7 +544,7 @@
|
||||||
"hostname": "主机名",
|
"hostname": "主机名",
|
||||||
"inactive": "禁用",
|
"inactive": "禁用",
|
||||||
"kind": "类型",
|
"kind": "类型",
|
||||||
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
|
"lookup_mx": "应当为一个正则表达式,用于匹配 MX 记录 (例如 <code>.*\\.google\\.com</code> 将转发所有拥有以 google.com 结尾的 MX 记录的邮件)",
|
||||||
"mailbox": "编辑邮箱",
|
"mailbox": "编辑邮箱",
|
||||||
"mailbox_quota_def": "邮箱默认配额",
|
"mailbox_quota_def": "邮箱默认配额",
|
||||||
"mailbox_relayhost_info": "只适用于邮箱和邮箱别名,不会覆盖域名的中继主机。",
|
"mailbox_relayhost_info": "只适用于邮箱和邮箱别名,不会覆盖域名的中继主机。",
|
||||||
|
@ -661,7 +661,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "请在 %s 秒后重新登录。",
|
"delayed": "请在 %s 秒后重新登录。",
|
||||||
"fido2_webauthn": "使用 FIDO2/WebAuthn 登录",
|
"fido2_webauthn": "使用 FIDO2/WebAuthn Login 登录",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
"mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。",
|
"mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。",
|
||||||
"other_logins": "Key 登录",
|
"other_logins": "Key 登录",
|
||||||
|
|
|
@ -213,7 +213,7 @@
|
||||||
"loading": "請稍等...",
|
"loading": "請稍等...",
|
||||||
"login_time": "登入時間",
|
"login_time": "登入時間",
|
||||||
"logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。",
|
"logo_info": "你的起始頁面圖片會在頂部導覽列的限制下被縮放為 40px 高,以及最大 250px 高度。強烈推薦使用能較好縮放的圖片。",
|
||||||
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
|
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*\\.google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
|
||||||
"main_name": "\"mailcow UI\" 名稱",
|
"main_name": "\"mailcow UI\" 名稱",
|
||||||
"merged_vars_hint": "灰色列來自 <code>vars.(local.)inc.php</code> 並且不能修改。",
|
"merged_vars_hint": "灰色列來自 <code>vars.(local.)inc.php</code> 並且不能修改。",
|
||||||
"message": "訊息",
|
"message": "訊息",
|
||||||
|
@ -540,7 +540,7 @@
|
||||||
"inactive": "停用",
|
"inactive": "停用",
|
||||||
"kind": "種類",
|
"kind": "種類",
|
||||||
"last_modified": "上次修改時間",
|
"last_modified": "上次修改時間",
|
||||||
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
|
"lookup_mx": "目的地是可以用來匹配 MX 紀錄的正規表達式 (<code>.*\\.google\\.com</code> 會將所有 MX 結尾於 google.com 的郵件轉發到此主機。)",
|
||||||
"mailbox": "編輯信箱",
|
"mailbox": "編輯信箱",
|
||||||
"mailbox_quota_def": "預設信箱容量配額",
|
"mailbox_quota_def": "預設信箱容量配額",
|
||||||
"mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。",
|
"mailbox_relayhost_info": "只會套用於信箱和直接別名,不會覆寫域名中繼主機。",
|
||||||
|
@ -655,7 +655,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"delayed": "請在 %s 秒後重新登入。",
|
"delayed": "請在 %s 秒後重新登入。",
|
||||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
"fido2_webauthn": "FIDO2/WebAuthn Login",
|
||||||
"login": "登入",
|
"login": "登入",
|
||||||
"mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。",
|
"mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。",
|
||||||
"other_logins": "金鑰登入",
|
"other_logins": "金鑰登入",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue