Merge remote-tracking branch 'origin/staging' into add-podman-support

This commit is contained in:
Roy Lenferink 2023-02-24 13:32:02 +01:00
commit 482854f7fa
65 changed files with 5912 additions and 5769 deletions

View File

@ -1,15 +1,19 @@
{
"enabled": true,
"timezone": "Europe/Berlin",
"dependencyDashboard": false,
"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"],
"enabledManagers": ["github-actions", "regex", "docker-compose"],
"ignorePaths": [
"data\/web\/inc\/lib\/vendor\/matthiasmullie\/minify\/**"
],
"regexManagers": [
{
"fileMatch": ["^helper-scripts\/nextcloud.sh$"],

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:
fetch-depth: 0
- name: Run the Action
uses: devops-infra/action-pull-request@v0.5.3
uses: devops-infra/action-pull-request@v0.5.5
with:
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}

View File

@ -26,7 +26,7 @@ jobs:
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
context: .
file: data/Dockerfiles/backup/Dockerfile

View File

@ -1,20 +0,0 @@
name: "Tweet trigger release"
on:
release:
types: [published]
jobs:
tweet:
runs-on: ubuntu-latest
steps:
- name: "Get Release Tag"
run: |
RELEASE_TAG=$(curl https://api.github.com/repos/mailcow/mailcow-dockerized/releases/latest | jq -r '.tag_name')
- name: Tweet-trigger-publish-release
uses: mugi111/tweet-trigger-release@v1.2
with:
consumer_key: ${{ secrets.CONSUMER_KEY }}
consumer_secret: ${{ secrets.CONSUMER_SECRET }}
access_token_key: ${{ secrets.ACCESS_TOKEN_KEY }}
access_token_secret: ${{ secrets.ACCESS_TOKEN_SECRET }}
tweet_body: 'A new mailcow update has just been released! Checkout the GitHub Page for changelog and more informations: https://github.com/mailcow/mailcow-dockerized/releases/latest'

View File

@ -1,6 +1,5 @@
# mailcow: dockerized - 🐮 + 🐋 = 💕
[![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/)
[![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)

View File

@ -213,11 +213,13 @@ while true; do
done
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
# Start IP detection
log_f "Detecting IP addresses..."
IPV4=$(get_ipv4)
IPV6=$(get_ipv6)
log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}"
fi
#########################################
# IP and webroot challenge verification #

View File

@ -1,4 +1,4 @@
FROM clamav/clamav:1.0_base
FROM clamav/clamav:1.0.1-1_base
LABEL maintainer "André Peters <andre.peters@servercow.de>"

View File

@ -13,6 +13,7 @@ RUN apk add --update --no-cache python3 \
fastapi \
uvicorn \
aiodocker \
docker \
redis
COPY docker-entrypoint.sh /app/

View File

@ -1,5 +1,6 @@
from fastapi import FastAPI, Response, Request
import aiodocker
import docker
import psutil
import sys
import re
@ -9,11 +10,38 @@ import json
import asyncio
import redis
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 = []
host_stats_isUpdating = False
app = FastAPI()
logger = logging.getLogger('api-logger')
@app.get("/host/stats")
@ -21,18 +49,15 @@ async def get_host_update_stats():
global host_stats_isUpdating
if host_stats_isUpdating == False:
print("start host stats task")
asyncio.create_task(get_host_stats())
host_stats_isUpdating = True
while True:
if redis_client.exists('host_stats'):
break
print("wait for host_stats results")
await asyncio.sleep(1.5)
print("host stats pulled")
stats = json.loads(redis_client.get('host_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
@ -106,14 +131,14 @@ async def post_containers(container_id : str, post_action : str, request: Reques
else:
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"))
print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return await api_call_method(container_id, request_json)
logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id))
return api_call_method(container_id, request_json)
except Exception as e:
print("error - container_post: %s" % str(e))
logger.error("error - container_post: %s" % str(e))
res = {
"type": "danger",
"msg": str(e)
@ -152,398 +177,289 @@ class DockerUtils:
self.docker_client = docker_client
# api call: container_post - post_action: stop
async def container_post__stop(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.stop()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
def container_post__stop(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
container.stop()
res = { 'type': 'success', 'msg': 'command completed successfully'}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: start
async def container_post__start(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.start()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
def container_post__start(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
container.start()
res = { 'type': 'success', 'msg': 'command completed successfully'}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: restart
async def container_post__restart(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
await container.restart()
res = {
'type': 'success',
'msg': 'command completed successfully'
}
def container_post__restart(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
container.restart()
res = { 'type': 'success', 'msg': 'command completed successfully'}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: top
async def container_post__top(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
ps_exec = await container.exec("ps")
async with ps_exec.start(detach=False) as stream:
ps_return = await stream.read_out()
exec_details = await ps_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': ps_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': ''
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
def container_post__top(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
res = { 'type': 'success', 'msg': container.top()}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: stats
def container_post__stats(self, container_id, request_json):
for container in self.docker_client.containers.list(all=True, filters={"id": container_id}):
for stat in container.stats(decode=True, stream=True):
res = { 'type': 'success', 'msg': stat}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: delete
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:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-d %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids))
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
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:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-h %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids))
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
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)
# 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:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
sanitized_string = str(' '.join(filtered_qids))
sanitized_string = str(' '.join(filtered_qids));
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
return await exec_run_handler('utf8_text_only', postcat_exec)
for container in self.docker_client.containers.list(filters={"id": container_id}):
postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
if not postcat_return:
postcat_return = 'err: invalid'
return exec_run_handler('utf8_text_only', postcat_return)
# 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:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-H %s' % i for i in filtered_qids]
sanitized_string = str(' '.join(flagged_qids))
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
return await exec_run_handler('generic', postsuper_r_exec)
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)
# 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:
r = re.compile("^[0-9a-fA-F]+$")
filtered_qids = filter(r.match, request_json['items'])
if filtered_qids:
flagged_qids = ['-i %s' % i for i in filtered_qids]
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
for i in flagged_qids:
postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
async with postsuper_r_exec.start(detach=False) as stream:
postsuper_r_return = await stream.read_out()
# todo: check each exit code
res = {
'type': 'success',
'msg': 'Scheduled immediate delivery'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: list
async def container_post__exec__mailq__list(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
return await exec_run_handler('utf8_text_only', mailq_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: flush
async def container_post__exec__mailq__flush(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: mailq - task: super_delete
async def container_post__exec__mailq__super_delete(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
return await exec_run_handler('generic', postsuper_r_exec)
# api call: container_post - post_action: exec - cmd: system - task: fts_rescan
async def container_post__exec__system__fts_rescan(self, container_id, request_json):
if 'username' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
async with rescan_exec.start(detach=False) as stream:
rescan_return = await stream.read_out()
exec_details = await rescan_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': 'fts_rescan: rescan triggered'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if 'all' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
async with rescan_exec.start(detach=False) as stream:
rescan_return = await stream.read_out()
exec_details = await rescan_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'success',
'msg': 'fts_rescan: rescan triggered'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'warning',
'msg': 'fts_rescan error'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: df
async def container_post__exec__system__df(self, container_id, request_json):
if 'dir' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
async with df_exec.start(detach=False) as stream:
df_return = await stream.read_out()
print(df_return)
print(await df_exec.inspect())
exec_details = await df_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
return df_return.data.decode('utf-8').rstrip()
else:
return "0,0,0,0,0,0"
# api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
async def container_post__exec__system__mysql_upgrade(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql')
async with sql_exec.start(detach=False) as stream:
sql_return = await stream.read_out()
exec_details = await sql_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
matched = False
for line in sql_return.data.decode('utf-8').split("\n"):
if 'is already upgraded to' in line:
matched = True
if matched:
res = {
'type': 'success',
'msg': 'mysql_upgrade: already upgraded',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
await container.restart()
res = {
'type': 'warning',
'msg': 'mysql_upgrade: upgrade was applied',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'error',
'msg': 'mysql_upgrade: error running command',
'text': sql_return.data.decode('utf-8')
}
for container in self.docker_client.containers.list(filters={"id": container_id}):
for i in flagged_qids:
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
# todo: check each exit code
res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
async def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sql_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql')
async with sql_exec.start(detach=False) as stream:
sql_return = await stream.read_out()
exec_details = await sql_exec.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
'type': 'info',
'msg': 'mysql_tzinfo_to_sql: command completed successfully',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'error',
'msg': 'mysql_tzinfo_to_sql: error running command',
'text': sql_return.data.decode('utf-8')
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: reload - task: dovecot
async def container_post__exec__reload__dovecot(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: postfix
async def container_post__exec__reload__postfix(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: reload - task: nginx
async def container_post__exec__reload__nginx(self, container_id, request_json):
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
return await exec_run_handler('generic', reload_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: list
async def container_post__exec__sieve__list(self, container_id, request_json):
if 'username' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: sieve - task: print
async def container_post__exec__sieve__print(self, container_id, request_json):
if 'username' in request_json and 'script_name' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
sieve_exec = await container.exec(cmd)
return await exec_run_handler('utf8_text_only', sieve_exec)
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup
async def container_post__exec__maildir__cleanup(self, container_id, request_json):
if 'maildir' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
sane_name = re.sub(r'\W+', '', request_json['maildir'])
cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
maildir_cleanup_exec = await container.exec(cmd, user='vmail')
return await exec_run_handler('generic', maildir_cleanup_exec)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
if 'raw' in request_json:
for container in (await self.docker_client.containers.list()):
if container._id == container_id:
cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
rspamd_password_exec = await container.exec(cmd, user='_rspamd')
async with rspamd_password_exec.start(detach=False) as stream:
rspamd_password_return = await stream.read_out()
matched = False
if "OK" in rspamd_password_return.data.decode('utf-8'):
# api call: container_post - post_action: exec - cmd: mailq - task: list
def container_post__exec__mailq__list(self, container_id, request_json):
for container in self.docker_client.containers.list(filters={"id": container_id}):
mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
return exec_run_handler('utf8_text_only', mailq_return)
# api call: container_post - post_action: exec - cmd: mailq - task: flush
def container_post__exec__mailq__flush(self, 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
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:
res = {
'type': 'success',
'msg': 'command completed successfully'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
else:
res = {
'type': 'danger',
'msg': 'command did not complete'
}
return Response(content=json.dumps(res, indent=4), media_type="application/json")
matched = False
for line in cmd_response.split("\n"):
if '$2$' in line:
hash = line.strip()
hash_out = re.search('\$2\$.+$', hash).group(0)
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
cmd_response = exec_cmd_container(container, cmd, user="_rspamd")
if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response:
container.restart()
matched = True
if matched:
res = { 'type': 'success', 'msg': 'command completed successfully' }
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):
async with exec_obj.start(detach=False) as stream:
exec_return = await stream.read_out()
def recv_socket_data(c_socket, timeout):
c_socket.setblocking(0)
total_data=[]
data=''
begin=time.time()
while True:
if total_data and time.time()-begin > timeout:
break
elif time.time()-begin > timeout*2:
break
try:
data = c_socket.recv(8192)
if data:
total_data.append(data.decode('utf-8'))
#change the beginning time for measurement
begin=time.time()
else:
#sleep for sometime to indicate a gap
time.sleep(0.1)
break
except:
pass
return ''.join(total_data)
if exec_return == None:
exec_return = ""
else:
exec_return = exec_return.data.decode('utf-8')
if type == 'generic':
exec_details = await exec_obj.inspect()
if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
res = {
"type": "success",
"msg": "command completed successfully"
}
try :
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
if not cmd.endswith("\n"):
cmd = cmd + "\n"
socket.send(cmd.encode('utf-8'))
data = recv_socket_data(socket, timeout)
socket.close()
return data
except Exception as e:
logger.error("error - exec_cmd_container: %s" % str(e))
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")
else:
res = {
"type": "success",
"msg": "'command failed: " + exec_return
}
res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') }
return Response(content=json.dumps(res, indent=4), media_type="application/json")
if type == 'utf8_text_only':
return Response(content=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):
global host_stats_isUpdating
@ -570,12 +486,10 @@ async def get_host_stats(wait=5):
"type": "danger",
"msg": str(e)
}
print(json.dumps(res, indent=4))
await asyncio.sleep(wait)
host_stats_isUpdating = False
async def get_container_stats(container_id, wait=5, stop=False):
global containerIds_to_update
@ -598,13 +512,11 @@ async def get_container_stats(container_id, wait=5, stop=False):
"type": "danger",
"msg": str(e)
}
print(json.dumps(res, indent=4))
else:
res = {
"type": "danger",
"msg": "no or invalid id defined"
}
print(json.dumps(res, indent=4))
await asyncio.sleep(wait)
if stop == True:
@ -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)
if os.environ['REDIS_SLAVEOF_IP'] != "":
redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
else:
redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
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')
logger.info('DockerApi started')

View File

@ -2,7 +2,8 @@ FROM debian:bullseye-slim
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG DOVECOT=2.3.19.1
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced
ARG DOVECOT=2.3.20
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced
ARG GOSU_VERSION=1.16
ENV LC_ALL C

View File

@ -359,21 +359,28 @@ def snat4(snat_target):
chain = iptc.Chain(table, 'POSTROUTING')
table.autocommit = False
new_rule = get_snat4_rule()
for position, rule in enumerate(chain.rules):
match = all((
new_rule.get_src() == rule.get_src(),
new_rule.get_dst() == rule.get_dst(),
new_rule.target.parameters == rule.target.parameters,
new_rule.target.name == rule.target.name
))
if position == 0:
if not match:
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
chain.insert_rule(new_rule)
else:
if match:
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
chain.delete_rule(rule)
if not chain.rules:
# if there are no rules in the chain, insert the new rule directly
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
chain.insert_rule(new_rule)
else:
for position, rule in enumerate(chain.rules):
match = all((
new_rule.get_src() == rule.get_src(),
new_rule.get_dst() == rule.get_dst(),
new_rule.target.parameters == rule.target.parameters,
new_rule.target.name == rule.target.name
))
if position == 0:
if not match:
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
chain.insert_rule(new_rule)
else:
if match:
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
chain.delete_rule(rule)
table.commit()
table.autocommit = True
except:

View File

@ -1,12 +1,18 @@
FROM php:8.1-fpm-alpine3.17
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV APCU_PECL 5.1.22
ENV IMAGICK_PECL 3.7.0
ENV MAILPARSE_PECL 3.1.4
ENV MEMCACHED_PECL 3.2.0
ENV REDIS_PECL 5.3.7
ENV COMPOSER 2.4.4
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced
ARG APCU_PECL_VERSION=5.1.22
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced
ARG IMAGICK_PECL_VERSION=3.7.0
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced
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 \
aspell-dev \
@ -55,11 +61,11 @@ RUN apk add -U --no-cache autoconf \
samba-client \
zlib-dev \
tzdata \
&& pecl install mailparse-${MAILPARSE_PECL} \
&& pecl install redis-${REDIS_PECL} \
&& pecl install memcached-${MEMCACHED_PECL} \
&& pecl install APCu-${APCU_PECL} \
&& pecl install imagick-${IMAGICK_PECL} \
&& pecl install APCu-${APCU_PECL_VERSION} \
&& pecl install imagick-${IMAGICK_PECL_VERSION} \
&& pecl install mailparse-${MAILPARSE_PECL_VERSION} \
&& pecl install memcached-${MEMCACHED_PECL_VERSION} \
&& pecl install redis-${REDIS_PECL_VERSION} \
&& docker-php-ext-enable apcu imagick memcached mailparse redis \
&& pecl clear-cache \
&& docker-php-ext-configure intl \
@ -72,7 +78,7 @@ RUN apk add -U --no-cache autoconf \
&& 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-install -j 4 imap \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER} \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
&& mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \
&& apk del --purge autoconf \
@ -102,4 +108,4 @@ COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["php-fpm"]
CMD ["php-fpm"]

View File

@ -175,7 +175,7 @@ BAD_SUBJECT_00 {
type = "header";
header = "subject";
regexp = true;
map = "http://nullnull.org/bad-subject-regex.txt";
map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
score = 6.0;
symbols_set = ["BAD_SUBJECT_00"];
}

View File

@ -699,6 +699,38 @@ paths:
type: string
type: object
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:
post:
responses:
@ -1999,7 +2031,7 @@ paths:
- domain.tld
- domain2.tld
properties:
items:
items:
type: array
items:
type: string
@ -2993,7 +3025,7 @@ paths:
application/json:
schema:
type: array
items:
items:
type: object
properties:
log:
@ -5586,6 +5618,8 @@ tags:
description: Manage DKIM keys
- name: Domain admin
description: Create or udpdate domain admin users
- name: Single Sign-On
description: Issue tokens for users
- name: Address Rewriting
description: Create BCC maps or recipient maps
- name: Outgoing TLS Policy Map Overrides

View File

@ -77,4 +77,22 @@ li .dtr-data {
table.dataTable>tbody>tr.child span.dtr-title {
width: 30%;
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

@ -370,14 +370,3 @@ button[aria-expanded='true'] > .caret {
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: left;
}
div.dataTables_wrapper div.dataTables_length {
text-align: right;
}
.dataTables_paginate, .dataTables_length, .dataTables_filter {
margin: 10px 0!important;
}

View File

@ -203,6 +203,9 @@
text-align: left;
}
.senders-mw220 {
max-width: 100% !important;
}
}
@media (max-width: 350px) {

View File

@ -1,102 +1,104 @@
.pagination a {
text-decoration: none !important;
}
.panel.panel-default {
overflow: visible !important;
}
.table-responsive {
overflow: visible !important;
}
.table-responsive {
overflow-x: scroll !important;
}
.footer-add-item {
display: block;
text-align: center;
font-style: italic;
padding: 10px;
background: #F5F5F5;
}
@media (min-width: 992px) {
.container {
width: 100%;
}
}
@media (min-width: 1920px) {
.container {
width: 80%;
}
}
.mass-actions-quarantine {
user-select: none;
}
.inputMissingAttr {
border-color: #FF4136;
}
.modal#qidDetailModal p {
word-break: break-all;
}
span#qid_detail_score {
font-weight: 700;
margin-left: 5px;
}
span.rspamd-symbol {
display: inline-block;
margin: 2px 6px 2px 0;
border-radius: 4px;
padding: 0 7px;
}
span.rspamd-symbol.positive {
background: #4CAF50;
border: 1px solid #4CAF50;
color: white;
}
span.rspamd-symbol.negative {
background: #ff4136;
border: 1px solid #ff4136;
color: white;
}
span.rspamd-symbol.neutral {
background: #f5f5f5;
color: #333;
border: 1px solid #ccc;
}
span.rspamd-symbol span.score {
font-weight: 700;
}
span.mail-address-item {
background-color: #f5f5f5;
border-radius: 4px;
border: 1px solid #ccc;
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
}
table tbody tr {
cursor: pointer;
}
table tbody tr td input[type="checkbox"] {
cursor: pointer;
}
.label-rspamd-action {
font-size:110%;
margin:20px;
}
.pagination a {
text-decoration: none !important;
}
.panel.panel-default {
overflow: visible !important;
}
.table-responsive {
overflow: visible !important;
}
.table-responsive {
overflow-x: scroll !important;
}
.footer-add-item {
display: block;
text-align: center;
font-style: italic;
padding: 10px;
background: #F5F5F5;
}
@media (min-width: 992px) {
.container {
width: 100%;
}
}
@media (min-width: 1920px) {
.container {
width: 80%;
}
}
.mass-actions-quarantine {
user-select: none;
}
.inputMissingAttr {
border-color: #FF4136;
}
.modal#qidDetailModal p {
word-break: break-all;
}
span#qid_detail_score {
font-weight: 700;
margin-left: 5px;
}
span.rspamd-symbol {
display: inline-block;
margin: 2px 6px 2px 0;
border-radius: 4px;
padding: 0 7px;
}
span.rspamd-symbol.positive {
background: #4CAF50;
border: 1px solid #4CAF50;
color: white;
}
span.rspamd-symbol.negative {
background: #ff4136;
border: 1px solid #ff4136;
color: white;
}
span.rspamd-symbol.neutral {
background: #f5f5f5;
color: #333;
border: 1px solid #ccc;
}
span.rspamd-symbol span.score {
font-weight: 700;
}
span.mail-address-item {
background-color: #f5f5f5;
border-radius: 4px;
border: 1px solid #ccc;
padding: 2px 7px;
display: inline-block;
margin: 2px 6px 2px 0;
}
table tbody tr {
cursor: pointer;
}
table tbody tr td input[type="checkbox"] {
cursor: pointer;
}
.label-rspamd-action {
font-size:110%;
margin:20px;
}
.senders-mw220 {
max-width: 220px;
}

View File

@ -1,407 +1,468 @@
<?php
function domain_admin($_action, $_data = null) {
global $pdo;
global $lang;
$_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
!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_pass2']) ?: $_data_log['user_new_pass2'] = '*';
switch ($_action) {
case 'add':
$username = strtolower(trim($_data['username']));
$password = $_data['password'];
$password2 = $_data['password2'];
$domains = (array)$_data['domains'];
$active = intval($_data['active']);
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (empty($domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'domain_invalid'
);
return false;
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username)
);
return false;
}
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
foreach ($num_results as $num_results_each) {
if ($num_results_each != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_exists', htmlspecialchars($username))
);
return false;
}
}
if (password_check($password, $password2) !== true) {
continue;
}
$password_hashed = hash_password($password);
$valid_domains = 0;
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue;
}
$valid_domains++;
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username, :domain, :created, :active)");
$stmt->execute(array(
':username' => $username,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
if ($valid_domains != 0) {
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '0', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
}
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_added', htmlspecialchars($username))
);
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
// Administrator
if ($_SESSION['mailcow_cc_role'] == "admin") {
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
$is_now = domain_admin('details', $username);
$domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null;
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
$username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
continue;
}
$password = $_data['password'];
$password2 = $_data['password2'];
if (!empty($domains)) {
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue 2;
}
}
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username_new)
);
continue;
}
if ($username_new != $username) {
if (!empty(domain_admin('details', $username_new)['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username_new)
);
continue;
}
}
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(
':username_new' => $username_new,
':username' => $username
));
if (!empty($domains)) {
foreach ($domains as $domain) {
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username_new, :domain, :created, :active)");
$stmt->execute(array(
':username_new' => $username_new,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
}
if (!empty($password)) {
if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username_new' => $username_new,
':username' => $username,
':active' => $active
));
if (isset($_data['disable_tfa'])) {
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
}
else {
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
}
}
else {
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
$stmt->execute(array(
':username_new' => $username_new,
':username' => $username,
':active' => $active
));
if (isset($_data['disable_tfa'])) {
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
}
else {
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_modified', htmlspecialchars($username))
);
}
return true;
}
// Domain administrator
// Can only edit itself
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
$username = $_SESSION['mailcow_cc_username'];
$password_old = $_data['user_old_pass'];
$password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2'];
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (password_check($password_new, $password_new2) !== true) {
return false;
}
$password_hashed = hash_password($password_new);
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_modified', htmlspecialchars($username))
);
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$usernames = (array)$_data['username'];
foreach ($usernames as $username) {
if (empty(domain_admin('details', $username))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username)
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_removed', htmlspecialchars($username))
);
}
break;
case 'get':
$domainadmins = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->query("SELECT DISTINCT
`username`
FROM `domain_admins`
WHERE `username` IN (
SELECT `username` FROM `admin`
WHERE `superadmin`!='1'
)");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$domainadmins[] = $row['username'];
}
return $domainadmins;
break;
case 'details':
$domainadmindata = array();
if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) {
return false;
}
elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
return false;
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) {
return false;
}
$stmt = $pdo->prepare("SELECT
`tfa`.`active` AS `tfa_active`,
`domain_admins`.`username`,
`domain_admins`.`created`,
`domain_admins`.`active` AS `active`
FROM `domain_admins`
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
WHERE `domain_admins`.`username`= :domain_admin");
$stmt->execute(array(
':domain_admin' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$domainadmindata['username'] = $row['username'];
$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['active'] = $row['active'];
$domainadmindata['active_int'] = $row['active'];
$domainadmindata['created'] = $row['created'];
// GET SELECTED
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` IN (
SELECT `domain` FROM `domain_admins`
WHERE `username`= :domain_admin)");
$stmt->execute(array(':domain_admin' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domainadmindata['selected_domains'][] = $row['domain'];
}
// GET UNSELECTED
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` NOT IN (
SELECT `domain` FROM `domain_admins`
WHERE `username`= :domain_admin)");
$stmt->execute(array(':domain_admin' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domainadmindata['unselected_domains'][] = $row['domain'];
}
if (!isset($domainadmindata['unselected_domains'])) {
$domainadmindata['unselected_domains'] = "";
}
return $domainadmindata;
break;
}
}
<?php
function domain_admin($_action, $_data = null) {
global $pdo;
global $lang;
$_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
!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_pass2']) ?: $_data_log['user_new_pass2'] = '*';
switch ($_action) {
case 'add':
$username = strtolower(trim($_data['username']));
$password = $_data['password'];
$password2 = $_data['password2'];
$domains = (array)$_data['domains'];
$active = intval($_data['active']);
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (empty($domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'domain_invalid'
);
return false;
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username)
);
return false;
}
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `username` FROM `admin`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
foreach ($num_results as $num_results_each) {
if ($num_results_each != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_exists', htmlspecialchars($username))
);
return false;
}
}
if (password_check($password, $password2) !== true) {
continue;
}
$password_hashed = hash_password($password);
$valid_domains = 0;
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue;
}
$valid_domains++;
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username, :domain, :created, :active)");
$stmt->execute(array(
':username' => $username,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
if ($valid_domains != 0) {
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '0', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
}
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_added', htmlspecialchars($username))
);
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
// Administrator
if ($_SESSION['mailcow_cc_role'] == "admin") {
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
$is_now = domain_admin('details', $username);
$domains = (isset($_data['domains'])) ? (array)$_data['domains'] : null;
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
$username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
continue;
}
$password = $_data['password'];
$password2 = $_data['password2'];
if (!empty($domains)) {
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue 2;
}
}
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username_new)
);
continue;
}
if ($username_new != $username) {
if (!empty(domain_admin('details', $username_new)['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username_new)
);
continue;
}
}
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("UPDATE `da_acl` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(
':username_new' => $username_new,
':username' => $username
));
if (!empty($domains)) {
foreach ($domains as $domain) {
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username_new, :domain, :created, :active)");
$stmt->execute(array(
':username_new' => $username_new,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
}
if (!empty($password)) {
if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username_new' => $username_new,
':username' => $username,
':active' => $active
));
if (isset($_data['disable_tfa'])) {
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
}
else {
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
}
}
else {
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
$stmt->execute(array(
':username_new' => $username_new,
':username' => $username,
':active' => $active
));
if (isset($_data['disable_tfa'])) {
$stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
}
else {
$stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
$stmt->execute(array(':username_new' => $username_new, ':username' => $username));
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_modified', htmlspecialchars($username))
);
}
return true;
}
// Domain administrator
// Can only edit itself
elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
$username = $_SESSION['mailcow_cc_username'];
$password_old = $_data['user_old_pass'];
$password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2'];
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (password_check($password_new, $password_new2) !== true) {
return false;
}
$password_hashed = hash_password($password_new);
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_modified', htmlspecialchars($username))
);
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$usernames = (array)$_data['username'];
foreach ($usernames as $username) {
if (empty(domain_admin('details', $username))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('username_invalid', $username)
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `da_acl` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_admin_removed', htmlspecialchars($username))
);
}
break;
case 'get':
$domainadmins = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->query("SELECT DISTINCT
`username`
FROM `domain_admins`
WHERE `username` IN (
SELECT `username` FROM `admin`
WHERE `superadmin`!='1'
)");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$domainadmins[] = $row['username'];
}
return $domainadmins;
break;
case 'details':
$domainadmindata = array();
if ($_SESSION['mailcow_cc_role'] == "domainadmin" && $_data != $_SESSION['mailcow_cc_username']) {
return false;
}
elseif ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) {
return false;
}
if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $_data))) {
return false;
}
$stmt = $pdo->prepare("SELECT
`tfa`.`active` AS `tfa_active`,
`domain_admins`.`username`,
`domain_admins`.`created`,
`domain_admins`.`active` AS `active`
FROM `domain_admins`
LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
WHERE `domain_admins`.`username`= :domain_admin");
$stmt->execute(array(
':domain_admin' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$domainadmindata['username'] = $row['username'];
$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['active'] = $row['active'];
$domainadmindata['active_int'] = $row['active'];
$domainadmindata['created'] = $row['created'];
// GET SELECTED
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` IN (
SELECT `domain` FROM `domain_admins`
WHERE `username`= :domain_admin)");
$stmt->execute(array(':domain_admin' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domainadmindata['selected_domains'][] = $row['domain'];
}
// GET UNSELECTED
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` NOT IN (
SELECT `domain` FROM `domain_admins`
WHERE `username`= :domain_admin)");
$stmt->execute(array(':domain_admin' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domainadmindata['unselected_domains'][] = $row['domain'];
}
if (!isset($domainadmindata['unselected_domains'])) {
$domainadmindata['unselected_domains'] = "";
}
return $domainadmindata;
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(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'authenticator not found')
'msg' => array('webauthn_authenticator_failed')
);
return false;
}
@ -1748,11 +1748,20 @@ function verify_tfa_login($username, $_data) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'publicKey not found')
'msg' => array('webauthn_publickey_failed')
);
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 {
$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(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('webauthn_verification_failed', 'could not determine user role')
'msg' => array('webauthn_role_failed')
);
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['tfa_id'] = $process_webauthn['id'];
$_SESSION['authReq'] = null;

View File

@ -5264,7 +5264,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
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();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,140 +1,140 @@
<?php
// Start session
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_httponly", 1);
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
elseif (isset($_SERVER['HTTPS'])) {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
else {
$IS_HTTPS = false;
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['CSRF']['TOKEN'])) {
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
}
// Set session UA
if (!isset($_SESSION['SESS_REMOTE_UA'])) {
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
}
// Keep session active
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
session_unset();
session_destroy();
}
$_SESSION['LAST_ACTIVITY'] = time();
// API
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
$stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
$stmt->execute(array(
':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
));
$api_return = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($api_return['api_key'])) {
$skip_ip_check = ($api_return['skip_ip_check'] == 1);
$remote = get_remote_ip(false);
$allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
if ($skip_ip_check === true || ip_acl($remote, $allow_from)) {
$_SESSION['mailcow_cc_username'] = 'API';
$_SESSION['mailcow_cc_role'] = 'admin';
$_SESSION['mailcow_cc_api'] = true;
if ($api_return['access'] == 'rw') {
$_SESSION['mailcow_cc_api_access'] = 'rw';
}
else {
$_SESSION['mailcow_cc_api_access'] = 'ro';
}
}
else {
$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']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR']
));
unset($_POST);
exit();
}
}
else {
$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']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'authentication failed'
));
unset($_POST);
exit();
}
}
// Handle logouts
if (isset($_POST["logout"])) {
if (isset($_SESSION["dual-login"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]);
header("Location: /mailbox");
exit();
}
else {
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
header("Location: /");
}
}
// Check session
function session_check() {
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
return true;
}
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_ua'
);
return false;
}
if (!empty($_POST)) {
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_token'
);
return false;
}
unset($_POST['csrf_token']);
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
$_SESSION['CSRF']['TIME'] = time();
}
return true;
}
if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
$_POST = array();
$_FILES = array();
}
<?php
// Start session
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_httponly", 1);
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
elseif (isset($_SERVER['HTTPS'])) {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
else {
$IS_HTTPS = false;
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['CSRF']['TOKEN'])) {
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
}
// Set session UA
if (!isset($_SESSION['SESS_REMOTE_UA'])) {
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
}
// Keep session active
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
session_unset();
session_destroy();
}
$_SESSION['LAST_ACTIVITY'] = time();
// API
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
$stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
$stmt->execute(array(
':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
));
$api_return = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($api_return['api_key'])) {
$skip_ip_check = ($api_return['skip_ip_check'] == 1);
$remote = get_remote_ip(false);
$allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
if ($skip_ip_check === true || ip_acl($remote, $allow_from)) {
$_SESSION['mailcow_cc_username'] = 'API';
$_SESSION['mailcow_cc_role'] = 'admin';
$_SESSION['mailcow_cc_api'] = true;
if ($api_return['access'] == 'rw') {
$_SESSION['mailcow_cc_api_access'] = 'rw';
}
else {
$_SESSION['mailcow_cc_api_access'] = 'ro';
}
}
else {
$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']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR']
));
unset($_POST);
exit();
}
}
else {
$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']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'authentication failed'
));
unset($_POST);
exit();
}
}
// Handle logouts
if (isset($_POST["logout"])) {
if (isset($_SESSION["dual-login"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]);
header("Location: /mailbox");
exit();
}
else {
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
header("Location: /");
}
}
// Check session
function session_check() {
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
return true;
}
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_ua'
);
return false;
}
if (!empty($_POST)) {
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_token'
);
return false;
}
unset($_POST['csrf_token']);
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
$_SESSION['CSRF']['TIME'] = time();
}
return true;
}
if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
$_POST = array();
$_FILES = array();
}

View File

@ -1,4 +1,15 @@
<?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 (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
$_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_role']);
unset($_SESSION['pending_tfa_methods']);
header("Location: /user");
} else {
unset($_SESSION['pending_mailcow_cc_username']);
@ -34,7 +45,7 @@ if (isset($_POST["quick_delete"])) {
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"]);
if ($as == "admin") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "admin";

View File

@ -124,7 +124,7 @@ $MAILCOW_APPS = array(
);
// Rows until pagination begins
$PAGINATION_SIZE = 20;
$PAGINATION_SIZE = 25;
// Default number of rows/lines to display (log table)
$LOG_LINES = 1000;

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ $(document).ready(function() {
});
// set update loop container list
containersToUpdate = {}
containersToUpdate = {};
// set default ChartJs Font Color
Chart.defaults.color = '#999';
// create host cpu and mem charts
@ -44,14 +44,13 @@ $(document).ready(function() {
check_update(mailcow_info.version_tag, mailcow_info.project_url);
}
$("#maiclow_version").click(function(){
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" ||
mailcow_info.branch !== "master")
if (mailcow_cc_role !== "admin" && mailcow_cc_role !== "domainadmin" || mailcow_info.branch !== "master")
return;
showVersionModal("Version " + mailcow_info.version_tag, mailcow_info.version_tag);
})
// get public ips
$("#host_show_ip").click(function(){
$("#host_show_ip").click(function(){
$("#host_show_ip").find(".text").addClass("d-none");
$("#host_show_ip").find(".spinner-border").removeClass("d-none");
@ -76,7 +75,7 @@ $(document).ready(function() {
$("#host_ipv6").addClass("d-block");
}).catch(function(error){
console.log(error);
$("#host_ipv6").removeClass("d-none");
$("#host_ipv6").addClass("d-block");
$("#host_ipv6").addClass("text-danger");
@ -119,10 +118,11 @@ jQuery(function($){
}
var table = $('#autodiscover_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -188,10 +188,11 @@ jQuery(function($){
}
var table = $('#postfix_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -242,10 +243,11 @@ jQuery(function($){
}
var table = $('#watchdog_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -300,10 +302,11 @@ jQuery(function($){
}
var table = $('#api_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -352,7 +355,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-api-logs', '#api_log');
});
@ -365,10 +368,11 @@ jQuery(function($){
}
var table = $('#rl_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -455,7 +459,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-rl-logs', '#rl_log');
});
@ -468,10 +472,11 @@ jQuery(function($){
}
var table = $('#ui_logs').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -538,7 +543,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-ui-logs', '#ui_log');
});
@ -551,10 +556,11 @@ jQuery(function($){
}
var table = $('#sasl_logs').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -598,7 +604,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-sasl-logs', '#sasl_logs');
});
@ -611,10 +617,11 @@ jQuery(function($){
}
var table = $('#acme_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -647,7 +654,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-acme-logs', '#acme_log');
});
@ -660,10 +667,11 @@ jQuery(function($){
}
var table = $('#netfilter_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -701,7 +709,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-netfilter-logs', '#netfilter_log');
});
@ -714,10 +722,11 @@ jQuery(function($){
}
var table = $('#sogo_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -755,7 +764,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-sogo-logs', '#sogo_log');
});
@ -768,10 +777,11 @@ jQuery(function($){
}
var table = $('#dovecot_log').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -883,10 +893,11 @@ jQuery(function($){
}
var table = $('#rspamd_history').DataTable({
responsive: true,
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: log_pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
@ -983,7 +994,7 @@ jQuery(function($){
}
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#tab-rspamd-history', '#rspamd_history');
});
@ -998,31 +1009,31 @@ jQuery(function($){
item.rcpt = escapeHtml(item.rcpt_smtp.join(", "));
}
item.symbols = Object.keys(item.symbols).sort(function (a, b) {
if (item.symbols[a].score === 0) return 1
if (item.symbols[b].score === 0) return -1
if (item.symbols[a].score === 0) return 1;
if (item.symbols[b].score === 0) return -1;
if (item.symbols[b].score < 0 && item.symbols[a].score < 0) {
return item.symbols[a].score - item.symbols[b].score
return item.symbols[a].score - item.symbols[b].score;
}
if (item.symbols[b].score > 0 && item.symbols[a].score > 0) {
return item.symbols[b].score - item.symbols[a].score
return item.symbols[b].score - item.symbols[a].score;
}
return item.symbols[b].score - item.symbols[a].score
return item.symbols[b].score - item.symbols[a].score;
}).map(function(key) {
var sym = item.symbols[key];
if (sym.score < 0) {
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)'
sym.score_formatted = '(<span class="text-success"><b>' + sym.score + '</b></span>)';
}
else if (sym.score === 0) {
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)'
sym.score_formatted = '(<span><b>' + sym.score + '</b></span>)';
}
else {
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)'
sym.score_formatted = '(<span class="text-danger"><b>' + sym.score + '</b></span>)';
}
var str = '<strong>' + key + '</strong> ' + sym.score_formatted;
if (sym.options) {
str += ' [' + escapeHtml(sym.options.join(", ")) + "]";
}
return str
return str;
}).join('<br>\n');
item.subject = escapeHtml(item.subject);
var scan_time = item.time_real.toFixed(3);
@ -1155,14 +1166,14 @@ jQuery(function($){
}
});
}
return data
return data;
};
$('.add_log_lines').on('click', function (e) {
e.preventDefault();
var log_table= $(this).data("table")
var new_nrows = $(this).data("nrows")
var post_process = $(this).data("post-process")
var log_url = $(this).data("log-url")
var log_table= $(this).data("table");
var new_nrows = $(this).data("nrows");
var post_process = $(this).data("post-process");
var log_url = $(this).data("log-url");
if (log_table === undefined || new_nrows === undefined || post_process === undefined || log_url === undefined) {
console.log("no data-table or data-nrows or log_url or data-post-process attr found");
return;
@ -1184,9 +1195,9 @@ jQuery(function($){
})
function hideTableExpandCollapseBtn(tab, table){
if ($(table).hasClass('collapsed'))
$(tab).find(".table_collapse_option").show();
$(tab).find(".table_collapse_option").show();
else
$(tab).find(".table_collapse_option").hide();
$(tab).find(".table_collapse_option").hide();
}
// detect element visibility changes
@ -1220,7 +1231,6 @@ jQuery(function($){
onVisible("[id^=rspamd_donut]", () => rspamd_pie_graph());
// start polling host stats if tab is active
onVisible("[id^=tab-containers]", () => update_stats());
// start polling container stats if collapse is active
@ -1303,9 +1313,9 @@ function update_stats(timeout=5){
if (mem_chart.data.labels.length > 30) mem_chart.data.labels.shift();
cpu_chart.data.datasets[0].data.push(data.cpu.usage);
if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift();
if (cpu_chart.data.datasets[0].data.length > 30) cpu_chart.data.datasets[0].data.shift();
mem_chart.data.datasets[0].data.push(data.memory.usage);
if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift();
if (mem_chart.data.datasets[0].data.length > 30) mem_chart.data.datasets[0].data.shift();
cpu_chart.update();
mem_chart.update();
@ -1464,23 +1474,23 @@ function createReadWriteChart(chart_id, read_lable, write_lable){
};
var optionsNet = {
interaction: {
mode: 'index'
mode: 'index'
},
scales: {
yAxis: {
min: 0,
grid: {
display: false
display: false
},
ticks: {
callback: function(i, index, ticks) {
return formatBytes(i);
return formatBytes(i);
}
}
},
xAxis: {
grid: {
display: false
display: false
}
}
}
@ -1528,13 +1538,13 @@ function createHostCpuAndMemChart(){
};
var optionsCpu = {
interaction: {
mode: 'index'
mode: 'index'
},
scales: {
yAxis: {
min: 0,
grid: {
display: false
display: false
},
ticks: {
callback: function(i, index, ticks) {
@ -1544,7 +1554,7 @@ function createHostCpuAndMemChart(){
},
xAxis: {
grid: {
display: false
display: false
}
}
}
@ -1566,13 +1576,13 @@ function createHostCpuAndMemChart(){
};
var optionsMem = {
interaction: {
mode: 'index'
mode: 'index'
},
scales: {
yAxis: {
min: 0,
grid: {
display: false
display: false
},
ticks: {
callback: function(i, index, ticks) {
@ -1582,7 +1592,7 @@ function createHostCpuAndMemChart(){
},
xAxis: {
grid: {
display: false
display: false
}
}
}
@ -1678,22 +1688,22 @@ function parseGithubMarkdownLinks(inputText) {
replacePattern1 = /(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, (matched, index, original, input_string) => {
if (matched.includes('github.com')){
// return short link if it's github link
last_uri_path = matched.split('/');
last_uri_path = last_uri_path[last_uri_path.length - 1];
if (matched.includes('github.com')){
// return short link if it's github link
last_uri_path = matched.split('/');
last_uri_path = last_uri_path[last_uri_path.length - 1];
// adjust Full Changelog link to match last git version and new git version, if link is a compare link
if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag);
last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
}
// adjust Full Changelog link to match last git version and new git version, if link is a compare link
if (matched.includes('/compare/') && mailcow_info.last_version_tag !== ''){
matched = matched.replace(last_uri_path, mailcow_info.last_version_tag + '...' + mailcow_info.version_tag);
last_uri_path = mailcow_info.last_version_tag + '...' + mailcow_info.version_tag;
}
return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
};
return '<a href="' + matched + '" target="_blank">' + last_uri_path + '</a><br>';
};
// if it's not a github link, return complete link
return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
// if it's not a github link, return complete link
return '<a href="' + matched + '" target="_blank">' + matched + '</a>';
});
return replacedText;

View File

@ -1,220 +1,222 @@
$(document).ready(function() {
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
$("#pushover_delete").click(function() { return confirm(lang.delete_ays); });
$(".goto_checkbox").click(function( event ) {
$("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false);
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
$('#textarea_alias_goto').prop('disabled', true);
}
else {
$("#textarea_alias_goto").removeAttr('disabled');
}
});
$("#disable_sender_check").click(function( event ) {
if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) {
$('#editSelectSenderACL').prop('disabled', true);
$('#editSelectSenderACL').selectpicker('refresh');
}
else {
$('#editSelectSenderACL').prop('disabled', false);
$('#editSelectSenderACL').selectpicker('refresh');
}
});
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
$('#textarea_alias_goto').prop('disabled', true);
}
$("#mailbox-password-warning-close").click(function( event ) {
$('#mailbox-passwd-hidden-info').addClass('hidden');
$('#mailbox-passwd-form-groups').removeClass('hidden');
});
// Sender ACL
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
$("#sender_acl_disabled").show();
}
$('#editSelectSenderACL').change(function() {
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
$("#sender_acl_disabled").show();
}
else {
$("#sender_acl_disabled").hide();
}
});
// Resources
if ($("#editSelectMultipleBookings").val() == "custom") {
$("#multiple_bookings_custom_div").show();
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
}
$("#editSelectMultipleBookings").change(function() {
$('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val());
if ($('input[name=multiple_bookings]').val() == "custom") {
$("#multiple_bookings_custom_div").show();
}
else {
$("#multiple_bookings_custom_div").hide();
}
});
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
});
// load tags
if ($('#tags').length){
var tagsEl = $('#tags').parent().find('.tag-values')[0];
console.log($(tagsEl).val())
var tags = JSON.parse($(tagsEl).val());
$(tagsEl).val("");
for (var i = 0; i < tags.length; i++)
addTag($('#tags'), tags[i]);
}
});
jQuery(function($){
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
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,}))$/;
return re.test(email);
}
function draw_wl_policy_domain_table() {
$('#wl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
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 + '" />';
}
else {
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
}
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
});
}
function draw_bl_policy_domain_table() {
$('#bl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
dataSrc: function(data){
$.each(data, function (i, item) {
if (!validateEmail(item.object)) {
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
}
else {
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
}
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
});
}
// detect element visibility changes
function onVisible(element, callback) {
$(document).ready(function() {
element_object = document.querySelector(element);
if (element_object === null) return;
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());
});
$(document).ready(function() {
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
$("#pushover_delete").click(function() { return confirm(lang.delete_ays); });
$(".goto_checkbox").click(function( event ) {
$("form[data-id='editalias'] .goto_checkbox").not(this).prop('checked', false);
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
$('#textarea_alias_goto').prop('disabled', true);
}
else {
$("#textarea_alias_goto").removeAttr('disabled');
}
});
$("#disable_sender_check").click(function( event ) {
if ($("form[data-id='editmailbox'] #disable_sender_check:checked").length > 0) {
$('#editSelectSenderACL').prop('disabled', true);
$('#editSelectSenderACL').selectpicker('refresh');
}
else {
$('#editSelectSenderACL').prop('disabled', false);
$('#editSelectSenderACL').selectpicker('refresh');
}
});
if ($("form[data-id='editalias'] .goto_checkbox:checked").length > 0) {
$('#textarea_alias_goto').prop('disabled', true);
}
$("#mailbox-password-warning-close").click(function( event ) {
$('#mailbox-passwd-hidden-info').addClass('hidden');
$('#mailbox-passwd-form-groups').removeClass('hidden');
});
// Sender ACL
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
$("#sender_acl_disabled").show();
}
$('#editSelectSenderACL').change(function() {
if ($("#editSelectSenderACL option[value='\*']:selected").length > 0){
$("#sender_acl_disabled").show();
}
else {
$("#sender_acl_disabled").hide();
}
});
// Resources
if ($("#editSelectMultipleBookings").val() == "custom") {
$("#multiple_bookings_custom_div").show();
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
}
$("#editSelectMultipleBookings").change(function() {
$('input[name=multiple_bookings]').val($("#editSelectMultipleBookings").val());
if ($('input[name=multiple_bookings]').val() == "custom") {
$("#multiple_bookings_custom_div").show();
}
else {
$("#multiple_bookings_custom_div").hide();
}
});
$("#multiple_bookings_custom").bind("change keypress keyup blur", function() {
$('input[name=multiple_bookings]').val($("#multiple_bookings_custom").val());
});
// load tags
if ($('#tags').length){
var tagsEl = $('#tags').parent().find('.tag-values')[0];
console.log($(tagsEl).val())
var tags = JSON.parse($(tagsEl).val());
$(tagsEl).val("");
for (var i = 0; i < tags.length; i++)
addTag($('#tags'), tags[i]);
}
});
jQuery(function($){
// http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
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,}))$/;
return re.test(email);
}
function draw_wl_policy_domain_table() {
$('#wl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_wl_domain/' + table_for_domain,
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 + '" />';
}
else {
item.chkbox = '<input type="checkbox" disabled title="' + lang_user.spamfilter_table_domain_policy + '" />';
}
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
});
}
function draw_bl_policy_domain_table() {
$('#bl_policy_domain_table').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
ajax: {
type: "GET",
url: '/api/v1/get/policy_bl_domain/' + table_for_domain,
dataSrc: function(data){
$.each(data, function (i, item) {
if (!validateEmail(item.object)) {
item.chkbox = '<input type="checkbox" data-id="policy_bl_domain" name="multi_select" value="' + item.prefid + '" />';
}
else {
item.chkbox = '<input type="checkbox" disabled tooltip="' + lang_user.spamfilter_table_domain_policy + '" />';
}
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'prefid',
defaultContent: ''
},
{
title: lang_user.spamfilter_table_rule,
data: 'value',
defaultContent: ''
},
{
title: 'Scope',
data: 'object',
defaultContent: ''
}
]
});
}
// detect element visibility changes
function onVisible(element, callback) {
$(document).ready(function() {
element_object = document.querySelector(element);
if (element_object === null) return;
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());
});

File diff suppressed because it is too large Load Diff

View File

@ -1,71 +1,71 @@
jQuery(function($){
var qitem = $('legend').data('hash');
var qError = $("#qid_error");
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { hash: qitem },
dataType: 'json',
success: function(data){
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').html('');
$('#qid_detail_symbols').html('');
$('#qid_detail_recipients').html('');
$('#qid_detail_fuzzy').html('');
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
});
} else {
$('#qid_detail_fuzzy').append('-');
}
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1
if (b.score === 0) return -1
if (b.score < 0 && a.score < 0) {
return a.score - b.score
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score
}
return b.score - a.score
})
$.each(data.symbols, function (index, value) {
var highlightClass = ''
if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive'
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>');
});
$('[data-bs-toggle="tooltip"]').tooltip()
}
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action === "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action === "reject") {
$('#qid_detail_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") {
$('#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') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
},
error: function(data){
if (typeof data.error !== 'undefined') {
qError.text("Error loading quarantine item");
qError.show();
}
}
});
});
jQuery(function($){
var qitem = $('legend').data('hash');
var qError = $("#qid_error");
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { hash: qitem },
dataType: 'json',
success: function(data){
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').html('');
$('#qid_detail_symbols').html('');
$('#qid_detail_recipients').html('');
$('#qid_detail_fuzzy').html('');
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
});
} else {
$('#qid_detail_fuzzy').append('-');
}
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1;
if (b.score === 0) return -1;
if (b.score < 0 && a.score < 0) {
return a.score - b.score;
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score;
}
return b.score - a.score;
})
$.each(data.symbols, function (index, value) {
var highlightClass = '';
if (value.score > 0) highlightClass = 'negative';
else if (value.score < 0) highlightClass = 'positive';
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>');
});
$('[data-bs-toggle="tooltip"]').tooltip();
}
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action === "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action === "reject") {
$('#qid_detail_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") {
$('#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') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
},
error: function(data){
if (typeof data.error !== 'undefined') {
qError.text("Error loading quarantine item");
qError.show();
}
}
});
});

View File

@ -1,286 +1,297 @@
// 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}};
jQuery(function($){
acl_data = JSON.parse(acl);
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
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]}
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload();
});
function draw_quarantine_table() {
var table = $('#quarantinetable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#quarantinetable');
},
ajax: {
type: "GET",
url: "/api/v1/get/quarantine/all",
dataSrc: function(data){
$.each(data, function (i, item) {
if (item.subject === null) {
item.subject = '';
} else {
item.subject = escapeHtml(item.subject);
}
if (item.score === null) {
item.score = '-';
}
if (item.virus_flag > 0) {
item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
} else {
item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
}
if (item.action === "reject") {
item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
} else if (item.action === "add header") {
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
} else if (item.action === "rewrite subject") {
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
}
if(item.notified > 0) {
item.notified = '&#10004;';
} else {
item.notified = '&#10006;';
}
if (acl_data.login_as === 1) {
item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>';
}
else {
item.action = '<div class="btn-group">' +
'<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>' +
'</div>';
}
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.qid,
data: 'qid',
defaultContent: ''
},
{
title: lang.sender,
data: 'sender',
defaultContent: ''
},
{
title: lang.subj,
data: 'subject',
defaultContent: ''
},
{
title: lang.rspamd_result,
data: 'rspamdaction',
defaultContent: ''
},
{
title: lang.rcpt,
data: 'rcpt',
defaultContent: ''
},
{
title: lang.danger,
data: 'virus',
defaultContent: ''
},
{
title: lang.spam_score,
data: 'score',
defaultContent: ''
},
{
title: lang.notified,
data: 'notified',
defaultContent: ''
},
{
title: lang.received,
data: 'created',
defaultContent: '',
createdCell: function(td, cellData) {
$(td).attr({
"data-order": cellData,
"data-sort": cellData
});
var date = new Date(cellData ? cellData * 1000 : 0);
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
$(td).html(dateString);
}
},
{
title: lang.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#quarantinetable');
});
}
$('body').on('click', '.show_qid_info', function (e) {
e.preventDefault();
var qitem = $(this).attr('data-item');
var qError = $("#qid_error");
$('#qidDetailModal').modal('show');
qError.hide();
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { id: qitem },
dataType: 'json',
success: function(data){
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$("#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')");
$("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')");
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').html('');
$('#qid_detail_recipients').html('');
$('#qid_detail_symbols').html('');
$('#qid_detail_fuzzy').html('');
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1
if (b.score === 0) return -1
if (b.score < 0 && a.score < 0) {
return a.score - b.score
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score
}
return b.score - a.score
})
$.each(data.symbols, function (index, value) {
var highlightClass = ''
if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive'
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>');
});
$('[data-bs-toggle="tooltip"]').tooltip()
}
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
});
} else {
$('#qid_detail_fuzzy').append('-');
}
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action == "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action == "reject") {
$('#qid_detail_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") {
$('#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') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
$('#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') {
qAtts.text('');
$.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] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}
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();
}
});
// 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}};
jQuery(function($){
acl_data = JSON.parse(acl);
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
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]}
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload();
});
function draw_quarantine_table() {
var table = $('#quarantinetable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
order: [[2, 'desc']],
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, 'all']
],
pagingType: 'first_last_numbers',
aColumns: [
{ sWidth: '8.25%' },
{ sClass: 'classDataTable' }
],
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: lang_datatables,
initComplete: function(){
hideTableExpandCollapseBtn('#quarantinetable');
},
ajax: {
type: "GET",
url: "/api/v1/get/quarantine/all",
dataSrc: function(data){
$.each(data, function (i, item) {
if (item.subject === null) {
item.subject = '';
} else {
item.subject = escapeHtml(item.subject);
}
if (item.score === null) {
item.score = '-';
}
if (item.virus_flag > 0) {
item.virus = '<span class="badge fs-6 bg-danger">' + lang.high_danger + '</span>';
} else {
item.virus = '<span class="badge fs-6 bg-secondary">' + lang.neutral_danger + '</span>';
}
if (item.action === "reject") {
item.rspamdaction = '<span class="badge fs-6 bg-danger">' + lang.rejected + '</span>';
} else if (item.action === "add header") {
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.junk_folder + '</span>';
} else if (item.action === "rewrite subject") {
item.rspamdaction = '<span class="badge fs-6 bg-warning">' + lang.rewrite_subject + '</span>';
}
if(item.notified > 0) {
item.notified = '&#10004;';
} else {
item.notified = '&#10006;';
}
if (acl_data.login_as === 1) {
item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-info show_qid_info"><i class="bi bi-box-arrow-up-right"></i> ' + lang.show_item + '</a>' +
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
'</div>';
}
else {
item.action = '<div class="btn-group">' +
'<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>' +
'</div>';
}
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'ID',
data: 'id',
defaultContent: ''
},
{
title: lang.qid,
data: 'qid',
defaultContent: ''
},
{
title: lang.sender,
data: 'sender',
className: 'senders-mw220',
defaultContent: ''
},
{
title: lang.subj,
data: 'subject',
defaultContent: ''
},
{
title: lang.rspamd_result,
data: 'rspamdaction',
defaultContent: ''
},
{
title: lang.rcpt,
data: 'rcpt',
defaultContent: ''
},
{
title: lang.danger,
data: 'virus',
defaultContent: ''
},
{
title: lang.spam_score,
data: 'score',
defaultContent: ''
},
{
title: lang.notified,
data: 'notified',
defaultContent: ''
},
{
title: lang.received,
data: 'created',
defaultContent: '',
createdCell: function(td, cellData) {
$(td).attr({
"data-order": cellData,
"data-sort": cellData
});
var date = new Date(cellData ? cellData * 1000 : 0);
var dateString = date.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
$(td).html(dateString);
}
},
{
title: lang.action,
data: 'action',
className: 'dt-text-right dt-sm-head-hidden',
defaultContent: ''
},
]
});
table.on('responsive-resize', function (e, datatable, columns){
hideTableExpandCollapseBtn('#quarantinetable');
});
}
$('body').on('click', '.show_qid_info', function (e) {
e.preventDefault();
var qitem = $(this).attr('data-item');
var qError = $("#qid_error");
$('#qidDetailModal').modal('show');
qError.hide();
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { id: qitem },
dataType: 'json',
success: function(data){
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$("#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')");
$("#quick_delete_link").attr("onclick", "window.open('/inc/ajax/qitem_details.php?id=" + qitem + "&quick_delete', '_blank')");
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').html('');
$('#qid_detail_recipients').html('');
$('#qid_detail_symbols').html('');
$('#qid_detail_fuzzy').html('');
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1;
if (b.score === 0) return -1;
if (b.score < 0 && a.score < 0) {
return a.score - b.score;
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score;
}
return b.score - a.score;
})
$.each(data.symbols, function (index, value) {
var highlightClass = '';
if (value.score > 0) highlightClass = 'negative';
else if (value.score < 0) highlightClass = 'positive';
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>');
});
$('[data-bs-toggle="tooltip"]').tooltip();
}
if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) {
$.each(data.fuzzy_hashes, function (index, value) {
$('#qid_detail_fuzzy').append('<p style="font-family:monospace">' + value + '</p>');
});
} else {
$('#qid_detail_fuzzy').append('-');
}
if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') {
if (data.action == "add header") {
$('#qid_detail_score').append('<span class="label-rspamd-action badge fs-6 bg-warning"><b>' + data.score + '</b> - ' + lang.junk_folder + '</span>');
} else if (data.action == "reject") {
$('#qid_detail_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") {
$('#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') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
$('#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') {
qAtts.text('');
$.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] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}
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,128 +1,128 @@
jQuery(function($){
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).DataTable().ajax.reload();
});
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + 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
$('#showQueuedMsg').on('show.bs.modal', function (e) {
$('#queue_msg_content').text(lang.loading);
button = $(e.relatedTarget)
if (button != null) {
$('#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;
// Queue item
$('#showQueuedMsg').on('show.bs.modal', function (e) {
$('#queue_msg_content').text(lang.loading);
button = $(e.relatedTarget)
if (button != null) {
$('#queue_id').text(button.data('queue-id'));
}
$.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({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
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.queue_show_message + '</a>' +
function draw_queue() {
// just recalc width if instance already exists
if ($.fn.DataTable.isDataTable('#queuetable') ) {
$('#queuetable').DataTable().columns.adjust().responsive.recalc();
return;
}
$('#queuetable').DataTable({
responsive: true,
processing: true,
serverSide: false,
stateSave: true,
pageLength: pagination_size,
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
"tr" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
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>';
});
return data;
}
},
columns: [
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'QID',
data: 'queue_id',
defaultContent: ''
},
{
title: 'Queue',
data: 'queue_name',
defaultContent: ''
},
{
title: lang_admin.arrival_time,
data: 'arrival_time',
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_admin.message_size,
data: 'message_size',
defaultContent: '',
render: function (data, type){
return humanFileSize(data);
}
},
{
title: lang_admin.sender,
data: 'sender',
defaultContent: ''
},
{
title: lang_admin.recipients,
data: 'recipients',
defaultContent: ''
},
{
title: lang_admin.action,
data: 'action',
className: 'text-md-end dt-sm-head-hidden dt-body-right',
defaultContent: ''
},
{
// placeholder, so checkbox will not block child row toggle
title: '',
data: null,
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: '',
data: 'chkbox',
searchable: false,
orderable: false,
defaultContent: ''
},
{
title: 'QID',
data: 'queue_id',
defaultContent: ''
},
{
title: 'Queue',
data: 'queue_name',
defaultContent: ''
},
{
title: lang_admin.arrival_time,
data: 'arrival_time',
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_admin.message_size,
data: 'message_size',
defaultContent: '',
render: function (data, type){
return humanFileSize(data);
}
},
{
title: lang_admin.sender,
data: 'sender',
defaultContent: ''
},
{
title: lang_admin.recipients,
data: 'recipients',
defaultContent: ''
},
{
title: lang_admin.action,
data: 'action',
className: 'dt-sm-head-hidden dt-text-right',
defaultContent: ''
},
]
});
}
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":
process_add_return(domain_admin('add', $attr));
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":
process_add_return(admin('add', $attr));
break;

View File

@ -650,7 +650,7 @@
},
"login": {
"delayed": "Přihlášení zpožděno o %s sekund.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Přihlásit",
"mobileconfig_info": "Ke stažení profilového souboru se přihlaste jako uživatel schránky.",
"other_logins": "Přihlášení klíčem",

View File

@ -1,6 +1,6 @@
{
"acl": {
"alias_domains": "Tilføj kældenavn domæner",
"alias_domains": "Tilføj domænealias",
"app_passwds": "Administrer app-adgangskoder",
"bcc_maps": "BCC kort",
"delimiter_action": "Afgrænsning handling",
@ -22,9 +22,9 @@
"spam_alias": "Midlertidige aliasser",
"spam_policy": "Sortliste / hvidliste",
"spam_score": "Spam-score",
"syncjobs": "Synkroniser job",
"syncjobs": "Synkroniserings job",
"tls_policy": "TLS politik",
"unlimited_quota": "Ubegrænset quote for mailbokse",
"unlimited_quota": "Ubegrænset plads for mailbokse",
"domain_desc": "Skift domæne beskrivelse"
},
"add": {
@ -33,7 +33,7 @@
"add": "Tilføj",
"add_domain_only": "Tilføj kun domæne",
"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_domain": "Alias-domæne",
"alias_domain_info": "<small>Kun gyldige domænenavne (kommasepareret).</small>",
@ -586,7 +586,7 @@
},
"login": {
"delayed": "Login blev forsinket med% s sekunder.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Login",
"mobileconfig_info": "Log ind som postkassebruger for at downloade den anmodede Apple-forbindelsesprofil.",
"other_logins": "Nøgle login",

View File

@ -339,7 +339,8 @@
"oauth2_add_client": "Füge OAuth2 Client hinzu",
"api_read_only": "Schreibgeschützter Zugriff",
"api_read_write": "Lese-Schreib-Zugriff",
"oauth2_apps": "OAuth2 Apps"
"oauth2_apps": "OAuth2 Apps",
"queue_unban": "entsperren"
},
"danger": {
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
@ -366,7 +367,7 @@
"domain_not_empty": "Domain %s ist nicht leer",
"domain_not_found": "Domain %s nicht gefunden",
"domain_quota_m_in_use": "Domain-Speicherplatzlimit muss größer oder gleich %d MiB sein",
"extended_sender_acl_denied": "Keine Rechte zum setzen von externen Absenderadressen",
"extended_sender_acl_denied": "Keine Rechte zum Setzen von externen Absenderadressen",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain",
"fido2_verification_failed": "FIDO2-Verifizierung fehlgeschlagen: %s",
@ -454,17 +455,23 @@
"totp_verification_failed": "TOTP-Verifizierung fehlgeschlagen",
"transport_dest_exists": "Transport-Maps-Ziel \"%s\" existiert bereits",
"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_tfa_method": "Unbekannte TFA-Methode",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
"username_invalid": "Benutzername %s kann nicht verwendet werden",
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
"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": {
"collapse_all": "Alle Einklappen",
"decimal": "",
"decimal": ",",
"emptyTable": "Keine Daten in der Tabelle vorhanden",
"expand_all": "Alle Ausklappen",
"info": "_START_ bis _END_ von _TOTAL_ Einträgen",
@ -498,7 +505,7 @@
"current_time": "Systemzeit",
"disk_usage": "Festplattennutzung",
"docs": "Dokumente",
"error_show_ip": "konnte die öffentlichen IP Adressen nicht auflösen",
"error_show_ip": "Konnte die öffentlichen IP Adressen nicht auflösen",
"external_logs": "Externe Logs",
"history_all_servers": "History (alle Server)",
"in_memory_logs": "In-memory Logs",
@ -651,7 +658,8 @@
"title": "Objekt bearbeiten",
"unchanged_if_empty": "Unverändert, wenn leer",
"username": "Benutzername",
"validate_save": "Validieren und speichern"
"validate_save": "Validieren und speichern",
"pushover_sound": "Ton"
},
"fido2": {
"confirm": "Bestätigen",
@ -692,7 +700,8 @@
"quarantine": "Quarantäne",
"restart_netfilter": "Netfilter neustarten",
"restart_sogo": "SOGo neustarten",
"user_settings": "Benutzereinstellungen"
"user_settings": "Benutzereinstellungen",
"mailcow_system": "System"
},
"info": {
"awaiting_tfa_confirmation": "Warte auf TFA-Verifizierung",
@ -701,7 +710,7 @@
},
"login": {
"delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Anmelden",
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
"other_logins": "Key Login",
@ -1236,7 +1245,8 @@
"syncjob_EXIT_CONNECTION_FAILURE": "Verbindungsproblem",
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
"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": {
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",

View File

@ -458,6 +458,9 @@
"totp_verification_failed": "TOTP verification failed",
"transport_dest_exists": "Transport destination \"%s\" exists",
"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_tfa_method": "Unknown TFA method",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
@ -468,7 +471,7 @@
},
"datatables": {
"collapse_all": "Collapse All",
"decimal": "",
"decimal": ".",
"emptyTable": "No data available in table",
"expand_all": "Expand All",
"info": "Showing _START_ to _END_ of _TOTAL_ entries",
@ -707,7 +710,7 @@
},
"login": {
"delayed": "Login was delayed by %s seconds.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Login",
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
"other_logins": "Key login",

View File

@ -612,7 +612,7 @@
},
"login": {
"delayed": "La connexion a été retardée de %s secondes.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Connexion",
"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",

View File

@ -674,7 +674,7 @@
},
"login": {
"delayed": "L'accesso è stato ritardato di %s secondi.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Login",
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
"other_logins": "Key login",

View File

@ -3,7 +3,8 @@
"bcc_maps": "BCC kartes",
"filters": "Filtri",
"recipient_maps": "Saņēmēja kartes",
"syncjobs": "Sinhronizācijas uzdevumi"
"syncjobs": "Sinhronizācijas uzdevumi",
"spam_score": "Mēstules novērtējums"
},
"add": {
"activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.",
@ -104,10 +105,10 @@
"host": "Hosts",
"import": "Importēt",
"import_private_key": "Importēt privātu atslēgu",
"in_use_by": "Tiek lietots ar",
"in_use_by": "Izmanto",
"inactive": "Neaktīvs",
"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",
"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.",
@ -144,7 +145,10 @@
"ui_texts": "UI etiķetes un teksti",
"unchanged_if_empty": "Ja nav veiktas izmaiņas, atstājiet tukšu",
"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": {
"access_denied": "Piekļuve liegta, vai nepareizi dati",
@ -170,7 +174,7 @@
"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_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",
"mailbox_invalid": "Pastkastes vārds ir nederīgs",
"mailbox_quota_exceeded": "Kvota pārsniedz domēna limitu (max. %d MiB)",
@ -262,7 +266,8 @@
"title": "Labot priekšmetu",
"unchanged_if_empty": "Ja neizmainīts atstājiet tukšu",
"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": {
"cancel": "Atcelt",
@ -314,21 +319,21 @@
"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_local_dest": "Vietējais galamērķis",
"bcc_map_type": "BCC tips",
"bcc_map_type": "BCC veids",
"bcc_maps": "BCC kartes",
"bcc_rcpt_map": "saņēmēja karte",
"bcc_sender_map": "Sūtītāja karte",
"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_type": "BCC tips",
"deactivate": "Deaktivizēt",
"deactivate": "Deaktivēt",
"description": "Apraksts",
"dkim_key_length": "DKIM atslēgas garums (bits)",
"domain": "Domēns",
"domain_admins": "Domēna administratori",
"domain_aliases": "Domēna aliases",
"domain_quota": "Kvota",
"domain_quota_total": "Kopējā domēna kvota",
"domain_quota_total": "Kopējais domēna ierobežojums",
"domains": "Domēns",
"edit": "Labot",
"empty": "Nav rezultātu",
@ -341,7 +346,7 @@
"inactive": "Neaktīvs",
"kind": "Veids",
"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",
"mailboxes": "Pastkaste",
"max_aliases": "Maks. iespejamās aliases",
@ -374,7 +379,13 @@
"tls_enforce_out": "Piespiest TLS izejošajiem",
"toggle_all": "Pārslēgt visu",
"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": {
"action": "Darbības",
@ -547,5 +558,14 @@
"waiting": "Waiting",
"week": "Nedēļa",
"weeks": "Nedēļas"
},
"datatables": {
"paginate": {
"first": "Pirmā",
"last": "Pēdējā"
}
},
"debug": {
"last_modified": "Pēdējoreiz mainīts"
}
}

View File

@ -598,7 +598,7 @@
},
"login": {
"delayed": "Aanmelding vertraagd met %s seconden.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Aanmelden",
"mobileconfig_info": "Log in als mailboxgebruiker om het Apple-verbindingsprofiel te downloaden.",
"other_logins": "Meld aan met key",

View File

@ -656,7 +656,7 @@
},
"login": {
"delayed": "Conectarea a fost întârziată cu %s secunde.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Autentificare",
"mobileconfig_info": "Autentificați-vă cu adresa de email pentru a descărca profilul de conexiune Apple.",
"other_logins": "Autentificare cu cheie",

View File

@ -37,7 +37,7 @@
"add_domain_only": "Только добавить домен",
"add_domain_restart": "Добавить домен и перезапустить SOGo",
"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_info": "<small>Действительные имена доменов, раздёленные запятыми.</small>",
"app_name": "Название приложения",
@ -335,7 +335,8 @@
"username": "Имя пользователя",
"validate_license_now": "Получить лицензию на основе GUID с сервера лицензий",
"verify": "Проверить",
"yes": "&#10003;"
"yes": "&#10003;",
"queue_unban": "разблокировать"
},
"danger": {
"access_denied": "Доступ запрещён, или указаны неверные данные",
@ -654,7 +655,7 @@
},
"login": {
"delayed": "Вход был отложен на %s секунд.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Войти",
"mobileconfig_info": "Пожалуйста, войдите в систему как пользователь почтового аккаунта для загрузки профиля подключения Apple.",
"other_logins": "Вход с помощью ключа",

View File

@ -106,7 +106,8 @@
"username": "Používateľské meno",
"validate": "Overiť",
"validation_success": "Úspešne overené",
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie"
"app_passwd_protocols": "Povolené protokoly k heslu aplikácie",
"tags": "Štítky"
},
"admin": {
"access": "Prístup",
@ -656,7 +657,7 @@
},
"login": {
"delayed": "Prihlásenie bolo oneskorené o %s sekúnd.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Prihlásenie",
"mobileconfig_info": "Prosím, prihláste sa ako mailový používateľ pre stiahnutie požadovaného Apple profilu.",
"other_logins": "Prihlásenie kľúčom",

View File

@ -618,7 +618,7 @@
},
"login": {
"delayed": "Av säkerhetsskäl har inloggning inaktiverats i %s sekunder.",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Logga in",
"mobileconfig_info": "Logga in som en användare av brevlåda för att ladda ner den begärda Apple-anslutningsprofilen.",
"other_logins": "Loggain med nyckel",

View File

@ -656,7 +656,7 @@
"awaiting_tfa_confirmation": "В очікуванні підтвердження TFA"
},
"login": {
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "Увійти",
"other_logins": "Вхід за допомогою ключа",
"password": "Пароль",

View File

@ -661,7 +661,7 @@
},
"login": {
"delayed": "请在 %s 秒后重新登录。",
"fido2_webauthn": "使用 FIDO2/WebAuthn 登录",
"fido2_webauthn": "使用 FIDO2/WebAuthn Login 登录",
"login": "登录",
"mobileconfig_info": "请使用邮箱用户登录以下载 Apple 连接描述文件。",
"other_logins": "Key 登录",

View File

@ -655,7 +655,7 @@
},
"login": {
"delayed": "請在 %s 秒後重新登入。",
"fido2_webauthn": "FIDO2/WebAuthn",
"fido2_webauthn": "FIDO2/WebAuthn Login",
"login": "登入",
"mobileconfig_info": "請使用信箱使用者登入以下載 Apple 連接描述檔案。",
"other_logins": "金鑰登入",

View File

@ -57,7 +57,7 @@
</div>
</div> <!-- /col-md-12 -->
</div> <!-- /row -->
</div>
</div>
{% include 'modals/admin.twig' %}
@ -66,7 +66,7 @@ var lang = {{ lang_admin|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var admin_username = '{{ mailcow_cc_username }}';
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var log_pagination_size = '{{ log_pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var log_pagination_size = Math.trunc('{{ log_pagination_size }}');
</script>
{% endblock %}

File diff suppressed because one or more lines are too long

View File

@ -46,7 +46,7 @@
<div class="col-sm-3 col-5 text-end">{{ lang.fido2.known_ids }}:</div>
<div class="col-sm-9 col-7">
<div class="table-responsive">
<table class="table table-striped table-hover table-condensed" id="fido2_keys">
<table class="table table-striped table-hover table-condensed w-100" id="fido2_keys">
<tr>
<th>ID</th>
<th style="min-width:240px;text-align: right">{{ lang.admin.action }}</th>

View File

@ -26,7 +26,7 @@
var lang_user = {{ lang_user|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var table_for_domain = '{{ domain }}';
</script>
{% endblock %}

View File

@ -38,15 +38,8 @@
</div>
</div>
<div class="d-flex mt-4" style="position: relative">
<div class="btn-group">
<div class="btn-group">
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
<button type="button" class="btn btn-xs-lg btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</a></li>
</ul>
</div>
</div>
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
<button type="button" class="btn btn-xs-lg btn-success ms-2" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</button>
{% if not oauth2_request %}
<button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-secondary ms-auto dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span>

View File

@ -58,7 +58,7 @@
var lang_rl = {{ lang_rl|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var role = '{{ role }}';
var is_dual = {{ is_dual }};
var ALLOW_ADMIN_EMAIL_LOGIN = {{ allow_admin_email_login }};

View File

@ -37,7 +37,7 @@
</p>
{% endif %}
</p>
<table id="quarantinetable" class="table table-striped"></table>
<table id="quarantinetable" class="table table-striped w-100"></table>
<div class="mass-actions-quarantine mt-4">
<div class="btn-group" data-acl="{{ acl.quarantine }}">
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="qitems" href="#"><i class="bi bi-check-all"></i> {{ lang.quarantine.toggle_all }}</a>
@ -66,7 +66,7 @@ var acl = '{{ acl_json|raw }}';
var lang = {{ lang_quarantine|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var role = '{{ role }}';
</script>
{% endblock %}

View File

@ -55,7 +55,7 @@
var lang = {{ lang_queue|raw }};
var lang_datatables = {{ lang_datatables|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var table_for_domain = '{{ domain }}';
</script>
{% endblock %}

View File

@ -4,7 +4,7 @@
var acl = '{{ acl_json|raw }}';
var lang = {{ lang_user|raw }};
var csrf_token = '{{ csrf_token }}';
var pagination_size = '{{ pagination_size }}';
var pagination_size = Math.trunc('{{ pagination_size }}');
var mailcow_cc_username = '{{ mailcow_cc_username }}';
var user_spam_score = [{{ user_spam_score }}];
var lang_datatables = {{ lang_datatables|raw }};

View File

@ -20,6 +20,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
'tfa_data' => $tfa_data,
'fido2_data' => $fido2_data,
'lang_user' => json_encode($lang['user']),
'lang_datatables' => json_encode($lang['datatables']),
];
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {

View File

@ -64,7 +64,7 @@ services:
- redis
clamd-mailcow:
image: mailcow/clamd:1.60
image: mailcow/clamd:1.61
restart: always
depends_on:
- unbound-mailcow
@ -116,7 +116,7 @@ services:
- rspamd
php-fpm-mailcow:
image: mailcow/phpfpm:1.81
image: mailcow/phpfpm:1.82
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on:
- redis-mailcow
@ -181,7 +181,7 @@ services:
- phpfpm
sogo-mailcow:
image: mailcow/sogo:1.113
image: mailcow/sogo:1.115
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
@ -230,7 +230,7 @@ services:
- sogo
dovecot-mailcow:
image: mailcow/dovecot:1.21
image: mailcow/dovecot:1.22
depends_on:
- mysql-mailcow
dns:
@ -411,7 +411,7 @@ services:
acme-mailcow:
depends_on:
- nginx-mailcow
image: mailcow/acme:1.83
image: mailcow/acme:1.84
dns:
- ${IPV4_NETWORK:-172.22.1}.254
environment:
@ -449,7 +449,7 @@ services:
- acme
netfilter-mailcow:
image: mailcow/netfilter:1.50
image: mailcow/netfilter:1.51
stop_grace_period: 30s
depends_on:
- dovecot-mailcow
@ -538,11 +538,10 @@ services:
- watchdog
dockerapi-mailcow:
image: mailcow/dockerapi:2.0
image: mailcow/dockerapi:2.01
security_opt:
- label=disable
restart: always
oom_kill_disable: true
dns:
- ${IPV4_NETWORK:-172.22.1}.254
environment:

View File

@ -56,9 +56,8 @@ done
MAILCOW_DOCKER_COMPOSE=${MAILCOW_DOCKER_COMPOSE:-"docker-compose"}
if [[ "${CONTAINER_ENGINE}" == "docker" ]] && command -v docker compose > /dev/null 2>&1; then
version=$(docker compose version --short)
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
if [[ "${CONTAINER_ENGINE}" == "docker" ]] && docker compose > /dev/null 2>&1; then
if docker compose version --short | grep "^2." > /dev/null 2>&1; then
COMPOSE_VERSION=native
echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
@ -66,12 +65,12 @@ if [[ "${CONTAINER_ENGINE}" == "docker" ]] && command -v docker compose > /dev/n
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually! \e[0m"
else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
fi
elif command -v $MAILCOW_DOCKER_COMPOSE > /dev/null 2>&1; then
version=$($MAILCOW_DOCKER_COMPOSE version --short)
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
elif $MAILCOW_DOCKER_COMPOSE > /dev/null 2>&1; then
if ! [[ $(alias $MAILCOW_DOCKER_COMPOSE 2> /dev/null) ]] ; then
if $MAILCOW_DOCKER_COMPOSE version --short | grep "^2." > /dev/null 2>&1; then
COMPOSE_VERSION=standalone
echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
@ -82,9 +81,11 @@ elif command -v $MAILCOW_DOCKER_COMPOSE > /dev/null 2>&1; then
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
fi
fi
else
echo -e "\e[31mCannot find Docker Compose.\e[0m"
echo -e "\e[31mPlease install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
echo -e "\e[31mPlease install it regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
fi
@ -520,6 +521,15 @@ case ${git_branch} in
mailcow_last_git_version=""
;;
esac
# if [ ${git_branch} == "master" ]; then
# mailcow_git_version=$(git describe --tags `git rev-list --tags --max-count=1`)
# elif [ ${git_branch} == "nightly" ]; then
# mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
# mailcow_last_git_version=""
# else
# mailcow_git_version=$(git rev-parse --short HEAD)
# mailcow_last_git_version=""
# fi
if [[ $SKIP_BRANCH != "y" ]]; then
mailcow_git_commit=$(git rev-parse origin/${git_branch})

View File

@ -26,6 +26,6 @@ services:
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
mysql-mailcow:
image: alpine:3.10
image: alpine:3.17
command: /bin/true
restart: "no"

View File

@ -1,17 +1,25 @@
#!/usr/bin/env bash
# renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
NEXTCLOUD_VERSION=25.0.2
NEXTCLOUD_VERSION=25.0.4
for bin in curl dirmngr; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
echo -ne "Checking prerequisites..."
sleep 1
for bin in curl dirmngr tar bzip2; do
if [[ -z $(which ${bin}) ]]; then echo -ne "\r\033[31mCannot find ${bin}, exiting...\033[0m\n"; exit 1; fi
done
echo -ne "\r\033[32mFound all prerequisites! Continuing...\033[0m\n"
[[ -z ${1} ]] && NC_HELP=y
while [ "$1" != '' ]; do
if [[ $# -ne 1 ]]; then
echo -e "\033[31mPlease use only one parameter at the same time!\033[0m" >&2
exit 2
fi
case "${1}" in
-p|--purge) NC_PURGE=y && shift;;
-i|--install) NC_INSTALL=y && shift;;
-u|--update) NC_UPDATE=y && shift;;
-r|--resetpw) NC_RESETPW=y && shift;;
-h|--help) NC_HELP=y && shift;;
*) echo "Unknown parameter: ${1}" && shift;;
@ -22,13 +30,11 @@ if [[ ${NC_HELP} == "y" ]]; then
printf 'Usage:\n\n'
printf ' -p|--purge\n Purge Nextcloud\n'
printf ' -i|--install\n Install Nextcloud\n'
printf ' -u|--update\n Update Nextcloud\n'
printf ' -r|--resetpw\n Reset password\n\n'
exit 0
fi
[[ ${NC_PURGE} == "y" ]] && [[ ${NC_INSTALL} == "y" ]] && { echo "Cannot use -p and -i at the same time!"; exit 1; }
[[ ${NC_PURGE} == "y" ]] && [[ ${NC_RESETPW} == "y" ]] && { echo "Cannot use -p and -r at the same time!"; exit 1; }
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ${SCRIPT_DIR}/../
source mailcow.conf
@ -41,8 +47,27 @@ if [[ ${NC_PURGE} == "y" ]]; then
exit 1
fi
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)"
echo -e "\033[33mDetecting Database information...\033[0m"
if [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "Show databases" | grep "nextcloud") ]]; then
echo -e "\033[32mFound seperate Nextcloud database (newer scheme)!\033[0m"
echo -e "\033[31mPurging...\033[0m"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP DATABASE nextcloud;" > /dev/null
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "DROP USER 'nextcloud'@'%';" > /dev/null
elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'oc_%'") && $? -eq 0 ]]; then
echo -e "\033[32mFound Nextcloud (oc) tables inside of mailcow database (old scheme)!\033[0m"
echo -e "\033[31mPurging...\033[0m"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'oc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null
elif [[ $(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} mailcow -e "SHOW TABLES LIKE 'nc_%'") && $? -eq 0 ]]; then
echo -e "\033[32mFound Nextcloud (nc) tables inside of mailcow database (old scheme)!\033[0m"
echo -e "\033[31mPurging...\033[0m"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT IFNULL(GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';'),'SELECT NULL;') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)" > /dev/null
else
echo -e "\033[31mError: No Nextcloud databases/tables found!"
echo -e "\033[33mNot purging anything...\033[0m"
exit 1
fi
docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c ' cat <<EOF | redis-cli
SELECT 10
FLUSHDB
@ -58,9 +83,10 @@ EOF
docker restart $(docker ps -aqf name=nginx-mailcow)
echo -e "\033[32mNextcloud has been uninstalled sucessfully!\033[0m"
elif [[ ${NC_UPDATE} == "y" ]]; then
exit;
read -r -p "Are you sure you want to update Nextcloud? [y/N] " response
read -r -p "Are you sure you want to update Nextcloud (with Nextclouds own updater)? [y/N] " response
response=${response,,}
if [[ ! "$response" =~ ^(yes|y)$ ]]; then
echo "OK, aborting."
@ -68,23 +94,14 @@ elif [[ ${NC_UPDATE} == "y" ]]; then
fi
if [ ! -f data/web/nextcloud/occ ]; then
echo "Nextcloud occ not found. Is Nextcloud installed?"
echo -e "\033[31mError: Nextcloud occ not found. Is Nextcloud installed?\033[0m"
exit 1
fi
if ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
echo "Nextcloud seems not to be installed."
exit 1
elif ! grep -q 'version: 20\.' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
echo "Cannot upgrade to new major version, please update manually."
exit 1
else
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/nextcloud-$NEXTCLOUD_VERSION.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \
&& mkdir -p ./data/web/nextcloud/data \
&& chmod +x ./data/web/nextcloud/occ \
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" \
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "php /web/nextcloud/updater/updater.phar"
fi
elif [[ ${NC_INSTALL} == "y" ]]; then
@ -97,25 +114,48 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
[[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; }
fi
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
echo -e "\033[33mDownloading \033[34mNextcloud ${NEXTCLOUD_VERSION}\033[33m...\033[0m"
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/nextcloud-$NEXTCLOUD_VERSION.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \
&& mkdir -p ./data/web/nextcloud/data \
&& chmod +x ./data/web/nextcloud/occ
echo -e "\033[33mCreating 'nextcloud' database...\033[0m"
NC_DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
NC_DBUSER=nextcloud
NC_DBNAME=nextcloud
echo -ne "[1/3] Creating 'nextcloud' database"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE DATABASE ${NC_DBNAME};"
sleep 2
echo -ne "\r[2/3] Creating 'nextcloud' database user"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "CREATE USER '${NC_DBUSER}'@'%' IDENTIFIED BY '${NC_DBPASS}';"
sleep 2
echo -ne "\r[3/3] Granting 'nextcloud' user all permissions on database 'nextcloud'"
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "GRANT ALL PRIVILEGES ON ${NC_DBNAME}.* TO '${NC_DBUSER}'@'%';"
sleep 2
echo ""
echo -e "\033[33mInstalling Nextcloud...\033[0m"
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
echo -ne "[1/4] Setting correct permissions for www-data"
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud"
sleep 2
echo -ne "\r[2/4] Running occ maintenance:install to install Nextcloud"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \
--database mysql \
--database-host mysql \
--database-name ${DBNAME} \
--database-user ${DBUSER} \
--database-pass ${DBPASS} \
--database-name ${NC_DBNAME} \
--database-user ${NC_DBUSER} \
--database-pass ${NC_DBPASS} \
--admin-user admin \
--admin-pass ${ADMIN_NC_PASS} \
--data-dir /web/nextcloud/data
--data-dir /web/nextcloud/data > /dev/null 2>&1
echo -ne "\r[3/4] Setting custom parameters inside the Nextcloud config file"
echo ""
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings config:system:set redis host --value=redis --type=string; \
/web/nextcloud/occ --no-warnings config:system:set redis port --value=6379 --type=integer; \
/web/nextcloud/occ --no-warnings config:system:set redis timeout --value=0.0 --type=integer; \
@ -141,13 +181,28 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
#/web/nextcloud/occ --no-warnings config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}; \
#/web/nextcloud/occ --no-warnings config:system:set user_backends 0 class --value=OC_User_IMAP; \
echo -e "\r[4/4] Enabling Nginx Configuration"
cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf
sleep 2
echo "Restarting Nginx..."
echo ""
echo -e "\033[33mFinalizing installation...\033[0m"
docker restart $(docker ps -aqf name=nginx-mailcow)
echo "Login as admin with password: ${ADMIN_NC_PASS}"
echo ""
echo "******************************************"
echo "* SAVE THESE CREDENTIALS *"
echo "* INSTALL DATE: $(date +%Y-%m-%d_%H-%M-%S) *"
echo "******************************************"
echo ""
echo -e "\033[36mDatabase name: ${NC_DBNAME}\033[0m"
echo -e "\033[36mDatabase user: ${NC_DBUSER}\033[0m"
echo -e "\033[36mDatabase password: ${NC_DBPASS}\033[0m"
echo ""
echo -e "\033[31mUI admin password: ${ADMIN_NC_PASS}\033[0m"
echo ""
elif [[ ${NC_RESETPW} == "y" ]]; then
printf 'You are about to set a new password for a Nextcloud user.\n\nDo not use this option if your Nextcloud is configured to use mailcow for authentication.\nSet a new password for the corresponding mailbox in mailcow, instead.\n\n'
@ -163,5 +218,4 @@ elif [[ ${NC_RESETPW} == "y" ]]; then
read -p "Enter the username: " NC_USER
done
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ user:resetpassword ${NC_USER}
fi

View File

@ -23,9 +23,8 @@ function validate_input()
function detect_docker_compose_command()
{
if ! [[ "${DOCKER_COMPOSE_VERSION}" == "native" ]] && ! [[ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]]; then
if command -v docker compose > /dev/null 2>&1; then
version=$(docker compose version --short)
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
if docker compose > /dev/null 2>&1; then
if docker compose version --short | grep "2." > /dev/null 2>&1; then
DOCKER_COMPOSE_VERSION=native
COMPOSE_COMMAND="docker compose"
echo -e "\e[31mFound Docker Compose Plugin (native).\e[0m"
@ -37,19 +36,20 @@ function detect_docker_compose_command()
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
fi
elif command -v docker-compose > /dev/null 2>&1; then
version=$(docker-compose version --short)
if [[ $version =~ ^2\.([0-9]+)\.([0-9]+) ]]; then
DOCKER_COMPOSE_VERSION=standalone
COMPOSE_COMMAND="docker-compose"
echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
sleep 2
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
elif docker-compose > /dev/null 2>&1; then
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
DOCKER_COMPOSE_VERSION=standalone
COMPOSE_COMMAND="docker-compose"
echo -e "\e[31mFound Docker Compose Standalone.\e[0m"
echo -e "\e[31mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
sleep 2
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install regarding to this doc site: https://mailcow.github.io/mailcow-dockerized-docs/i_u_m/i_u_m_install/\e[0m"
exit 1
fi
fi
else