[Rspamd] add domain wide footer
This commit is contained in:
parent
ee607dc3cc
commit
f295b8cd91
@ -499,3 +499,123 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'MOO_FOOTER',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local lua_mime = require "lua_mime"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_redis = require "rspamd_redis"
|
||||
local ucl = require "ucl"
|
||||
local redis_params = rspamd_parse_redis_server('footer')
|
||||
local envfrom = task:get_from(1)
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
local uname = uname:lower()
|
||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||
|
||||
local function newline(task)
|
||||
local t = task:get_newlines_type()
|
||||
|
||||
if t == 'cr' then
|
||||
return '\r'
|
||||
elseif t == 'lf' then
|
||||
return '\n'
|
||||
end
|
||||
|
||||
return '\r\n'
|
||||
end
|
||||
local function redis_cb_footer(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
-- parse json string
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_string(data)
|
||||
if not res then
|
||||
rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
local footer = parser:get_object()
|
||||
|
||||
if footer and type(footer) == "table" and (footer.html or footer.plain) then
|
||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s", uname, footer.html, footer.plain)
|
||||
|
||||
-- add footer
|
||||
local out = {}
|
||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||
|
||||
local seen_cte
|
||||
local newline_s = newline(task)
|
||||
|
||||
local function rewrite_ct_cb(name, hdr)
|
||||
if rewrite.need_rewrite_ct then
|
||||
if name:lower() == 'content-type' then
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
out[#out + 1] = nct
|
||||
return
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
out[#out + 1] = string.format('%s: %s',
|
||||
'Content-Transfer-Encoding', 'quoted-printable')
|
||||
seen_cte = true
|
||||
return
|
||||
end
|
||||
end
|
||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||
end
|
||||
|
||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||
end
|
||||
|
||||
-- End of headers
|
||||
out[#out + 1] = newline_s
|
||||
|
||||
if rewrite.out then
|
||||
for _,o in ipairs(rewrite.out) do
|
||||
out[#out + 1] = o
|
||||
end
|
||||
else
|
||||
out[#out + 1] = task:get_rawbody()
|
||||
end
|
||||
local out_parts = {}
|
||||
for _,o in ipairs(out) do
|
||||
if type(o) ~= 'table' then
|
||||
out_parts[#out_parts + 1] = o
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
else
|
||||
out_parts[#out_parts + 1] = o[1]
|
||||
if o[2] then
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
end
|
||||
end
|
||||
end
|
||||
task:set_message(out_parts)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_footer = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
env_from_domain, -- hash key
|
||||
false, -- is write
|
||||
redis_cb_footer, --callback
|
||||
'HGET', -- command
|
||||
{"DOMAIN_WIDE_FOOTER", env_from_domain} -- arguments
|
||||
)
|
||||
if not redis_ret_footer then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load footer for domain")
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
priority = 1
|
||||
})
|
||||
|
@ -47,6 +47,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$quota_notification_bcc = quota_notification_bcc('get', $domain);
|
||||
$rl = ratelimit('get', 'domain', $domain);
|
||||
$rlyhosts = relayhost('get');
|
||||
$domain_footer = mailbox('get', 'domain_wide_footer', $domain);
|
||||
$template = 'edit/domain.twig';
|
||||
$template_data = [
|
||||
'acl' => $_SESSION['acl'],
|
||||
@ -56,6 +57,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'rlyhosts' => $rlyhosts,
|
||||
'dkim' => dkim('details', $domain),
|
||||
'domain_details' => $result,
|
||||
'domain_footer' => $domain_footer,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -3320,6 +3320,45 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'domain_wide_footer':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'domain_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$footers = array();
|
||||
$footers['html'] = isset($_data['footer_html']) ? $_data['footer_html'] : '';
|
||||
$footers['plain'] = isset($_data['footer_plain']) ? $_data['footer_plain'] : '';
|
||||
try {
|
||||
$redis->hSet('DOMAIN_WIDE_FOOTER', $domain, json_encode($footers));
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('domain_footer_modified', htmlspecialchars($domain))
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'get':
|
||||
@ -4399,6 +4438,40 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
return $resourcedata;
|
||||
break;
|
||||
case 'domain_wide_footer':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'domain_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$footers = $redis->hGet('DOMAIN_WIDE_FOOTER', $domain);
|
||||
$footers = json_decode($footers, true);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $footers;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
|
@ -1867,6 +1867,9 @@ if (isset($_GET['query'])) {
|
||||
case "quota_notification_bcc":
|
||||
process_edit_return(quota_notification_bcc('edit', $attr));
|
||||
break;
|
||||
case "domain-wide-footer":
|
||||
process_edit_return(mailbox('edit', 'domain_wide_footer', $attr));
|
||||
break;
|
||||
case "mailq":
|
||||
process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
|
||||
break;
|
||||
|
@ -576,6 +576,10 @@
|
||||
"disable_login": "Login verbieten (Mails werden weiterhin angenommen)",
|
||||
"domain": "Domain bearbeiten",
|
||||
"domain_admin": "Domain-Administrator bearbeiten",
|
||||
"domain_footer": "Domain wide footer",
|
||||
"domain_footer_html": "HTML footer",
|
||||
"domain_footer_info": "Domain wide footer werden allen E-Mails hinzugefügt, die von der angegebenen Domain gesendet werden.",
|
||||
"domain_footer_plain": "PLAIN footer",
|
||||
"domain_quota": "Domain Speicherplatz gesamt (MiB)",
|
||||
"domains": "Domains",
|
||||
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen",
|
||||
@ -1011,6 +1015,7 @@
|
||||
"domain_admin_added": "Domain-Administrator %s wurde angelegt",
|
||||
"domain_admin_modified": "Änderungen an Domain-Administrator %s wurden gespeichert",
|
||||
"domain_admin_removed": "Domain-Administrator %s wurde entfernt",
|
||||
"domain_footer_modified": "Änderungen an Domain Footer %s wurden gespeichert",
|
||||
"domain_modified": "Änderungen an Domain %s wurden gespeichert",
|
||||
"domain_removed": "Domain %s wurde entfernt",
|
||||
"dovecot_restart_success": "Dovecot wurde erfolgreich neu gestartet",
|
||||
|
@ -576,6 +576,10 @@
|
||||
"disable_login": "Disallow login (incoming mail is still accepted)",
|
||||
"domain": "Edit domain",
|
||||
"domain_admin": "Edit domain administrator",
|
||||
"domain_footer": "Domain wide footer",
|
||||
"domain_footer_html": "HTML footer",
|
||||
"domain_footer_info": "Domain wide footers will be added to all emails sent from the specified domain.",
|
||||
"domain_footer_plain": "PLAIN footer",
|
||||
"domain_quota": "Domain quota",
|
||||
"domains": "Domains",
|
||||
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
|
||||
@ -1018,6 +1022,7 @@
|
||||
"domain_admin_added": "Domain administrator %s has been added",
|
||||
"domain_admin_modified": "Changes to domain administrator %s have been saved",
|
||||
"domain_admin_removed": "Domain administrator %s has been removed",
|
||||
"domain_footer_modified": "Changes to domain footer %s have been saved",
|
||||
"domain_modified": "Changes to domain %s have been saved",
|
||||
"domain_removed": "Domain %s has been removed",
|
||||
"dovecot_restart_success": "Dovecot was restarted successfully",
|
||||
|
@ -7,6 +7,7 @@
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dratelimit">{{ lang.edit.ratelimit }}</button></li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dspamfilter">{{ lang.edit.spam_filter }}</button></li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dqwbcc">{{ lang.edit.quota_warning_bcc }}</button></li>
|
||||
<li role="presentation" class="nav-item"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#dfooter">{{ lang.edit.domain_footer }}</button></li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div class="tab-content">
|
||||
@ -229,6 +230,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dfooter" class="tab-pane fade" role="tabpanel" aria-labelledby="domain-footer">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h4>{{ lang.edit.domain_footer }}</h4>
|
||||
<p>{{ lang.edit.domain_footer_info|raw }}</p>
|
||||
<form class="form-horizontal" data-id="domain_footer">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="domain_footer_html">{{ lang.edit.domain_footer_html }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="10" id="domain_footer_html" name="footer_html">{{ domain_footer.html }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="domain_footer_plain">{{ lang.edit.domain_footer_plain }}:</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control" rows="10" id="domain_footer_plain" name="footer_plain">{{ domain_footer.plain }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="domain_footer" data-item="domain_footer" data-api-url='edit/domain-wide-footer' data-api-attr='{"domain":"{{ domain }}"}' href="#">{{ lang.edit.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
|
Loading…
Reference in New Issue
Block a user