Merge branch 'master' into feature/fts-xapian

This commit is contained in:
DerLinkman 2023-03-15 16:13:51 +01:00
commit f09c8534f5
140 changed files with 10177 additions and 8771 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://mailcow.github.io/mailcow-dockerized-docs/#help-mailcow custom: ["https://www.servercow.de/mailcow?lang=en#sal"]

View File

@ -7,8 +7,8 @@ body:
label: Contribution guidelines label: Contribution guidelines
description: Please read the contribution guidelines before proceeding. description: Please read the contribution guidelines before proceeding.
options: options:
- label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree - label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree
required: true required: true
- type: checkboxes - type: checkboxes
attributes: attributes:
label: I've found a bug and checked that ... label: I've found a bug and checked that ...
@ -26,70 +26,132 @@ body:
attributes: attributes:
label: Description label: Description
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI. description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Logs label: "Logs:"
description: Please take a look at the [official documentation](https://mailcow.github.io/mailcow-dockerized-docs/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks. description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks."
render: bash render: plain text
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Steps to reproduce label: "Steps to reproduce:"
description: Please describe the steps to reproduce the bug. Screenshots can be added, if helpful. description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
render: plain text
placeholder: |- placeholder: |-
1. ... 1. ...
2. ... 2. ...
3. ... 3. ...
validations: validations:
required: true required: true
- type: textarea - type: markdown
attributes: attributes:
label: System information value: |
description: In this stage we would kindly ask you to attach general system information about your setup. ## System information
value: |- ### In this stage we would kindly ask you to attach general system information about your setup.
| Question | Answer | - type: dropdown
| --- | --- | attributes:
| My operating system | I_DO_REPLY_HERE | label: "Which branch are you using?"
| Is Apparmor, SELinux or similar active? | I_DO_REPLY_HERE | description: "#### `git rev-parse --abbrev-ref HEAD`"
| Virtualization technology (KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported** | I_DO_REPLY_HERE | multiple: false
| Server/VM specifications (Memory, CPU Cores) | I_DO_REPLY_HERE | options:
| Docker version (`docker version`) | I_DO_REPLY_HERE | - master
| docker-compose version (`docker-compose version`) | I_DO_REPLY_HERE | - nightly
| mailcow version (```git describe --tags `git rev-list --tags --max-count=1` ```) | I_DO_REPLY_HERE | validations:
| Reverse proxy (custom solution) | I_DO_REPLY_HERE | required: true
- type: input
Output of `git diff origin/master`, any other changes to the code? If so, **please post them**: attributes:
``` label: "Operating System:"
YOUR OUTPUT GOES HERE placeholder: "e.g. Ubuntu 22.04 LTS"
``` validations:
required: true
All third-party firewalls and custom iptables rules are unsupported. **Please check the Docker docs about how to use Docker with your own ruleset**. Nevertheless, iptabels output can help us to help you: - type: input
iptables -L -vn: attributes:
``` label: "Server/VM specifications:"
YOUR OUTPUT GOES HERE placeholder: "Memory, CPU Cores"
``` validations:
required: true
ip6tables -L -vn: - type: input
``` attributes:
YOUR OUTPUT GOES HERE label: "Is Apparmor, SELinux or similar active?"
``` placeholder: "yes/no"
validations:
iptables -L -vn -t nat: required: true
``` - type: input
YOUR OUTPUT GOES HERE attributes:
``` label: "Virtualization technology:"
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**"
ip6tables -L -vn -t nat: validations:
``` required: true
YOUR OUTPUT GOES HERE - type: input
``` attributes:
label: "Docker version:"
DNS problems? Please run `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network) and post the output: description: "#### `docker version`"
``` placeholder: "20.10.21"
YOUR OUTPUT GOES HERE validations:
``` required: true
- type: input
attributes:
label: "docker-compose version or docker compose version:"
description: "#### `docker-compose version` or `docker compose version`"
placeholder: "v2.12.2"
validations:
required: true
- type: input
attributes:
label: "mailcow version:"
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
placeholder: "2022-08"
validations:
required: true
- type: input
attributes:
label: "Reverse proxy:"
placeholder: "e.g. Nginx/Traefik"
validations:
required: true
- type: textarea
attributes:
label: "Logs of git diff:"
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of iptables -L -vn:"
description: "#### Output of `iptables -L -vn`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of ip6tables -L -vn:"
description: "#### Output of `ip6tables -L -vn`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of iptables -L -vn -t nat:"
description: "#### Output of `iptables -L -vn -t nat`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Logs of ip6tables -L -vn -t nat:"
description: "#### Output of `ip6tables -L -vn -t nat`"
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "DNS check:"
description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)"
render: plain text
validations: validations:
required: true required: true

31
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,31 @@
{
"enabled": true,
"timezone": "Europe/Berlin",
"dependencyDashboard": true,
"dependencyDashboardTitle": "Renovate Dashboard",
"commitBody": "Signed-off-by: milkmaker <milkmaker@mailcow.de>",
"rebaseWhen": "auto",
"labels": ["renovate"],
"assignees": [
"@magiccc"
],
"baseBranches": ["staging"],
"enabledManagers": ["github-actions", "regex", "docker-compose"],
"ignorePaths": [
"data\/web\/inc\/lib\/vendor\/matthiasmullie\/minify\/**"
],
"regexManagers": [
{
"fileMatch": ["^helper-scripts\/nextcloud.sh$"],
"matchStrings": [
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?( extractVersion=(?<extractVersion>.*?))?\\s.*?_VERSION=(?<currentValue>.*)"
]
},
{
"fileMatch": ["(^|/)Dockerfile[^/]*$"],
"matchStrings": [
"#\\srenovate:\\sdatasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s(ENV|ARG) .*?_VERSION=(?<currentValue>.*)\\s"
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1,33 @@
name: Check PRs if on staging
on:
pull_request_target:
types: [opened, edited]
permissions: {}
jobs:
is_not_staging:
runs-on: ubuntu-latest
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
steps:
- name: Send message
uses: thollander/actions-comment-pull-request@v2.3.1
with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: |
Thanks for contributing!
I noticed that you didn't select `staging` as your base branch. Please change the base branch to `staging`.
See the attached picture on how to change the base branch to `staging`:
![check_prs_if_on_staging.png](https://raw.githubusercontent.com/mailcow/mailcow-dockerized/master/.github/workflows/assets/check_prs_if_on_staging.png)
- name: Fail #we want to see failed checks in the PR
if: ${{ success() }} #set exit code to 1 even if commenting somehow failed
run: exit 1
is_staging:
runs-on: ubuntu-latest
if: github.event.pull_request.base.ref == 'staging' #check if the target branch is staging
steps:
- name: Success
run: exit 0

View File

@ -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@v6.0.1 uses: actions/stale@v7.0.0
with: with:
repo-token: ${{ secrets.STALE_ACTION_PAT }} repo-token: ${{ secrets.STALE_ACTION_PAT }}
days-before-stale: 60 days-before-stale: 60

View File

@ -33,13 +33,11 @@ jobs:
run: | run: |
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
sudo service docker start sudo service docker start
sudo curl -L https://github.com/docker/compose/releases/download/v$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Prepair Image Builds - name: Prepair Image Builds
run: | run: |
cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml
- name: Build Docker Images - name: Build Docker Images
run: | run: |
docker-compose build ${image} docker compose build ${image}
env: env:
image: ${{ matrix.images }} image: ${{ matrix.images }}

View File

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

View File

@ -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.1 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}}

View File

@ -0,0 +1,34 @@
name: Build mailcow backup image
on:
schedule:
# At 00:00 on Sunday
- cron: "0 0 * * 0"
workflow_dispatch: # Allow to run workflow manually
jobs:
docker_image_build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }}
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: data/Dockerfiles/backup/Dockerfile
push: true
tags: mailcow/backup:latest

View File

@ -1,17 +0,0 @@
name: "Tweet trigger release"
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Tweet-trigger-publish-release
uses: mugi111/tweet-trigger-release@v1.1
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-dockerized Release has been Released on GitHub! Checkout our GitHub Page for the latest Release: github.com/mailcow/mailcow-dockerized/releases/latest'

View File

@ -1,8 +1,5 @@
# mailcow: dockerized - 🐮 + 🐋 = 💕 # mailcow: dockerized - 🐮 + 🐋 = 💕
## We stand with 🇺🇦
[![Mailcow Integration Tests](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml/badge.svg?branch=master)](https://github.com/mailcow/mailcow-dockerized/actions/workflows/integration_tests.yml)
[![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/) [![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/)
[![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email) [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email)
@ -36,3 +33,9 @@ Telegram desktop clients are available for [multiple platforms](https://desktop.
**Important**: mailcow makes use of various open-source software. Please assure you agree with their license before using mailcow. **Important**: mailcow makes use of various open-source software. Please assure you agree with their license before using mailcow.
Any part of mailcow itself is released under **GNU General Public License, Version 3**. Any part of mailcow itself is released under **GNU General Public License, Version 3**.
mailcow is a registered word mark of The Infrastructure Company GmbH, Parkstr. 42, 47877 Willich, Germany.
The project is managed and maintained by The Infrastructure Company GmbH.
Originated from @andryyy (André)

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

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

View File

@ -1,4 +1,4 @@
FROM clamav/clamav:0.105.1_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>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \
fastapi \ fastapi \
uvicorn \ uvicorn \
aiodocker \ aiodocker \
docker \
redis redis
COPY docker-entrypoint.sh /app/ COPY docker-entrypoint.sh /app/

View File

@ -1,5 +1,6 @@
from fastapi import FastAPI, Response, Request from fastapi import FastAPI, Response, Request
import aiodocker import aiodocker
import docker
import psutil import psutil
import sys import sys
import re import re
@ -9,11 +10,38 @@ import json
import asyncio import asyncio
import redis import redis
from datetime import datetime from datetime import datetime
import logging
from logging.config import dictConfig
log_config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(levelprefix)s %(asctime)s %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
},
"loggers": {
"api-logger": {"handlers": ["default"], "level": "INFO"},
},
}
dictConfig(log_config)
containerIds_to_update = [] containerIds_to_update = []
host_stats_isUpdating = False host_stats_isUpdating = False
app = FastAPI() app = FastAPI()
logger = logging.getLogger('api-logger')
@app.get("/host/stats") @app.get("/host/stats")
@ -21,18 +49,15 @@ async def get_host_update_stats():
global host_stats_isUpdating global host_stats_isUpdating
if host_stats_isUpdating == False: if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats()) asyncio.create_task(get_host_stats())
host_stats_isUpdating = True host_stats_isUpdating = True
while True: while True:
if redis_client.exists('host_stats'): if redis_client.exists('host_stats'):
break break
print("wait for host_stats results")
await asyncio.sleep(1.5) await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats')) stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json") return Response(content=json.dumps(stats, indent=4), media_type="application/json")
@ -106,14 +131,14 @@ async def post_containers(container_id : str, post_action : str, request: Reques
else: else:
api_call_method_name = '__'.join(['container_post', str(post_action) ]) api_call_method_name = '__'.join(['container_post', str(post_action) ])
docker_utils = DockerUtils(async_docker_client) docker_utils = DockerUtils(sync_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")) 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)) logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return await api_call_method(container_id, request_json) return api_call_method(container_id, request_json)
except Exception as e: except Exception as e:
print("error - container_post: %s" % str(e)) logger.error("error - container_post: %s" % str(e))
res = { res = {
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
@ -152,398 +177,289 @@ class DockerUtils:
self.docker_client = docker_client self.docker_client = docker_client
# api call: container_post - post_action: stop # api call: container_post - post_action: stop
async def container_post__stop(self, container_id, request_json): def container_post__stop(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.stop()
await container.stop()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
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 # api call: container_post - post_action: start
async def container_post__start(self, container_id, request_json): def container_post__start(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.start()
await container.start()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: restart # api call: container_post - post_action: restart
async def container_post__restart(self, container_id, request_json): def container_post__restart(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: container.restart()
await container.restart()
res = { res = { 'type': 'success', 'msg': 'command completed successfully'}
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: top # api call: container_post - post_action: top
async def container_post__top(self, container_id, request_json): def container_post__top(self, container_id, request_json):
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
if container._id == container_id: res = { 'type': 'success', 'msg': container.top()}
ps_exec = await container.exec("ps") return Response(content=json.dumps(res, indent=4), media_type="application/json")
async with ps_exec.start(detach=False) as stream: # api call: container_post - post_action: stats
ps_return = await stream.read_out() def container_post__stats(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
exec_details = await ps_exec.inspect() for stat in container.stats(decode=True, stream=True):
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: res = { 'type': 'success', 'msg': stat}
res = { return Response(content=json.dumps(res, indent=4), media_type="application/json")
'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 # api call: container_post - post_action: exec - cmd: mailq - task: delete
async def container_post__exec__mailq__delete(self, container_id, request_json): def container_post__exec__mailq__delete(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-d %s' % i for i in filtered_qids] flagged_qids = ['-d %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)) sanitized_string = str(' '.join(flagged_qids));
for container in self.docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return exec_run_handler('generic', postsuper_r)
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 # api call: container_post - post_action: exec - cmd: mailq - task: hold
async def container_post__exec__mailq__hold(self, container_id, request_json): def container_post__exec__mailq__hold(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-h %s' % i for i in filtered_qids] flagged_qids = ['-h %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)) sanitized_string = str(' '.join(flagged_qids));
for container in self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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 # api call: container_post - post_action: exec - cmd: mailq - task: cat
async def container_post__exec__mailq__cat(self, container_id, request_json): def container_post__exec__mailq__cat(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
sanitized_string = str(' '.join(filtered_qids)) sanitized_string = str(' '.join(filtered_qids));
for container in (await self.docker_client.containers.list()): for container in self.docker_client.containers.list(filters={"id": container_id}):
if container._id == container_id: postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') if not postcat_return:
return await exec_run_handler('utf8_text_only', postcat_exec) postcat_return = 'err: invalid'
return exec_run_handler('utf8_text_only', postcat_return)
# api call: container_post - post_action: exec - cmd: mailq - task: unhold # api call: container_post - post_action: exec - cmd: mailq - task: unhold
async def container_post__exec__mailq__unhold(self, container_id, request_json): def container_post__exec__mailq__unhold(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-H %s' % i for i in filtered_qids] flagged_qids = ['-H %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids)) sanitized_string = str(' '.join(flagged_qids));
for container in self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
if container._id == container_id: return exec_run_handler('generic', postsuper_r)
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 # api call: container_post - post_action: exec - cmd: mailq - task: deliver
async def container_post__exec__mailq__deliver(self, container_id, request_json): def container_post__exec__mailq__deliver(self, container_id, request_json):
if 'items' in request_json: if 'items' in request_json:
r = re.compile("^[0-9a-fA-F]+$") r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items']) filtered_qids = filter(r.match, request_json['items'])
if filtered_qids: if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids] flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in self.docker_client.containers.list(filters={"id": container_id}):
for container in (await self.docker_client.containers.list()): for i in flagged_qids:
if container._id == container_id: postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
for i in flagged_qids: # todo: check each exit code
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
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") 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" # api call: container_post - post_action: exec - cmd: mailq - task: list
rspamd_password_exec = await container.exec(cmd, user='_rspamd') def container_post__exec__mailq__list(self, container_id, request_json):
async with rspamd_password_exec.start(detach=False) as stream: for container in self.docker_client.containers.list(filters={"id": container_id}):
rspamd_password_return = await stream.read_out() mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
return exec_run_handler('utf8_text_only', mailq_return)
matched = False # api call: container_post - post_action: exec - cmd: mailq - task: flush
if "OK" in rspamd_password_return.data.decode('utf-8'): def container_post__exec__mailq__flush(self, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
return 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, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
return 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, container_id, request_json):
if 'username' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}):
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.docker_client.containers.list(filters={"id": container_id}):
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, container_id, request_json):
if 'dir' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}):
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, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
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 matched = True
await container.restart() 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, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
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, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
return exec_run_handler('generic', reload_return)
# api call: container_post - post_action: exec - cmd: reload - task: postfix
def container_post__exec__reload__postfix(self, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
return exec_run_handler('generic', reload_return)
# api call: container_post - post_action: exec - cmd: reload - task: nginx
def container_post__exec__reload__nginx(self, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
return exec_run_handler('generic', reload_return)
# api call: container_post - post_action: exec - cmd: sieve - task: list
def container_post__exec__sieve__list(self, container_id, request_json):
if 'username' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}):
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
return 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, container_id, request_json):
if 'username' in request.json and 'script_name' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}):
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 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, container_id, request_json):
if 'maildir' in request_json:
for container in self.docker_client.containers.list(filters={"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 = container.exec_run(cmd, user='vmail')
return 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, container_id, request_json):
if 'raw' in request_json:
for container in self.docker_client.containers.list(filters={"id": container_id}):
cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
if matched: matched = False
res = { for line in cmd_response.split("\n"):
'type': 'success', if '$2$' in line:
'msg': 'command completed successfully' hash = line.strip()
} hash_out = re.search('\$2\$.+$', hash).group(0)
return Response(content=json.dumps(res, indent=4), media_type="application/json") rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
else: rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
res = { cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
'type': 'danger', cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
'msg': 'command did not complete' if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
} container.restart()
return Response(content=json.dumps(res, indent=4), media_type="application/json") matched = True
if matched:
res = { 'type': 'success', 'msg': 'command completed successfully' }
logger.info('success changing Rspamd password')
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
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")
def exec_cmd_container(container, cmd, user, timeout=2, shell_cmd="/bin/bash"):
async def exec_run_handler(type, exec_obj): def recv_socket_data(c_socket, timeout):
async with exec_obj.start(detach=False) as stream: c_socket.setblocking(0)
exec_return = await stream.read_out() 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)
if exec_return == None: try :
exec_return = "" socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
else: if not cmd.endswith("\n"):
exec_return = exec_return.data.decode('utf-8') cmd = cmd + "\n"
socket.send(cmd.encode('utf-8'))
if type == 'generic': data = recv_socket_data(socket, timeout)
exec_details = await exec_obj.inspect() socket.close()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0: return data
res = { except Exception as e:
"type": "success", logger.error("error - exec_cmd_container: %s" % str(e))
"msg": "command completed successfully" traceback.print_exc(file=sys.stdout)
} def exec_run_handler(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") return Response(content=json.dumps(res, indent=4), media_type="application/json")
else: else:
res = { res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
"type": "success",
"msg": "'command failed: " + exec_return
}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only': if type == 'utf8_text_only':
return Response(content=exec_return, media_type="text/plain") return Response(content=output.output.decode('utf-8'), media_type="text/plain")
async def get_host_stats(wait=5): async def get_host_stats(wait=5):
global host_stats_isUpdating global host_stats_isUpdating
@ -570,12 +486,10 @@ async def get_host_stats(wait=5):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
host_stats_isUpdating = False host_stats_isUpdating = False
async def get_container_stats(container_id, wait=5, stop=False): async def get_container_stats(container_id, wait=5, stop=False):
global containerIds_to_update global containerIds_to_update
@ -598,13 +512,11 @@ async def get_container_stats(container_id, wait=5, stop=False):
"type": "danger", "type": "danger",
"msg": str(e) "msg": str(e)
} }
print(json.dumps(res, indent=4))
else: else:
res = { res = {
"type": "danger", "type": "danger",
"msg": "no or invalid id defined" "msg": "no or invalid id defined"
} }
print(json.dumps(res, indent=4))
await asyncio.sleep(wait) await asyncio.sleep(wait)
if stop == True: if stop == True:
@ -615,9 +527,13 @@ async def get_container_stats(container_id, wait=5, stop=False):
await get_container_stats(container_id, wait=0, stop=True) await get_container_stats(container_id, wait=0, stop=True)
if os.environ['REDIS_SLAVEOF_IP'] != "": if os.environ['REDIS_SLAVEOF_IP'] != "":
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0) redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
else: else:
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0) redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
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') async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
logger.info('DockerApi started')

View File

@ -6,7 +6,7 @@ ARG DOVECOT=2.3.19.1
ARG FLATCURVE=v0.3.2 ARG FLATCURVE=v0.3.2
ARG XAPIAN=1.4.21 ARG XAPIAN=1.4.21
ENV LC_ALL C ENV LC_ALL C
ENV GOSU_VERSION 1.14
# Add groups and users before installing Dovecot to not break compatibility # Add groups and users before installing Dovecot to not break compatibility
RUN touch /etc/default/locale \ RUN touch /etc/default/locale \

View File

@ -51,8 +51,8 @@ sub sig_handler {
die "sig_handler received signal, preparing to exit...\n"; die "sig_handler received signal, preparing to exit...\n";
}; };
open my $file, '<', "/etc/sogo/sieve.creds"; open my $file, '<', "/etc/sogo/sieve.creds";
my $creds = <$file>; my $creds = <$file>;
close $file; close $file;
my ($master_user, $master_pass) = split /:/, $creds; my ($master_user, $master_pass) = split /:/, $creds;
my $sth = $dbh->prepare("SELECT id, my $sth = $dbh->prepare("SELECT id,
@ -166,17 +166,11 @@ while ($row = $sth->fetchrow_arrayref()) {
$success = 1; $success = 1;
} }
$keep_job_active = 1; $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ? WHERE id = ?");
if (defined $exit_status && $exit_status eq "EXIT_AUTHENTICATION_FAILURE_USER1") {
$keep_job_active = 0;
}
$update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ?, active = ? WHERE id = ?");
$update->bind_param( 1, ${stdout} ); $update->bind_param( 1, ${stdout} );
$update->bind_param( 2, ${success} ); $update->bind_param( 2, ${success} );
$update->bind_param( 3, ${exit_status} ); $update->bind_param( 3, ${exit_status} );
$update->bind_param( 4, ${keep_job_active} ); $update->bind_param( 4, ${id} );
$update->bind_param( 5, ${id} );
$update->execute(); $update->execute();
} catch { } catch {
$update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?"); $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?");

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV XTABLES_LIBDIR /usr/lib/xtables ENV XTABLES_LIBDIR /usr/lib/xtables

View File

@ -97,9 +97,9 @@ def refreshF2bregex():
f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
f2bregex[6] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
f2bregex[7] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[8] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
@ -359,21 +359,28 @@ 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: match = all((
if not match: new_rule.get_src() == rule.get_src(),
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') new_rule.get_dst() == rule.get_dst(),
chain.insert_rule(new_rule) new_rule.target.parameters == rule.target.parameters,
else: new_rule.target.name == rule.target.name
if match: ))
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') if position == 0:
chain.delete_rule(rule) 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:

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
WORKDIR /app WORKDIR /app

View File

@ -1,12 +1,18 @@
FROM php:8.0-fpm-alpine3.16 FROM php:8.1-fpm-alpine3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.21 # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
ENV IMAGICK_PECL 3.7.0 ARG APCU_PECL_VERSION=5.1.22
# Mailparse is pulled from master branch # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced
#ENV MAILPARSE_PECL 3.0.2 ARG IMAGICK_PECL_VERSION=3.7.0
ENV MEMCACHED_PECL 3.2.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced
ENV REDIS_PECL 5.3.7 ARG MAILPARSE_PECL_VERSION=3.1.4
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced
ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced
ARG REDIS_PECL_VERSION=5.3.7
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced
ARG COMPOSER_VERSION=2.5.4
RUN apk add -U --no-cache autoconf \ RUN apk add -U --no-cache autoconf \
aspell-dev \ aspell-dev \
@ -18,6 +24,7 @@ RUN apk add -U --no-cache autoconf \
freetype-dev \ freetype-dev \
g++ \ g++ \
git \ git \
gettext \
gettext-dev \ gettext-dev \
gmp-dev \ gmp-dev \
gnupg \ gnupg \
@ -27,8 +34,11 @@ RUN apk add -U --no-cache autoconf \
imagemagick-dev \ imagemagick-dev \
imap-dev \ imap-dev \
jq \ jq \
libavif \
libavif-dev \
libjpeg-turbo \ libjpeg-turbo \
libjpeg-turbo-dev \ libjpeg-turbo-dev \
libmemcached \
libmemcached-dev \ libmemcached-dev \
libpng \ libpng \
libpng-dev \ libpng-dev \
@ -38,7 +48,9 @@ RUN apk add -U --no-cache autoconf \
libtool \ libtool \
libwebp-dev \ libwebp-dev \
libxml2-dev \ libxml2-dev \
libxpm \
libxpm-dev \ libxpm-dev \
libzip \
libzip-dev \ libzip-dev \
make \ make \
mysql-client \ mysql-client \
@ -49,22 +61,24 @@ RUN apk add -U --no-cache autoconf \
samba-client \ samba-client \
zlib-dev \ zlib-dev \
tzdata \ tzdata \
&& git clone https://github.com/php/pecl-mail-mailparse \ && pecl install APCu-${APCU_PECL_VERSION} \
&& cd pecl-mail-mailparse \ && pecl install imagick-${IMAGICK_PECL_VERSION} \
&& pecl install package.xml \ && pecl install mailparse-${MAILPARSE_PECL_VERSION} \
&& cd .. \ && pecl install memcached-${MEMCACHED_PECL_VERSION} \
&& rm -r pecl-mail-mailparse \ && pecl install redis-${REDIS_PECL_VERSION} \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
&& docker-php-ext-enable apcu imagick memcached mailparse redis \ && docker-php-ext-enable apcu imagick memcached mailparse redis \
&& pecl clear-cache \ && pecl clear-cache \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
&& docker-php-ext-configure exif \ && docker-php-ext-configure exif \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \ && docker-php-ext-configure gd --with-freetype=/usr/include/ \
--with-jpeg=/usr/include/ \ --with-jpeg=/usr/include/ \
--with-webp \
--with-xpm \
--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 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 \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \
&& apk del --purge autoconf \ && apk del --purge autoconf \
@ -72,15 +86,21 @@ RUN apk add -U --no-cache autoconf \
cyrus-sasl-dev \ cyrus-sasl-dev \
freetype-dev \ freetype-dev \
g++ \ g++ \
gettext-dev \
icu-dev \ icu-dev \
imagemagick-dev \ imagemagick-dev \
imap-dev \ imap-dev \
libavif-dev \
libjpeg-turbo-dev \ libjpeg-turbo-dev \
libmemcached-dev \
libpng-dev \ libpng-dev \
libressl-dev \ libressl-dev \
libwebp-dev \ libwebp-dev \
libxml2-dev \ libxml2-dev \
libxpm-dev \
libzip-dev \
make \ make \
openldap-dev \
pcre-dev \ pcre-dev \
zlib-dev zlib-dev
@ -88,4 +108,4 @@ COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["php-fpm"] CMD ["php-fpm"]

View File

@ -3,8 +3,9 @@ LABEL maintainer "Andre Peters <andre.peters@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/
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
ENV LC_ALL C ENV LC_ALL C
ENV GOSU_VERSION 1.14
# Prerequisites # Prerequisites
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \

View File

@ -2,7 +2,8 @@ FROM solr:7.7-slim
USER root USER root
ENV GOSU_VERSION 1.11 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
COPY solr.sh / COPY solr.sh /
COPY solr-config-7.7.0.xml / COPY solr-config-7.7.0.xml /

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.16 FROM alpine:3.17
LABEL maintainer "André Peters <andre.peters@servercow.de>" LABEL maintainer "André Peters <andre.peters@servercow.de>"
# Installation # Installation

View File

@ -3,7 +3,6 @@
/.*episerver.*/i /.*episerver.*/i
/.*supergewinne.*/i /.*supergewinne.*/i
/List-Unsubscribe.*nbps\.eu/i /List-Unsubscribe.*nbps\.eu/i
/X-Mailer: AWeber.*/i
/.*regiofinder.*/i /.*regiofinder.*/i
/.*EmailSocket.*/i /.*EmailSocket.*/i
/List-Unsubscribe:.*respread.*/i /List-Unsubscribe:.*respread.*/i

View File

@ -16,8 +16,7 @@ rules {
backend = "http"; backend = "http";
url = "http://nginx:9081/pushover.php"; url = "http://nginx:9081/pushover.php";
selector = "mailcow_rcpt"; selector = "mailcow_rcpt";
# Only return msgid, do not parse the full message formatter = "json";
formatter = "msgid";
meta_headers = true; meta_headers = true;
} }
} }

View File

@ -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"];
} }

View File

@ -47,12 +47,14 @@ if (!function_exists('getallheaders')) {
} }
$headers = getallheaders(); $headers = getallheaders();
$json_body = json_decode(file_get_contents('php://input'));
$qid = $headers['X-Rspamd-Qid']; $qid = $headers['X-Rspamd-Qid'];
$rcpts = $headers['X-Rspamd-Rcpt']; $rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $headers['X-Rspamd-From']; $sender = $headers['X-Rspamd-From'];
$ip = $headers['X-Rspamd-Ip']; $ip = $headers['X-Rspamd-Ip'];
$subject = $headers['X-Rspamd-Subject']; $subject = $headers['X-Rspamd-Subject'];
$messageid= $json_body->message_id;
$priority = 0; $priority = 0;
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); $symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
@ -65,6 +67,20 @@ if (is_array($symbols_array)) {
} }
} }
$sender_address = $json_body->header_from[0];
$sender_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
$sender_address = $matches['address'];
$sender_name = trim($matches['name'], '"\' ');
}
$to_address = $json_body->header_to[0];
$to_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$to_address = $matches['address'];
$to_name = trim($matches['name'], '"\' ');
}
$rcpt_final_mailboxes = array(); $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
@ -229,9 +245,16 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
$post_fields = array( $post_fields = array(
"token" => $api_data['token'], "token" => $api_data['token'],
"user" => $api_data['key'], "user" => $api_data['key'],
"title" => sprintf("%s", str_replace(array('{SUBJECT}', '{SENDER}'), array($subject, $sender), $title)), "title" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title)
),
"priority" => $priority, "priority" => $priority,
"message" => sprintf("%s", str_replace(array('{SUBJECT}', '{SENDER}'), array($subject, $sender), $text)) "message" => sprintf("%s", str_replace(
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'),
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text)
),
"sound" => $attributes['sound'] ?? "pushover"
); );
if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) { if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) {
$post_fields['expire'] = 600; $post_fields['expire'] = 600;

View File

@ -13,12 +13,12 @@
Please check the logs or contact support if the error persists.</p> Please check the logs or contact support if the error persists.</p>
<h2>Quick debugging</h2> <h2>Quick debugging</h2>
<p>Check Nginx and PHP logs:</p> <p>Check Nginx and PHP logs:</p>
<pre>docker-compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre> <pre>docker compose logs --tail=200 php-fpm-mailcow nginx-mailcow</pre>
<p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p> <p>Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:</p>
<pre>source mailcow.conf ; docker-compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre> <pre>source mailcow.conf ; docker compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}</pre>
<p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p> <p>In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (<b>NEVER</b> do this with a production system, it will remove <b>ALL</b> data):</p>
<pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre> <pre>BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all</pre>
<pre>docker-compose down --volumes ; docker-compose up -d</pre> <pre>docker compose down --volumes ; docker compose up -d</pre>
<p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p> <p>Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a> for a list.</p>
<br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a> <br>Click to learn more about <a style="color:red;text-decoration:none;" href="https://mailcow.github.io/mailcow-dockerized-docs/#get-support" target="_blank">getting support.</a>
</body> </body>

View File

@ -10,9 +10,6 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa(); $tfa_data = get_tfa();
$fido2_data = fido2(array("action" => "get_friendly_names")); $fido2_data = fido2(array("action" => "get_friendly_names"));
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/admin.js'); $js_minifier->add('/web/js/site/admin.js');
$js_minifier->add('/web/js/presets/rspamd.js'); $js_minifier->add('/web/js/presets/rspamd.js');
@ -89,7 +86,6 @@ $template_data = [
'tfa_id' => @$_SESSION['tfa_id'], 'tfa_id' => @$_SESSION['tfa_id'],
'fido2_cid' => @$_SESSION['fido2_cid'], 'fido2_cid' => @$_SESSION['fido2_cid'],
'fido2_data' => $fido2_data, 'fido2_data' => $fido2_data,
'gal' => @$_SESSION['gal'],
'api' => [ 'api' => [
'ro' => admin_api('ro', 'get'), 'ro' => admin_api('ro', 'get'),
'rw' => admin_api('rw', 'get'), 'rw' => admin_api('rw', 'get'),
@ -107,6 +103,7 @@ $template_data = [
'rsettings' => $rsettings, 'rsettings' => $rsettings,
'rspamd_regex_maps' => $rspamd_regex_maps, 'rspamd_regex_maps' => $rspamd_regex_maps,
'logo_specs' => customize('get', 'main_logo_specs'), 'logo_specs' => customize('get', 'main_logo_specs'),
'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'],
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),

View File

@ -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:
@ -3349,6 +3381,7 @@ paths:
evaluate_x_prio: "0" evaluate_x_prio: "0"
key: 21e8918e1jksdjcpis712 key: 21e8918e1jksdjcpis712
only_x_prio: "0" only_x_prio: "0"
sound: "pushover"
senders: "" senders: ""
senders_regex: "" senders_regex: ""
text: "" text: ""
@ -3392,6 +3425,7 @@ paths:
evaluate_x_prio: "0" evaluate_x_prio: "0"
key: 21e8918e1jksdjcpis712 key: 21e8918e1jksdjcpis712
only_x_prio: "0" only_x_prio: "0"
sound: "pushover"
senders: "" senders: ""
senders_regex: "" senders_regex: ""
text: "" text: ""
@ -3413,6 +3447,9 @@ paths:
only_x_prio: only_x_prio:
description: Only send push for prio mails description: Only send push for prio mails
type: number type: number
sound:
description: Set notification sound
type: string
senders: senders:
description: Only send push for emails from these senders description: Only send push for emails from these senders
type: string type: string
@ -5501,6 +5538,60 @@ paths:
attr: attr:
spam_score: "8,15" spam_score: "8,15"
summary: Edit mailbox spam filter score summary: Edit mailbox spam filter score
"/api/v1/get/mailbox/all/{domain}":
get:
parameters:
- description: name of domain
in: path
name: domain
required: false
schema:
type: string
- description: e.g. api-key-string
example: api-key-string
in: header
name: X-API-Key
required: false
schema:
type: string
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- active: "1"
attributes:
force_pw_update: "0"
mailbox_format: "maildir:"
quarantine_notification: never
sogo_access: "1"
tls_enforce_in: "0"
tls_enforce_out: "0"
domain: domain3.tld
is_relayed: 0
local_part: info
max_new_quota: 10737418240
messages: 0
name: Full name
percent_class: success
percent_in_use: 0
quota: 3221225472
quota_used: 0
rl: false
spam_aliases: 0
username: info@domain3.tld
tags: ["tag1", "tag2"]
description: OK
headers: {}
tags:
- Mailboxes
description: You can list all mailboxes existing in system for a specific domain.
operationId: Get mailboxes of a domain
summary: Get mailboxes of a domain
tags: tags:
- name: Domains - name: Domains
@ -5527,6 +5618,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

View File

@ -4,10 +4,10 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.12.0/r-2.3.0/sl-1.4.0 * https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0
* *
* Included libraries: * Included libraries:
* DataTables 1.12.0, Responsive 2.3.0, Select 1.4.0 * DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0
*/ */
@charset "UTF-8"; @charset "UTF-8";
@ -63,7 +63,7 @@ table.dataTable thead > tr > td.sorting_desc_disabled:after {
opacity: 0.125; opacity: 0.125;
right: 10px; right: 10px;
line-height: 9px; line-height: 9px;
font-size: 0.9em; font-size: 0.8em;
} }
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before, table.dataTable thead > tr > td.sorting:before,
@ -72,7 +72,7 @@ table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before, table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before { table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%; bottom: 50%;
content: ""; content: "";
} }
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after, table.dataTable thead > tr > td.sorting:after,
@ -81,7 +81,7 @@ table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after, table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after { table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%; top: 50%;
content: ""; content: "";
} }
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before, table.dataTable thead > tr > td.sorting_asc:before,
@ -287,6 +287,9 @@ table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
color: white; color: white;
} }
table.dataTable > tbody > tr.selected a {
color: #090a0b;
}
table.dataTable.table-striped > tbody > tr.odd > * { table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05);
} }
@ -335,6 +338,9 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination {
white-space: nowrap; white-space: nowrap;
justify-content: flex-end; justify-content: flex-end;
} }
div.dataTables_wrapper div.dt-row {
position: relative;
}
div.dataTables_scrollHead table.dataTable { div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important; margin-bottom: 0 !important;
@ -380,17 +386,6 @@ div.dataTables_wrapper div.dataTables_paginate {
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) { table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px; padding-right: 20px;
} }
table.dataTable.table-sm .sorting:before,
table.dataTable.table-sm .sorting_asc:before,
table.dataTable.table-sm .sorting_desc:before {
top: 5px;
right: 0.85em;
}
table.dataTable.table-sm .sorting:after,
table.dataTable.table-sm .sorting_asc:after,
table.dataTable.table-sm .sorting_desc:after {
top: 5px;
}
table.table-bordered.dataTable { table.table-bordered.dataTable {
border-right-width: 0; border-right-width: 0;
@ -629,13 +624,13 @@ table.dataTable > tbody > tr > .selected {
background-color: rgba(13, 110, 253, 0.9); background-color: rgba(13, 110, 253, 0.9);
color: white; color: white;
} }
table.dataTable tbody td.select-checkbox, table.dataTable > tbody > tr > td.select-checkbox,
table.dataTable tbody th.select-checkbox { table.dataTable > tbody > tr > th.select-checkbox {
position: relative; position: relative;
} }
table.dataTable tbody td.select-checkbox:before, table.dataTable tbody td.select-checkbox:after, table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after,
table.dataTable tbody th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before,
table.dataTable tbody th.select-checkbox:after { table.dataTable > tbody > tr > th.select-checkbox:after {
display: block; display: block;
position: absolute; position: absolute;
top: 1.2em; top: 1.2em;
@ -644,20 +639,20 @@ table.dataTable tbody th.select-checkbox:after {
height: 12px; height: 12px;
box-sizing: border-box; box-sizing: border-box;
} }
table.dataTable tbody td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:before,
table.dataTable tbody th.select-checkbox:before { table.dataTable > tbody > tr > th.select-checkbox:before {
content: " "; content: " ";
margin-top: -5px; margin-top: -5px;
margin-left: -6px; margin-left: -6px;
border: 1px solid black; border: 1px solid black;
border-radius: 3px; border-radius: 3px;
} }
table.dataTable tr.selected td.select-checkbox:before, table.dataTable > tbody > tr.selected > td.select-checkbox:before,
table.dataTable tr.selected th.select-checkbox:before { table.dataTable > tbody > tr.selected > th.select-checkbox:before {
border: 1px solid white; border: 1px solid white;
} }
table.dataTable tr.selected td.select-checkbox:after, table.dataTable > tbody > tr.selected > td.select-checkbox:after,
table.dataTable tr.selected th.select-checkbox:after { table.dataTable > tbody > tr.selected > th.select-checkbox:after {
content: "✓"; content: "✓";
font-size: 20px; font-size: 20px;
margin-top: -19px; margin-top: -19px;
@ -665,12 +660,12 @@ table.dataTable tr.selected th.select-checkbox:after {
text-align: center; text-align: center;
text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;
} }
table.dataTable.compact tbody td.select-checkbox:before, table.dataTable.compact > tbody > tr > td.select-checkbox:before,
table.dataTable.compact tbody th.select-checkbox:before { table.dataTable.compact > tbody > tr > th.select-checkbox:before {
margin-top: -12px; margin-top: -12px;
} }
table.dataTable.compact tr.selected td.select-checkbox:after, table.dataTable.compact > tbody > tr.selected > td.select-checkbox:after,
table.dataTable.compact tr.selected th.select-checkbox:after { table.dataTable.compact > tbody > tr.selected > th.select-checkbox:after {
margin-top: -16px; margin-top: -16px;
} }
@ -690,4 +685,3 @@ table.dataTable.table-sm tbody td.select-checkbox::before {
margin-top: -9px; margin-top: -9px;
} }

View File

@ -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;
}

View File

@ -199,6 +199,13 @@
display: none !important; display: none !important;
} }
div.dataTables_wrapper div.dataTables_length {
text-align: left;
}
.senders-mw220 {
max-width: 100% !important;
}
} }
@media (max-width: 350px) { @media (max-width: 350px) {

View File

@ -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;
}

View File

@ -11,7 +11,86 @@
* Copyright 2011-2021 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,700;1,400&display=swap");
/* source-sans-pro-300 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-300italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 300;
src: url('/fonts/source-sans-pro-v21-latin-300italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-300italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-300italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-regular - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-regular.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-regular.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 400;
src: url('/fonts/source-sans-pro-v21-latin-italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700 - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
/* source-sans-pro-700italic - latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: italic;
font-weight: 700;
src: url('/fonts/source-sans-pro-v21-latin-700italic.eot'); /* IE9 Compat Modes */
src: local(''),
url('/fonts/source-sans-pro-v21-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/source-sans-pro-v21-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/source-sans-pro-v21-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/source-sans-pro-v21-latin-700italic.svg#SourceSansPro') format('svg'); /* Legacy iOS */
}
:root { :root {
--bs-blue: #158cba; --bs-blue: #158cba;
--bs-indigo: #6610f2; --bs-indigo: #6610f2;

View File

@ -358,3 +358,11 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
background: #333; background: #333;
} }
span.mail-address-item {
background-color: #333;
border-radius: 4px;
border: 1px solid #555;
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
}

View File

@ -11,6 +11,11 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$solr_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_SOLR"])) ? false : solr_status(); $solr_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_SOLR"])) ? false : solr_status();
$clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true; $clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true;
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/debug.js'); $js_minifier->add('/web/js/site/debug.js');
// vmail df // vmail df
@ -54,11 +59,13 @@ $template_data = [
'vmail_df' => $vmail_df, 'vmail_df' => $vmail_df,
'hostname' => $hostname, 'hostname' => $hostname,
'timezone' => $timezone, 'timezone' => $timezone,
'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'), 'license_guid' => license('guid'),
'solr_status' => $solr_status, 'solr_status' => $solr_status,
'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60), 'solr_uptime' => round($solr_status['status']['dovecot-fts']['uptime'] / 1000 / 60 / 60),
'clamd_status' => $clamd_status, 'clamd_status' => $clamd_status,
'containers' => $containers, 'containers' => $containers,
'ip_check' => customize('get', 'ip_check'),
'lang_admin' => json_encode($lang['admin']), 'lang_admin' => json_encode($lang['admin']),
'lang_debug' => json_encode($lang['debug']), 'lang_debug' => json_encode($lang['debug']),
'lang_datatables' => json_encode($lang['datatables']), 'lang_datatables' => json_encode($lang['datatables']),

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -160,6 +160,25 @@ function customize($_action, $_item, $_data = null) {
'msg' => 'ui_texts' 'msg' => 'ui_texts'
); );
break; break;
case 'ip_check':
$ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0;
try {
$redis->set('IP_CHECK', $ip_check);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => 'ip_check_opt_in_modified'
);
break;
} }
break; break;
case 'delete': case 'delete':
@ -276,6 +295,20 @@ function customize($_action, $_item, $_data = null) {
return false; return false;
} }
break; break;
case 'ip_check':
try {
$ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0;
return $ip_check;
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_item, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
break;
} }
break; break;
} }

View File

@ -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;
}
}

View File

@ -1739,7 +1739,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 +1748,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 +1793,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;

View File

@ -1420,11 +1420,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// check attributes // check attributes
$attr = array(); $attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
$attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0; $attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400;
$attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0; $attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10;
$attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0; $attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576;
$attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0; $attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576;
$attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0; $attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576;
$attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
$attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
$attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1;
@ -1435,7 +1435,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
$attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
// save template // save template
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
VALUES (:type, :template, :attributes)"); VALUES (:type, :template, :attributes)");
@ -2880,67 +2879,68 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied' 'msg' => 'extended_sender_acl_denied'
); );
return false;
} }
$extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl'])); else {
foreach ($extra_acls as $i => &$extra_acl) { $extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl']));
if (empty($extra_acl)) { foreach ($extra_acls as $i => &$extra_acl) {
continue; if (empty($extra_acl)) {
} continue;
if (substr($extra_acl, 0, 1) === "@") { }
$extra_acl = ltrim($extra_acl, '@'); if (substr($extra_acl, 0, 1) === "@") {
} $extra_acl = ltrim($extra_acl, '@');
if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) { }
$_SESSION['return'][] = array( if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) {
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
);
unset($extra_acls[$i]);
continue;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
$extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (in_array($extra_acl_domain, $domains)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) 'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
); );
unset($extra_acls[$i]); unset($extra_acls[$i]);
continue; continue;
} }
} $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
else { if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
if (in_array($extra_acl, $domains)) { $extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$_SESSION['return'][] = array( if (in_array($extra_acl_domain, $domains)) {
'type' => 'danger', $_SESSION['return'][] = array(
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'type' => 'danger',
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
); 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
unset($extra_acls[$i]); );
continue; unset($extra_acls[$i]);
continue;
}
}
else {
if (in_array($extra_acl, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
$extra_acl = '@' . $extra_acl;
} }
$extra_acl = '@' . $extra_acl;
} }
} $extra_acls = array_filter($extra_acls);
$extra_acls = array_filter($extra_acls); $extra_acls = array_values($extra_acls);
$extra_acls = array_values($extra_acls); $extra_acls = array_unique($extra_acls);
$extra_acls = array_unique($extra_acls); $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array( $stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username ':username' => $username
)); ));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username
));
}
} }
} }
if (isset($_data['sender_acl'])) { if (isset($_data['sender_acl'])) {
@ -4756,15 +4756,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
":id" => $id, ":id" => $id,
":type" => "domain", ":type" => "domain",
":template" => "Default" ":template" => "Default"
)); ));
}
$_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' => 'template_removed' 'msg' => array('template_removed', htmlspecialchars($id))
); );
return true; return true;
}
break; break;
case 'alias': case 'alias':
if (!is_array($_data['id'])) { if (!is_array($_data['id'])) {
@ -5171,15 +5171,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$tags = $_data['tags']; $tags = $_data['tags'];
if (!is_array($tags)) $tags = array(); if (!is_array($tags)) $tags = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$wasModified = false; $wasModified = false;
foreach ($domains as $domain) { foreach ($domains as $domain) {
@ -5191,7 +5182,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach($tags as $tag){ foreach($tags as $tag){
// delete tag // delete tag
$wasModified = true; $wasModified = true;
@ -5265,7 +5264,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', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view(); update_sogo_static_view();
} }
} }

View File

@ -51,6 +51,7 @@ function pushover($_action, $_data = null) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio']; $evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio'];
$only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio']; $only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio'];
$sound = (isset($_data['sound'])) ? $_data['sound'] : $is_now['sound'];
} }
else { else {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -101,7 +102,8 @@ function pushover($_action, $_data = null) {
$po_attributes = json_encode( $po_attributes = json_encode(
array( array(
'evaluate_x_prio' => strval(intval($evaluate_x_prio)), 'evaluate_x_prio' => strval(intval($evaluate_x_prio)),
'only_x_prio' => strval(intval($only_x_prio)) 'only_x_prio' => strval(intval($only_x_prio)),
'sound' => strval($sound)
) )
); );
$stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`) $stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`)

File diff suppressed because it is too large Load Diff

View File

@ -234,7 +234,7 @@ if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
// Try suggest match // Try suggest match
// e.g. suggest en-gb when only en-us is provided // e.g. suggest en-gb when only en-us is provided
if (!isset($_COOKIE['mailcow_locale'])) { if (!isset($_SESSION['mailcow_locale'])) {
foreach ($lang2pref as $lang => $q) { foreach ($lang2pref as $lang => $q) {
if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) { if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)]; $_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)];

View File

@ -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();
} }

View File

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

View File

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

View File

@ -4,20 +4,20 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-1.12.0/r-2.3.0/sl-1.4.0 * https://datatables.net/download/#bs5/dt-1.13.1/r-2.4.0/sl-1.5.0
* *
* Included libraries: * Included libraries:
* DataTables 1.12.0, Responsive 2.3.0, Select 1.4.0 * DataTables 1.13.1, Responsive 2.4.0, Select 1.5.0
*/ */
/*! DataTables 1.12.0 /*! DataTables 1.13.1
* ©2008-2022 SpryMedia Ltd - datatables.net/license * ©2008-2022 SpryMedia Ltd - datatables.net/license
*/ */
/** /**
* @summary DataTables * @summary DataTables
* @description Paginate, search and order HTML tables * @description Paginate, search and order HTML tables
* @version 1.12.0 * @version 1.13.1
* @author SpryMedia Ltd * @author SpryMedia Ltd
* @contact www.datatables.net * @contact www.datatables.net
* @copyright SpryMedia Ltd. * @copyright SpryMedia Ltd.
@ -1162,6 +1162,10 @@
$( rowOne[0] ).children('th, td').each( function (i, cell) { $( rowOne[0] ).children('th, td').each( function (i, cell) {
var col = oSettings.aoColumns[i]; var col = oSettings.aoColumns[i];
if (! col) {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
}
if ( col.mData === i ) { if ( col.mData === i ) {
var sort = a( cell, 'sort' ) || a( cell, 'order' ); var sort = a( cell, 'sort' ) || a( cell, 'order' );
var filter = a( cell, 'filter' ) || a( cell, 'search' ); var filter = a( cell, 'filter' ) || a( cell, 'search' );
@ -3166,6 +3170,11 @@
create = nTrIn ? false : true; create = nTrIn ? false : true;
nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
if (! nTd) {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
}
nTd._DT_CellIndex = { nTd._DT_CellIndex = {
row: iRow, row: iRow,
column: i column: i
@ -3316,10 +3325,16 @@
for ( i=0, ien=cells.length ; i<ien ; i++ ) { for ( i=0, ien=cells.length ; i<ien ; i++ ) {
column = columns[i]; column = columns[i];
column.nTf = cells[i].cell;
if ( column.sClass ) { if (column) {
$(column.nTf).addClass( column.sClass ); column.nTf = cells[i].cell;
if ( column.sClass ) {
$(column.nTf).addClass( column.sClass );
}
}
else {
_fnLog( oSettings, 0, 'Incorrect column count', 18 );
} }
} }
} }
@ -5079,6 +5094,10 @@
_fnDraw( settings ); _fnDraw( settings );
} }
} }
else {
// No change event - paging was called, but no change
_fnCallbackFire( settings, null, 'page-nc', [settings] );
}
return changed; return changed;
} }
@ -5348,6 +5367,7 @@
footerCopy = footer.clone().prependTo( table ); footerCopy = footer.clone().prependTo( table );
footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
footerSrcEls = footerCopy.find('tr'); footerSrcEls = footerCopy.find('tr');
footerCopy.find('[id]').removeAttr('id');
} }
// Clone the current header and footer elements and then place it into the inner table // Clone the current header and footer elements and then place it into the inner table
@ -5355,6 +5375,7 @@
headerTrgEls = header.find('tr'); // original header is in its own table headerTrgEls = header.find('tr'); // original header is in its own table
headerSrcEls = headerCopy.find('tr'); headerSrcEls = headerCopy.find('tr');
headerCopy.find('th, td').removeAttr('tabindex'); headerCopy.find('th, td').removeAttr('tabindex');
headerCopy.find('[id]').removeAttr('id');
/* /*
@ -8332,8 +8353,12 @@
$(document).on('plugin-init.dt', function (e, context) { $(document).on('plugin-init.dt', function (e, context) {
var api = new _Api( context ); var api = new _Api( context );
const namespace = 'on-plugin-init';
const stateSaveParamsEvent = `stateSaveParams.${namespace}`;
const destroyEvent = `destroy.${namespace}`;
api.on( 'stateSaveParams', function ( e, settings, d ) { api.on( stateSaveParamsEvent, function ( e, settings, d ) {
// This could be more compact with the API, but it is a lot faster as a simple // This could be more compact with the API, but it is a lot faster as a simple
// internal loop // internal loop
var idFn = settings.rowIdFn; var idFn = settings.rowIdFn;
@ -8347,7 +8372,11 @@
} }
d.childRows = ids; d.childRows = ids;
}) });
api.on( destroyEvent, function () {
api.off(`${stateSaveParamsEvent} ${destroyEvent}`);
});
var loaded = api.state.loaded(); var loaded = api.state.loaded();
@ -9668,7 +9697,7 @@
* @type string * @type string
* @default Version number * @default Version number
*/ */
DataTable.version = "1.12.0"; DataTable.version = "1.13.1";
/** /**
* Private data store, containing all of the settings objects that are * Private data store, containing all of the settings objects that are
@ -14092,7 +14121,7 @@
* *
* @type string * @type string
*/ */
build:"bs5/dt-1.12.0/r-2.3.0/sl-1.4.0", build:"bs5/dt-1.13.1/r-2.4.0/sl-1.5.0",
/** /**
@ -14730,7 +14759,7 @@
var classes = settings.oClasses; var classes = settings.oClasses;
var lang = settings.oLanguage.oPaginate; var lang = settings.oLanguage.oPaginate;
var aria = settings.oLanguage.oAria.paginate || {}; var aria = settings.oLanguage.oAria.paginate || {};
var btnDisplay, btnClass, counter=0; var btnDisplay, btnClass;
var attach = function( container, buttons ) { var attach = function( container, buttons ) {
var i, ien, node, button, tabIndex; var i, ien, node, button, tabIndex;
@ -14805,7 +14834,7 @@
'class': classes.sPageButton+' '+btnClass, 'class': classes.sPageButton+' '+btnClass,
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'data-dt-idx': counter, 'data-dt-idx': button,
'tabindex': tabIndex, 'tabindex': tabIndex,
'id': idx === 0 && typeof button === 'string' ? 'id': idx === 0 && typeof button === 'string' ?
settings.sTableId +'_'+ button : settings.sTableId +'_'+ button :
@ -14817,8 +14846,6 @@
_fnBindAction( _fnBindAction(
node, {action: button}, clickHandler node, {action: button}, clickHandler
); );
counter++;
} }
} }
} }
@ -15163,7 +15190,7 @@
} }
} }
else if (window.luxon) { else if (window.luxon) {
dt = format dt = format && typeof d === 'string'
? window.luxon.DateTime.fromFormat( d, format ) ? window.luxon.DateTime.fromFormat( d, format )
: window.luxon.DateTime.fromISO( d ); : window.luxon.DateTime.fromISO( d );
@ -15303,14 +15330,26 @@
} }
// Based on locale, determine standard number formatting // Based on locale, determine standard number formatting
var __thousands = ''; // Fallback for legacy browsers is US English
var __decimal = ''; var __thousands = ',';
var __decimal = '.';
if (Intl) { if (Intl) {
var num = new Intl.NumberFormat().formatToParts(1000.1); try {
var num = new Intl.NumberFormat().formatToParts(100000.1);
__thousands = num[1].value;
__decimal = num[3].value; for (var i=0 ; i<num.length ; i++) {
if (num[i].type === 'group') {
__thousands = num[i].value;
}
else if (num[i].type === 'decimal') {
__decimal = num[i].value;
}
}
}
catch (e) {
// noop
}
} }
// Formatted date time detection - use by declaring the formats you are going to use // Formatted date time detection - use by declaring the formats you are going to use
@ -15379,6 +15418,10 @@
return d; return d;
} }
if (d === '' || d === null) {
return d;
}
var negative = d < 0 ? '-' : ''; var negative = d < 0 ? '-' : '';
var flo = parseFloat( d ); var flo = parseFloat( d );
@ -15569,7 +15612,7 @@
$.each( DataTable, function ( prop, val ) { $.each( DataTable, function ( prop, val ) {
$.fn.DataTable[ prop ] = val; $.fn.DataTable[ prop ] = val;
} ); } );
return DataTable; return DataTable;
})); }));
@ -15578,14 +15621,6 @@
* 2020 SpryMedia Ltd - datatables.net/license * 2020 SpryMedia Ltd - datatables.net/license
*/ */
/**
* DataTables integration for Bootstrap 4. This requires Bootstrap 5 and
* DataTables 1.10 or newer.
*
* This file sets the defaults and adds options to DataTables to style its
* controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
* for further information.
*/
(function( factory ){ (function( factory ){
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
@ -15597,16 +15632,22 @@
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
// Require DataTables, which attaches to jQuery, including $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
// jQuery if needed and have a $ property so we can access the require('jquery') :
// jQuery object that is used require('jquery')( root );
$ = require('datatables.net')(root, $).$;
} }
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@ -15619,11 +15660,21 @@
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
/**
* DataTables integration for Bootstrap 5. This requires Bootstrap 5 and
* DataTables 1.10 or newer.
*
* This file sets the defaults and adds options to DataTables to style its
* controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
* for further information.
*/
/* Set the defaults for DataTables initialisation */ /* Set the defaults for DataTables initialisation */
$.extend( true, DataTable.defaults, { $.extend( true, DataTable.defaults, {
dom: dom:
"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" + "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" + "<'row dt-row'<'col-sm-12'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>>",
renderer: 'bootstrap' renderer: 'bootstrap'
} ); } );
@ -15645,7 +15696,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
var classes = settings.oClasses; var classes = settings.oClasses;
var lang = settings.oLanguage.oPaginate; var lang = settings.oLanguage.oPaginate;
var aria = settings.oLanguage.oAria.paginate || {}; var aria = settings.oLanguage.oAria.paginate || {};
var btnDisplay, btnClass, counter=0; var btnDisplay, btnClass;
var attach = function( container, buttons ) { var attach = function( container, buttons ) {
var i, ien, node, button; var i, ien, node, button;
@ -15714,7 +15765,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
'href': '#', 'href': '#',
'aria-controls': settings.sTableId, 'aria-controls': settings.sTableId,
'aria-label': aria[ button ], 'aria-label': aria[ button ],
'data-dt-idx': counter, 'data-dt-idx': button,
'tabindex': settings.iTabIndex, 'tabindex': settings.iTabIndex,
'class': 'page-link' 'class': 'page-link'
} ) } )
@ -15725,13 +15776,12 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
settings.oApi._fnBindAction( settings.oApi._fnBindAction(
node, {action: button}, clickHandler node, {action: button}, clickHandler
); );
counter++;
} }
} }
} }
}; };
var hostEl = $(host);
// IE9 throws an 'unknown error' if document.activeElement is used // IE9 throws an 'unknown error' if document.activeElement is used
// inside an iframe or frame. // inside an iframe or frame.
var activeEl; var activeEl;
@ -15741,17 +15791,26 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
// elements, focus is lost on the select button which is bad for // elements, focus is lost on the select button which is bad for
// accessibility. So we want to restore focus once the draw has // accessibility. So we want to restore focus once the draw has
// completed // completed
activeEl = $(host).find(document.activeElement).data('dt-idx'); activeEl = hostEl.find(document.activeElement).data('dt-idx');
} }
catch (e) {} catch (e) {}
var paginationEl = hostEl.children('ul.pagination');
if (paginationEl.length) {
paginationEl.empty();
}
else {
paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination');
}
attach( attach(
$(host).empty().html('<ul class="pagination"/>').children('ul'), paginationEl,
buttons buttons
); );
if ( activeEl !== undefined ) { if ( activeEl !== undefined ) {
$(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); hostEl.find('[data-dt-idx='+activeEl+']').trigger('focus');
} }
}; };
@ -15760,14 +15819,54 @@ return DataTable;
})); }));
/*! Responsive 2.3.0 /*! Responsive 2.4.0
* 2014-2022 SpryMedia Ltd - datatables.net/license * 2014-2022 SpryMedia Ltd - datatables.net/license
*/ */
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery', 'datatables.net'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window;
}
if ( ! $ ) {
$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
}
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
/** /**
* @summary Responsive * @summary Responsive
* @description Responsive tables plug-in for DataTables * @description Responsive tables plug-in for DataTables
* @version 2.3.0 * @version 2.4.0
* @author SpryMedia Ltd (www.sprymedia.co.uk) * @author SpryMedia Ltd (www.sprymedia.co.uk)
* @contact www.sprymedia.co.uk/contact * @contact www.sprymedia.co.uk/contact
* @copyright SpryMedia Ltd. * @copyright SpryMedia Ltd.
@ -15781,35 +15880,6 @@ return DataTable;
* *
* For details please refer to: http://www.datatables.net * For details please refer to: http://www.datatables.net
*/ */
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery', 'datatables.net'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
root = window;
}
if ( ! $ || ! $.fn.dataTable ) {
$ = require('datatables.net')(root, $).$;
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
/** /**
* Responsive is a plug-in for the DataTables library that makes use of * Responsive is a plug-in for the DataTables library that makes use of
@ -15863,9 +15933,10 @@ var Responsive = function ( settings, opts ) {
} }
this.s = { this.s = {
dt: new DataTable.Api( settings ), childNodeStore: {},
columns: [], columns: [],
current: [] current: [],
dt: new DataTable.Api( settings )
}; };
// Check if responsive has already been initialised on this table // Check if responsive has already been initialised on this table
@ -16070,6 +16141,63 @@ $.extend( Responsive.prototype, {
* Private methods * Private methods
*/ */
/**
* Get and store nodes from a cell - use for node moving renderers
*
* @param {*} dt DT instance
* @param {*} row Row index
* @param {*} col Column index
*/
_childNodes: function( dt, row, col ) {
var name = row+'-'+col;
if ( this.s.childNodeStore[ name ] ) {
return this.s.childNodeStore[ name ];
}
// https://jsperf.com/childnodes-array-slice-vs-loop
var nodes = [];
var children = dt.cell( row, col ).node().childNodes;
for ( var i=0, ien=children.length ; i<ien ; i++ ) {
nodes.push( children[i] );
}
this.s.childNodeStore[ name ] = nodes;
return nodes;
},
/**
* Restore nodes from the cache to a table cell
*
* @param {*} dt DT instance
* @param {*} row Row index
* @param {*} col Column index
*/
_childNodesRestore: function( dt, row, col ) {
var name = row+'-'+col;
if ( ! this.s.childNodeStore[ name ] ) {
return;
}
var node = dt.cell( row, col ).node();
var store = this.s.childNodeStore[ name ];
var parent = store[0].parentNode;
var parentChildren = parent.childNodes;
var a = [];
for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
a.push( parentChildren[i] );
}
for ( var j=0, jen=a.length ; j<jen ; j++ ) {
node.appendChild( a[j] );
}
this.s.childNodeStore[ name ] = undefined;
},
/** /**
* Calculate the visibility for the columns in a table for a given * Calculate the visibility for the columns in a table for a given
* breakpoint. The result is pre-determined based on the class logic if * breakpoint. The result is pre-determined based on the class logic if
@ -16399,8 +16527,8 @@ $.extend( Responsive.prototype, {
: details.renderer; : details.renderer;
var res = details.display( row, update, function () { var res = details.display( row, update, function () {
return renderer( return renderer.call(
dt, row[0], that._detailsObj(row[0]) that, dt, row[0], that._detailsObj(row[0])
); );
} ); } );
@ -16622,9 +16750,11 @@ $.extend( Responsive.prototype, {
} }
} ); } );
if ( changed ) { // Always need to update the display, regardless of if it has changed or not, so nodes
this._redrawChildren(); // can be re-inserted for listHiddenNodes
this._redrawChildren();
if ( changed ) {
// Inform listeners of the change // Inform listeners of the change
$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] ); $(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
@ -16650,6 +16780,7 @@ $.extend( Responsive.prototype, {
{ {
var dt = this.s.dt; var dt = this.s.dt;
var columns = this.s.columns; var columns = this.s.columns;
var that = this;
// Are we allowed to do auto sizing? // Are we allowed to do auto sizing?
if ( ! this.c.auto ) { if ( ! this.c.auto ) {
@ -16663,11 +16794,11 @@ $.extend( Responsive.prototype, {
} }
// Need to restore all children. They will be reinstated by a re-render // Need to restore all children. They will be reinstated by a re-render
if ( ! $.isEmptyObject( _childNodeStore ) ) { if ( ! $.isEmptyObject( this.s.childNodeStore ) ) {
$.each( _childNodeStore, function ( key ) { $.each( this.s.childNodeStore, function ( key ) {
var idx = key.split('-'); var idx = key.split('-');
_childNodesRestore( dt, idx[0]*1, idx[1]*1 ); that._childNodesRestore( dt, idx[0]*1, idx[1]*1 );
} ); } );
} }
@ -16787,6 +16918,7 @@ $.extend( Responsive.prototype, {
*/ */
_setColumnVis: function ( col, showHide ) _setColumnVis: function ( col, showHide )
{ {
var that = this;
var dt = this.s.dt; var dt = this.s.dt;
var display = showHide ? '' : 'none'; // empty string will remove the attr var display = showHide ? '' : 'none'; // empty string will remove the attr
@ -16803,9 +16935,9 @@ $.extend( Responsive.prototype, {
.toggleClass('dtr-hidden', !showHide); .toggleClass('dtr-hidden', !showHide);
// If the are child nodes stored, we might need to reinsert them // If the are child nodes stored, we might need to reinsert them
if ( ! $.isEmptyObject( _childNodeStore ) ) { if ( ! $.isEmptyObject( this.s.childNodeStore ) ) {
dt.cells( null, col ).indexes().each( function (idx) { dt.cells( null, col ).indexes().each( function (idx) {
_childNodesRestore( dt, idx.row, idx.column ); that._childNodesRestore( dt, idx.row, idx.column );
} ); } );
} }
}, },
@ -16972,52 +17104,6 @@ Responsive.display = {
}; };
var _childNodeStore = {};
function _childNodes( dt, row, col ) {
var name = row+'-'+col;
if ( _childNodeStore[ name ] ) {
return _childNodeStore[ name ];
}
// https://jsperf.com/childnodes-array-slice-vs-loop
var nodes = [];
var children = dt.cell( row, col ).node().childNodes;
for ( var i=0, ien=children.length ; i<ien ; i++ ) {
nodes.push( children[i] );
}
_childNodeStore[ name ] = nodes;
return nodes;
}
function _childNodesRestore( dt, row, col ) {
var name = row+'-'+col;
if ( ! _childNodeStore[ name ] ) {
return;
}
var node = dt.cell( row, col ).node();
var store = _childNodeStore[ name ];
var parent = store[0].parentNode;
var parentChildren = parent.childNodes;
var a = [];
for ( var i=0, ien=parentChildren.length ; i<ien ; i++ ) {
a.push( parentChildren[i] );
}
for ( var j=0, jen=a.length ; j<jen ; j++ ) {
node.appendChild( a[j] );
}
_childNodeStore[ name ] = undefined;
}
/** /**
* Display methods - functions which define how the hidden data should be shown * Display methods - functions which define how the hidden data should be shown
* in the table. * in the table.
@ -17029,6 +17115,7 @@ function _childNodesRestore( dt, row, col ) {
Responsive.renderer = { Responsive.renderer = {
listHiddenNodes: function () { listHiddenNodes: function () {
return function ( api, rowIdx, columns ) { return function ( api, rowIdx, columns ) {
var that = this;
var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>'); var ul = $('<ul data-dtr-index="'+rowIdx+'" class="dtr-details"/>');
var found = false; var found = false;
@ -17045,7 +17132,7 @@ Responsive.renderer = {
'</span> '+ '</span> '+
'</li>' '</li>'
) )
.append( $('<span class="dtr-data"/>').append( _childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) ) .append( $('<span class="dtr-data"/>').append( that._childNodes( api, col.rowIndex, col.columnIndex ) ) )// api.cell( col.rowIndex, col.columnIndex ).node().childNodes ) )
.appendTo( ul ); .appendTo( ul );
found = true; found = true;
@ -17229,7 +17316,7 @@ Api.registerPlural( 'columns().responsiveHidden()', 'column().responsiveHidden()
* @name Responsive.version * @name Responsive.version
* @static * @static
*/ */
Responsive.version = '2.3.0'; Responsive.version = '2.4.0';
$.fn.dataTable.Responsive = Responsive; $.fn.dataTable.Responsive = Responsive;
@ -17256,12 +17343,12 @@ $(document).on( 'preInit.dt.dtr', function (e, settings, json) {
} ); } );
return Responsive; return DataTable;
})); }));
/*! Bootstrap 5 integration for DataTables' Responsive /*! Bootstrap 5 integration for DataTables' Responsive
* ©2021 SpryMedia Ltd - datatables.net/license * © SpryMedia Ltd - datatables.net/license
*/ */
(function( factory ){ (function( factory ){
@ -17275,17 +17362,26 @@ return Responsive;
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
$ = require('datatables.net-bs5')(root, $).$; $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
} }
if ( ! $.fn.dataTable.Responsive ) { if ( ! $.fn.dataTable ) {
require('datatables.net-bs5')(root, $);
}
if ( ! $.fn.dataTable ) {
require('datatables.net-responsive')(root, $); require('datatables.net-responsive')(root, $);
} }
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@ -17298,6 +17394,7 @@ return Responsive;
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
var _display = DataTable.Responsive.display; var _display = DataTable.Responsive.display;
var _original = _display.modal; var _original = _display.modal;
var _modal = $( var _modal = $(
@ -17359,33 +17456,14 @@ _display.modal = function ( options ) {
}; };
return DataTable.Responsive; return DataTable;
})); }));
/*! Select for DataTables 1.4.0 /*! Select for DataTables 1.5.0
* 2015-2021 SpryMedia Ltd - datatables.net/license/mit * 2015-2021 SpryMedia Ltd - datatables.net/license/mit
*/ */
/**
* @summary Select for DataTables
* @description A collection of API methods, events and buttons for DataTables
* that provides selection options of the items in a DataTable
* @version 1.4.0
* @file dataTables.select.js
* @author SpryMedia Ltd (www.sprymedia.co.uk)
* @contact datatables.net/forums
* @copyright Copyright 2015-2021 SpryMedia Ltd.
*
* This source file is free software, available under the following license:
* MIT license - http://datatables.net/license/mit
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*
* For details please refer to: http://www.datatables.net/extensions/select
*/
(function( factory ){ (function( factory ){
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
@ -17397,13 +17475,22 @@ return DataTable.Responsive;
// CommonJS // CommonJS
module.exports = function (root, $) { module.exports = function (root, $) {
if ( ! root ) { if ( ! root ) {
// CommonJS environments without a window global must pass a
// root. This will give an error otherwise
root = window; root = window;
} }
if ( ! $ || ! $.fn.dataTable ) { if ( ! $ ) {
$ = require('datatables.net')(root, $).$; $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
require('jquery') :
require('jquery')( root );
} }
if ( ! $.fn.dataTable ) {
require('datatables.net')(root, $);
}
return factory( $, root, root.document ); return factory( $, root, root.document );
}; };
} }
@ -17416,10 +17503,11 @@ return DataTable.Responsive;
var DataTable = $.fn.dataTable; var DataTable = $.fn.dataTable;
// Version information for debugger // Version information for debugger
DataTable.select = {}; DataTable.select = {};
DataTable.select.version = '1.4.0'; DataTable.select.version = '1.5.0';
DataTable.select.init = function ( dt ) { DataTable.select.init = function ( dt ) {
var ctx = dt.settings()[0]; var ctx = dt.settings()[0];
@ -18688,7 +18776,6 @@ $(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
} ); } );
return DataTable.select; return DataTable;
})); }));

View File

@ -1,353 +1,363 @@
$(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) {
msg = $('<span/>').text(message).text(); msg = $('<span/>').text(message).text();
if (type == 'danger' || type == 'info') { if (type == 'danger' || type == 'info') {
auto_hide = 0; auto_hide = 0;
$('#' + localStorage.getItem("add_modal")).modal('show'); $('#' + localStorage.getItem("add_modal")).modal('show');
localStorage.removeItem("add_modal"); localStorage.removeItem("add_modal");
} else { } else {
auto_hide = 5000; auto_hide = 5000;
} }
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}}); $.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
} }
$(".generate_password").click(function( event ) { $(".generate_password").click(async function( event ) {
event.preventDefault(); try {
$('[data-hibp]').trigger('input'); var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
if (typeof($(this).closest("form").data('pwgen-length')) == "number") { var password_policy = await password_policy.json();
var random_passwd = GPW.pronounceable($(this).closest("form").data('pwgen-length')) random_passwd_length = password_policy.length;
} } catch(err) {
else { var random_passwd_length = 8;
var random_passwd = GPW.pronounceable(8) }
}
$(this).closest("form").find('[data-pwgen-field]').attr('type', 'text'); event.preventDefault();
$(this).closest("form").find('[data-pwgen-field]').val(random_passwd); $('[data-hibp]').trigger('input');
}); if (typeof($(this).closest("form").data('pwgen-length')) == "number") {
function str_rot13(str) { var random_passwd = GPW.pronounceable($(this).closest("form").data('pwgen-length'))
return (str + '').replace(/[a-z]/gi, function(s){ }
return String.fromCharCode(s.charCodeAt(0) + (s.toLowerCase() < 'n' ? 13 : -13)) else {
}) var random_passwd = GPW.pronounceable(random_passwd_length)
} }
$(".rot-enc").html(function(){ $(this).closest("form").find('[data-pwgen-field]').attr('type', 'text');
return str_rot13($(this).html()) $(this).closest("form").find('[data-pwgen-field]').val(random_passwd);
}); });
// https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate function str_rot13(str) {
function shake(div,interval,distance,times) { return (str + '').replace(/[a-z]/gi, function(s){
if(typeof interval === 'undefined') { return String.fromCharCode(s.charCodeAt(0) + (s.toLowerCase() < 'n' ? 13 : -13))
interval = 100; })
} }
if(typeof distance === 'undefined') { $(".rot-enc").html(function(){
distance = 10; return str_rot13($(this).html())
} });
if(typeof times === 'undefined') { // https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate
times = 4; function shake(div,interval,distance,times) {
} if(typeof interval === 'undefined') {
$(div).css('position','relative'); interval = 100;
for(var iter=0;iter<(times+1);iter++){ }
$(div).animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval); if(typeof distance === 'undefined') {
} distance = 10;
$(div).animate({ left: 0},interval); }
} if(typeof times === 'undefined') {
times = 4;
// form cache }
$('[data-cached-form="true"]').formcache({key: $(this).data('id')}); $(div).css('position','relative');
for(var iter=0;iter<(times+1);iter++){
// tooltips $(div).animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval);
$(function () { }
$('[data-bs-toggle="tooltip"]').tooltip() $(div).animate({ left: 0},interval);
}); }
// remember last navigation pill // form cache
(function () { $('[data-cached-form="true"]').formcache({key: $(this).data('id')});
'use strict';
// remember desktop tabs // tooltips
$('button[data-bs-toggle="tab"]').on('click', function (e) { $(function () {
if ($(this).data('dont-remember') == 1) { $('[data-bs-toggle="tooltip"]').tooltip()
return true; });
}
var id = $(this).parents('[role="tablist"]').attr('id'); // remember last navigation pill
var key = 'lastTag'; (function () {
if (id) { 'use strict';
key += ':' + id; // remember desktop tabs
} $('button[data-bs-toggle="tab"]').on('click', function (e) {
if ($(this).data('dont-remember') == 1) {
var tab_id = $(e.target).attr('data-bs-target').substring(1); return true;
localStorage.setItem(key, tab_id); }
}); var id = $(this).parents('[role="tablist"]').attr('id');
// remember mobile tabs var key = 'lastTag';
$('button[data-bs-target^="#collapse-tab-"]').on('click', function (e) { if (id) {
// only remember tab if its being opened key += ':' + id;
if ($(this).hasClass('collapsed')) return false; }
var tab_id = $(this).closest('div[role="tabpanel"]').attr('id');
var tab_id = $(e.target).attr('data-bs-target').substring(1);
if ($(this).data('dont-remember') == 1) { localStorage.setItem(key, tab_id);
return true; });
} // remember mobile tabs
var id = $(this).parents('[role="tablist"]').attr('id');; $('button[data-bs-target^="#collapse-tab-"]').on('click', function (e) {
var key = 'lastTag'; // only remember tab if its being opened
if (id) { if ($(this).hasClass('collapsed')) return false;
key += ':' + id; var tab_id = $(this).closest('div[role="tabpanel"]').attr('id');
}
if ($(this).data('dont-remember') == 1) {
localStorage.setItem(key, tab_id); return true;
}); }
// open last tab var id = $(this).parents('[role="tablist"]').attr('id');;
$('[role="tablist"]').each(function (idx, elem) { var key = 'lastTag';
var id = $(elem).attr('id'); if (id) {
var key = 'lastTag'; key += ':' + id;
if (id) { }
key += ':' + id;
} localStorage.setItem(key, tab_id);
var lastTab = localStorage.getItem(key); });
if (lastTab) { // open last tab
$('[data-bs-target="#' + lastTab + '"]').click(); $('[role="tablist"]').each(function (idx, elem) {
var tab = $('[id^="' + lastTab + '"]'); var id = $(elem).attr('id');
$(tab).find('.card-body.collapse').collapse('show'); var key = 'lastTag';
} if (id) {
}); key += ':' + id;
})(); }
var lastTab = localStorage.getItem(key);
// IE fix to hide scrollbars when table body is empty if (lastTab) {
$('tbody').filter(function (index) { $('[data-bs-target="#' + lastTab + '"]').click();
return $(this).children().length < 1; var tab = $('[id^="' + lastTab + '"]');
}).remove(); $(tab).find('.card-body.collapse').collapse('show');
}
// selectpicker });
$('select').selectpicker({ })();
'styleBase': 'btn btn-xs-lg',
'noneSelectedText': lang_footer.nothing_selected // IE fix to hide scrollbars when table body is empty
}); $('tbody').filter(function (index) {
return $(this).children().length < 1;
// haveibeenpwned and passwd policy }).remove();
$.ajax({
url: '/api/v1/get/passwordpolicy/html', // selectpicker
type: 'GET', $('select').selectpicker({
success: function(res) { 'styleBase': 'btn btn-xs-lg',
$(".hibp-out").after(res); 'noneSelectedText': lang_footer.nothing_selected
} });
});
$('[data-hibp]').after('<p class="small haveibeenpwned"><i class="bi bi-shield-fill-exclamation"></i> ' + lang_footer.hibp_check + '</p><span class="hibp-out"></span>'); // haveibeenpwned and passwd policy
$('[data-hibp]').on('input', function() { $.ajax({
out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out'); url: '/api/v1/get/passwordpolicy/html',
}); type: 'GET',
$('.haveibeenpwned:not(.task-running)').on('click', function() { success: function(res) {
var hibp_field = $(this) $(".hibp-out").after(res);
$(hibp_field).addClass('task-running'); }
var hibp_result = $(hibp_field).next('.hibp-out') });
var password_field = $(this).prev('[data-hibp]') $('[data-hibp]').after('<p class="small haveibeenpwned"><i class="bi bi-shield-fill-exclamation"></i> ' + lang_footer.hibp_check + '</p><span class="hibp-out"></span>');
if ($(password_field).val() == '') { $('[data-hibp]').on('input', function() {
shake(password_field); out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out');
} });
else { $('.haveibeenpwned:not(.task-running)').on('click', function() {
$(hibp_result).attr('class', 'hibp-out badge fs-5 bg-info'); var hibp_field = $(this)
$(hibp_result).text(lang_footer.loading); $(hibp_field).addClass('task-running');
var password_digest = $.sha1($(password_field).val()) var hibp_result = $(hibp_field).next('.hibp-out')
var digest_five = password_digest.substring(0, 5).toUpperCase(); var password_field = $(this).prev('[data-hibp]')
var queryURL = "https://api.pwnedpasswords.com/range/" + digest_five; if ($(password_field).val() == '') {
var compl_digest = password_digest.substring(5, 41).toUpperCase(); shake(password_field);
$.ajax({ }
url: queryURL, else {
type: 'GET', $(hibp_result).attr('class', 'hibp-out badge fs-5 bg-info');
success: function(res) { $(hibp_result).text(lang_footer.loading);
if (res.search(compl_digest) > -1){ var password_digest = $.sha1($(password_field).val())
$(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-danger'); var digest_five = password_digest.substring(0, 5).toUpperCase();
$(hibp_result).text(lang_footer.hibp_nok) var queryURL = "https://api.pwnedpasswords.com/range/" + digest_five;
} else { var compl_digest = password_digest.substring(5, 41).toUpperCase();
$(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-success'); $.ajax({
$(hibp_result).text(lang_footer.hibp_ok) url: queryURL,
} type: 'GET',
$(hibp_field).removeClass('task-running'); success: function(res) {
}, if (res.search(compl_digest) > -1){
error: function(xhr, status, error) { $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-danger');
$(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-warning'); $(hibp_result).text(lang_footer.hibp_nok)
$(hibp_result).text('API error: ' + xhr.responseText) } else {
$(hibp_field).removeClass('task-running'); $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-success');
} $(hibp_result).text(lang_footer.hibp_ok)
}); }
} $(hibp_field).removeClass('task-running');
}); },
error: function(xhr, status, error) {
// Disable disallowed inputs $(hibp_result).removeClass('badge fs-5 bg-info').addClass('badge fs-5 bg-warning');
$('[data-acl="0"]').each(function(event){ $(hibp_result).text('API error: ' + xhr.responseText)
if ($(this).is("a")) { $(hibp_field).removeClass('task-running');
$(this).removeAttr("data-bs-toggle"); }
$(this).removeAttr("data-bs-target"); });
$(this).removeAttr("data-action"); }
$(this).click(function(event) { });
event.preventDefault();
}); // Disable disallowed inputs
} $('[data-acl="0"]').each(function(event){
if ($(this).is("select")) { if ($(this).is("a")) {
$(this).selectpicker('destroy'); $(this).removeAttr("data-bs-toggle");
$(this).replaceWith(function() { $(this).removeAttr("data-bs-target");
return '<label class="control-label"><b>' + this.innerText + '</b></label>'; $(this).removeAttr("data-action");
}); $(this).click(function(event) {
} event.preventDefault();
if ($(this).hasClass('btn-group')) { });
$(this).find('a').each(function(){ }
$(this).removeClass('dropdown-toggle') if ($(this).is("select")) {
.removeAttr('data-bs-toggle') $(this).selectpicker('destroy');
.removeAttr('data-bs-target') $(this).replaceWith(function() {
.removeAttr('data-action') return '<label class="control-label"><b>' + this.innerText + '</b></label>';
.removeAttr('id') });
.attr("disabled", true); }
$(this).click(function(event) { if ($(this).hasClass('btn-group')) {
event.preventDefault(); $(this).find('a').each(function(){
return; $(this).removeClass('dropdown-toggle')
}); .removeAttr('data-bs-toggle')
}); .removeAttr('data-bs-target')
$(this).find('button').each(function() { .removeAttr('data-action')
$(this).attr("disabled", true); .removeAttr('id')
}); .attr("disabled", true);
} else if ($(this).hasClass('input-group')) { $(this).click(function(event) {
$(this).find('input').each(function() { event.preventDefault();
$(this).removeClass('dropdown-toggle') return;
.removeAttr('data-bs-toggle') });
.attr("disabled", true); });
$(this).click(function(event) { $(this).find('button').each(function() {
event.preventDefault(); $(this).attr("disabled", true);
}); });
}); } else if ($(this).hasClass('input-group')) {
$(this).find('button').each(function() { $(this).find('input').each(function() {
$(this).attr("disabled", true); $(this).removeClass('dropdown-toggle')
}); .removeAttr('data-bs-toggle')
} else if ($(this).hasClass('form-group')) { .attr("disabled", true);
$(this).find('input').each(function() { $(this).click(function(event) {
$(this).attr("disabled", true); event.preventDefault();
}); });
} else if ($(this).hasClass('btn')) { });
$(this).attr("disabled", true); $(this).find('button').each(function() {
} else if ($(this).attr('data-provide') == 'slider') { $(this).attr("disabled", true);
$(this).attr('disabled', true); });
} else if ($(this).is(':checkbox')) { } else if ($(this).hasClass('form-group')) {
$(this).attr("disabled", true); $(this).find('input').each(function() {
} $(this).attr("disabled", true);
$(this).data("toggle", "tooltip"); });
$(this).attr("title", lang_acl.prohibited); } else if ($(this).hasClass('btn')) {
$(this).tooltip(); $(this).attr("disabled", true);
}); } else if ($(this).attr('data-provide') == 'slider') {
$(this).attr('disabled', true);
// disable submit after submitting form (not API driven buttons) } else if ($(this).is(':checkbox')) {
$('form').submit(function() { $(this).attr("disabled", true);
if ($('form button[type="submit"]').data('submitted') == '1') { }
return false; $(this).data("toggle", "tooltip");
} else { $(this).attr("title", lang_acl.prohibited);
$(this).find('button[type="submit"]').first().text(lang_footer.loading); $(this).tooltip();
$('form button[type="submit"]').attr('data-submitted', '1'); });
function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
$(document).on("keydown", disableF5); // disable submit after submitting form (not API driven buttons)
} $('form').submit(function() {
}); if ($('form button[type="submit"]').data('submitted') == '1') {
// Textarea line numbers return false;
$(".textarea-code").numberedtextarea({allowTabChar: true}); } else {
// trigger container restart $(this).find('button[type="submit"]').first().text(lang_footer.loading);
$('#RestartContainer').on('show.bs.modal', function(e) { $('form button[type="submit"]').attr('data-submitted', '1');
var container = $(e.relatedTarget).data('container'); function disableF5(e) { if ((e.which || e.keyCode) == 116 || (e.which || e.keyCode) == 82) e.preventDefault(); };
$('#containerName').text(container); $(document).on("keydown", disableF5);
$('#triggerRestartContainer').click(function(){ }
$(this).prop("disabled",true); });
$(this).html('<div class="spinner-border text-white" role="status"><span class="visually-hidden">Loading...</span></div>'); // Textarea line numbers
$('#statusTriggerRestartContainer').html(lang_footer.restarting_container); $(".textarea-code").numberedtextarea({allowTabChar: true});
$.ajax({ // trigger container restart
method: 'get', $('#RestartContainer').on('show.bs.modal', function(e) {
url: '/inc/ajax/container_ctrl.php', var container = $(e.relatedTarget).data('container');
timeout: docker_timeout, $('#containerName').text(container);
data: { $('#triggerRestartContainer').click(function(){
'service': container, $(this).prop("disabled",true);
'action': 'restart' $(this).html('<div class="spinner-border text-white" role="status"><span class="visually-hidden">Loading...</span></div>');
} $('#statusTriggerRestartContainer').html(lang_footer.restarting_container);
}) $.ajax({
.always( function (data, status) { method: 'get',
$('#statusTriggerRestartContainer').append(data); url: '/inc/ajax/container_ctrl.php',
var htmlResponse = $.parseHTML(data) timeout: docker_timeout,
if ($(htmlResponse).find('span').hasClass('text-success')) { data: {
$('#triggerRestartContainer').html('<i class="bi bi-check-lg"></i> '); 'service': container,
setTimeout(function(){ 'action': 'restart'
$('#RestartContainer').modal('toggle'); }
window.location = window.location.href.split("#")[0]; })
}, 1200); .always( function (data, status) {
} else { $('#statusTriggerRestartContainer').append(data);
$('#triggerRestartContainer').html('<i class="bi bi-slash-lg"></i> '); var htmlResponse = $.parseHTML(data)
} if ($(htmlResponse).find('span').hasClass('text-success')) {
}) $('#triggerRestartContainer').html('<i class="bi bi-check-lg"></i> ');
}); setTimeout(function(){
}) $('#RestartContainer').modal('toggle');
window.location = window.location.href.split("#")[0];
// Jquery Datatables, enable responsive plugin }, 1200);
$.extend($.fn.dataTable.defaults, { } else {
responsive: true $('#triggerRestartContainer').html('<i class="bi bi-slash-lg"></i> ');
}); }
})
// tag boxes });
$('.tag-box .tag-add').click(function(){ })
addTag(this);
}); // Jquery Datatables, enable responsive plugin
$(".tag-box .tag-input").keydown(function (e) { $.extend($.fn.dataTable.defaults, {
if (e.which == 13){ responsive: true
e.preventDefault(); });
addTag(this); // disable default datatable click listener
} $(document).off('click', 'tbody>tr');
});
// tag boxes
// Dark Mode Loader $('.tag-box .tag-add').click(function(){
$('#dark-mode-toggle').click(toggleDarkMode); addTag(this);
if ($('#dark-mode-theme').length) { });
$('#dark-mode-toggle').prop('checked', true); $(".tag-box .tag-input").keydown(function (e) {
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png'); if (e.which == 13){
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png'); e.preventDefault();
} addTag(this);
function toggleDarkMode(){ }
if($('#dark-mode-theme').length){ });
$('#dark-mode-theme').remove();
$('#dark-mode-toggle').prop('checked', false); // Dark Mode Loader
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png'); $('#dark-mode-toggle').click(toggleDarkMode);
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png'); if ($('#dark-mode-theme').length) {
localStorage.setItem('darkmode', 'false'); $('#dark-mode-toggle').prop('checked', true);
}else{ if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
$('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">'); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
$('#dark-mode-toggle').prop('checked', true); }
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png'); function toggleDarkMode(){
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png'); if($('#dark-mode-theme').length){
localStorage.setItem('darkmode', 'true'); $('#dark-mode-theme').remove();
} $('#dark-mode-toggle').prop('checked', false);
} if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png');
}); if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
localStorage.setItem('theme', 'light');
}else{
// https://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery $('head').append('<link id="dark-mode-theme" rel="stylesheet" type="text/css" href="/css/themes/mailcow-darkmode.css">');
function escapeHtml(n){var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} $('#dark-mode-toggle').prop('checked', true);
function unescapeHtml(t){var n={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x2F;":"/","&#x60;":"`","&#x3D;":"="};return String(t).replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F|&#x60|&#x3D;/g,function(t){return n[t]})} if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
function addTag(tagAddElem, tag = null){ localStorage.setItem('theme', 'dark');
var tagboxElem = $(tagAddElem).parent(); }
var tagInputElem = $(tagboxElem).find(".tag-input")[0]; }
var tagValuesElem = $(tagboxElem).find(".tag-values")[0]; });
if (!tag)
tag = $(tagInputElem).val(); // https://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
if (!tag) return; function escapeHtml(n){var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
var value_tags = []; function unescapeHtml(t){var n={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#x2F;":"/","&#x60;":"`","&#x3D;":"="};return String(t).replace(/&amp;|&lt;|&gt;|&quot;|&#39;|&#x2F|&#x60|&#x3D;/g,function(t){return n[t]})}
try {
value_tags = JSON.parse($(tagValuesElem).val()); function addTag(tagAddElem, tag = null){
} catch {} var tagboxElem = $(tagAddElem).parent();
if (!Array.isArray(value_tags)) value_tags = []; var tagInputElem = $(tagboxElem).find(".tag-input")[0];
if (value_tags.includes(tag)) return; var tagValuesElem = $(tagboxElem).find(".tag-values")[0];
$('<span class="badge bg-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(tag) + '</span>').insertBefore('.tag-input').click(function(){ if (!tag)
var del_tag = unescapeHtml($(this).text()); tag = $(tagInputElem).val();
var del_tags = []; if (!tag) return;
try { var value_tags = [];
del_tags = JSON.parse($(tagValuesElem).val()); try {
} catch {} value_tags = JSON.parse($(tagValuesElem).val());
if (Array.isArray(del_tags)){ } catch {}
del_tags.splice(del_tags.indexOf(del_tag), 1); if (!Array.isArray(value_tags)) value_tags = [];
$(tagValuesElem).val(JSON.stringify(del_tags)); if (value_tags.includes(tag)) return;
}
$(this).remove(); $('<span class="badge bg-primary tag-badge btn-badge"><i class="bi bi-tag-fill"></i> ' + escapeHtml(tag) + '</span>').insertBefore('.tag-input').click(function(){
}); var del_tag = unescapeHtml($(this).text());
var del_tags = [];
value_tags.push(tag); try {
$(tagValuesElem).val(JSON.stringify(value_tags)); del_tags = JSON.parse($(tagValuesElem).val());
$(tagInputElem).val(''); } catch {}
} if (Array.isArray(del_tags)){
del_tags.splice(del_tags.indexOf(del_tag), 1);
$(tagValuesElem).val(JSON.stringify(del_tags));
}
$(this).remove();
});
value_tags.push(tag);
$(tagValuesElem).val(JSON.stringify(value_tags));
$(tagInputElem).val('');
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,210 +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({
processing: true, responsive: true,
serverSide: false, processing: true,
language: lang_datatables, serverSide: false,
ajax: { stateSave: true,
type: "GET", pageLength: pagination_size,
url: '/api/v1/get/policy_wl_domain/' + table_for_domain, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
dataSrc: function(data){ "tr" +
$.each(data, function (i, item) { "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
if (!validateEmail(item.object)) { language: lang_datatables,
item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />'; ajax: {
} type: "GET",
else { url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />'; dataSrc: function(data){
} $.each(data, function (i, item) {
}); if (!validateEmail(item.object)) {
item.chkbox = '<input type="checkbox" data-id="policy_wl_domain" name="multi_select" value="' + item.prefid + '" />';
return data; }
} else {
}, item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
columns: [ }
{ });
// placeholder, so checkbox will not block child row toggle
title: '', return data;
data: null, }
searchable: false, },
orderable: false, columns: [
defaultContent: '' {
}, // placeholder, so checkbox will not block child row toggle
{ title: '',
title: '', data: null,
data: 'chkbox', searchable: false,
searchable: false, orderable: false,
orderable: false, defaultContent: ''
defaultContent: '' },
}, {
{ title: '',
title: 'ID', data: 'chkbox',
data: 'prefid', searchable: false,
defaultContent: '' orderable: false,
}, defaultContent: ''
{ },
title: lang_user.spamfilter_table_rule, {
data: 'value', title: 'ID',
defaultContent: '' data: 'prefid',
}, defaultContent: ''
{ },
title: 'Scope', {
data: 'object', title: lang_user.spamfilter_table_rule,
defaultContent: '' data: 'value',
} defaultContent: ''
] },
}); {
} title: 'Scope',
function draw_bl_policy_domain_table() { data: 'object',
$('#bl_policy_domain_table').DataTable({ defaultContent: ''
processing: true, }
serverSide: false, ]
language: lang_datatables, });
ajax: { }
type: "GET", function draw_bl_policy_domain_table() {
url: '/api/v1/get/policy_bl_domain/' + table_for_domain, $('#bl_policy_domain_table').DataTable({
dataSrc: function(data){ responsive: true,
$.each(data, function (i, item) { processing: true,
if (!validateEmail(item.object)) { serverSide: false,
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />'; stateSave: true,
} pageLength: pagination_size,
else { dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />'; "tr" +
} "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
}); language: lang_datatables,
ajax: {
return data; type: "GET",
} url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
}, dataSrc: function(data){
columns: [ $.each(data, function (i, item) {
{ if (!validateEmail(item.object)) {
// placeholder, so checkbox will not block child row toggle item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
title: '', }
data: null, else {
searchable: false, item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
orderable: false, }
defaultContent: '' });
},
{ return data;
title: '', }
data: 'chkbox', },
searchable: false, columns: [
orderable: false, {
defaultContent: '' // placeholder, so checkbox will not block child row toggle
}, title: '',
{ data: null,
title: 'ID', searchable: false,
data: 'prefid', orderable: false,
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang_user.spamfilter_table_rule, title: '',
data: 'value', data: 'chkbox',
defaultContent: '' searchable: false,
}, orderable: false,
{ defaultContent: ''
title: 'Scope', },
data: 'object', {
defaultContent: '' title: 'ID',
} data: 'prefid',
] defaultContent: ''
}); },
} {
title: lang_user.spamfilter_table_rule,
data: 'value',
// detect element visibility changes defaultContent: ''
function onVisible(element, callback) { },
$(document).ready(function() { {
element_object = document.querySelector(element); title: 'Scope',
if (element_object === null) return; data: 'object',
defaultContent: ''
new IntersectionObserver((entries, observer) => { }
entries.forEach(entry => { ]
if(entry.intersectionRatio > 0) { });
callback(element_object); }
observer.disconnect();
}
}); // detect element visibility changes
}).observe(element_object); function onVisible(element, callback) {
}); $(document).ready(function() {
} element_object = document.querySelector(element);
// Draw Table if tab is active if (element_object === null) return;
onVisible("[id^=wl_policy_domain_table]", () => draw_wl_policy_domain_table());
onVisible("[id^=bl_policy_domain_table]", () => draw_bl_policy_domain_table()); new IntersectionObserver((entries, observer) => {
}); entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element_object);
observer.disconnect();
}
});
}).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());
});

View File

@ -1,5 +1,5 @@
$(document).ready(function() { $(document).ready(function() {
var darkmode = localStorage.getItem("darkmode"); var theme = localStorage.getItem("theme");
localStorage.clear(); localStorage.clear();
localStorage.setItem("darkmode", darkmode); localStorage.setItem("theme", theme);
}); });

File diff suppressed because it is too large Load Diff

View File

@ -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();
} }
} }
}); });
}); });

View File

@ -1,260 +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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
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() {
$('#quarantinetable').DataTable({ var table = $('#quarantinetable').DataTable({
processing: true, responsive: true,
serverSide: false, processing: true,
language: lang_datatables, serverSide: false,
ajax: { stateSave: true,
type: "GET", pageLength: pagination_size,
url: "/api/v1/get/quarantine/all", order: [[2, 'desc']],
dataSrc: function(data){ lengthMenu: [
$.each(data, function (i, item) { [10, 25, 50, 100, -1],
if (item.subject === null) { [10, 25, 50, 100, 'all']
item.subject = ''; ],
} else { pagingType: 'first_last_numbers',
item.subject = escapeHtml(item.subject); aColumns: [
} { sWidth: '8.25%' },
if (item.score === null) { { sClass: 'classDataTable' }
item.score = '-'; ],
} dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
if (item.virus_flag > 0) { "tr" +
item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>'; "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
} else { language: lang_datatables,
item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>'; initComplete: function(){
} hideTableExpandCollapseBtn('#quarantinetable');
if (item.action === "reject") { },
item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>'; ajax: {
} else if (item.action === "add header") { type: "GET",
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>'; url: "/api/v1/get/quarantine/all",
} else if (item.action === "rewrite subject") { dataSrc: function(data){
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>'; $.each(data, function (i, item) {
} if (item.subject === null) {
if(item.notified > 0) { item.subject = '';
item.notified = '&#10004;'; } else {
} else { item.subject = escapeHtml(item.subject);
item.notified = '&#10006;'; }
} if (item.score === null) {
if (acl_data.login_as === 1) { item.score = '-';
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>' + if (item.virus_flag > 0) {
'<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>' + item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
'</div>'; } else {
} item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
else { }
item.action = '<div class="btn-group">' + if (item.action === "reject") {
'<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>' + item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
'</div>'; } else if (item.action === "add header") {
} item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />'; } else if (item.action === "rewrite subject") {
}); item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
}
return data; if(item.notified > 0) {
} item.notified = '&#10004;';
}, } else {
columns: [ item.notified = '&#10006;';
{ }
// placeholder, so checkbox will not block child row toggle if (acl_data.login_as === 1) {
title: '', item.action = '<div class="btn-group">' +
data: null, '<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>' +
searchable: false, '<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>' +
orderable: false, '</div>';
defaultContent: '' }
}, else {
{ item.action = '<div class="btn-group">' +
title: '', '<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>' +
data: 'chkbox', '</div>';
searchable: false, }
orderable: false, item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
defaultContent: '' });
},
{ return data;
title: 'ID', }
data: 'id', },
defaultContent: '' columns: [
}, {
{ // placeholder, so checkbox will not block child row toggle
title: lang.qid, title: '',
data: 'qid', data: null,
defaultContent: '' searchable: false,
}, orderable: false,
{ defaultContent: ''
title: lang.sender, },
data: 'sender', {
defaultContent: '' title: '',
}, data: 'chkbox',
{ searchable: false,
title: lang.subj, orderable: false,
data: 'sender', defaultContent: ''
defaultContent: '' },
}, {
{ title: 'ID',
title: lang.rspamd_result, data: 'id',
data: 'rspamdaction', defaultContent: ''
defaultContent: '' },
}, {
{ title: lang.qid,
title: lang.rcpt, data: 'qid',
data: 'rcpt', defaultContent: ''
defaultContent: '' },
}, {
{ title: lang.sender,
title: lang.danger, data: 'sender',
data: 'virus', className: 'senders-mw220',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.spam_score, title: lang.subj,
data: 'score', data: 'subject',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.notified, title: lang.rspamd_result,
data: 'notified', data: 'rspamdaction',
defaultContent: '' defaultContent: ''
}, },
{ {
title: lang.received, title: lang.rcpt,
data: 'created', data: 'rcpt',
defaultContent: '', defaultContent: ''
render: function (data,type) { },
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"}); title: lang.danger,
} data: 'virus',
}, defaultContent: ''
{ },
title: lang.action, {
data: 'action', title: lang.spam_score,
className: 'text-md-end dt-sm-head-hidden dt-body-right', data: 'score',
defaultContent: '' defaultContent: ''
}, },
] {
}); title: lang.notified,
} data: 'notified',
defaultContent: ''
$('body').on('click', '.show_qid_info', function (e) { },
e.preventDefault(); {
var qitem = $(this).attr('data-item'); title: lang.received,
var qError = $("#qid_error"); data: 'created',
defaultContent: '',
$('#qidDetailModal').modal('show'); createdCell: function(td, cellData) {
qError.hide(); $(td).attr({
"data-order": cellData,
$.ajax({ "data-sort": cellData
url: '/inc/ajax/qitem_details.php', });
data: { id: qitem },
dataType: 'json', var date = new Date(cellData ? cellData * 1000 : 0);
success: function(data){ 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);
$('[data-id="qitems_single"]').each(function(index) { }
$(this).attr("data-item", qitem); },
}); {
title: lang.action,
$("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')"); data: 'action',
$("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')"); className: 'dt-text-right dt-sm-head-hidden',
$("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')"); defaultContent: ''
},
$('#qid_detail_subj').text(data.subject); ]
$('#qid_detail_hfrom').text(data.header_from); });
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').html(''); table.on('responsive-resize', function (e, datatable, columns){
$('#qid_detail_recipients').html(''); hideTableExpandCollapseBtn('#quarantinetable');
$('#qid_detail_symbols').html(''); });
$('#qid_detail_fuzzy').html(''); }
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) { $('body').on('click', '.show_qid_info', function (e) {
if (a.score === 0) return 1 e.preventDefault();
if (b.score === 0) return -1 var qitem = $(this).attr('data-item');
if (b.score < 0 && a.score < 0) { var qError = $("#qid_error");
return a.score - b.score
} $('#qidDetailModal').modal('show');
if (b.score > 0 && a.score > 0) { qError.hide();
return b.score - a.score
} $.ajax({
return b.score - a.score url: '/inc/ajax/qitem_details.php',
}) data: { id: qitem },
$.each(data.symbols, function (index, value) { dataType: 'json',
var highlightClass = '' success: function(data){
if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive' $('[data-id="qitems_single"]').each(function(index) {
else highlightClass = 'neutral' $(this).attr("data-item", qitem);
$('#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() $("#quick_download_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&eml', '_blank')");
} $("#quick_release_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_release', '_blank')");
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { $("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')");
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>'); $('#qid_detail_subj').text(data.subject);
}); $('#qid_detail_hfrom').text(data.header_from);
} else { $('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_fuzzy').append('-'); $('#qid_detail_score').html('');
} $('#qid_detail_recipients').html('');
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { $('#qid_detail_symbols').html('');
if (data.action == "add header") { $('#qid_detail_fuzzy').html('');
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>'); if (typeof data.symbols !== 'undefined') {
} else if (data.action == "reject") { data.symbols.sort(function (a, b) {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>'); if (a.score === 0) return 1;
} else if (data.action == "rewrite subject") { if (b.score === 0) return -1;
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.rewrite_subject + '</span>'); if (b.score < 0 && a.score < 0) {
} return a.score - b.score;
} }
if (typeof data.recipients !== 'undefined') { if (b.score > 0 && a.score > 0) {
$.each(data.recipients, function(index, value) { return b.score - a.score;
var elem = $('<span class="mail-address-item"></span>'); }
elem.text(value.address + ' (' + value.type.toUpperCase() + ')'); return b.score - a.score;
$('#qid_detail_recipients').append(elem); })
}); $.each(data.symbols, function (index, value) {
} var highlightClass = '';
$('#qid_detail_text').text(data.text_plain); if (value.score > 0) highlightClass = 'negative';
$('#qid_detail_text_from_html').text(data.text_html); else if (value.score < 0) highlightClass = 'positive';
var qAtts = $("#qid_detail_atts"); else highlightClass = 'neutral';
if (typeof data.attachments !== 'undefined') { $('#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>');
qAtts.text(''); });
$.each(data.attachments, function(index, value) { $('[data-bs-toggle="tooltip"]').tooltip();
qAtts.append( }
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' + if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>' $.each(data.fuzzy_hashes, function (index, value) {
); $('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
}); });
} } else {
else { $('#qid_detail_fuzzy').append('-');
qAtts.text('-'); }
} if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
}, if (data.action == "add header") {
error: function(data){ $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
if (typeof data.error !== 'undefined') { } else if (data.action == "reject") {
$('#qid_detail_subj').text('-'); $('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-danger"><b>' + data.score + '</b> - ' + lang.rejected + '</span>');
$('#qid_detail_hfrom').text('-'); } else if (data.action == "rewrite subject") {
$('#qid_detail_efrom').text('-'); $('#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').html('-'); }
$('#qid_detail_recipients').html('-'); }
$('#qid_detail_symbols').html('-'); if (typeof data.recipients !== 'undefined') {
$('#qid_detail_fuzzy').html('-'); $.each(data.recipients, function(index, value) {
$('#qid_detail_text').text('-'); var elem = $('<span class="mail-address-item"></span>');
$('#qid_detail_text_from_html').text('-'); elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
qError.text("Error loading quarantine item"); $('#qid_detail_recipients').append(elem);
qError.show(); });
} }
} $('#qid_detail_text').text(data.text_plain);
}); $('#qid_detail_text_from_html').text(data.text_html);
}); var qAtts = $("#qid_detail_atts");
if (typeof data.attachments !== 'undefined') {
$('body').on('click', 'span.footable-toggle', function () { qAtts.text('');
event.stopPropagation(); $.each(data.attachments, function(index, value) {
}) qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
// Initial table drawings ' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
draw_quarantine_table(); );
}); });
}
else {
qAtts.text('-');
}
},
error: function(data){
if (typeof data.error !== 'undefined') {
$('#qid_detail_subj').text('-');
$('#qid_detail_hfrom').text('-');
$('#qid_detail_efrom').text('-');
$('#qid_detail_score').html('-');
$('#qid_detail_recipients').html('-');
$('#qid_detail_symbols').html('-');
$('#qid_detail_fuzzy').html('-');
$('#qid_detail_text').text('-');
$('#qid_detail_text_from_html').text('-');
qError.text("Error loading quarantine item");
qError.show();
}
}
});
});
$('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();
}
});

View File

@ -1,123 +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() {
processing: true, // just recalc width if instance already exists
serverSide: false, if ($.fn.DataTable.isDataTable('#queuetable') ) {
language: lang_datatables, $('#queuetable').DataTable().columns.adjust().responsive.recalc();
ajax: { return;
type: "GET", }
url: "/api/v1/get/mailq/all",
dataSrc: function(data){ $('#queuetable').DataTable({
$.each(data, function (i, item) { responsive: true,
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />'; processing: true,
rcpts = $.map(item.recipients, function(i) { serverSide: false,
return escapeHtml(i); stateSave: true,
}); pageLength: pagination_size,
item.recipients = rcpts.join('<hr style="margin:1px!important">'); dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
item.action = '<div class="btn-group">' + "tr" +
'<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>' + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: "/api/v1/get/mailq/all",
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

View File

@ -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;
@ -561,6 +573,15 @@ if (isset($_GET['query'])) {
echo '{}'; echo '{}';
} }
break; break;
default:
$password_complexity_rules = password_complexity('get');
if ($password_complexity_rules !== false) {
process_get_return($password_complexity_rules);
}
else {
echo '{}';
}
break;
} }
break; break;
@ -1544,14 +1565,15 @@ if (isset($_GET['query'])) {
} }
else if ($extra == "ip") { else if ($extra == "ip") {
// get public ips // get public ips
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0); curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
curl_setopt($curl, CURLOPT_URL, 'http://ipv4.mailcow.email');
$ipv4 = curl_exec($curl); $ipv4 = curl_exec($curl);
curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email'); curl_setopt($curl, CURLOPT_URL, 'http://ipv6.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$ipv6 = curl_exec($curl); $ipv6 = curl_exec($curl);
$ips = array( $ips = array(
"ipv4" => $ipv4, "ipv4" => $ipv4,
@ -1913,6 +1935,9 @@ if (isset($_GET['query'])) {
case "ui_texts": case "ui_texts":
process_edit_return(customize('edit', 'ui_texts', $attr)); process_edit_return(customize('edit', 'ui_texts', $attr));
break; break;
case "ip_check":
process_edit_return(customize('edit', 'ip_check', $attr));
break;
case "self": case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") { if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr)); process_edit_return(domain_admin('edit', $attr));

View File

@ -393,16 +393,7 @@
"toggle_all": "Marcar tots" "toggle_all": "Marcar tots"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "queue_manager": "Queue Manager"
"flush_queue": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_command_success": "Queue command completed successfully",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue Manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold"
}, },
"start": { "start": {
"help": "Mostrar/Ocultar panell d'ajuda", "help": "Mostrar/Ocultar panell d'ajuda",

View File

@ -650,7 +650,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",
@ -889,15 +889,7 @@
"type": "Typ" "type": "Typ"
}, },
"queue": { "queue": {
"delete_queue": "Smazat vše", "queue_manager": "Správce fronty"
"flush_queue": "Vyprázdnit frontu (opětovně doručit)",
"queue_ays": "Potvrďte prosím, že chcete odstranit všechny položky z aktuální fronty.",
"queue_deliver_mail": "Doručit",
"queue_hold_mail": "Zadržet",
"queue_manager": "Správce fronty",
"queue_show_message": "Zobrazit zprávu",
"queue_unban": "odblokovat",
"queue_unhold_mail": "Propustit"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Vypnuto", "disabled": "Vypnuto",

View File

@ -1,6 +1,6 @@
{ {
"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",
@ -22,9 +22,9 @@
"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"
}, },
"add": { "add": {
@ -33,7 +33,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>",
@ -586,7 +586,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",
@ -640,7 +640,7 @@
"deactivate": "Deaktiver", "deactivate": "Deaktiver",
"description": "Beskrivelse", "description": "Beskrivelse",
"disable_login": "Tillad ikke login (indgående mail accepteres stadig)", "disable_login": "Tillad ikke login (indgående mail accepteres stadig)",
"disable_x": "Deaktiver", "disable_x": "Deaktiver",
"dkim_domains_selector": "Vælger", "dkim_domains_selector": "Vælger",
"dkim_key_length": "DKIM nøgle længde (bits)", "dkim_key_length": "DKIM nøgle længde (bits)",
"domain": "Domæne", "domain": "Domæne",
@ -656,7 +656,7 @@
"filter_table": "Filtertabel", "filter_table": "Filtertabel",
"filters": "Filtre", "filters": "Filtre",
"fname": "Fulde navn", "fname": "Fulde navn",
"force_pw_update": "Tving adgangskodeopdatering til næste login", "force_pw_update": "Tving adgangskodeopdatering til næste login",
"gal": "Global adresseliste", "gal": "Global adresseliste",
"hourly": "Hver time", "hourly": "Hver time",
"in_use": "I brug (%)", "in_use": "I brug (%)",
@ -803,15 +803,7 @@
"toggle_all": "Skift alt" "toggle_all": "Skift alt"
}, },
"queue": { "queue": {
"delete_queue": "Slet alt", "queue_manager": "Køadministrator"
"flush_queue": "Tøm kø",
"queue_ays": "Bekræft venligst, at du vil slette alle emner fra den aktuelle kø.",
"queue_deliver_mail": "Aflevere",
"queue_hold_mail": "Hold",
"queue_manager": "Køadministrator",
"queue_unban": "kø ikke udeluk",
"queue_unhold_mail": "Unhold",
"queue_show_message": "Vis besked"
}, },
"start": { "start": {
"help": "Vis / skjul hjælpepanel", "help": "Vis / skjul hjælpepanel",

View File

@ -204,6 +204,9 @@
"include_exclude": "Ein- und Ausschlüsse", "include_exclude": "Ein- und Ausschlüsse",
"include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.", "include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.",
"includes": "Diese Empfänger einschließen", "includes": "Diese Empfänger einschließen",
"ip_check": "IP Check",
"ip_check_disabled": "IP check ist deaktiviert. Unter dem angegebenen Pfad kann es aktiviert werden<br> <strong>System > Konfiguration > Einstellungen > UI-Anpassung</strong>",
"ip_check_opt_in": "Opt-In für die Nutzung der Drittanbieter-Dienste <strong>ipv4.mailcow.email</strong> und <strong>ipv6.mailcow.email</strong> zur Auflösung externer IP-Adressen.",
"is_mx_based": "MX-basiert", "is_mx_based": "MX-basiert",
"last_applied": "Zuletzt angewendet", "last_applied": "Zuletzt angewendet",
"license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow-GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.", "license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow-GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.",
@ -228,6 +231,7 @@
"oauth2_renew_secret": "Neues Client Secret generieren", "oauth2_renew_secret": "Neues Client Secret generieren",
"oauth2_revoke_tokens": "Alle Client Tokens entfernen", "oauth2_revoke_tokens": "Alle Client Tokens entfernen",
"optional": "Optional", "optional": "Optional",
"options": "Einstellungen",
"password": "Passwort", "password": "Passwort",
"password_length": "Passwortlänge", "password_length": "Passwortlänge",
"password_policy": "Passwortrichtlinie", "password_policy": "Passwortrichtlinie",
@ -256,8 +260,8 @@
"quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>", "quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>",
"quota_notification_sender": "Benachrichtigungs-E-Mail Absender", "quota_notification_sender": "Benachrichtigungs-E-Mail Absender",
"quota_notification_subject": "Benachrichtigungs-E-Mail Betreff", "quota_notification_subject": "Benachrichtigungs-E-Mail Betreff",
"quota_notifications": "Quota Benachrichtigungen", "quota_notifications": "Quota-Benachrichtigungen",
"quota_notifications_info": "Quota Benachrichtigungen werden an Mailboxen versendet, die 80 respektive 95 Prozent der zur Verfügung stehenden Quota überschreiten.", "quota_notifications_info": "Quota-Benachrichtigungen werden an Mailboxen versendet, die 80 respektive 95 Prozent der zur Verfügung stehenden Quota überschreiten.",
"quota_notifications_vars": "{{percent}} entspricht der aktuellen Quota in Prozent<br>{{username}} entspricht dem Mailbox-Namen", "quota_notifications_vars": "{{percent}} entspricht der aktuellen Quota in Prozent<br>{{username}} entspricht dem Mailbox-Namen",
"r_active": "Aktive Restriktionen", "r_active": "Aktive Restriktionen",
"r_inactive": "Inaktive Restriktionen", "r_inactive": "Inaktive Restriktionen",
@ -335,7 +339,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",
@ -362,6 +367,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",
"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",
@ -449,49 +455,57 @@
"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",
"infoEmpty": "0 bis 0 von 0 Einträgen", "infoEmpty": "0 bis 0 von 0 Einträgen",
"infoFiltered": "(gefiltert von _MAX_ Einträgen)", "infoFiltered": "(gefiltert von _MAX_ Einträgen)",
"infoPostFix": "", "infoPostFix": "",
"thousands": ".", "thousands": ".",
"lengthMenu": "_MENU_ Einträge anzeigen", "lengthMenu": "_MENU_ Einträge anzeigen",
"loadingRecords": "Wird geladen...", "loadingRecords": "Wird geladen...",
"processing": "Bitte warten...", "processing": "Bitte warten...",
"search": "Suchen", "search": "Suchen",
"zeroRecords": "Keine Einträge vorhanden.", "zeroRecords": "Keine Einträge vorhanden.",
"paginate": { "paginate": {
"first": "Erste", "first": "Erste",
"previous": "Zurück", "previous": "Zurück",
"next": "Nächste", "next": "Nächste",
"last": "Letzte" "last": "Letzte"
}, },
"aria": { "aria": {
"sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren", "sortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
"sortDescending": ": aktivieren, um Spalte absteigend zu sortieren" "sortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
} }
}, },
"debug": { "debug": {
"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",
"container_disabled": "Container gestoppt oder deaktiviert",
"container_stopped": "Angehalten", "container_stopped": "Angehalten",
"cores": "Kerne", "cores": "Kerne",
"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",
"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",
@ -504,6 +518,7 @@
"online_users": "Benutzer online", "online_users": "Benutzer online",
"restart_container": "Neustart", "restart_container": "Neustart",
"service": "Dienst", "service": "Dienst",
"show_ip": "Zeige öffentliche IP",
"size": "Größe", "size": "Größe",
"solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.", "solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.",
"solr_status": "Solr Status", "solr_status": "Solr Status",
@ -643,7 +658,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",
@ -684,7 +700,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",
@ -693,7 +710,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",
@ -714,6 +731,7 @@
"add_filter": "Filter erstellen", "add_filter": "Filter erstellen",
"add_mailbox": "Mailbox hinzufügen", "add_mailbox": "Mailbox hinzufügen",
"add_recipient_map_entry": "Empfängerumschreibung hinzufügen", "add_recipient_map_entry": "Empfängerumschreibung hinzufügen",
"add_template": "Vorlage hinzufügen",
"add_resource": "Ressource hinzufügen", "add_resource": "Ressource hinzufügen",
"add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen", "add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen",
"address_rewriting": "Adressumschreibung", "address_rewriting": "Adressumschreibung",
@ -755,6 +773,7 @@
"domain": "Domain", "domain": "Domain",
"domain_admins": "Domain-Administratoren", "domain_admins": "Domain-Administratoren",
"domain_aliases": "Domain-Aliasse", "domain_aliases": "Domain-Aliasse",
"domain_templates": "Domainweite Vorlagen",
"domain_quota": "Gesamtspeicher", "domain_quota": "Gesamtspeicher",
"domain_quota_total": "Domain-Speicherplatz gesamt", "domain_quota_total": "Domain-Speicherplatz gesamt",
"domains": "Domains", "domains": "Domains",
@ -781,6 +800,7 @@
"mailbox_defaults": "Standardeinstellungen", "mailbox_defaults": "Standardeinstellungen",
"mailbox_defaults_info": "Steuert die Standardeinstellungen für neue Mailboxen.", "mailbox_defaults_info": "Steuert die Standardeinstellungen für neue Mailboxen.",
"mailbox_defquota": "Standard-Quota", "mailbox_defquota": "Standard-Quota",
"mailbox_templates": "Mailboxweite Vorlagen",
"mailbox_quota": "Max. Größe einer Mailbox", "mailbox_quota": "Max. Größe einer Mailbox",
"mailboxes": "Mailboxen", "mailboxes": "Mailboxen",
"max_aliases": "Max. mögliche Aliasse", "max_aliases": "Max. mögliche Aliasse",
@ -810,6 +830,7 @@
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.", "recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_maps": "Empfängerumschreibungen", "recipient_maps": "Empfängerumschreibungen",
"relay_all": "Alle Empfänger-Adressen relayen", "relay_all": "Alle Empfänger-Adressen relayen",
"relay_unknown": "Unbekannte Mailboxen relayen",
"remove": "Entfernen", "remove": "Entfernen",
"resources": "Ressourcen", "resources": "Ressourcen",
"running": "In Ausführung", "running": "In Ausführung",
@ -836,6 +857,8 @@
"table_size_show_n": "Zeige %s Einträge", "table_size_show_n": "Zeige %s Einträge",
"target_address": "Ziel-Adresse", "target_address": "Ziel-Adresse",
"target_domain": "Ziel-Domain", "target_domain": "Ziel-Domain",
"templates": "Vorlagen",
"template": "Vorlage",
"tls_enforce_in": "TLS eingehend erzwingen", "tls_enforce_in": "TLS eingehend erzwingen",
"tls_enforce_out": "TLS ausgehend erzwingen", "tls_enforce_out": "TLS ausgehend erzwingen",
"tls_map_dest": "Ziel", "tls_map_dest": "Ziel",
@ -932,16 +955,20 @@
"type": "Typ" "type": "Typ"
}, },
"queue": { "queue": {
"delete_queue": "Queue löschen", "delete": "Queue löschen",
"flush_queue": "Queue flushen", "flush": "Queue flushen",
"queue_ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?", "info": "In der Mailqueue befinden sich alle E-Mails, welche auf eine Zustellung warten. Sollte eine E-Mail eine längere Zeit innerhalb der Mailqueue stecken wird diese automatisch vom System gelöscht.<br>Die Fehlermeldung der jeweiligen Mail gibt aufschluss darüber, warum diese nicht zugestellt werden konnte",
"queue_command_success": "Queue-Aufgabe erfolgreich ausgeführt", "legend": "Funktionen der Mailqueue Aktionen:",
"queue_deliver_mail": "Ausliefern", "ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"queue_hold_mail": "Zurückhalten", "deliver_mail": "Ausliefern",
"deliver_mail_legend": "Versucht eine erneute Zustellung der ausgwählten Mails.",
"hold_mail": "Zurückhalten",
"hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)",
"queue_manager": "Queue Manager", "queue_manager": "Queue Manager",
"queue_show_message": "Nachricht anzeigen", "show_message": "Nachricht anzeigen",
"queue_unban": "queue unban", "unban": "queue unban",
"queue_unhold_mail": "Freigeben" "unhold_mail": "Freigeben",
"unhold_mail_legend": "Gibt ausgewählte Mails zur Auslieferung frei. (Erfordert vorheriges Zurückhalten)"
}, },
"start": { "start": {
"help": "Hilfe ein-/ausblenden", "help": "Hilfe ein-/ausblenden",
@ -989,6 +1016,7 @@
"forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt", "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
"global_filter_written": "Filterdatei wurde erfolgreich geschrieben", "global_filter_written": "Filterdatei wurde erfolgreich geschrieben",
"hash_deleted": "Hash wurde gelöscht", "hash_deleted": "Hash wurde gelöscht",
"ip_check_opt_in_modified": "IP Check wurde erfolgreich gespeichert",
"item_deleted": "Objekt %s wurde entfernt", "item_deleted": "Objekt %s wurde entfernt",
"item_released": "Objekt %s freigegeben", "item_released": "Objekt %s freigegeben",
"items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt", "items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt",
@ -1018,6 +1046,9 @@
"saved_settings": "Regel wurde gespeichert", "saved_settings": "Regel wurde gespeichert",
"settings_map_added": "Regel wurde gespeichert", "settings_map_added": "Regel wurde gespeichert",
"settings_map_removed": "Regeln wurden entfernt: %s", "settings_map_removed": "Regeln wurden entfernt: %s",
"template_added": "Template %s hinzugefügt",
"template_modified": "Änderungen am Template %s wurden gespeichert",
"template_removed": "Template ID %s wurde gelöscht",
"sogo_profile_reset": "ActiveSync-Gerät des Benutzers %s wurde zurückgesetzt", "sogo_profile_reset": "ActiveSync-Gerät des Benutzers %s wurde zurückgesetzt",
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht", "tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert", "tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
@ -1214,7 +1245,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",

View File

@ -206,6 +206,9 @@
"include_exclude": "Include/Exclude", "include_exclude": "Include/Exclude",
"include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed", "include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed",
"includes": "Include these recipients", "includes": "Include these recipients",
"ip_check": "IP Check",
"ip_check_disabled": "IP check is disabled. You can enable it under<br> <strong>System > Configuration > Options > Customize</strong>",
"ip_check_opt_in": "Opt-In for using third party service <strong>ipv4.mailcow.email</strong> and <strong>ipv6.mailcow.email</strong> to resolve external IP addresses.",
"is_mx_based": "MX based", "is_mx_based": "MX based",
"last_applied": "Last applied", "last_applied": "Last applied",
"license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>", "license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>",
@ -232,6 +235,7 @@
"oauth2_renew_secret": "Generate new client secret", "oauth2_renew_secret": "Generate new client secret",
"oauth2_revoke_tokens": "Revoke all client tokens", "oauth2_revoke_tokens": "Revoke all client tokens",
"optional": "optional", "optional": "optional",
"options": "Options",
"password": "Password", "password": "Password",
"password_length": "Password length", "password_length": "Password length",
"password_policy": "Password policy", "password_policy": "Password policy",
@ -263,6 +267,7 @@
"quota_notifications": "Quota notifications", "quota_notifications": "Quota notifications",
"quota_notifications_info": "Quota notifications are sent to users once when crossing 80% and once when crossing 95% usage.", "quota_notifications_info": "Quota notifications are sent to users once when crossing 80% and once when crossing 95% usage.",
"quota_notifications_vars": "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name", "quota_notifications_vars": "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name",
"queue_unban": "unban",
"r_active": "Active restrictions", "r_active": "Active restrictions",
"r_inactive": "Inactive restrictions", "r_inactive": "Inactive restrictions",
"r_info": "Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway. <br>You can add new elements in <code>inc/vars.local.inc.php</code> to be able to toggle them.", "r_info": "Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway. <br>You can add new elements in <code>inc/vars.local.inc.php</code> to be able to toggle them.",
@ -362,6 +367,7 @@
"domain_not_empty": "Cannot remove non-empty domain %s", "domain_not_empty": "Cannot remove non-empty domain %s",
"domain_not_found": "Domain %s not found", "domain_not_found": "Domain %s not found",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB", "domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
"extended_sender_acl_denied": "missing ACL to set external sender addresses",
"extra_acl_invalid": "External sender address \"%s\" is invalid", "extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain", "extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s", "fido2_verification_failed": "FIDO2 verification failed: %s",
@ -452,6 +458,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",
@ -461,40 +470,42 @@
"yotp_verification_failed": "Yubico OTP verification failed: %s" "yotp_verification_failed": "Yubico OTP verification failed: %s"
}, },
"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",
"infoEmpty": "Showing 0 to 0 of 0 entries", "infoEmpty": "Showing 0 to 0 of 0 entries",
"infoFiltered": "(filtered from _MAX_ total entries)", "infoFiltered": "(filtered from _MAX_ total entries)",
"infoPostFix": "", "infoPostFix": "",
"thousands": ",", "thousands": ",",
"lengthMenu": "Show _MENU_ entries", "lengthMenu": "Show _MENU_ entries",
"loadingRecords": "Loading...", "loadingRecords": "Loading...",
"processing": "Please wait...", "processing": "Please wait...",
"search": "Search:", "search": "Search:",
"zeroRecords": "No matching records found", "zeroRecords": "No matching records found",
"paginate": { "paginate": {
"first": "First", "first": "First",
"last": "Last", "last": "Last",
"next": "Next", "next": "Next",
"previous": "Previous" "previous": "Previous"
}, },
"aria": { "aria": {
"sortAscending": ": activate to sort column ascending", "sortAscending": ": activate to sort column ascending",
"sortDescending": ": activate to sort column descending" "sortDescending": ": activate to sort column descending"
} }
}, },
"debug": { "debug": {
"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",
"container_disabled": "Container stopped or disabled",
"container_stopped": "Stopped", "container_stopped": "Stopped",
"cores": "Cores", "cores": "Cores",
"current_time": "System Time", "current_time": "System Time",
"disk_usage": "Disk usage", "disk_usage": "Disk usage",
"docs": "Docs", "docs": "Docs",
"error_show_ip": "Could not resolve the public IP addresses",
"external_logs": "External logs", "external_logs": "External logs",
"history_all_servers": "History (all servers)", "history_all_servers": "History (all servers)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
@ -507,6 +518,7 @@
"online_users": "Users online", "online_users": "Users online",
"restart_container": "Restart", "restart_container": "Restart",
"service": "Service", "service": "Service",
"show_ip": "Show public IP",
"size": "Size", "size": "Size",
"solr_dead": "Solr is starting, disabled or died.", "solr_dead": "Solr is starting, disabled or died.",
"solr_status": "Solr status", "solr_status": "Solr status",
@ -606,6 +618,7 @@
"pushover_sender_regex": "Consider the following sender regex", "pushover_sender_regex": "Consider the following sender regex",
"pushover_text": "Notification text", "pushover_text": "Notification text",
"pushover_title": "Notification title", "pushover_title": "Notification title",
"pushover_sound": "Sound",
"pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)", "pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)",
"pushover_verify": "Verify credentials", "pushover_verify": "Verify credentials",
"quota_mb": "Quota (MiB)", "quota_mb": "Quota (MiB)",
@ -697,7 +710,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",
@ -942,23 +955,27 @@
"type": "Type" "type": "Type"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "delete": "Delete all",
"flush_queue": "Flush queue", "flush": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.", "info": "The mail queue contains all e-mails that are waiting for delivery. If an email is stuck in the mail queue for a long time, it is automatically deleted by the system.<br>The error message of the respective mail gives information about why the mail could not be delivered.",
"queue_command_success": "Queue command completed successfully", "legend": "Mail queue actions functions:",
"queue_deliver_mail": "Deliver", "ays": "Please confirm you want to delete all items from the current queue.",
"queue_hold_mail": "Hold", "deliver_mail": "Deliver",
"deliver_mail_legend": "Attempts to redeliver selected mails.",
"hold_mail": "Hold",
"hold_mail_legend": "Holds the selected mails. (Prevents further delivery attempts)",
"queue_manager": "Queue Manager", "queue_manager": "Queue Manager",
"queue_show_message": "Show message", "show_message": "Show message",
"queue_unban": "queue unban", "unban": "queue unban",
"queue_unhold_mail": "Unhold" "unhold_mail": "Unhold",
"unhold_mail_legend": "Releases selected mails for delivery. (Requires prior hold)"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Disabled", "disabled": "Disabled",
"second": "msgs / second", "second": "msgs / second",
"minute": "msgs / minute", "minute": "msgs / minute",
"hour": "msgs / hour", "hour": "msgs / hour",
"day": "msgs / day" "day": "msgs / day"
}, },
"start": { "start": {
"help": "Show/Hide help panel", "help": "Show/Hide help panel",
@ -1006,6 +1023,7 @@
"forwarding_host_removed": "Forwarding host %s has been removed", "forwarding_host_removed": "Forwarding host %s has been removed",
"global_filter_written": "Filter was successfully written to file", "global_filter_written": "Filter was successfully written to file",
"hash_deleted": "Hash deleted", "hash_deleted": "Hash deleted",
"ip_check_opt_in_modified": "IP check was saved successfully",
"item_deleted": "Item %s successfully deleted", "item_deleted": "Item %s successfully deleted",
"item_released": "Item %s released", "item_released": "Item %s released",
"items_deleted": "Item %s successfully deleted", "items_deleted": "Item %s successfully deleted",
@ -1021,6 +1039,7 @@
"password_policy_saved": "Password policy was saved successfully", "password_policy_saved": "Password policy was saved successfully",
"pushover_settings_edited": "Pushover settings successfully set, please verify credentials.", "pushover_settings_edited": "Pushover settings successfully set, please verify credentials.",
"qlearn_spam": "Message ID %s was learned as spam and deleted", "qlearn_spam": "Message ID %s was learned as spam and deleted",
"queue_command_success": "Queue command completed successfully",
"recipient_map_entry_deleted": "Recipient map ID %s has been deleted", "recipient_map_entry_deleted": "Recipient map ID %s has been deleted",
"recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved", "recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved",
"relayhost_added": "Map entry %s has been added", "relayhost_added": "Map entry %s has been added",
@ -1037,6 +1056,7 @@
"sogo_profile_reset": "SOGo profile for user %s was reset", "sogo_profile_reset": "SOGo profile for user %s was reset",
"template_added": "Added template %s", "template_added": "Added template %s",
"template_modified": "Changes to template %s have been saved", "template_modified": "Changes to template %s have been saved",
"template_removed": "Template ID %s has been deleted",
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted", "tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved", "tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"ui_texts": "Saved changes to UI texts", "ui_texts": "Saved changes to UI texts",
@ -1160,6 +1180,7 @@
"pushover_sender_regex": "Match senders by the following regex", "pushover_sender_regex": "Match senders by the following regex",
"pushover_text": "Notification text", "pushover_text": "Notification text",
"pushover_title": "Notification title", "pushover_title": "Notification title",
"pushover_sound": "Sound",
"pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)", "pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)",
"pushover_verify": "Verify credentials", "pushover_verify": "Verify credentials",
"q_add_header": "Junk folder", "q_add_header": "Junk folder",

View File

@ -602,14 +602,7 @@
"toggle_all": "Seleccionar todos" "toggle_all": "Seleccionar todos"
}, },
"queue": { "queue": {
"delete_queue": "Eliminar todos", "queue_manager": "Administrador de cola"
"flush_queue": "Vaciar la cola",
"queue_ays": "Confirme que desea eliminar todos los elementos de la cola actual.",
"queue_deliver_mail": "Entregar",
"queue_hold_mail": "Retener",
"queue_manager": "Administrador de cola",
"queue_unban": "Encolar desbloqueo",
"queue_unhold_mail": "Liberar retención"
}, },
"start": { "start": {
"help": "Mostrar/Ocultar panel de ayuda", "help": "Mostrar/Ocultar panel de ayuda",

View File

@ -559,7 +559,7 @@
"daily": "Päivittäin", "daily": "Päivittäin",
"deactivate": "Deaktivoi", "deactivate": "Deaktivoi",
"description": "Kuvaus", "description": "Kuvaus",
"disable_x": "Poista käytöstä", "disable_x": "Poista käytöstä",
"dkim_domains_selector": "Valitsin", "dkim_domains_selector": "Valitsin",
"dkim_key_length": "DKIM avaimen pituus (bits)", "dkim_key_length": "DKIM avaimen pituus (bits)",
"domain": "Verkkotunnukset", "domain": "Verkkotunnukset",
@ -686,14 +686,7 @@
"toggle_all": "Valitse kaikki" "toggle_all": "Valitse kaikki"
}, },
"queue": { "queue": {
"delete_queue": "Poista kaikki", "queue_manager": "Jonon hallinta"
"flush_queue": "Tyhjennä jono",
"queue_ays": "Vahvista, että haluat poistaa kaikki nykyisen jonon kohteet.",
"queue_deliver_mail": "Toimittaa",
"queue_hold_mail": "Pidossa",
"queue_manager": "Jonon hallinta",
"queue_unban": "jono unban",
"queue_unhold_mail": "Poista pidosta"
}, },
"start": { "start": {
"help": "Näytä/Piilota help paneeli", "help": "Näytä/Piilota help paneeli",

View File

@ -26,7 +26,9 @@
"syncjobs": "Tâches de synchronisation", "syncjobs": "Tâches de synchronisation",
"tls_policy": "Police TLS", "tls_policy": "Police 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",
"mailbox_relayhost": "Changer le relais dune boîte de réception"
}, },
"add": { "add": {
"activate_filter_warn": "Tous les autres filtres seront désactivés, quand activé est coché.", "activate_filter_warn": "Tous les autres filtres seront désactivés, quand activé est coché.",
@ -103,7 +105,8 @@
"username": "Nom d'utilisateur", "username": "Nom d'utilisateur",
"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"
}, },
"admin": { "admin": {
"access": "Accès", "access": "Accès",
@ -316,7 +319,9 @@
"oauth2_add_client": "Ajouter un client OAuth2", "oauth2_add_client": "Ajouter un client OAuth2",
"password_policy": "Politique de mots de passe", "password_policy": "Politique de mots de passe",
"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_numbers": "Doit contenir au moins un chiffre"
}, },
"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",
@ -607,7 +612,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 quutilisateur de la boîte pour télécharger le profil de connexion Apple demandé.", "mobileconfig_info": "Veuillez vous connecter en tant quutilisateur de la boîte pour télécharger le profil de connexion Apple demandé.",
"other_logins": "Clé d'authentification", "other_logins": "Clé d'authentification",
@ -675,10 +680,10 @@
"enable_x": "Activer", "enable_x": "Activer",
"excludes": "Exclut", "excludes": "Exclut",
"filter_table": "Table de filtre", "filter_table": "Table de filtre",
"filters": "Filtres", "filters": "Filtres",
"fname": "Nom complet", "fname": "Nom complet",
"force_pw_update": "Forcer la mise à jour du mot de passe à la prochaine ouverture de session", "force_pw_update": "Forcer la mise à jour du mot de passe à la prochaine ouverture de session",
"gal": "Carnet d'Adresses Global (GAL)", "gal": "Carnet d'Adresses Global (GAL)",
"hourly": "Horaire", "hourly": "Horaire",
"in_use": "Utilisé (%)", "in_use": "Utilisé (%)",
"inactive": "Inactif", "inactive": "Inactif",
@ -827,15 +832,7 @@
"toggle_all": "Tout basculer" "toggle_all": "Tout basculer"
}, },
"queue": { "queue": {
"delete_queue": "Tout supprimer", "queue_manager": "Gestion de la file d'attente"
"flush_queue": "Vider la file d'attente",
"queue_ays": "Veuillez confirmer que vous voulez supprimer tous les éléments de la file dattente actuelle.",
"queue_deliver_mail": "Délivrer",
"queue_hold_mail": "Garder",
"queue_manager": "Gestion de la file d'attente",
"queue_unban": "file dattente unban",
"queue_unhold_mail": "Ne pas garder",
"queue_show_message": "Montrer message"
}, },
"start": { "start": {
"help": "Afficher/masquer le panneau daide", "help": "Afficher/masquer le panneau daide",

View File

@ -216,16 +216,7 @@
"toggle_all": "Összes átkapcsolása" "toggle_all": "Összes átkapcsolása"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "queue_manager": "Queue Manager"
"flush_queue": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_command_success": "Queue command completed successfully",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue Manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold"
}, },
"start": { "start": {
"help": "Súgó panel megjelenítése/elrejtése", "help": "Súgó panel megjelenítése/elrejtése",

View File

@ -43,7 +43,7 @@
"app_name": "Nome app", "app_name": "Nome app",
"app_password": "Aggiungi la password dell'app", "app_password": "Aggiungi la password dell'app",
"automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)", "automap": "Prova a mappare automaticamente le cartelle (\"Sent items\", \"Sent\" => \"Posta inviata\" ecc.)",
"backup_mx_options": "Relay options", "backup_mx_options": "Opzioni di inoltro",
"comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente", "comment_info": "Un commento privato non è visibile all'utente, mentre un commento pubblico viene mostrato come suggerimento quando si passa con il mouse nella panoramica di un utente",
"custom_params": "Parametri personalizzati", "custom_params": "Parametri personalizzati",
"custom_params_hint": "Corretto: --param=xy, errato: --param xy", "custom_params_hint": "Corretto: --param=xy, errato: --param xy",
@ -303,7 +303,7 @@
"spamfilter": "Filtri spam", "spamfilter": "Filtri spam",
"subject": "Oggetto", "subject": "Oggetto",
"success": "Successo", "success": "Successo",
"sys_mails": "System mails", "sys_mails": "Mail di sistema",
"text": "Testo", "text": "Testo",
"time": "Orario", "time": "Orario",
"title": "Titolo", "title": "Titolo",
@ -335,7 +335,8 @@
"api_read_write": "Accesso in lettura-scrittura", "api_read_write": "Accesso in lettura-scrittura",
"oauth2_apps": "App OAuth2", "oauth2_apps": "App OAuth2",
"oauth2_add_client": "Aggiungere il client OAuth2", "oauth2_add_client": "Aggiungere il client OAuth2",
"rsettings_preset_4": "Disattivare Rspamd per un dominio" "rsettings_preset_4": "Disattivare Rspamd per un dominio",
"options": "Opzioni"
}, },
"danger": { "danger": {
"access_denied": "Accesso negato o form di login non corretto", "access_denied": "Accesso negato o form di login non corretto",
@ -364,7 +365,7 @@
"extra_acl_invalid": "External sender address \"%s\" is invalid", "extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain", "extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain",
"fido2_verification_failed": "FIDO2 verification failed: %s", "fido2_verification_failed": "FIDO2 verification failed: %s",
"file_open_error": "File cannot be opened for writing", "file_open_error": "Il file non può essere aperto per la scrittura",
"filter_type": "Wrong filter type", "filter_type": "Wrong filter type",
"from_invalid": "Il mittente non può essere vuoto", "from_invalid": "Il mittente non può essere vuoto",
"global_filter_write_error": "Could not write filter file: %s", "global_filter_write_error": "Could not write filter file: %s",
@ -397,7 +398,7 @@
"mailbox_quota_exceeds_domain_quota": "Lo spazio massimo supera la spazio del dominio", "mailbox_quota_exceeds_domain_quota": "Lo spazio massimo supera la spazio del dominio",
"mailbox_quota_left_exceeded": "Non c'è abbastanza spazio libero (space left: %d MiB)", "mailbox_quota_left_exceeded": "Non c'è abbastanza spazio libero (space left: %d MiB)",
"mailboxes_in_use": "Lo spazio massimo della casella deve essere maggiore o uguale a %d", "mailboxes_in_use": "Lo spazio massimo della casella deve essere maggiore o uguale a %d",
"malformed_username": "Malformed username", "malformed_username": "Nome utente non valido",
"map_content_empty": "Map content cannot be empty", "map_content_empty": "Map content cannot be empty",
"max_alias_exceeded": "Numero massimo di alias superato", "max_alias_exceeded": "Numero massimo di alias superato",
"max_mailbox_exceeded": "Numero massimo di caselle superato (%d of %d)", "max_mailbox_exceeded": "Numero massimo di caselle superato (%d of %d)",
@ -429,18 +430,18 @@
"resource_invalid": "Il nome della risorsa non è valido", "resource_invalid": "Il nome della risorsa non è valido",
"rl_timeframe": "Rate limit time frame is incorrect", "rl_timeframe": "Rate limit time frame is incorrect",
"rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long", "rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long",
"script_empty": "Script cannot be empty", "script_empty": "Lo script non può essere vuoto",
"sender_acl_invalid": "Il valore di Sender ACL non è valido", "sender_acl_invalid": "Il valore di Sender ACL non è valido",
"set_acl_failed": "Failed to set ACL", "set_acl_failed": "Failed to set ACL",
"settings_map_invalid": "Settings map ID %s invalid", "settings_map_invalid": "Settings map ID %s invalid",
"sieve_error": "Sieve parser error: %s", "sieve_error": "Sieve parser error: %s",
"spam_learn_error": "Spam learn error: %s", "spam_learn_error": "Spam learn error: %s",
"subject_empty": "Subject must not be empty", "subject_empty": "L'oggetto non deve essere vuoto",
"target_domain_invalid": "Goto domain non è valido", "target_domain_invalid": "Goto domain non è valido",
"targetd_not_found": "Il target del dominio non è stato trovato", "targetd_not_found": "Il target del dominio non è stato trovato",
"targetd_relay_domain": "Target domain %s is a relay domain", "targetd_relay_domain": "Target domain %s is a relay domain",
"temp_error": "Temporary error", "temp_error": "Errore temporaneo",
"text_empty": "Text must not be empty", "text_empty": "Il testo non deve essere vuoto",
"tfa_token_invalid": "TFA token invalid", "tfa_token_invalid": "TFA token invalid",
"tls_policy_map_dest_invalid": "Policy destination is invalid", "tls_policy_map_dest_invalid": "Policy destination is invalid",
"tls_policy_map_entry_exists": "A TLS policy map entry \"%s\" exists", "tls_policy_map_entry_exists": "A TLS policy map entry \"%s\" exists",
@ -448,40 +449,54 @@
"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",
"unknown": "An unknown error occurred", "unknown": "Si è verificato un errore sconosciuto",
"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",
"username_invalid": "Username %s non può essere utilizzato", "username_invalid": "Il nome utente %s non può essere utilizzato",
"validity_missing": "Assegnare un periodo di validità", "validity_missing": "Assegnare un periodo di validità",
"value_missing": "Si prega di fornire tutti i valori", "value_missing": "Si prega di fornire tutti i valori",
"yotp_verification_failed": "Verifica OTP Yubico fallita: %s" "yotp_verification_failed": "Verifica OTP Yubico fallita: %s",
"demo_mode_enabled": "La modalità demo è abilitata",
"template_name_invalid": "Nome template non valido",
"template_exists": "Il template %s esiste già",
"template_id_invalid": "Il template con ID %s non è valido"
}, },
"debug": { "debug": {
"chart_this_server": "Grafico (questo server)", "chart_this_server": "Grafico (questo server)",
"containers_info": "Container information", "containers_info": "Informazioni sul container",
"disk_usage": "Uso del disco", "disk_usage": "Uso del disco",
"docs": "Docs", "docs": "Docs",
"external_logs": "External logs", "external_logs": "Log esterni",
"history_all_servers": "History (all servers)", "history_all_servers": "Cronologia (tutti i server)",
"in_memory_logs": "In-memory logs", "in_memory_logs": "In-memory logs",
"jvm_memory_solr": "JVM memory usage", "jvm_memory_solr": "JVM memory usage",
"last_modified": "Ultima modifica", "last_modified": "Ultima modifica",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>", "log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"login_time": "Time", "login_time": "Orario",
"logs": "Logs", "logs": "Logs",
"online_users": "Users online", "online_users": "Utenti online",
"restart_container": "Riavvio", "restart_container": "Riavvio",
"service": "Servizio", "service": "Servizio",
"size": "Size", "size": "Dimensione",
"solr_dead": "Solr is starting, disabled or died.", "solr_dead": "Solr sta partendo, è disabilitato o morto.",
"solr_status": "Stato Solr", "solr_status": "Stato Solr",
"started_at": "Started at", "started_at": "Iniziato alle",
"started_on": "Started on", "started_on": "Iniziato",
"static_logs": "Static logs", "static_logs": "Log statici",
"success": "Successo", "success": "Successo",
"system_containers": "System & Containers", "system_containers": "Sistema & Containers",
"uptime": "Tempo di attività", "uptime": "Tempo di attività",
"username": "Username" "username": "Nome utente",
"container_disabled": "Container arrestato o disattivato",
"update_available": "È disponibile un aggiornamento",
"container_running": "In esecuzione",
"container_stopped": "Arrestato",
"cores": "Cores",
"current_time": "Orario di sistema",
"memory": "Memoria",
"timezone": "Fuso orario",
"no_update_available": "Il sistema è aggiornato all'ultima versione",
"update_failed": "Impossibile verificare la presenza di un aggiornamento"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Valore letto dal record A/AAAA. Questo è supportato finché il record punta alla risorsa corretta.", "cname_from_a": "Valore letto dal record A/AAAA. Questo è supportato finché il record punta alla risorsa corretta.",
@ -514,7 +529,7 @@
"delete1": "Elimina dalla sorgente al termine", "delete1": "Elimina dalla sorgente al termine",
"delete2": "Delete messages on destination that are not on source", "delete2": "Delete messages on destination that are not on source",
"delete2duplicates": "Elimina duplicati nella destinazione", "delete2duplicates": "Elimina duplicati nella destinazione",
"delete_ays": "Please confirm the deletion process.", "delete_ays": "Si prega di confermare il processo di eliminazione.",
"description": "Descrizione", "description": "Descrizione",
"disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)", "disable_login": "Disabilita l'accesso (la posta in arrivo viene correttamente recapitata)",
"domain": "Modifica dominio", "domain": "Modifica dominio",
@ -527,17 +542,16 @@
"exclude": "Escludi oggetti (regex)", "exclude": "Escludi oggetti (regex)",
"extended_sender_acl": "External sender addresses", "extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.", "extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"force_pw_update": "Force password update at next login", "force_pw_update": "Forza l'aggiornamento della password al prossimo accesso",
"force_pw_update_info": "Questo utente potrà accedere solo a %s.", "force_pw_update_info": "Questo utente potrà accedere solo a %s.",
"full_name": "Nome completo", "full_name": "Nome completo",
"gal": "Global Address List", "gal": "Global Address List",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>", "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
"generate": "generate", "generate": "crea",
"grant_types": "Grant types", "grant_types": "Grant types",
"hostname": "Hostname", "hostname": "Hostname",
"inactive": "Inattivo", "inactive": "Inattivo",
"kind": "Genere", "kind": "Genere",
"last_mail_login": "Last mail login",
"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",
@ -550,7 +564,7 @@
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.", "mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
"mins_interval": "Intervallo (min)", "mins_interval": "Intervallo (min)",
"multiple_bookings": "Prenotazioni multiple", "multiple_bookings": "Prenotazioni multiple",
"nexthop": "Next hop", "nexthop": "Prossimo hop",
"password": "Password", "password": "Password",
"password_repeat": "Conferma password (riscrivi)", "password_repeat": "Conferma password (riscrivi)",
"previous": "Pagina precedente", "previous": "Pagina precedente",
@ -562,9 +576,9 @@
"pushover_sender_array": "Only consider the following sender email addresses <small>(comma-separated)</small>", "pushover_sender_array": "Only consider the following sender email addresses <small>(comma-separated)</small>",
"pushover_sender_regex": "Consider the following sender regex", "pushover_sender_regex": "Consider the following sender regex",
"pushover_text": "Notification text", "pushover_text": "Notification text",
"pushover_title": "Notification title", "pushover_title": "Titolo della notifica",
"pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)", "pushover_vars": "When no sender filter is defined, all mails will be considered.<br>Regex filters as well as exact sender checks can be defined individually and will be considered sequentially. They do not depend on each other.<br>Useable variables for text and title (please take note of data protection policies)",
"pushover_verify": "Verify credentials", "pushover_verify": "Verifica credenziali",
"quota_mb": "Spazio (MiB)", "quota_mb": "Spazio (MiB)",
"quota_warning_bcc": "Quota warning BCC", "quota_warning_bcc": "Quota warning BCC",
"quota_warning_bcc_info": "Warnings will be sent as separate copies to the following recipients. The subject will be suffixed by the corresponding username in brackets, for example: <code>Quota warning (user@example.com)</code>.", "quota_warning_bcc_info": "Warnings will be sent as separate copies to the following recipients. The subject will be suffixed by the corresponding username in brackets, for example: <code>Quota warning (user@example.com)</code>.",
@ -583,42 +597,44 @@
"sender_acl": "Consenti di inviare come", "sender_acl": "Consenti di inviare come",
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>", "sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Sender check is disabled</span>",
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.", "sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"sieve_desc": "Short description", "sieve_desc": "Breve descrizione",
"sieve_type": "Filter type", "sieve_type": "Filter type",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)", "skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"sogo_visible": "Alias is visible in SOGo", "sogo_visible": "L'alias è visibile in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.", "sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"spam_alias": "Create or change time limited alias addresses", "spam_alias": "Create or change time limited alias addresses",
"spam_filter": "Spam filter", "spam_filter": "Spam filter",
"spam_policy": "Add or remove items to white-/blacklist", "spam_policy": "Aggiungi o rimuovi elementi dalla whitelist/blacklist",
"spam_score": "Set a custom spam score", "spam_score": "Imposta un punteggio spam personalizzato",
"subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>", "subfolder2": "Sincronizza in una sottocartella<br /><small>(vuoto = non sincronizzare in sottocartella)</small>",
"syncjob": "Modifica sincronizzazione", "syncjob": "Modifica sincronizzazione",
"target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>", "target_address": "Vai all'indirizzo/i <small>(separato da virgola)</small>",
"target_domain": "Target dominio", "target_domain": "Target dominio",
"timeout1": "Timeout for connection to remote host", "timeout1": "Timeout per la connessione all'host remoto",
"timeout2": "Timeout for connection to local host", "timeout2": "Timeout per la connessione all'host remoto",
"title": "Modifica oggetto", "title": "Modifica oggetto",
"unchanged_if_empty": "Se immutato lasciare vuoto", "unchanged_if_empty": "Se immutato lasciare vuoto",
"username": "Username", "username": "Nome utente",
"validate_save": "Convalida e salva", "validate_save": "Convalida e salva",
"pushover": "Pushover", "pushover": "Pushover",
"sogo_access_info": "Il single-sign-on dall'interno dell'interfaccia di posta rimane funzionante. Questa impostazione non influisce sull'accesso a tutti gli altri servizi né cancella o modifica il profilo SOGo esistente dell'utente.", "sogo_access_info": "Il single-sign-on dall'interno dell'interfaccia di posta rimane funzionante. Questa impostazione non influisce sull'accesso a tutti gli altri servizi né cancella o modifica il profilo SOGo esistente dell'utente.",
"none_inherit": "Nessuno / Eredita", "none_inherit": "Nessuno / Eredita",
"sogo_access": "Concedere l'accesso diretto a SOGo", "sogo_access": "Concedere l'accesso diretto a SOGo",
"acl": "ACL (autorizzazione)", "acl": "ACL (autorizzazione)",
"app_passwd_protocols": "Protocolli consentiti per la password dell'app" "app_passwd_protocols": "Protocolli consentiti per la password dell'app",
"last_modified": "Ultima modifica",
"pushover_sound": "Suono"
}, },
"fido2": { "fido2": {
"confirm": "Confirm", "confirm": "Conferma",
"fido2_auth": "Login with FIDO2", "fido2_auth": "Login with FIDO2",
"fido2_success": "Device successfully registered", "fido2_success": "Dispositivo registrato con successo",
"fido2_validation_failed": "Validation failed", "fido2_validation_failed": "Validazione fallita",
"fn": "Friendly name", "fn": "Nome descrittivo",
"known_ids": "Known IDs", "known_ids": "ID conosciuti",
"none": "Disabled", "none": "Disabilitato",
"register_status": "Registration status", "register_status": "Stato di registrazione",
"rename": "Rename", "rename": "Rinominare",
"set_fido2": "Register FIDO2 device", "set_fido2": "Register FIDO2 device",
"set_fn": "Set friendly name", "set_fn": "Set friendly name",
"start_fido2_validation": "Start FIDO2 validation", "start_fido2_validation": "Start FIDO2 validation",
@ -642,13 +658,14 @@
"header": { "header": {
"administration": "Amministrazione", "administration": "Amministrazione",
"apps": "App", "apps": "App",
"debug": "Informazioni di sistema", "debug": "Informazioni",
"email": "E-Mail", "email": "E-Mail",
"mailcow_config": "Configurazione", "mailcow_config": "Configurazione",
"quarantine": "Quarantena", "quarantine": "Quarantena",
"restart_netfilter": "Riavvia netfilter", "restart_netfilter": "Riavvia netfilter",
"restart_sogo": "Riavvia SOGo", "restart_sogo": "Riavvia SOGo",
"user_settings": "Impostazioni utente" "user_settings": "Impostazioni utente",
"mailcow_system": "Sistema"
}, },
"info": { "info": {
"awaiting_tfa_confirmation": "In attesa di conferma TFA", "awaiting_tfa_confirmation": "In attesa di conferma TFA",
@ -657,12 +674,12 @@
}, },
"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",
"password": "Password", "password": "Password",
"username": "Username" "username": "Nome utente"
}, },
"mailbox": { "mailbox": {
"action": "Azione", "action": "Azione",
@ -734,7 +751,7 @@
"inactive": "Inattivo", "inactive": "Inattivo",
"insert_preset": "Insert example preset \"%s\"", "insert_preset": "Insert example preset \"%s\"",
"kind": "Tipo", "kind": "Tipo",
"last_mail_login": "Last mail login", "last_mail_login": "Ultimo accesso alla posta",
"last_modified": "Ultima modifica", "last_modified": "Ultima modifica",
"last_pw_change": "Ultima modifica della password", "last_pw_change": "Ultima modifica della password",
"last_run": "Ultima esecuzione", "last_run": "Ultima esecuzione",
@ -829,7 +846,15 @@
"sender": "Mittente", "sender": "Mittente",
"all_domains": "Tutti i domini", "all_domains": "Tutti i domini",
"recipient": "Destinatario", "recipient": "Destinatario",
"syncjob_EX_OK": "Successo" "syncjob_EX_OK": "Successo",
"add_template": "Aggiungi template",
"force_pw_update": "Forza il cambio della password al prossimo accesso",
"relay_unknown": "Inoltra a caselle di posta sconosciute",
"mailbox_templates": "Template della mailbox",
"domain_templates": "Template di dominio",
"gal": "Elenco indirizzi globale",
"templates": "Template",
"template": "Template"
}, },
"oauth2": { "oauth2": {
"access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.", "access_denied": "Effettua il login alla casella di posta per garantire l'accesso tramite OAuth2.",
@ -848,7 +873,7 @@
"confirm_delete": "Conferma l'eliminazione di questo elemento.", "confirm_delete": "Conferma l'eliminazione di questo elemento.",
"danger": "Pericolo", "danger": "Pericolo",
"deliver_inbox": "Consegna nella posta in arrivo", "deliver_inbox": "Consegna nella posta in arrivo",
"disabled_by_config": "The current system configuration disables the quarantine functionality. Please set \"retentions per mailbox\" and a \"maximum size\" for quarantine elements.", "disabled_by_config": "L'attuale configurazione del sistema disabilita la funzionalità di quarantena. Imposta \"conservazioni per casella di posta\" e \"dimensione massima\" per gli elementi di quarantena.",
"download_eml": "Download (.eml)", "download_eml": "Download (.eml)",
"empty": "Nessun risultato", "empty": "Nessun risultato",
"high_danger": "Alto", "high_danger": "Alto",
@ -894,15 +919,18 @@
"type": "Tipologia" "type": "Tipologia"
}, },
"queue": { "queue": {
"delete_queue": "Elimina tutto",
"flush_queue": "Svuota la coda",
"queue_ays": "Conferma di voler eliminare tutti gli elementi dalla coda corrente.",
"queue_deliver_mail": "Consegna",
"queue_hold_mail": "Trattieni",
"queue_manager": "Gestore code", "queue_manager": "Gestore code",
"queue_show_message": "Visualizza messaggio", "delete": "Cancella tutto",
"queue_unban": "queue unban", "ays": "Conferma che desideri eliminare tutti gli elementi dalla coda corrente.",
"queue_unhold_mail": "Rilascia" "info": "La coda di posta contiene tutte le e-mail in attesa di consegna. Se un'e-mail rimane a lungo nella coda di posta, viene automaticamente cancellata dal sistema.<br>Il messaggio di errore della rispettiva e-mail fornisce informazioni sul motivo per cui non è stato possibile consegnarla.",
"deliver_mail_legend": "Tenta di riconsegnare i messaggi selezionati.",
"hold_mail": "Blocca",
"flush": "Svuota la coda",
"deliver_mail": "Consegna",
"show_message": "Mostra messaggio",
"unhold_mail": "Sblocca",
"hold_mail_legend": "Blocca le mail selezionate. (Previene ulteriori tentativi di consegna)",
"legend": "Funzioni delle azioni della coda di posta:"
}, },
"start": { "start": {
"help": "Mostra/Nascondi pannello di aiuto", "help": "Mostra/Nascondi pannello di aiuto",
@ -987,7 +1015,10 @@
"verified_totp_login": "Verified TOTP login", "verified_totp_login": "Verified TOTP login",
"verified_webauthn_login": "Verified WebAuthn login", "verified_webauthn_login": "Verified WebAuthn login",
"verified_yotp_login": "Verified Yubico OTP login", "verified_yotp_login": "Verified Yubico OTP login",
"domain_add_dkim_available": "Esisteva già una chiave DKIM" "domain_add_dkim_available": "Esisteva già una chiave DKIM",
"template_added": "Aggiunto template %s",
"template_modified": "Le modifiche al template %s sono state salvate",
"template_removed": "Il template con ID %s è stato cancellato"
}, },
"tfa": { "tfa": {
"api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>", "api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>",
@ -1151,7 +1182,7 @@
"tls_enforce_in": "Imponi TLS in ingresso", "tls_enforce_in": "Imponi TLS in ingresso",
"tls_enforce_out": "Imponi TLS in uscita", "tls_enforce_out": "Imponi TLS in uscita",
"tls_policy": "Politica di crittografia", "tls_policy": "Politica di crittografia",
"tls_policy_warning": "<strong>Attenzione:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br />Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.", "tls_policy_warning": "<strong>Attenzione:</strong> Se decidi di applicare il trasferimento di posta crittografato, potresti perdere le email.<br />I messaggi che non soddisfano la politica verranno respinti con un hard fail dal sistema di posta.<br />This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.",
"user_settings": "Impostazioni utente", "user_settings": "Impostazioni utente",
"username": "Nome utente", "username": "Nome utente",
"verify": "Verifica", "verify": "Verifica",
@ -1175,7 +1206,8 @@
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto", "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Impossibile connettersi al server remoto",
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati", "syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nome utente o password errati",
"with_app_password": "con password dell'app", "with_app_password": "con password dell'app",
"direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile." "direct_protocol_access": "Questo utente della mailbox ha <b>accesso diretto ed esterno</b> ai seguenti protocolli e applicazioni. Questa impostazione è controllata dal tuo amministratore. Le password delle applicazioni possono essere create per garantire l'accesso ai singoli protocolli e applicazioni.<br>Il pulsante \"Accedi alla webmail\" fornisce un singolo accesso a SOGo ed è sempre disponibile.",
"pushover_sound": "Suono"
}, },
"warning": { "warning": {
"cannot_delete_self": "Cannot delete logged in user", "cannot_delete_self": "Cannot delete logged in user",
@ -1196,5 +1228,29 @@
"second": "messaggi / secondo", "second": "messaggi / secondo",
"hour": "messaggi / ora", "hour": "messaggi / ora",
"day": "messaggi / giorno" "day": "messaggi / giorno"
},
"datatables": {
"infoFiltered": "(filtrato da _MAX_ voci totali)",
"collapse_all": "Comprimi tutto",
"emptyTable": "Nessun dato disponibile nella tabella",
"expand_all": "Espandi tutto",
"info": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci",
"infoEmpty": "Visualizzazione da 0 a 0 di 0 voci",
"thousands": ".",
"loadingRecords": "Caricamento...",
"processing": "Attendere prego...",
"search": "Ricerca:",
"zeroRecords": "Nessuna corrispondenza trovata",
"paginate": {
"first": "Prima",
"last": "Ultima",
"next": "Prossima",
"previous": "Precedente"
},
"lengthMenu": "Mostra _MENU_ voci",
"aria": {
"sortAscending": ": attivare l'ordinamento crescente delle colonne",
"sortDescending": ": attivare l'ordinamento decrescente delle colonne"
}
} }
} }

View File

@ -777,15 +777,7 @@
"toggle_all": "선택 반전" "toggle_all": "선택 반전"
}, },
"queue": { "queue": {
"delete_queue": "전부 삭제", "queue_manager": "대기열 관리자"
"flush_queue": "큐 비우기",
"queue_ays": "현재 대기열에서 모든 항목을 삭제할지 확인하십시오.",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "대기열 관리자",
"queue_unban": "대기열 밴 해제",
"queue_unhold_mail": "Unhold",
"queue_show_message": "메시지 표시"
}, },
"start": { "start": {
"help": "Show/Hide help panel", "help": "Show/Hide help panel",

View File

@ -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",
@ -400,16 +411,7 @@
"toggle_all": "Pārslēgt visu" "toggle_all": "Pārslēgt visu"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "queue_manager": "Queue Manager"
"flush_queue": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_command_success": "Queue command completed successfully",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue Manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold"
}, },
"start": { "start": {
"help": "Rādīt/Paslēp palīdzības paneli", "help": "Rādīt/Paslēp palīdzības paneli",
@ -556,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"
} }
} }

View File

@ -528,6 +528,7 @@
"pushover_sender_regex": "Uitsluitend een afzender met de volgende regex", "pushover_sender_regex": "Uitsluitend een afzender met de volgende regex",
"pushover_text": "Meldingstekst ({SUBJECT} zal worden vervangen door het onderwerp)", "pushover_text": "Meldingstekst ({SUBJECT} zal worden vervangen door het onderwerp)",
"pushover_title": "Meldingstitel", "pushover_title": "Meldingstitel",
"pushover_sound": "Geluid",
"pushover_vars": "Wanneer er geen afzenders zijn uitgesloten zullen alle mails doorkomen.<br>Regex-filters en afzendercontroles kunnen individueel worden ingesteld en zullen in volgorde worden verwerkt. Ze zijn niet afhankelijk van elkaar.<br>Bruikbare variabelen voor tekst en titel (neem het gegevensbeschermingsbeleid in acht)", "pushover_vars": "Wanneer er geen afzenders zijn uitgesloten zullen alle mails doorkomen.<br>Regex-filters en afzendercontroles kunnen individueel worden ingesteld en zullen in volgorde worden verwerkt. Ze zijn niet afhankelijk van elkaar.<br>Bruikbare variabelen voor tekst en titel (neem het gegevensbeschermingsbeleid in acht)",
"pushover_verify": "Verifieer aanmeldingsgegevens", "pushover_verify": "Verifieer aanmeldingsgegevens",
"quota_mb": "Quota (MiB)", "quota_mb": "Quota (MiB)",
@ -597,7 +598,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",
@ -658,7 +659,6 @@
"domain_admins": "Domeinadministrators", "domain_admins": "Domeinadministrators",
"domain_aliases": "Domeinaliassen", "domain_aliases": "Domeinaliassen",
"domain_quota": "Quota", "domain_quota": "Quota",
"domain_quota_m": "Totale domeinquota",
"domains": "Domeinen", "domains": "Domeinen",
"edit": "Wijzig", "edit": "Wijzig",
"empty": "Geen resultaten", "empty": "Geen resultaten",
@ -815,15 +815,7 @@
"toggle_all": "Selecteer alles" "toggle_all": "Selecteer alles"
}, },
"queue": { "queue": {
"delete_queue": "Verwijder alles", "queue_manager": "Queue manager"
"flush_queue": "Leeg queue",
"queue_ays": "Bevestig het verwijderen van alle items uit de queue.",
"queue_deliver_mail": "Lever af",
"queue_hold_mail": "Houd vast",
"queue_manager": "Queue manager",
"queue_unban": "hef verbanning op",
"queue_unhold_mail": "Geef vrij",
"queue_show_message": "Toon item"
}, },
"start": { "start": {
"help": "Toon/verberg hulppaneel", "help": "Toon/verberg hulppaneel",
@ -1015,6 +1007,7 @@
"pushover_sender_regex": "Uitsluitend een afzender met de volgende regex", "pushover_sender_regex": "Uitsluitend een afzender met de volgende regex",
"pushover_text": "Meldingstekst ({SUBJECT} zal worden vervangen door het onderwerp)", "pushover_text": "Meldingstekst ({SUBJECT} zal worden vervangen door het onderwerp)",
"pushover_title": "Meldingstitel", "pushover_title": "Meldingstitel",
"pushover_sound": "Geluid",
"pushover_vars": "Wanneer er geen afzenders zijn uitgesloten zullen alle mails doorkomen.<br>Regex-filters en afzendercontroles kunnen individueel worden ingesteld en zullen in volgorde worden verwerkt. Ze zijn niet afhankelijk van elkaar.<br>Bruikbare variabelen voor tekst en titel (let op het gegevensbeschermingsbeleid)", "pushover_vars": "Wanneer er geen afzenders zijn uitgesloten zullen alle mails doorkomen.<br>Regex-filters en afzendercontroles kunnen individueel worden ingesteld en zullen in volgorde worden verwerkt. Ze zijn niet afhankelijk van elkaar.<br>Bruikbare variabelen voor tekst en titel (let op het gegevensbeschermingsbeleid)",
"pushover_verify": "Verifieer aanmeldingsgegevens", "pushover_verify": "Verifieer aanmeldingsgegevens",
"q_add_header": "Spamfolder", "q_add_header": "Spamfolder",

View File

@ -285,16 +285,7 @@
"toggle_all": "Zaznacz wszystkie" "toggle_all": "Zaznacz wszystkie"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "queue_manager": "Queue Manager"
"flush_queue": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_command_success": "Queue command completed successfully",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue Manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold"
}, },
"start": { "start": {
"help": "Pokaż/Ukryj panel pomocy", "help": "Pokaż/Ukryj panel pomocy",

View File

@ -193,16 +193,7 @@
"remove": "Remover" "remove": "Remover"
}, },
"queue": { "queue": {
"delete_queue": "Delete all", "queue_manager": "Queue Manager"
"flush_queue": "Flush queue",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"queue_command_success": "Queue command completed successfully",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_manager": "Queue Manager",
"queue_show_message": "Show message",
"queue_unban": "queue unban",
"queue_unhold_mail": "Unhold"
}, },
"start": { "start": {
"help": "Mostrar/Ocultar painel de ajuda", "help": "Mostrar/Ocultar painel de ajuda",

View File

@ -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",
@ -895,15 +895,7 @@
"toggle_all": "Comută toate" "toggle_all": "Comută toate"
}, },
"queue": { "queue": {
"delete_queue": "Șterge tot", "queue_manager": "Manager de coadă"
"flush_queue": "Elimină coadă",
"queue_ays": "Te rog confirmă că dorești să ștergi toate articolele din coada curentă.",
"queue_deliver_mail": "Livrează",
"queue_hold_mail": "Blochează",
"queue_manager": "Manager de coadă",
"queue_unban": "coadă pentru dezactivare interdicție",
"queue_unhold_mail": "Deblochează",
"queue_show_message": "Arată mesajul"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Dezactivat", "disabled": "Dezactivat",

View File

@ -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,8 @@
"username": "Имя пользователя", "username": "Имя пользователя",
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий", "validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
"verify": "Проверить", "verify": "Проверить",
"yes": "&#10003;" "yes": "&#10003;",
"queue_unban": "разблокировать"
}, },
"danger": { "danger": {
"access_denied": "Доступ запрещён, или указаны неверные данные", "access_denied": "Доступ запрещён, или указаны неверные данные",
@ -654,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": "Вход с помощью ключа",
@ -893,15 +894,7 @@
"type": "Тип" "type": "Тип"
}, },
"queue": { "queue": {
"delete_queue": "Удалить все сообщения", "queue_manager": "Очередь на отправку"
"flush_queue": "Отправить все сообщения",
"queue_ays": "Пожалуйста, подтвердите, что вы хотите удалить все элементы из очереди.",
"queue_deliver_mail": "Доставить",
"queue_hold_mail": "Поставить на удержание",
"queue_manager": "Очередь на отправку",
"queue_show_message": "Показать сообщение",
"queue_unban": "разблокировать очередь",
"queue_unhold_mail": "Снять с удержания"
}, },
"ratelimit": { "ratelimit": {
"disabled": "Отключен", "disabled": "Отключен",

Some files were not shown because too many files have changed in this diff Show More