| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 1 | rspamd_config.MAILCOW_AUTH = { |
| 2 | callback = function(task) |
| 3 | local uname = task:get_user() |
| 4 | if uname then |
| 5 | return 1 |
| 6 | end |
| 7 | end |
| 8 | } |
| 9 | |
| 10 | local monitoring_hosts = rspamd_config:add_map{ |
| 11 | url = "/etc/rspamd/custom/monitoring_nolog.map", |
| 12 | description = "Monitoring hosts", |
| 13 | type = "regexp" |
| 14 | } |
| 15 | |
| 16 | rspamd_config:register_symbol({ |
| 17 | name = 'SMTP_ACCESS', |
| 18 | type = 'postfilter', |
| 19 | callback = function(task) |
| 20 | local util = require("rspamd_util") |
| 21 | local rspamd_logger = require "rspamd_logger" |
| 22 | local rspamd_ip = require 'rspamd_ip' |
| 23 | local uname = task:get_user() |
| 24 | local limited_access = task:get_symbol("SMTP_LIMITED_ACCESS") |
| 25 | |
| 26 | if not uname then |
| 27 | return false |
| 28 | end |
| 29 | |
| 30 | if not limited_access then |
| 31 | return false |
| 32 | end |
| 33 | |
| 34 | local hash_key = 'SMTP_ALLOW_NETS_' .. uname |
| 35 | |
| 36 | local redis_params = rspamd_parse_redis_server('smtp_access') |
| 37 | local ip = task:get_from_ip() |
| 38 | |
| 39 | if ip == nil or not ip:is_valid() then |
| 40 | return false |
| 41 | end |
| 42 | |
| 43 | local from_ip_string = tostring(ip) |
| 44 | smtp_access_table = {from_ip_string} |
| 45 | |
| 46 | local maxbits = 128 |
| 47 | local minbits = 32 |
| 48 | if ip:get_version() == 4 then |
| 49 | maxbits = 32 |
| 50 | minbits = 8 |
| 51 | end |
| 52 | for i=maxbits,minbits,-1 do |
| 53 | local nip = ip:apply_mask(i):to_string() .. "/" .. i |
| 54 | table.insert(smtp_access_table, nip) |
| 55 | end |
| 56 | local function smtp_access_cb(err, data) |
| 57 | if err then |
| 58 | rspamd_logger.infox(rspamd_config, "smtp_access query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) |
| 59 | return false |
| 60 | else |
| 61 | rspamd_logger.infox(rspamd_config, "checking ip %s for smtp_access in %s", from_ip_string, hash_key) |
| 62 | for k,v in pairs(data) do |
| 63 | if (v and v ~= userdata and v == '1') then |
| 64 | rspamd_logger.infox(rspamd_config, "found ip in smtp_access map") |
| 65 | task:insert_result(true, 'SMTP_ACCESS', 0.0, from_ip_string) |
| 66 | return true |
| 67 | end |
| 68 | end |
| 69 | rspamd_logger.infox(rspamd_config, "couldnt find ip in smtp_access map") |
| 70 | task:insert_result(true, 'SMTP_ACCESS', 999.0, from_ip_string) |
| 71 | return true |
| 72 | end |
| 73 | end |
| 74 | table.insert(smtp_access_table, 1, hash_key) |
| 75 | local redis_ret_user = rspamd_redis_make_request(task, |
| 76 | redis_params, -- connect params |
| 77 | hash_key, -- hash key |
| 78 | false, -- is write |
| 79 | smtp_access_cb, --callback |
| 80 | 'HMGET', -- command |
| 81 | smtp_access_table -- arguments |
| 82 | ) |
| 83 | if not redis_ret_user then |
| 84 | rspamd_logger.infox(rspamd_config, "cannot check smtp_access redis map") |
| 85 | end |
| 86 | end, |
| 87 | priority = 10 |
| 88 | }) |
| 89 | |
| 90 | rspamd_config:register_symbol({ |
| 91 | name = 'POSTMASTER_HANDLER', |
| 92 | type = 'prefilter', |
| 93 | callback = function(task) |
| 94 | local rcpts = task:get_recipients('smtp') |
| 95 | local rspamd_logger = require "rspamd_logger" |
| 96 | local lua_util = require "lua_util" |
| 97 | local from = task:get_from(1) |
| 98 | |
| 99 | -- not applying to mails with more than one rcpt to avoid bypassing filters by addressing postmaster |
| 100 | if rcpts and #rcpts == 1 then |
| 101 | for _,rcpt in ipairs(rcpts) do |
| 102 | local rcpt_split = rspamd_str_split(rcpt['addr'], '@') |
| 103 | if #rcpt_split == 2 then |
| 104 | if rcpt_split[1] == 'postmaster' then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 105 | task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster') |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 106 | return |
| 107 | end |
| 108 | end |
| 109 | end |
| 110 | end |
| 111 | |
| 112 | if from then |
| 113 | for _,fr in ipairs(from) do |
| 114 | local fr_split = rspamd_str_split(fr['addr'], '@') |
| 115 | if #fr_split == 2 then |
| 116 | if fr_split[1] == 'postmaster' and task:get_user() then |
| 117 | -- no whitelist, keep signatures |
| 118 | task:insert_result(true, 'POSTMASTER_FROM', -2500.0) |
| 119 | return |
| 120 | end |
| 121 | end |
| 122 | end |
| 123 | end |
| 124 | |
| 125 | end, |
| 126 | priority = 10 |
| 127 | }) |
| 128 | |
| 129 | rspamd_config:register_symbol({ |
| 130 | name = 'KEEP_SPAM', |
| 131 | type = 'prefilter', |
| 132 | callback = function(task) |
| 133 | local util = require("rspamd_util") |
| 134 | local rspamd_logger = require "rspamd_logger" |
| 135 | local rspamd_ip = require 'rspamd_ip' |
| 136 | local uname = task:get_user() |
| 137 | |
| 138 | if uname then |
| 139 | return false |
| 140 | end |
| 141 | |
| 142 | local redis_params = rspamd_parse_redis_server('keep_spam') |
| 143 | local ip = task:get_from_ip() |
| 144 | |
| 145 | if ip == nil or not ip:is_valid() then |
| 146 | return false |
| 147 | end |
| 148 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 149 | -- Helper function to parse IPv6 into 8 segments |
| 150 | local function ipv6_to_segments(ip_str) |
| 151 | -- Remove zone identifier if present (e.g., %eth0) |
| 152 | ip_str = ip_str:gsub("%%.*$", "") |
| 153 | |
| 154 | local segments = {} |
| 155 | |
| 156 | -- Handle :: compression |
| 157 | if ip_str:find('::') then |
| 158 | local before, after = ip_str:match('^(.*)::(.*)$') |
| 159 | before = before or '' |
| 160 | after = after or '' |
| 161 | |
| 162 | local before_parts = {} |
| 163 | local after_parts = {} |
| 164 | |
| 165 | if before ~= '' then |
| 166 | for seg in before:gmatch('[^:]+') do |
| 167 | table.insert(before_parts, tonumber(seg, 16) or 0) |
| 168 | end |
| 169 | end |
| 170 | |
| 171 | if after ~= '' then |
| 172 | for seg in after:gmatch('[^:]+') do |
| 173 | table.insert(after_parts, tonumber(seg, 16) or 0) |
| 174 | end |
| 175 | end |
| 176 | |
| 177 | -- Add before segments |
| 178 | for _, seg in ipairs(before_parts) do |
| 179 | table.insert(segments, seg) |
| 180 | end |
| 181 | |
| 182 | -- Add compressed zeros |
| 183 | local zeros_needed = 8 - #before_parts - #after_parts |
| 184 | for i = 1, zeros_needed do |
| 185 | table.insert(segments, 0) |
| 186 | end |
| 187 | |
| 188 | -- Add after segments |
| 189 | for _, seg in ipairs(after_parts) do |
| 190 | table.insert(segments, seg) |
| 191 | end |
| 192 | else |
| 193 | -- No compression |
| 194 | for seg in ip_str:gmatch('[^:]+') do |
| 195 | table.insert(segments, tonumber(seg, 16) or 0) |
| 196 | end |
| 197 | end |
| 198 | |
| 199 | -- Ensure we have exactly 8 segments |
| 200 | while #segments < 8 do |
| 201 | table.insert(segments, 0) |
| 202 | end |
| 203 | |
| 204 | return segments |
| 205 | end |
| 206 | |
| 207 | -- Generate all common IPv6 notations |
| 208 | local function get_ipv6_variants(ip_str) |
| 209 | local variants = {} |
| 210 | local seen = {} |
| 211 | |
| 212 | local function add_variant(v) |
| 213 | if v and not seen[v] then |
| 214 | table.insert(variants, v) |
| 215 | seen[v] = true |
| 216 | end |
| 217 | end |
| 218 | |
| 219 | -- For IPv4, just return the original |
| 220 | if not ip_str:find(':') then |
| 221 | add_variant(ip_str) |
| 222 | return variants |
| 223 | end |
| 224 | |
| 225 | local segments = ipv6_to_segments(ip_str) |
| 226 | |
| 227 | -- 1. Fully expanded form (all zeros shown as 0000) |
| 228 | local expanded_parts = {} |
| 229 | for _, seg in ipairs(segments) do |
| 230 | table.insert(expanded_parts, string.format('%04x', seg)) |
| 231 | end |
| 232 | add_variant(table.concat(expanded_parts, ':')) |
| 233 | |
| 234 | -- 2. Standard form (no leading zeros, but all segments present) |
| 235 | local standard_parts = {} |
| 236 | for _, seg in ipairs(segments) do |
| 237 | table.insert(standard_parts, string.format('%x', seg)) |
| 238 | end |
| 239 | add_variant(table.concat(standard_parts, ':')) |
| 240 | |
| 241 | -- 3. Find all possible :: compressions |
| 242 | -- RFC 5952: compress the longest run of consecutive zeros |
| 243 | -- But we need to check all possibilities since Redis might have any form |
| 244 | |
| 245 | -- Find all zero runs |
| 246 | local zero_runs = {} |
| 247 | local in_run = false |
| 248 | local run_start = 0 |
| 249 | local run_length = 0 |
| 250 | |
| 251 | for i = 1, 8 do |
| 252 | if segments[i] == 0 then |
| 253 | if not in_run then |
| 254 | in_run = true |
| 255 | run_start = i |
| 256 | run_length = 1 |
| 257 | else |
| 258 | run_length = run_length + 1 |
| 259 | end |
| 260 | else |
| 261 | if in_run then |
| 262 | if run_length >= 1 then -- Allow single zero compression too |
| 263 | table.insert(zero_runs, {start = run_start, length = run_length}) |
| 264 | end |
| 265 | in_run = false |
| 266 | end |
| 267 | end |
| 268 | end |
| 269 | |
| 270 | -- Don't forget the last run |
| 271 | if in_run and run_length >= 1 then |
| 272 | table.insert(zero_runs, {start = run_start, length = run_length}) |
| 273 | end |
| 274 | |
| 275 | -- Generate variant for each zero run compression |
| 276 | for _, run in ipairs(zero_runs) do |
| 277 | local parts = {} |
| 278 | |
| 279 | -- Before compression |
| 280 | for i = 1, run.start - 1 do |
| 281 | table.insert(parts, string.format('%x', segments[i])) |
| 282 | end |
| 283 | |
| 284 | -- The compression |
| 285 | if run.start == 1 then |
| 286 | table.insert(parts, '') |
| 287 | table.insert(parts, '') |
| 288 | elseif run.start + run.length - 1 == 8 then |
| 289 | table.insert(parts, '') |
| 290 | table.insert(parts, '') |
| 291 | else |
| 292 | table.insert(parts, '') |
| 293 | end |
| 294 | |
| 295 | -- After compression |
| 296 | for i = run.start + run.length, 8 do |
| 297 | table.insert(parts, string.format('%x', segments[i])) |
| 298 | end |
| 299 | |
| 300 | local compressed = table.concat(parts, ':'):gsub('::+', '::') |
| 301 | add_variant(compressed) |
| 302 | end |
| 303 | |
| 304 | return variants |
| 305 | end |
| 306 | |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 307 | local from_ip_string = tostring(ip) |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 308 | local ip_check_table = {} |
| 309 | |
| 310 | -- Add all variants of the exact IP |
| 311 | for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do |
| 312 | table.insert(ip_check_table, variant) |
| 313 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 314 | |
| 315 | local maxbits = 128 |
| 316 | local minbits = 32 |
| 317 | if ip:get_version() == 4 then |
| 318 | maxbits = 32 |
| 319 | minbits = 8 |
| 320 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 321 | |
| 322 | -- Add all CIDR notations with variants |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 323 | for i=maxbits,minbits,-1 do |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 324 | local masked_ip = ip:apply_mask(i) |
| 325 | local cidr_base = masked_ip:to_string() |
| 326 | |
| 327 | for _, variant in ipairs(get_ipv6_variants(cidr_base)) do |
| 328 | local cidr = variant .. "/" .. i |
| 329 | table.insert(ip_check_table, cidr) |
| 330 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 331 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 332 | |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 333 | local function keep_spam_cb(err, data) |
| 334 | if err then |
| 335 | rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) |
| 336 | return false |
| 337 | else |
| 338 | for k,v in pairs(data) do |
| 339 | if (v and v ~= userdata and v == '1') then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 340 | rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k]) |
| 341 | task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam') |
| 342 | task:set_flag('no_stat') |
| 343 | return |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 344 | end |
| 345 | end |
| 346 | end |
| 347 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 348 | |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 349 | table.insert(ip_check_table, 1, 'KEEP_SPAM') |
| 350 | local redis_ret_user = rspamd_redis_make_request(task, |
| 351 | redis_params, -- connect params |
| 352 | 'KEEP_SPAM', -- hash key |
| 353 | false, -- is write |
| 354 | keep_spam_cb, --callback |
| 355 | 'HMGET', -- command |
| 356 | ip_check_table -- arguments |
| 357 | ) |
| 358 | if not redis_ret_user then |
| 359 | rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map") |
| 360 | end |
| 361 | end, |
| 362 | priority = 19 |
| 363 | }) |
| 364 | |
| 365 | rspamd_config:register_symbol({ |
| 366 | name = 'TLS_HEADER', |
| 367 | type = 'postfilter', |
| 368 | callback = function(task) |
| 369 | local rspamd_logger = require "rspamd_logger" |
| 370 | local tls_tag = task:get_request_header('TLS-Version') |
| 371 | if type(tls_tag) == 'nil' then |
| 372 | task:set_milter_reply({ |
| 373 | add_headers = {['X-Last-TLS-Session-Version'] = 'None'} |
| 374 | }) |
| 375 | else |
| 376 | task:set_milter_reply({ |
| 377 | add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)} |
| 378 | }) |
| 379 | end |
| 380 | end, |
| 381 | priority = 12 |
| 382 | }) |
| 383 | |
| 384 | rspamd_config:register_symbol({ |
| 385 | name = 'TAG_MOO', |
| 386 | type = 'postfilter', |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 387 | flags = 'ignore_passthrough', |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 388 | callback = function(task) |
| 389 | local util = require("rspamd_util") |
| 390 | local rspamd_logger = require "rspamd_logger" |
| 391 | local redis_params = rspamd_parse_redis_server('taghandler') |
| 392 | local rspamd_http = require "rspamd_http" |
| 393 | local rcpts = task:get_recipients('smtp') |
| 394 | local lua_util = require "lua_util" |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 395 | local tagged_rcpt = task:get_symbol("TAGGED_RCPT") |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 396 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 397 | local function remove_moo_tag() |
| 398 | local moo_tag_header = task:get_header('X-Moo-Tag', false) |
| 399 | if moo_tag_header then |
| 400 | task:set_milter_reply({ |
| 401 | remove_headers = {['X-Moo-Tag'] = 0}, |
| 402 | }) |
| 403 | end |
| 404 | return true |
| 405 | end |
| 406 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 407 | -- Check if we have exactly one recipient |
| 408 | if not (rcpts and #rcpts == 1) then |
| 409 | rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0) |
| 410 | remove_moo_tag() |
| 411 | return |
| 412 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 413 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 414 | local rcpt_addr = rcpts[1]['addr'] |
| 415 | local rcpt_user = rcpts[1]['user'] |
| 416 | local rcpt_domain = rcpts[1]['domain'] |
| 417 | |
| 418 | -- Check if recipient has a tag (contains '+') |
| 419 | local tag = nil |
| 420 | if tagged_rcpt ~= nil then |
| 421 | tag = tagged_rcpt |
| 422 | rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag) |
| 423 | end |
| 424 | |
| 425 | if not tag then |
| 426 | rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr) |
| 427 | remove_moo_tag() |
| 428 | return |
| 429 | end |
| 430 | |
| 431 | -- Optional: Check if domain is a mailcow domain |
| 432 | -- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set |
| 433 | -- If the mail is being delivered, we can assume it's valid |
| 434 | local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN") |
| 435 | if not mailcow_domain then |
| 436 | rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain) |
| 437 | end |
| 438 | |
| 439 | local action = task:get_metric_action('default') |
| 440 | rspamd_logger.infox("TAG_MOO: metric action: %s", action) |
| 441 | |
| 442 | -- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER) |
| 443 | local allow_processing = false |
| 444 | |
| 445 | if task.has_pre_result then |
| 446 | local has_pre, pre_action = task:has_pre_result() |
| 447 | if has_pre then |
| 448 | rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action)) |
| 449 | if pre_action == 'accept' then |
| 450 | allow_processing = true |
| 451 | rspamd_logger.infox("TAG_MOO: pre-result is accept, will process") |
| 452 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 453 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 454 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 455 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 456 | -- Allow processing for mild actions or when we have pre-result accept |
| 457 | if not allow_processing and action ~= 'no action' and action ~= 'greylist' then |
| 458 | rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action) |
| 459 | remove_moo_tag() |
| 460 | return true |
| 461 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 462 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 463 | rspamd_logger.infox("TAG_MOO: processing allowed") |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 464 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 465 | local function http_callback(err_message, code, body, headers) |
| 466 | if body ~= nil and body ~= "" then |
| 467 | rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body) |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 468 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 469 | local function tag_callback_subject(err, data) |
| 470 | if err or type(data) ~= 'string' or data == '' then |
| 471 | rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err) |
| 472 | |
| 473 | local function tag_callback_subfolder(err, data) |
| 474 | if err or type(data) ~= 'string' or data == '' then |
| 475 | rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 476 | remove_moo_tag() |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 477 | else |
| 478 | rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header") |
| 479 | task:set_milter_reply({ |
| 480 | add_headers = {['X-Moo-Tag'] = 'YES'} |
| 481 | }) |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 482 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 483 | end |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 484 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 485 | local redis_ret_subfolder = rspamd_redis_make_request(task, |
| 486 | redis_params, -- connect params |
| 487 | body, -- hash key |
| 488 | false, -- is write |
| 489 | tag_callback_subfolder, --callback |
| 490 | 'HGET', -- command |
| 491 | {'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments |
| 492 | ) |
| 493 | if not redis_ret_subfolder then |
| 494 | rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 495 | remove_moo_tag() |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 496 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 497 | |
| 498 | else |
| 499 | rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail") |
| 500 | local sbj = task:get_header('Subject') or '' |
| 501 | local tag_value = tag[1] and tag[1].options and tag[1].options[1] or '' |
| 502 | new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?=' |
| 503 | task:set_milter_reply({ |
| 504 | remove_headers = { |
| 505 | ['Subject'] = 1, |
| 506 | ['X-Moo-Tag'] = 0 |
| 507 | }, |
| 508 | add_headers = {['Subject'] = new_sbj} |
| 509 | }) |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 510 | end |
| 511 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 512 | |
| 513 | local redis_ret_subject = rspamd_redis_make_request(task, |
| 514 | redis_params, -- connect params |
| 515 | body, -- hash key |
| 516 | false, -- is write |
| 517 | tag_callback_subject, --callback |
| 518 | 'HGET', -- command |
| 519 | {'RCPT_WANTS_SUBJECT_TAG', body} -- arguments |
| 520 | ) |
| 521 | if not redis_ret_subject then |
| 522 | rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") |
| 523 | remove_moo_tag() |
| 524 | end |
| 525 | else |
| 526 | rspamd_logger.infox("TAG_MOO: alias expansion returned empty body") |
| 527 | remove_moo_tag() |
| 528 | end |
| 529 | end |
| 530 | |
| 531 | local rcpt_split = rspamd_str_split(rcpt_addr, '@') |
| 532 | if #rcpt_split == 2 then |
| 533 | if rcpt_split[1]:match('^postmaster') then |
| 534 | rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias") |
| 535 | remove_moo_tag() |
| 536 | else |
| 537 | rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr) |
| 538 | rspamd_http.request({ |
| 539 | task=task, |
| 540 | url='http://nginx:8081/aliasexp.php', |
| 541 | body='', |
| 542 | callback=http_callback, |
| 543 | headers={Rcpt=rcpt_addr}, |
| 544 | }) |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 545 | end |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 546 | else |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 547 | rspamd_logger.infox("TAG_MOO: invalid rcpt format") |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 548 | remove_moo_tag() |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 549 | end |
| 550 | end, |
| 551 | priority = 19 |
| 552 | }) |
| 553 | |
| 554 | rspamd_config:register_symbol({ |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 555 | name = 'BCC', |
| 556 | type = 'postfilter', |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 557 | flags = 'ignore_passthrough', |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 558 | callback = function(task) |
| 559 | local util = require("rspamd_util") |
| 560 | local rspamd_http = require "rspamd_http" |
| 561 | local rspamd_logger = require "rspamd_logger" |
| 562 | |
| 563 | local from_table = {} |
| 564 | local rcpt_table = {} |
| 565 | |
| 566 | if task:has_symbol('ENCRYPTED_CHAT') then |
| 567 | return -- stop |
| 568 | end |
| 569 | |
| 570 | local send_mail = function(task, bcc_dest) |
| 571 | local lua_smtp = require "lua_smtp" |
| 572 | local function sendmail_cb(ret, err) |
| 573 | if not ret then |
| 574 | rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err) |
| 575 | else |
| 576 | rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest) |
| 577 | end |
| 578 | end |
| 579 | if not bcc_dest then |
| 580 | return -- stop |
| 581 | end |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 582 | -- dot stuff content before sending |
| 583 | local email_content = tostring(task:get_content()) |
| 584 | email_content = string.gsub(email_content, "\r\n%.", "\r\n..") |
| 585 | -- send mail |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 586 | local from_smtp = task:get_from('smtp') |
| 587 | local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost' |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 588 | lua_smtp.sendmail({ |
| 589 | task = task, |
| 590 | host = os.getenv("IPV4_NETWORK") .. '.253', |
| 591 | port = 591, |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 592 | from = from_addr, |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 593 | recipients = bcc_dest, |
| 594 | helo = 'bcc', |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 595 | timeout = 20, |
| 596 | }, email_content, sendmail_cb) |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 597 | end |
| 598 | |
| 599 | -- determine from |
| 600 | local from = task:get_from('smtp') |
| 601 | if from then |
| 602 | for _, a in ipairs(from) do |
| 603 | table.insert(from_table, a['addr']) -- add this rcpt to table |
| 604 | table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table |
| 605 | end |
| 606 | else |
| 607 | return -- stop |
| 608 | end |
| 609 | |
| 610 | -- determine rcpts |
| 611 | local rcpts = task:get_recipients('smtp') |
| 612 | if rcpts then |
| 613 | for _, a in ipairs(rcpts) do |
| 614 | table.insert(rcpt_table, a['addr']) -- add this rcpt to table |
| 615 | table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table |
| 616 | end |
| 617 | else |
| 618 | return -- stop |
| 619 | end |
| 620 | |
| 621 | local action = task:get_metric_action('default') |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 622 | rspamd_logger.infox("BCC: metric action: %s", action) |
| 623 | |
| 624 | -- Check for pre-result accept (e.g., from KEEP_SPAM) |
| 625 | local allow_bcc = false |
| 626 | if task.has_pre_result then |
| 627 | local has_pre, pre_action = task:has_pre_result() |
| 628 | if has_pre and pre_action == 'accept' then |
| 629 | allow_bcc = true |
| 630 | rspamd_logger.infox("BCC: pre-result accept detected, will send BCC") |
| 631 | end |
| 632 | end |
| 633 | |
| 634 | -- Allow BCC for mild actions or when we have pre-result accept |
| 635 | if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then |
| 636 | rspamd_logger.infox("BCC: skipping for action: %s", action) |
| 637 | return |
| 638 | end |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 639 | |
| 640 | local function rcpt_callback(err_message, code, body, headers) |
| 641 | if err_message == nil and code == 201 and body ~= nil then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 642 | rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body) |
| 643 | send_mail(task, body) |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 644 | end |
| 645 | end |
| 646 | |
| 647 | local function from_callback(err_message, code, body, headers) |
| 648 | if err_message == nil and code == 201 and body ~= nil then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 649 | rspamd_logger.infox("BCC: sending BCC to %s for from match", body) |
| 650 | send_mail(task, body) |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 651 | end |
| 652 | end |
| 653 | |
| 654 | if rcpt_table then |
| 655 | for _,e in ipairs(rcpt_table) do |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 656 | rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e) |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 657 | rspamd_http.request({ |
| 658 | task=task, |
| 659 | url='http://nginx:8081/bcc.php', |
| 660 | body='', |
| 661 | callback=rcpt_callback, |
| 662 | headers={Rcpt=e} |
| 663 | }) |
| 664 | end |
| 665 | end |
| 666 | |
| 667 | if from_table then |
| 668 | for _,e in ipairs(from_table) do |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 669 | rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e) |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 670 | rspamd_http.request({ |
| 671 | task=task, |
| 672 | url='http://nginx:8081/bcc.php', |
| 673 | body='', |
| 674 | callback=from_callback, |
| 675 | headers={From=e} |
| 676 | }) |
| 677 | end |
| 678 | end |
| 679 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 680 | -- Don't return true to avoid symbol being logged |
| Matthias Andreas Benkard | 8f91594 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 681 | end, |
| 682 | priority = 20 |
| 683 | }) |
| 684 | |
| 685 | rspamd_config:register_symbol({ |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 686 | name = 'DYN_RL_CHECK', |
| 687 | type = 'prefilter', |
| 688 | callback = function(task) |
| 689 | local util = require("rspamd_util") |
| 690 | local redis_params = rspamd_parse_redis_server('dyn_rl') |
| 691 | local rspamd_logger = require "rspamd_logger" |
| 692 | local envfrom = task:get_from(1) |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 693 | local envrcpt = task:get_recipients(1) or {} |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 694 | local uname = task:get_user() |
| 695 | if not envfrom or not uname then |
| 696 | return false |
| 697 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 698 | |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 699 | local uname = uname:lower() |
| 700 | |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 701 | if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then |
| 702 | return false |
| 703 | end |
| 704 | |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 705 | local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case |
| 706 | |
| 707 | local function redis_cb_user(err, data) |
| 708 | |
| 709 | if err or type(data) ~= 'string' then |
| 710 | rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", uname, data, err) |
| 711 | |
| 712 | local function redis_key_cb_domain(err, data) |
| 713 | if err or type(data) ~= 'string' then |
| 714 | rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err) |
| 715 | else |
| 716 | rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data) |
| 717 | task:insert_result('DYN_RL', 0.0, data, env_from_domain) |
| 718 | end |
| 719 | end |
| 720 | |
| 721 | local redis_ret_domain = rspamd_redis_make_request(task, |
| 722 | redis_params, -- connect params |
| 723 | env_from_domain, -- hash key |
| 724 | false, -- is write |
| 725 | redis_key_cb_domain, --callback |
| 726 | 'HGET', -- command |
| 727 | {'RL_VALUE', env_from_domain} -- arguments |
| 728 | ) |
| 729 | if not redis_ret_domain then |
| 730 | rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain") |
| 731 | end |
| 732 | else |
| 733 | rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", uname, data) |
| 734 | task:insert_result('DYN_RL', 0.0, data, uname) |
| 735 | end |
| 736 | |
| 737 | end |
| 738 | |
| 739 | local redis_ret_user = rspamd_redis_make_request(task, |
| 740 | redis_params, -- connect params |
| 741 | uname, -- hash key |
| 742 | false, -- is write |
| 743 | redis_cb_user, --callback |
| 744 | 'HGET', -- command |
| 745 | {'RL_VALUE', uname} -- arguments |
| 746 | ) |
| 747 | if not redis_ret_user then |
| 748 | rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user") |
| 749 | end |
| 750 | return true |
| 751 | end, |
| 752 | flags = 'empty', |
| 753 | priority = 20 |
| 754 | }) |
| 755 | |
| 756 | rspamd_config:register_symbol({ |
| 757 | name = 'NO_LOG_STAT', |
| 758 | type = 'postfilter', |
| 759 | callback = function(task) |
| 760 | local from = task:get_header('From') |
| Matthias Andreas Benkard | b4b5cc1 | 2021-12-28 18:02:04 +0100 | [diff] [blame] | 761 | if from and (monitoring_hosts:get_key(from) or from == "watchdog@localhost") then |
| Matthias Andreas Benkard | 4edeb9f | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 762 | task:set_flag('no_log') |
| 763 | task:set_flag('no_stat') |
| 764 | end |
| 765 | end |
| 766 | }) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 767 | |
| 768 | rspamd_config:register_symbol({ |
| 769 | name = 'MOO_FOOTER', |
| 770 | type = 'prefilter', |
| 771 | callback = function(task) |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 772 | local cjson = require "cjson" |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 773 | local lua_mime = require "lua_mime" |
| 774 | local lua_util = require "lua_util" |
| 775 | local rspamd_logger = require "rspamd_logger" |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 776 | local rspamd_http = require "rspamd_http" |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 777 | local envfrom = task:get_from(1) |
| 778 | local uname = task:get_user() |
| 779 | if not envfrom or not uname then |
| 780 | return false |
| 781 | end |
| 782 | local uname = uname:lower() |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 783 | local env_from_domain = envfrom[1].domain:lower() |
| 784 | local env_from_addr = envfrom[1].addr:lower() |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 785 | |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 786 | -- determine newline type |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 787 | local function newline(task) |
| 788 | local t = task:get_newlines_type() |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 789 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 790 | if t == 'cr' then |
| 791 | return '\r' |
| 792 | elseif t == 'lf' then |
| 793 | return '\n' |
| 794 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 795 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 796 | return '\r\n' |
| 797 | end |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 798 | -- retrieve footer |
| 799 | local function footer_cb(err_message, code, data, headers) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 800 | if err or type(data) ~= 'string' then |
| 801 | rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) |
| 802 | else |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 803 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 804 | -- parse json string |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 805 | local footer = cjson.decode(data) |
| 806 | if not footer then |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 807 | rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) |
| 808 | else |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 809 | if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then |
| 810 | rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 811 | |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 812 | if footer.skip_replies ~= 0 then |
| 813 | in_reply_to = task:get_header_raw('in-reply-to') |
| 814 | if in_reply_to then |
| 815 | rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer") |
| 816 | return |
| 817 | end |
| 818 | end |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 819 | |
| 820 | local envfrom_mime = task:get_from(2) |
| 821 | local from_name = "" |
| 822 | if envfrom_mime and envfrom_mime[1].name then |
| 823 | from_name = envfrom_mime[1].name |
| 824 | elseif envfrom and envfrom[1].name then |
| 825 | from_name = envfrom[1].name |
| 826 | end |
| 827 | |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 828 | -- default replacements |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 829 | local replacements = { |
| 830 | auth_user = uname, |
| 831 | from_user = envfrom[1].user, |
| 832 | from_name = from_name, |
| 833 | from_addr = envfrom[1].addr, |
| 834 | from_domain = envfrom[1].domain:lower() |
| 835 | } |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 836 | -- add custom mailbox attributes |
| 837 | if footer.vars and type(footer.vars) == "string" then |
| 838 | local footer_vars = cjson.decode(footer.vars) |
| 839 | |
| 840 | if type(footer_vars) == "table" then |
| 841 | for key, value in pairs(footer_vars) do |
| 842 | replacements[key] = value |
| 843 | end |
| 844 | end |
| 845 | end |
| 846 | if footer.html and footer.html ~= "" then |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 847 | footer.html = lua_util.jinja_template(footer.html, replacements, true) |
| 848 | end |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 849 | if footer.plain and footer.plain ~= "" then |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 850 | footer.plain = lua_util.jinja_template(footer.plain, replacements, true) |
| 851 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 852 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 853 | -- add footer |
| 854 | local out = {} |
| 855 | local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {} |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 856 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 857 | local seen_cte |
| 858 | local newline_s = newline(task) |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 859 | -- use the CTE add_text_footer actually applied |
| 860 | local new_cte = rewrite.new_cte or 'quoted-printable' |
| 861 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 862 | local function rewrite_ct_cb(name, hdr) |
| 863 | if rewrite.need_rewrite_ct then |
| 864 | if name:lower() == 'content-type' then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 865 | -- include boundary if present |
| 866 | local boundary_part = rewrite.new_ct.boundary and |
| 867 | string.format('; boundary="%s"', rewrite.new_ct.boundary) or '' |
| 868 | local nct = string.format('%s: %s/%s; charset=utf-8%s', |
| 869 | 'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 870 | out[#out + 1] = nct |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 871 | -- update Content-Type header (include boundary if present) |
| 872 | task:set_milter_reply({ |
| 873 | remove_headers = {['Content-Type'] = 0}, |
| 874 | }) |
| 875 | task:set_milter_reply({ |
| 876 | add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s', |
| 877 | rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)} |
| 878 | }) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 879 | return |
| 880 | elseif name:lower() == 'content-transfer-encoding' then |
| 881 | out[#out + 1] = string.format('%s: %s', |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 882 | 'Content-Transfer-Encoding', new_cte) |
| 883 | -- update Content-Transfer-Encoding header |
| 884 | task:set_milter_reply({ |
| 885 | remove_headers = {['Content-Transfer-Encoding'] = 0}, |
| 886 | }) |
| 887 | task:set_milter_reply({ |
| 888 | add_headers = {['Content-Transfer-Encoding'] = new_cte} |
| 889 | }) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 890 | seen_cte = true |
| 891 | return |
| 892 | end |
| 893 | end |
| 894 | out[#out + 1] = hdr.raw:gsub('\r?\n?$', '') |
| 895 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 896 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 897 | task:headers_foreach(rewrite_ct_cb, {full = true}) |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 898 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 899 | if not seen_cte and rewrite.need_rewrite_ct then |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 900 | out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', new_cte) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 901 | end |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 902 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 903 | -- End of headers |
| 904 | out[#out + 1] = newline_s |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 905 | |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 906 | if rewrite.out then |
| 907 | for _,o in ipairs(rewrite.out) do |
| 908 | out[#out + 1] = o |
| 909 | end |
| 910 | else |
| 911 | out[#out + 1] = task:get_rawbody() |
| 912 | end |
| 913 | local out_parts = {} |
| 914 | for _,o in ipairs(out) do |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 915 | if type(o) ~= 'table' then |
| 916 | out_parts[#out_parts + 1] = o |
| 917 | out_parts[#out_parts + 1] = newline_s |
| 918 | else |
| Matthias Andreas Benkard | 97f1202 | 2026-10-23 08:46:40 +0200 | [diff] [blame^] | 919 | local removePrefix = "--\x0D\x0AContent-" |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 920 | if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then |
| 921 | o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1) |
| 922 | end |
| 923 | out_parts[#out_parts + 1] = o[1] |
| 924 | if o[2] then |
| 925 | out_parts[#out_parts + 1] = newline_s |
| 926 | end |
| 927 | end |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 928 | end |
| 929 | task:set_message(out_parts) |
| 930 | else |
| 931 | rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data) |
| 932 | end |
| 933 | end |
| 934 | end |
| 935 | end |
| 936 | |
| Matthias Andreas Benkard | 9dabdab | 2024-03-06 13:12:09 +0100 | [diff] [blame] | 937 | -- fetch footer |
| 938 | rspamd_http.request({ |
| 939 | task=task, |
| 940 | url='http://nginx:8081/footer.php', |
| 941 | body='', |
| 942 | callback=footer_cb, |
| 943 | headers={Domain=env_from_domain,Username=uname,From=env_from_addr}, |
| 944 | }) |
| Matthias Andreas Benkard | d63fe56 | 2023-11-18 13:18:30 +0100 | [diff] [blame] | 945 | |
| 946 | return true |
| 947 | end, |
| 948 | priority = 1 |
| 949 | }) |