Add Rspamd UI reverse-proxy view and sync mail stack helpers

- Insert rspamd_ui_proxy in emailPremium/views.py with http.client and csrf_exempt imports so /emailPremium/Rspamd/ui matches urls.py (Rspamd 3.8+ controller headers).
- Refresh plogical/mailUtilities.py from tested vendor fixes (Postfix/Dovecot/Rspamd install paths, SnappyMail-related mail flows).
This commit is contained in:
master3395
2026-04-11 19:38:27 +02:00
parent 7306fcb87d
commit ca6cbb7ebd
2 changed files with 167 additions and 5 deletions

View File

@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import time import time
import http.client
from django.shortcuts import redirect from django.shortcuts import redirect
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from loginSystem.models import Administrator from loginSystem.models import Administrator
from mailServer.models import Domains, EUsers from mailServer.models import Domains, EUsers
@@ -1268,6 +1270,153 @@ def Rspamd(request):
}, 'admin') }, 'admin')
return proc.render() return proc.render()
###Rspamd
_RSPAMD_UPSTREAM = ('127.0.0.1', 11334)
_RSPAMD_HOP_RESPONSE = frozenset({
'connection', 'transfer-encoding', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade', 'content-encoding',
})
@csrf_exempt
def rspamd_ui_proxy(request, subpath=None):
"""Reverse-proxy Rspamd controller UI (localhost:11334) for logged-in admins only."""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
except KeyError:
return redirect(loadLoginPage)
if mailUtilities.checkIfRspamdInstalled() != 1:
return HttpResponse(
'Rspamd is not installed.',
status=503,
content_type='text/plain; charset=utf-8',
)
proxy_base = request.build_absolute_uri('/emailPremium/Rspamd/ui').rstrip('/')
path = '/'
if subpath:
path = '/' + subpath.lstrip('/')
q = request.META.get('QUERY_STRING', '')
if q:
full_path = path + '?' + q
else:
full_path = path
forward_method = request.method
if forward_method == 'HEAD':
forward_method = 'GET'
body = None
if forward_method in ('POST', 'PUT', 'PATCH', 'DELETE'):
body = request.body
headers = {}
acc = request.META.get('HTTP_ACCEPT')
if acc:
headers['Accept'] = acc
al = request.META.get('HTTP_ACCEPT_LANGUAGE')
if al:
headers['Accept-Language'] = al
ua = request.META.get('HTTP_USER_AGENT')
if ua:
headers['User-Agent'] = ua
auth = request.META.get('HTTP_AUTHORIZATION')
if auth:
headers['Authorization'] = auth
ct = request.META.get('CONTENT_TYPE')
if ct and forward_method in ('POST', 'PUT', 'PATCH'):
headers['Content-Type'] = ct
cookie = request.META.get('HTTP_COOKIE')
if cookie:
headers['Cookie'] = cookie
xhr = request.META.get('HTTP_X_REQUESTED_WITH')
if xhr:
headers['X-Requested-With'] = xhr
# Rspamd 3.8+ controller uses custom headers (not only query params). The stock
# proxy whitelist omitted these, which breaks getmap/savemap and can break saveactions.
_rspamd_hdr_map = (
('HTTP_PASSWORD', 'Password'),
('HTTP_MAP', 'Map'),
('HTTP_IP', 'IP'),
('HTTP_USER', 'User'),
('HTTP_FROM', 'From'),
('HTTP_RCPT', 'Rcpt'),
('HTTP_HELO', 'Helo'),
('HTTP_HOSTNAME', 'Hostname'),
('HTTP_PASS', 'Pass'),
('HTTP_CLASSIFIER', 'classifier'),
('HTTP_CLASS', 'class'),
('HTTP_FLAG', 'flag'),
('HTTP_WEIGHT', 'weight'),
('HTTP_HASH', 'Hash'),
)
for meta_key, out_name in _rspamd_hdr_map:
val = request.META.get(meta_key)
if val:
headers[out_name] = val
conn = None
try:
conn = http.client.HTTPConnection(
_RSPAMD_UPSTREAM[0], _RSPAMD_UPSTREAM[1], timeout=120,
)
conn.request(forward_method, full_path, body=body, headers=headers)
upstream = conn.getresponse()
data = upstream.read()
status = upstream.status
except (ConnectionRefusedError, OSError, http.client.HTTPException) as _e:
logging.CyberCPLogFileWriter.writeToFile(
'rspamd_ui_proxy upstream error: %s' % (type(_e).__name__,),
)
return HttpResponse(
'Could not reach Rspamd on 127.0.0.1:11334. Is rspamd running?',
status=502,
content_type='text/plain; charset=utf-8',
)
finally:
if conn is not None:
try:
conn.close()
except Exception:
pass
if request.method == 'HEAD':
out = HttpResponse(status=status)
data = b''
else:
out = HttpResponse(data, status=status)
for hdr, val in upstream.getheaders():
key = hdr.lower()
if key in _RSPAMD_HOP_RESPONSE:
continue
if key == 'location':
val = _rewrite_rspamd_location(val, proxy_base)
if request.method == 'HEAD' and key == 'content-length':
continue
out[hdr] = val
return out
def _rewrite_rspamd_location(location, proxy_base):
if not location:
return location
if location.startswith('http://127.0.0.1:11334'):
return proxy_base + location[len('http://127.0.0.1:11334'):]
if location.startswith('http://[::1]:11334'):
return proxy_base + location[len('http://[::1]:11334'):]
if location.startswith('/') and not location.startswith('//'):
return proxy_base + location
return location
def installRspamd(request): def installRspamd(request):
try: try:
userID = request.session['userID'] userID = request.session['userID']

View File

@@ -211,13 +211,26 @@ class mailUtilities:
command = f'chown lscpd:lscpd /usr/local/lscp/cyberpanel/snappymail/data/_data_/_default_/plugins/mailbox-detect/index.php' command = f'chown lscpd:lscpd /usr/local/lscp/cyberpanel/snappymail/data/_data_/_default_/plugins/mailbox-detect/index.php'
ProcessUtilities.executioner(command) ProcessUtilities.executioner(command)
### Enable plugins: merge mailbox-detect + bundled list-unsubscribe (preserve other plugins) ### Enable plugins and enable mailbox creation plugin
from plogical.snappymail_plugin_utilities import merge_plugin_into_application_ini labsDataLines = open(labsPath, 'r').readlines()
from plogical.snappymail_plugin_utilities import install_and_enable_list_unsubscribe_header_plugin PluginsActivator = 0
WriteToFile = open(labsPath, 'w')
merge_plugin_into_application_ini(labsPath, 'mailbox-detect') for lines in labsDataLines:
install_and_enable_list_unsubscribe_header_plugin() if lines.find('[plugins]') > -1:
PluginsActivator = 1
WriteToFile.write(lines)
elif PluginsActivator and lines.find('enable = ') > -1:
WriteToFile.write(f'enable = On\n')
elif PluginsActivator and lines.find('enabled_list = ') > -1:
WriteToFile.write(f'enabled_list = "mailbox-detect"\n')
elif PluginsActivator == 1 and lines.find('[defaults]') > -1:
PluginsActivator = 0
WriteToFile.write(lines)
else:
WriteToFile.write(lines)
WriteToFile.close()
## enable auto create in the enabled plugin ## enable auto create in the enabled plugin
PluginsFilePath = '/usr/local/lscp/cyberpanel/snappymail/data/_data_/_default_/configs/plugin-mailbox-detect.json' PluginsFilePath = '/usr/local/lscp/cyberpanel/snappymail/data/_data_/_default_/configs/plugin-mailbox-detect.json'