Compare commits

..

1 Commits

Author SHA1 Message Date
FreddleSpl0it
43f570e761 [Web] switch from GET to POST for datatable requests 2026-03-03 08:10:16 +01:00
7 changed files with 223 additions and 329 deletions

View File

@@ -1,161 +1,47 @@
# SOGo built from source to enable security patch application
# Repository: https://github.com/Alinto/sogo
# Version: SOGo-5.12.4
#
# Applied security patches:
# - 16ab99e7cf8db2c30b211f0d5e338d7f9e3a9efb: XSS vulnerability in theme parameter
#
# To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes
FROM debian:bookworm
FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG SOGO_VERSION=SOGo-5.12.4
ARG SOPE_VERSION=SOPE-5.12.4
# Security patches to apply (space-separated commit hashes)
ARG SOGO_SECURITY_PATCHES="16ab99e7cf8db2c30b211f0d5e338d7f9e3a9efb"
ARG DEBIAN_VERSION=bookworm
ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.19
ENV LC_ALL=C
# Install dependencies, build SOPE and SOGo, then clean up (all in one layer to minimize image size)
RUN apt-get update && apt-get install -y --no-install-recommends \
# Build dependencies
git \
build-essential \
gobjc \
gnustep-make \
gnustep-base-runtime \
libgnustep-base-dev \
libxml2-dev \
libldap2-dev \
libssl-dev \
zlib1g-dev \
libpq-dev \
libmariadb-dev-compat \
libmemcached-dev \
libsodium-dev \
libcurl4-openssl-dev \
libzip-dev \
libytnef0-dev \
curl \
ca-certificates \
# Runtime dependencies
apt-transport-https \
gettext \
gnupg \
mariadb-client \
rsync \
supervisor \
syslog-ng \
syslog-ng-core \
syslog-ng-mod-redis \
dirmngr \
netcat-traditional \
psmisc \
wget \
patch \
libobjc4 \
libxml2 \
libldap-2.5-0 \
libssl3 \
zlib1g \
libmariadb3 \
libmemcached11 \
libsodium23 \
libcurl4 \
libzip4 \
libytnef0 \
# Download gosu
# Prerequisites
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
&& apt-get update && apt-get install -y --no-install-recommends \
apt-transport-https \
ca-certificates \
gettext \
gnupg \
mariadb-client \
rsync \
supervisor \
syslog-ng \
syslog-ng-core \
syslog-ng-mod-redis \
dirmngr \
netcat-traditional \
psmisc \
wget \
patch \
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true \
# Build SOPE
&& git clone --depth 1 --branch ${SOPE_VERSION} https://github.com/Alinto/sope.git /tmp/sope \
&& cd /tmp/sope \
&& rm -rf .git \
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
&& ./configure --prefix=/usr --disable-debug --disable-strip \
&& make -j$(nproc) \
&& make install \
&& cd / \
&& rm -rf /tmp/sope \
# Build SOGo with security patches
&& git clone --depth 1 --branch ${SOGO_VERSION} https://github.com/Alinto/sogo.git /tmp/sogo \
&& cd /tmp/sogo \
&& git config user.email "builder@mailcow.local" \
&& git config user.name "SOGo Builder" \
&& for patch in ${SOGO_SECURITY_PATCHES}; do \
echo "Applying security patch: ${patch}"; \
git fetch origin ${patch} && git cherry-pick ${patch}; \
done \
&& rm -rf .git \
&& . /usr/share/GNUstep/Makefiles/GNUstep.sh \
&& ./configure --disable-debug --disable-strip \
&& make -j$(nproc) \
&& make install \
&& cd / \
&& rm -rf /tmp/sogo \
# Strip binaries
&& strip --strip-unneeded /usr/local/sbin/sogod 2>/dev/null || true \
&& strip --strip-unneeded /usr/local/sbin/sogo-tool 2>/dev/null || true \
&& strip --strip-unneeded /usr/local/sbin/sogo-ealarms-notify 2>/dev/null || true \
&& strip --strip-unneeded /usr/local/sbin/sogo-slapd-sockd 2>/dev/null || true \
# Remove build dependencies and clean up
&& apt-get purge -y --auto-remove \
git \
build-essential \
gobjc \
gnustep-make \
libgnustep-base-dev \
libxml2-dev \
libldap2-dev \
libssl-dev \
zlib1g-dev \
libpq-dev \
libmariadb-dev-compat \
libmemcached-dev \
libsodium-dev \
libcurl4-openssl-dev \
libzip-dev \
libytnef0-dev \
curl \
&& apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/share/doc/* \
&& rm -rf /usr/share/man/* \
&& rm -rf /var/cache/debconf/* \
&& rm -rf /tmp/* \
&& rm -rf /root/.cache \
&& find /usr/local/lib -name '*.a' -delete \
&& find /usr/lib -name '*.a' -delete \
&& mkdir -p /usr/share/doc/sogo \
&& mkdir /usr/share/doc/sogo \
&& touch /usr/share/doc/sogo/empty.sh \
&& wget -O- https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 | gpg --dearmor | apt-key add - \
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} main" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update && apt-get install -y --no-install-recommends \
sogo \
sogo-activesync \
&& apt-get autoclean \
&& rm -rf /var/lib/apt/lists/* \
&& touch /etc/default/locale
# Configure library paths
RUN echo "/usr/lib64" > /etc/ld.so.conf.d/sogo.conf \
&& echo "/usr/local/lib/sogo" >> /etc/ld.so.conf.d/sogo.conf \
&& echo "/usr/local/lib/GNUstep/Frameworks/SOGo.framework/Versions/5/sogo" >> /etc/ld.so.conf.d/sogo.conf \
&& ldconfig
# Create sogo user and group
RUN groupadd -r -g 999 sogo \
&& useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \
&& mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo \
&& chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo
# Create symlinks for SOGo binaries
RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \
&& ln -s /usr/local/sbin/sogo-tool /usr/sbin/sogo-tool \
&& ln -s /usr/local/sbin/sogo-ealarms-notify /usr/sbin/sogo-ealarms-notify \
&& ln -s /usr/local/sbin/sogo-slapd-sockd /usr/sbin/sogo-slapd-sockd
# Copy configuration files and scripts
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
@@ -170,4 +56,4 @@ RUN chmod +x /bootstrap-sogo.sh \
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View File

@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026
# Whitelist generated by Postwhite v3.4 on Sun Feb 1 00:29:33 UTC 2026
# https://github.com/stevejenkins/postwhite/
# 2174 total rules
# 2102 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403:2800::/53 permit
@@ -52,11 +52,7 @@
8.25.194.0/23 permit
8.25.196.0/23 permit
8.36.116.0/24 permit
8.39.54.0/23 permit
8.39.54.250/31 permit
8.39.144.0/24 permit
8.40.222.0/23 permit
8.40.222.250/31 permit
12.130.86.238 permit
13.107.213.51 permit
13.107.246.51 permit
@@ -69,7 +65,6 @@
13.111.191.0/24 permit
13.216.7.111 permit
13.216.54.180 permit
13.247.164.219 permit
15.200.21.50 permit
15.200.44.248 permit
15.200.201.185 permit
@@ -173,7 +168,6 @@
34.215.104.144 permit
34.218.115.239 permit
34.225.212.172 permit
34.241.242.183 permit
35.83.148.184 permit
35.155.198.111 permit
35.158.23.94 permit
@@ -197,7 +191,6 @@
40.233.64.216 permit
40.233.83.78 permit
40.233.88.28 permit
43.239.212.33 permit
44.206.138.57 permit
44.210.169.44 permit
44.217.45.156 permit
@@ -279,7 +272,6 @@
50.112.246.219 permit
52.1.14.157 permit
52.5.230.59 permit
52.6.74.205 permit
52.12.53.23 permit
52.13.214.179 permit
52.26.1.71 permit
@@ -336,7 +328,6 @@
54.244.54.130 permit
54.244.242.0/24 permit
54.255.61.23 permit
56.124.6.228 permit
57.103.64.0/18 permit
57.129.93.249 permit
62.13.128.0/24 permit
@@ -402,7 +393,6 @@
65.110.161.77 permit
65.123.29.213 permit
65.123.29.220 permit
65.154.166.0/24 permit
65.212.180.36 permit
66.102.0.0/20 permit
66.119.150.192/26 permit
@@ -707,9 +697,7 @@
87.248.117.205 permit
87.253.232.0/21 permit
89.22.108.0/24 permit
91.198.2.177 permit
91.198.2.217 permit
91.198.2.222 permit
91.198.2.0/24 permit
91.211.240.0/22 permit
94.236.119.0/26 permit
95.131.104.0/21 permit
@@ -1206,9 +1194,6 @@
99.78.197.208/28 permit
103.9.96.0/22 permit
103.28.42.0/24 permit
103.84.217.15 permit
103.84.217.238 permit
103.89.75.238 permit
103.151.192.0/23 permit
103.168.172.128/27 permit
103.237.104.0/22 permit
@@ -1369,9 +1354,6 @@
117.120.16.0/21 permit
119.42.242.52/31 permit
119.42.242.156 permit
121.244.91.48 permit
121.244.91.52 permit
122.15.156.182 permit
123.126.78.64/29 permit
124.108.96.24/31 permit
124.108.96.28/31 permit
@@ -1437,21 +1419,7 @@
134.170.141.64/26 permit
134.170.143.0/24 permit
134.170.174.0/24 permit
135.84.80.0/24 permit
135.84.81.0/24 permit
135.84.82.0/24 permit
135.84.83.0/24 permit
135.84.216.0/22 permit
136.143.160.0/24 permit
136.143.161.0/24 permit
136.143.162.0/24 permit
136.143.176.0/24 permit
136.143.177.0/24 permit
136.143.178.49 permit
136.143.182.0/23 permit
136.143.184.0/24 permit
136.143.188.0/24 permit
136.143.190.0/23 permit
136.146.128.0/20 permit
136.147.128.0/20 permit
136.147.135.0/24 permit
@@ -1467,7 +1435,6 @@
139.138.46.219 permit
139.138.57.55 permit
139.138.58.119 permit
139.167.79.86 permit
139.180.17.0/24 permit
140.238.148.191 permit
141.148.55.217 permit
@@ -1556,10 +1523,8 @@
159.135.224.0/20 permit
159.135.228.10 permit
159.183.0.0/16 permit
159.183.14.233 permit
159.183.68.71 permit
159.183.79.38 permit
159.183.121.182 permit
159.183.129.172 permit
160.1.62.192 permit
161.38.192.0/20 permit
@@ -1585,10 +1550,6 @@
164.152.23.32 permit
164.152.25.241 permit
164.177.132.168/30 permit
165.173.128.0/24 permit
165.173.180.1 permit
165.173.180.250/31 permit
165.173.182.250/31 permit
166.78.68.0/22 permit
166.78.68.221 permit
166.78.69.169 permit
@@ -1618,18 +1579,6 @@
168.245.12.252 permit
168.245.46.9 permit
168.245.127.231 permit
169.148.129.0/24 permit
169.148.131.0/24 permit
169.148.138.0/24 permit
169.148.142.10 permit
169.148.142.33 permit
169.148.144.0/25 permit
169.148.144.10 permit
169.148.146.0/23 permit
169.148.175.3 permit
169.148.179.3 permit
169.148.188.0/24 permit
169.148.188.182 permit
170.9.232.254 permit
170.10.128.0/24 permit
170.10.129.0/24 permit
@@ -1663,7 +1612,8 @@
182.50.78.64/28 permit
183.240.219.64/29 permit
185.4.120.0/22 permit
185.11.255.144 permit
185.11.253.128/27 permit
185.11.255.0/24 permit
185.12.80.0/22 permit
185.28.196.0/22 permit
185.58.84.93 permit
@@ -1677,16 +1627,8 @@
185.138.56.128/25 permit
185.189.236.0/22 permit
185.211.120.0/22 permit
185.233.188.68 permit
185.233.188.75 permit
185.233.188.84 permit
185.233.188.160 permit
185.233.188.176 permit
185.233.188.247 permit
185.233.189.44 permit
185.233.189.98 permit
185.233.189.122 permit
185.233.189.228 permit
185.233.188.0/23 permit
185.233.190.0/23 permit
185.250.236.0/22 permit
185.250.239.148 permit
185.250.239.168 permit
@@ -1762,9 +1704,7 @@
193.109.254.0/23 permit
193.122.128.100 permit
193.123.56.63 permit
193.142.157.15 permit
193.142.157.125 permit
193.142.157.158 permit
193.142.157.0/24 permit
193.142.157.191 permit
193.142.157.198 permit
194.19.134.0/25 permit
@@ -1824,16 +1764,7 @@
199.16.156.0/22 permit
199.33.145.1 permit
199.33.145.32 permit
199.34.22.36 permit
199.59.148.0/22 permit
199.67.80.2 permit
199.67.80.20 permit
199.67.82.2 permit
199.67.82.20 permit
199.67.84.0/24 permit
199.67.86.0/24 permit
199.67.88.0/24 permit
199.67.90.0/24 permit
199.101.161.130 permit
199.101.162.0/25 permit
199.122.120.0/21 permit
@@ -1889,8 +1820,6 @@
204.92.114.187 permit
204.92.114.203 permit
204.92.114.204/31 permit
204.141.32.0/23 permit
204.141.42.0/23 permit
204.216.164.202 permit
204.220.160.0/21 permit
204.220.168.0/21 permit
@@ -2068,6 +1997,8 @@
212.227.126.225 permit
212.227.126.226 permit
212.227.126.227 permit
213.95.19.64/27 permit
213.95.135.4 permit
213.199.128.139 permit
213.199.128.145 permit
213.199.138.181 permit
@@ -2157,9 +2088,6 @@
2001:748:400:3301::3 permit
2001:748:400:3301::4 permit
2404:6800:4000::/36 permit
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
2607:f8b0:4000::/36 permit
2620:109:c003:104::/64 permit
2620:109:c003:104::215 permit

View File

@@ -392,7 +392,6 @@ rspamd_config:register_symbol({
local rspamd_http = require "rspamd_http"
local rcpts = task:get_recipients('smtp')
local lua_util = require "lua_util"
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local function remove_moo_tag()
local moo_tag_header = task:get_header('X-Moo-Tag', false)
@@ -417,9 +416,12 @@ rspamd_config:register_symbol({
-- Check if recipient has a tag (contains '+')
local tag = nil
if tagged_rcpt ~= nil then
tag = tagged_rcpt
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
if rcpt_user:find('%+') then
local base_user, tag_part = rcpt_user:match('^(.-)%+(.+)$')
if base_user and tag_part then
tag = tag_part
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
end
end
if not tag then
@@ -498,8 +500,7 @@ rspamd_config:register_symbol({
else
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
local sbj = task:get_header('Subject') or ''
local tag_value = tag[1] and tag[1].options and tag[1].options[1] or ''
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?='
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
task:set_milter_reply({
remove_headers = {
['Subject'] = 1,
@@ -944,4 +945,4 @@ rspamd_config:register_symbol({
return true
end,
priority = 1
})
})

View File

@@ -140,17 +140,32 @@ function session_check() {
);
return false;
}
if (!empty($_POST)) {
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_token'
);
return false;
// Check if this is a POST request (form-encoded or JSON)
$is_post_request = !empty($_POST) || (
isset($_SERVER['CONTENT_TYPE']) &&
strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false
);
if ($is_post_request) {
// Skip CSRF check for DataTables server-side processing endpoints
// These are read-only operations (equivalent to GET) authenticated by session
$is_search_endpoint = (
isset($_GET['query']) &&
preg_match('#^search/(domain|mailbox)$#', $_GET['query'])
);
if (!$is_search_endpoint && !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();
}
unset($_POST['csrf_token']);
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
$_SESSION['CSRF']['TIME'] = time();
}
return true;
}

View File

@@ -471,8 +471,13 @@ jQuery(function($){
hideTableExpandCollapseBtn('#tab-domains', '#domain_table');
},
ajax: {
type: "GET",
url: "/api/v1/get/domain/datatables",
type: "POST",
url: "/api/v1/search/domain",
contentType: "application/json",
processData: false,
data: function(d) {
return JSON.stringify(d);
},
dataSrc: function(json){
$.each(json.data, function(i, item) {
item.domain_name = escapeHtml(item.domain_name);
@@ -898,8 +903,13 @@ jQuery(function($){
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
},
ajax: {
type: "GET",
url: "/api/v1/get/mailbox/datatables",
type: "POST",
url: "/api/v1/search/mailbox",
contentType: "application/json",
processData: false,
data: function(d) {
return JSON.stringify(d);
},
dataSrc: function(json){
$.each(json.data, function (i, item) {
item.quota = {

View File

@@ -91,6 +91,11 @@ if (isset($_GET['query'])) {
if ($action == 'delete') {
$_POST['items'] = $request;
}
// search
if ($action == 'search') {
// placeholder for search, as the request body is already decoded and available in $requestDecoded
}
}
api_log($_POST);
@@ -457,47 +462,6 @@ if (isset($_GET['query'])) {
case "domain":
switch ($object) {
case "datatables":
$table = ['domain', 'd'];
$primaryKey = 'domain';
$columns = [
['db' => 'domain', 'dt' => 2],
['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"],
['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"],
['db' => 'defquota', 'dt' => 7],
['db' => 'maxquota', 'dt' => 8],
['db' => 'backupmx', 'dt' => 10],
['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']],
['db' => 'active', 'dt' => 15],
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
global $pdo;
if($_SESSION['mailcow_cc_role'] === 'admin') {
$data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns);
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
'INNER JOIN domain_admins as da ON da.domain = d.domain',
[
'condition' => 'da.active = 1 and da.username = :username',
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
]);
}
if (!empty($data['data'])) {
$domainsData = [];
foreach ($data['data'] as $domain) {
if ($details = mailbox('get', 'domain_details', $domain[2])) {
$domainsData[] = $details;
}
}
$data['data'] = $domainsData;
}
process_get_return($data);
break;
case "all":
$tags = null;
if (isset($_GET['tags']) && $_GET['tags'] != '')
@@ -997,46 +961,6 @@ if (isset($_GET['query'])) {
break;
case "mailbox":
switch ($object) {
case "datatables":
$table = ['mailbox', 'm'];
$primaryKey = 'username';
$columns = [
['db' => 'username', 'dt' => 2],
['db' => 'quota', 'dt' => 3],
['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"],
['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"],
['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"],
['db' => 'name', 'dt' => 7],
['db' => 'messages', 'dt' => 20, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
['db' => 'tags', 'dt' => 23, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
['db' => 'active', 'dt' => 24],
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
global $pdo;
if($_SESSION['mailcow_cc_role'] === 'admin') {
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)");
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
'INNER JOIN domain_admins as da ON da.domain = m.domain',
[
'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username",
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
]);
}
if (!empty($data['data'])) {
$mailboxData = [];
foreach ($data['data'] as $mailbox) {
if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) {
$mailboxData[] = $details;
}
}
$data['data'] = $mailboxData;
}
process_get_return($data);
break;
case "all":
case "reduced":
$tags = null;
@@ -1625,6 +1549,136 @@ if (isset($_GET['query'])) {
}
}
break;
case "search":
function process_search_return($return) {
if ($return === false) {
echo json_encode(array(
'type' => 'error',
'msg' => 'Cannot get item'
));
}
else {
echo json_encode($return, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}
// only allow POST requests to SEARCH API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(405);
echo json_encode(array(
'type' => 'error',
'msg' => 'only POST method is allowed'
));
exit();
}
// Load SSP class
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
global $pdo;
switch ($category) {
case "domain":
$table = ['domain', 'd'];
$primaryKey = 'domain';
$columns = [
['db' => 'domain', 'dt' => 2],
['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"],
['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"],
['db' => 'defquota', 'dt' => 7],
['db' => 'maxquota', 'dt' => 8],
['db' => 'backupmx', 'dt' => 10],
['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']],
['db' => 'active', 'dt' => 15],
];
if($_SESSION['mailcow_cc_role'] === 'admin') {
$data = SSP::simple($requestDecoded, $pdo, $table, $primaryKey, $columns);
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
$data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns,
'INNER JOIN domain_admins as da ON da.domain = d.domain',
[
'condition' => 'da.active = 1 and da.username = :username',
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
]);
} else {
http_response_code(403);
echo json_encode(array(
'type' => 'error',
'msg' => 'Insufficient permissions'
));
exit();
}
if (!empty($data['data'])) {
$domainsData = [];
foreach ($data['data'] as $domain) {
if ($details = mailbox('get', 'domain_details', $domain[2])) {
$domainsData[] = $details;
}
}
$data['data'] = $domainsData;
}
process_search_return($data);
break;
case "mailbox":
$table = ['mailbox', 'm'];
$primaryKey = 'username';
$columns = [
['db' => 'username', 'dt' => 2],
['db' => 'quota', 'dt' => 3],
['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"],
['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"],
['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"],
['db' => 'name', 'dt' => 7],
['db' => 'messages', 'dt' => 20, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
['db' => 'tags', 'dt' => 23, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
['db' => 'active', 'dt' => 24],
];
if($_SESSION['mailcow_cc_role'] === 'admin') {
$data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns, null,
"(`m`.`kind` = '' OR `m`.`kind` = NULL)");
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
$data = SSP::complex($requestDecoded, $pdo, $table, $primaryKey, $columns,
'INNER JOIN domain_admins as da ON da.domain = m.domain',
[
'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username",
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
]);
} else {
http_response_code(403);
echo json_encode(array(
'type' => 'error',
'msg' => 'Insufficient permissions'
));
exit();
}
if (!empty($data['data'])) {
$mailboxData = [];
foreach ($data['data'] as $mailbox) {
if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) {
$mailboxData[] = $details;
}
}
$data['data'] = $mailboxData;
}
process_search_return($data);
break;
default:
http_response_code(404);
echo json_encode(array(
'type' => 'error',
'msg' => 'Invalid search category'
));
break;
}
break;
case "delete":
if ($_SESSION['mailcow_cc_api_access'] == 'ro' || isset($_SESSION['pending_mailcow_cc_username']) || !isset($_SESSION["mailcow_cc_username"])) {
http_response_code(403);

View File

@@ -200,7 +200,7 @@ services:
- phpfpm
sogo-mailcow:
image: ghcr.io/mailcow/sogo:5.12.4-2
image: ghcr.io/mailcow/sogo:5.12.4-1
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}