diff options
Diffstat (limited to 'test')
21 files changed, 1121 insertions, 101 deletions
diff --git a/test/functional/cases/001_merged/350_magic.robot b/test/functional/cases/001_merged/350_magic.robot index 66a18f2af..b2746ce3c 100644 --- a/test/functional/cases/001_merged/350_magic.robot +++ b/test/functional/cases/001_merged/350_magic.robot @@ -65,3 +65,4 @@ Magic detections bundle 1 ... MAGIC_SYM_ICS_55 ... MAGIC_SYM_VCF_56 ... MAGIC_SYM_CSV_57 + ... MAGIC_SYM_HEIC_58 diff --git a/test/functional/cases/270_regexp_maps.robot b/test/functional/cases/270_regexp_maps.robot new file mode 100644 index 000000000..d89143bc8 --- /dev/null +++ b/test/functional/cases/270_regexp_maps.robot @@ -0,0 +1,48 @@ +*** Settings *** +Test Setup Rspamd Setup +Test Teardown Rspamd Teardown +Library ${RSPAMD_TESTDIR}/lib/rspamd.py +Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot +Variables ${RSPAMD_TESTDIR}/lib/vars.py + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/regexp_maps.conf +${MESSAGE1} ${RSPAMD_TESTDIR}/messages/advance_fee_fraud.eml +${MESSAGE2} ${RSPAMD_TESTDIR}/messages/spam_message.eml +${RSPAMD_SCOPE} Test +${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat + +*** Test Cases *** +Advance Fee Fraud Detection + [Documentation] Test that advance fee fraud rules match correctly + Scan File ${MESSAGE1} + Expect Symbol ADVANCE_FEE_2 + Expect Symbol ADVANCE_FEE_3 + # Verify filtered options (no __ atoms, max 5 options) + ${symbols} = Get From Dictionary ${SCAN_RESULT}[symbols] ADVANCE_FEE_2 + ${options} = Get From Dictionary ${symbols} options + ${options_count} = Get Length ${options} + Should Be True ${options_count} <= 5 msg=Too many options: ${options_count} + FOR ${option} IN @{options} + Should Not Match Regexp ${option} ^__ msg=Option should not start with __: ${option} + END + +Meta Rule Combination + [Documentation] Test that meta rules correctly combine atom results + Scan File ${MESSAGE1} + Expect Symbol With Score ADVANCE_FEE_2 4.0 + Expect Symbol With Score ADVANCE_FEE_3 5.0 + +No False Positives on Ham + [Documentation] Test that regexp rules don't trigger on legitimate messages + Scan File ${MESSAGE2} + Do Not Expect Symbol ADVANCE_FEE_2 + Do Not Expect Symbol ADVANCE_FEE_3 + +Atom Rules Availability + [Documentation] Test that individual atom rules are available for combination + Scan File ${MESSAGE1} + # These should be available internally but not shown as main results + # We test by ensuring the meta rules work correctly + Expect Symbol ADVANCE_FEE_2 + Expect Symbol ADVANCE_FEE_3 diff --git a/test/functional/cases/400_known_senders.robot b/test/functional/cases/400_known_senders.robot index d827acc0e..a7cde59cb 100644 --- a/test/functional/cases/400_known_senders.robot +++ b/test/functional/cases/400_known_senders.robot @@ -43,33 +43,37 @@ INCOMING MAIL SENDER IS UNKNOWN ... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]} Do Not Expect Symbol ${SYMBOL_GLOBAL} Do Not Expect Symbol ${SYMBOL_LOCAL} - + INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE UNKNOWN Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... User=xxx@abrakadabra.com + ... From=xxx@abrakadabra.com ... Settings=${SETTINGS_REPLIES} Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... Settings=${SETTINGS_REPLIES} + ... Rcpt=xxx@abrakadabra.com ... Settings=${SETTINGS_REPLIES} + ... From=user@emailbl.com Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 ... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]} Expect Symbol ${SYMBOL_GLOBAL} Do Not Expect Symbol ${SYMBOL_LOCAL} INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE KNOWN Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 User=user@emailbl.com From=user@emailbl.com ... Settings=${SETTINGS_REPLIES} Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com ... Settings=${SETTINGS_REPLIES} Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com ... Settings=${SETTINGS_REPLIES} Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com ... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]} Expect Symbol ${SYMBOL_GLOBAL} Expect Symbol ${SYMBOL_LOCAL} - diff --git a/test/functional/cases/410_replies.robot b/test/functional/cases/410_replies.robot index 23ad9df35..b6710149c 100644 --- a/test/functional/cases/410_replies.robot +++ b/test/functional/cases/410_replies.robot @@ -15,33 +15,36 @@ ${RSPAMD_SCOPE} Suite *** Test Cases *** Reply to 1 sender 1 recipients Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... User=xxx@abrakadabra.com + ... From=xxx@abrakadabra.com ... Settings=${SETTINGS_REPLIES} + ... Rcpt=user@emailbl.com Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... Rcpt=xxx@abrakadabra.com ... Settings=${SETTINGS_REPLIES} + ... From=user@emailbl.com Expect Symbol ${SYMBOL} -Reply to 1 sender 2 recipients first is set second is not +Reply to 1 sender 2 recipients but SMTP recipient matches Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_2_first.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... User=xxxx@emailbl.com ... Settings=${SETTINGS_REPLIES} Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2.eml - ... IP=8.8.8.8 User=user@emailbl.com + ... IP=8.8.8.8 + ... Rcpt=xxxx@emailbl.com ... Settings=${SETTINGS_REPLIES} Expect Symbol ${SYMBOL} -Reply to 1 sender 2 recipients 1 rcpt is same - Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2_s.eml - ... IP=8.8.8.8 User=user@emailbl.com +Reply to 1 sender 2 recipients but SMTP recipient NOT matches + Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_2_first.eml + ... IP=8.8.8.8 + ... User=user@emailbl.com ... Settings=${SETTINGS_REPLIES} - Expect Symbol ${SYMBOL} - -Reply to another sender 2 recipients - Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_2_2.eml - ... IP=8.8.8.8 User=another@emailbl.com + Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2.eml + ... IP=8.8.8.8 User=user@emailbl.com + ... Rcpt=another@emailbl.com ... Settings=${SETTINGS_REPLIES} - Scan File ${RSPAMD_TESTDIR}/messages/replyto_2_2.eml - ... IP=8.8.8.8 User=another@emailbl.com - ... Settings=${SETTINGS_REPLIES} - Expect Symbol ${SYMBOL} + Do Not Expect Symbol ${SYMBOL} diff --git a/test/functional/cases/410_logging/000_console/000_systemd_logger.robot b/test/functional/cases/411_logging/000_console/000_systemd_logger.robot index 88178461b..88178461b 100644 --- a/test/functional/cases/410_logging/000_console/000_systemd_logger.robot +++ b/test/functional/cases/411_logging/000_console/000_systemd_logger.robot diff --git a/test/functional/cases/410_logging/000_console/001_timestamps.robot b/test/functional/cases/411_logging/000_console/001_timestamps.robot index bd8e2c349..bd8e2c349 100644 --- a/test/functional/cases/410_logging/000_console/001_timestamps.robot +++ b/test/functional/cases/411_logging/000_console/001_timestamps.robot diff --git a/test/functional/cases/410_logging/001_file/000_json.robot b/test/functional/cases/411_logging/001_file/000_json.robot index a2f04e85c..a2f04e85c 100644 --- a/test/functional/cases/410_logging/001_file/000_json.robot +++ b/test/functional/cases/411_logging/001_file/000_json.robot diff --git a/test/functional/cases/550_milter_headers.robot b/test/functional/cases/550_milter_headers.robot index 80471b83c..c09659714 100644 --- a/test/functional/cases/550_milter_headers.robot +++ b/test/functional/cases/550_milter_headers.robot @@ -37,3 +37,14 @@ CHECK HEADERS WITHOUT TEST SYMBOL # Check X-Spam-Level header Do Not Expect Added Header X-Spam-Level Expect Removed Header X-Spam-Level + +CHECK HEADERS WITH OVERRIDE SETTINGS + # id_milter_headers_override setting enables only authentication-results and x-spam-level routines + Scan File ${MESSAGE} Settings-Id=id_milter_headers_override + # Test the milter_headers override behavior + # Check that Authentication-Results and X-Spam-Level headers are present (exact values are not important) + Expect Header Is Present Authentication-Results + Expect Header Is Present X-Spam-Level + # Verify other headers are not added since only authentication-results and x-spam-level routines run + Do Not Expect Added Header X-Virus + Do Not Expect Added Header My-Spamd-Bar diff --git a/test/functional/configs/maps/advance_fee_rules.map b/test/functional/configs/maps/advance_fee_rules.map new file mode 100644 index 000000000..d07b25c71 --- /dev/null +++ b/test/functional/configs/maps/advance_fee_rules.map @@ -0,0 +1,43 @@ +# SpamAssassin-style rules for advance fee fraud detection (simplified for testing) + +# Individual atom rules (these detect specific patterns) +header __FRAUD_VQE Subject =~ /^(?:Re:|\[.{1,10}\])?\s*(?:very )?urgent\s+(?:(?:and|&)\s+)?(?:confidential|assistance|business|attention|reply|response|help)\b/i + +body __FRAUD_DBI /(?:\bdollars?\b|\busd(?:ollars)?(?:[0-9]|\b)|\bus\$|\$[0-9,.]{6,}|\$[0-9].{0,8}[mb]illion|\$[0-9.,]{2,10} ?m|\beuros?\b|u[.]?s[.]? [0-9.]+ m)/i +body __FRAUD_KJV /(?:claim|concerning) (?:the|this) money/i +body __FRAUD_NEB /(?:government|bank) of nigeria/i +body __FRAUD_XJR /(?:who was a|as a|an? honest|you being a|to any) foreigner/i +body __FRAUD_JBU /\bforeign account\b/i +body __FRAUD_XVW /\bhonest cooperation\b/i +body __FRAUD_LTX /\bmilli?on (?:.{1,25} thousand\s*)?(?:(?:united states|u\.?s\.?) dollars|(?i:U\.?S\.?D?))\b/i +body __FRAUD_PVN /as the beneficiary/i +body __FRAUD_MQO /foreign (?:business partner|customer)/i +body __FRAUD_TCC /foreign (?:offshore )?(?:bank|account)/i +body __FRAUD_GBW /god gives .{1,10}second chance/i +body __FRAUD_NRG /i am contacting you/i +body __FRAUD_YPO /the total sum/i +body __FRAUD_UOQ /vital documents/i +body __FRAUD_BEP /\b(?:bank of nigeria|central bank of|trust bank|apex bank|amalgamated bank)\b/i +body __FRAUD_DPR /\b(?:(?:respond|reply) (?:urgently|immediately)|(?:urgent|immediate|earliest) (?:reply|response))\b/i +body __FRAUD_QXX /\b(?:my name is|i am) (?:mrs?|engr|barrister|dr|prince(?:ss)?)[. ]/i +body __FRAUD_PTS /\b(?:ass?ass?inat(?:ed|ion)|murder(?:e?d)?|kill(?:ed|ing)\b[^.]{0,99}\b(?:war veterans|rebels?))\b/i +body __FRAUD_TDP /\b(?:business partner(?:s|ship)?|silent partner(?:s|ship)?)\b/i +body __FRAUD_AON /\b(?:confidential|private|alternate|alternative) (?:(?:e-? *)?mail)\b/i +body __FRAUD_YWW /\bfurnish you with\b/i +body __FRAUD_ULK /\baffidavits?\b/i +body __FRAUD_IOU /\b(?:no risks?|risk-? *free|free of risks?|100% safe)\b/i +body __FRAUD_IRT /\b(?:compliments? of the|dear friend|dear sir|yours faithfully|season'?s greetings)\b/i +body __FRAUD_ETX /\byour\b[^.]{0,99}\b(?:contact (?:details|information)|private (?:e?[- ]?mail|telephone|tel|phone|fax))\b/i +body __FRAUD_WDR /\bprivate lawyer\b/i + +# Meta rules that combine multiple atoms +meta ADVANCE_FEE_2 (__FRAUD_KJV + __FRAUD_NEB + __FRAUD_XJR + __FRAUD_JBU + __FRAUD_XVW + __FRAUD_LTX + __FRAUD_PVN + __FRAUD_MQO + __FRAUD_TCC + __FRAUD_GBW + __FRAUD_NRG + __FRAUD_YPO + __FRAUD_UOQ + __FRAUD_DBI + __FRAUD_BEP + __FRAUD_DPR + __FRAUD_QXX + __FRAUD_PTS + __FRAUD_TDP + __FRAUD_AON + __FRAUD_YWW + __FRAUD_ULK + __FRAUD_IOU + __FRAUD_IRT + __FRAUD_ETX + __FRAUD_WDR > 2) + +meta ADVANCE_FEE_3 (__FRAUD_KJV + __FRAUD_NEB + __FRAUD_XJR + __FRAUD_JBU + __FRAUD_XVW + __FRAUD_LTX + __FRAUD_PVN + __FRAUD_MQO + __FRAUD_TCC + __FRAUD_GBW + __FRAUD_NRG + __FRAUD_YPO + __FRAUD_UOQ + __FRAUD_DBI + __FRAUD_BEP + __FRAUD_DPR + __FRAUD_QXX + __FRAUD_PTS + __FRAUD_TDP + __FRAUD_AON + __FRAUD_YWW + __FRAUD_ULK + __FRAUD_IOU + __FRAUD_IRT + __FRAUD_ETX + __FRAUD_WDR > 3) + +# Descriptions for the rules +describe ADVANCE_FEE_2 Appears to be advance fee fraud (Nigerian 419) - moderate confidence +describe ADVANCE_FEE_3 Appears to be advance fee fraud (Nigerian 419) - high confidence + +score ADVANCE_FEE_2 4.0 +score ADVANCE_FEE_3 5.0
\ No newline at end of file diff --git a/test/functional/configs/milter_headers.conf b/test/functional/configs/milter_headers.conf index 947bc28dd..502747605 100644 --- a/test/functional/configs/milter_headers.conf +++ b/test/functional/configs/milter_headers.conf @@ -22,3 +22,15 @@ milter_headers { } } + +settings { + id_milter_headers_override { + apply { + plugins { + milter_headers { + routines = [ authentication-results, x-spam-level ]; + } + } + } + } +} diff --git a/test/functional/configs/regexp_maps.conf b/test/functional/configs/regexp_maps.conf new file mode 100644 index 000000000..be4cd047b --- /dev/null +++ b/test/functional/configs/regexp_maps.conf @@ -0,0 +1,51 @@ +.include(duplicate=append,priority=0) "{= env.TESTDIR =}/configs/plugins.conf" + +# Configure multimap for regexp rules testing +multimap { + ADVANCE_FEE_SA_RULES { + type = "regexp_rules"; + map = "{= env.TESTDIR =}/configs/maps/advance_fee_rules.map"; + scope = "advance_fee_scope"; + description = "Advance fee fraud detection rules"; + } +} + +# Override symbol scores for testing +symbols { + ADVANCE_FEE_2 { + score = 4.0; + description = "Advance fee fraud pattern (medium confidence)"; + group = "scam"; + } + ADVANCE_FEE_3 { + score = 5.0; + description = "Advance fee fraud pattern (high confidence)"; + group = "scam"; + } +} + +# Set required score for testing +actions { + reject = 10; + add_header = 6; + greylist = 4; +} + +# Enable necessary modules +dmarc { } +spf { } +dkim { } + +# Module path for multimap +modules { + path = "{= env.TESTDIR =}/../../src/plugins/lua/" +} + +# Disable some modules that might interfere with testing +rbl { + enabled = false; +} + +fuzzy_check { + enabled = false; +} diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot index 9c30a97db..5d23e3ceb 100644 --- a/test/functional/lib/rspamd.robot +++ b/test/functional/lib/rspamd.robot @@ -120,6 +120,15 @@ Expect Added Header Should Be Equal ${SCAN_RESULT}[milter][add_headers][${header_name}][value] ${header_value} Should Be Equal as Numbers ${SCAN_RESULT}[milter][add_headers][${header_name}][order] ${pos} +Expect Header Is Present + [Arguments] ${header_name} + Dictionary Should Contain Key ${SCAN_RESULT} milter + ... msg=milter block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter] add_headers + ... msg=add_headers block was not present in protocol response + Dictionary Should Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name} + ... msg=${header_name} was not added + Expect Email [Arguments] ${email} List Should Contain Value ${SCAN_RESULT}[emails] ${email} diff --git a/test/functional/messages/advance_fee_fraud.eml b/test/functional/messages/advance_fee_fraud.eml new file mode 100644 index 000000000..d9b468b6a --- /dev/null +++ b/test/functional/messages/advance_fee_fraud.eml @@ -0,0 +1,37 @@ +From: Prince John Doe <prince@nigeria-bank.com> +To: recipient@example.com +Subject: URGENT BUSINESS ASSISTANCE NEEDED +Date: Thu, 26 Oct 2023 10:30:00 +0000 +Message-ID: <advance-fee-test@example.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 + +Dear Friend, + +Compliments of the season. I am contacting you with utmost confidence +regarding this money transfer opportunity that will be of mutual benefit +to both of us. + +My name is Prince John Doe, director of the Central Bank of Nigeria. +I am writing to you concerning the total sum of Twenty-Five Million +United States Dollars ($25,000,000 USD) that belongs to a foreigner +who died in a plane crash. + +As the beneficiary of this vast fortune, I need your honest cooperation +to claim this money. The Government of Nigeria has approved this transfer +to a foreign account, and you have been selected as a business partner +to assist in this confidential transaction. + +This is a risk-free business proposal, and I can furnish you with +all vital documents to prove the legitimacy of this transaction. +Your private email and contact details are needed to proceed immediately. + +Please respond urgently through my private lawyer, as this requires +your immediate attention. God gives everyone a second chance, and this +is yours. + +Yours faithfully, +Prince John Doe +Director, Bank of Nigeria + +Note: This is 100% safe and no risk is involved. diff --git a/test/functional/messages/gargantua.eml b/test/functional/messages/gargantua.eml index c6edc50f3..acb3d367f 100644 --- a/test/functional/messages/gargantua.eml +++ b/test/functional/messages/gargantua.eml @@ -23502,4 +23502,58 @@ X-Real-Type: csv ImZvbyIsImJhciIsImJheiIKIjEiLCIyIiwiMyIK +--XXX +Content-Type: image/heic +Content-Transfer-Encoding: base64 +X-Real-Type: heic + +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 +0YCJBxFcC867msO9wip2vP786vlLeC/fqKwSng== + --XXX-- diff --git a/test/lua/unit/html.lua b/test/lua/unit/html.lua index 81c52ec1b..1802dc984 100644 --- a/test/lua/unit/html.lua +++ b/test/lua/unit/html.lua @@ -1,11 +1,10 @@ context("HTML processing", function() local rspamd_util = require("rspamd_util") - local logger = require("rspamd_logger") local cases = { - -- Entities - {[[<html><body>.firebaseapp.com</body></html>]], - [[.firebaseapp.com]]}, - {[[ + -- Entities + { [[<html><body>.firebaseapp.com</body></html>]], + [[.firebaseapp.com]] }, + { [[ <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" @@ -22,8 +21,8 @@ context("HTML processing", function() </p> </body> - </html>]], 'Hello, world!\n'}, - {[[ + </html>]], 'Hello, world!\n' }, + { [[ <!DOCTYPE html> <html lang="en"> <head> @@ -39,8 +38,8 @@ context("HTML processing", function() Hello, world! </body> </html> - ]], 'Hello, world!'}, - {[[ + ]], 'Hello, world!' }, + { [[ <html lang="en"> <head> <meta charset="utf-8"> @@ -56,8 +55,8 @@ context("HTML processing", function() </div> </body> </html> - ]], 'Hello, world!\ntest\ncontentmore content\ncontent inside div\n'}, - {[[ + ]], 'Hello, world!\ntest\ncontentmore content\ncontent inside div\n' }, + { [[ <html lang="en"> <head> <meta charset="utf-8"> @@ -83,8 +82,8 @@ context("HTML processing", function() </body> </html> - ]], 'content\nheada headb\ndata1 data2\n'}, - {[[ + ]], 'content\nheada headb\ndata1 data2\n' }, + { [[ <html lang="en"> <head> <meta charset="utf-8"> @@ -97,17 +96,398 @@ context("HTML processing", function() a b a > b a < b a & b 'a "a" </body> </html> - ]], 'a b a > b a < b a & b \'a "a"'}, + ]], 'a b a > b a < b a & b \'a "a"' }, } - for i,c in ipairs(cases) do + for i, c in ipairs(cases) do test("Extract text from HTML " .. tostring(i), function() local t = rspamd_util.parse_html(c[1]) assert_not_nil(t) assert_equal(c[2], tostring(t), string.format("'%s' doesn't match with '%s'", - c[2], t)) - + c[2], t)) end) end + + -- Test cases for new HTML tag API methods + local function parse_html_and_extract_tags(html_content, pool) + local rspamd_parsers = require("rspamd_parsers") + + local parsed = rspamd_parsers.parse_html_content(html_content, pool) + local tags = {} + + if parsed then + parsed:foreach_tag("any", function(tag, content_length, is_leaf) + table.insert(tags, tag) + return false + end) + end + + return parsed, tags + end + + test("HTML tag get_all_attributes basic test", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<div class="test-class" id="test-id" style="color: red;" width="100">content</div>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + assert_true(#tags > 0) + + -- Find the div tag + local div_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" then + div_tag = tag + break + end + end + + assert_not_nil(div_tag) + + local attrs = div_tag:get_all_attributes() + assert_not_nil(attrs) + + -- Check that we have the expected attributes + assert_equal("test-class", attrs["class"]) + assert_equal("test-id", attrs["id"]) + assert_equal("color: red;", attrs["style"]) + assert_equal("100", attrs["width"]) + + pool:destroy() + end) + + test("HTML tag has_attribute test", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<img src="test.jpg" width="100" height="50" alt="Test image" hidden />]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local img_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "img" then + img_tag = tag + break + end + end + + assert_not_nil(img_tag) + + -- Test existing attributes + assert_true(img_tag:has_attribute("src")) + assert_true(img_tag:has_attribute("width")) + assert_true(img_tag:has_attribute("height")) + assert_true(img_tag:has_attribute("alt")) + assert_true(img_tag:has_attribute("hidden")) + + -- Test non-existing attributes + assert_false(img_tag:has_attribute("nonexistent")) + assert_false(img_tag:has_attribute("class")) + assert_false(img_tag:has_attribute("")) + + pool:destroy() + end) + + test("HTML tag get_numeric_attribute test", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<div width="200" height="150" font-size="14" opacity="0.8" tabindex="5">content</div>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local div_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" then + div_tag = tag + break + end + end + + assert_not_nil(div_tag) + + -- Test numeric attributes + assert_equal(200, div_tag:get_numeric_attribute("width")) + assert_equal(150, div_tag:get_numeric_attribute("height")) + assert_equal(14, div_tag:get_numeric_attribute("font-size")) + + -- Test opacity with floating-point tolerance + local opacity = div_tag:get_numeric_attribute("opacity") + assert_not_nil(opacity) + assert_true(math.abs(opacity - 0.8) < 0.01, string.format("Expected opacity ~0.8, got %f", opacity)) + + assert_equal(5, div_tag:get_numeric_attribute("tabindex")) + + -- Test non-numeric attributes + assert_nil(div_tag:get_numeric_attribute("nonexistent")) + + pool:destroy() + end) + + test("HTML tag get_unknown_attributes test", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<div class="known" data-track="analytics" unknown-attr="test-value" custom-id="12345">content</div>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local div_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" then + div_tag = tag + break + end + end + + assert_not_nil(div_tag) + + local unknown_attrs = div_tag:get_unknown_attributes() + assert_not_nil(unknown_attrs) + + -- Should include unknown attributes but not known ones like "class" + assert_not_nil(unknown_attrs["unknown-attr"]) + assert_equal("test-value", unknown_attrs["unknown-attr"]) + assert_not_nil(unknown_attrs["custom-id"]) + assert_equal("12345", unknown_attrs["custom-id"]) + + -- data-track should be recognized as a known attribute now + -- but if not, it would appear in unknown attributes + + pool:destroy() + end) + + test("HTML tag get_children test", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[ + <div id="parent"> + <p>First child</p> + <span>Second child</span> + <img src="test.jpg" /> + </div> + ]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local parent_div = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" and tag:has_attribute("id") and tag:get_attribute("id") == "parent" then + parent_div = tag + break + end + end + + assert_not_nil(parent_div) + + local children = parent_div:get_children() + assert_not_nil(children) + assert_equal(3, #children) + + -- Check child types + local child_types = {} + for _, child in ipairs(children) do + table.insert(child_types, child:get_type()) + end + + -- Should contain p, span, and img + local child_types_str = table.concat(child_types, ",") + assert_true(child_types_str:find("p") ~= nil) + assert_true(child_types_str:find("span") ~= nil) + assert_true(child_types_str:find("img") ~= nil) + + pool:destroy() + end) + + test("HTML tag get_attribute vs get_all_attributes consistency", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<a href="https://example.com" class="link" target="_blank" title="Example Link">Link</a>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local a_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "a" then + a_tag = tag + break + end + end + + assert_not_nil(a_tag) + + local all_attrs = a_tag:get_all_attributes() + + -- Test that individual get_attribute calls match get_all_attributes + for attr_name, attr_value in pairs(all_attrs) do + assert_equal(attr_value, a_tag:get_attribute(attr_name), + string.format("Attribute '%s' mismatch: get_attribute='%s', get_all_attributes='%s'", + attr_name, a_tag:get_attribute(attr_name) or "nil", attr_value)) + end + + -- Test specific expected attributes + assert_equal("https://example.com", a_tag:get_attribute("href")) + assert_equal("link", a_tag:get_attribute("class")) + assert_equal("_blank", a_tag:get_attribute("target")) + assert_equal("Example Link", a_tag:get_attribute("title")) + + pool:destroy() + end) + + + + test("HTML tag attribute edge cases", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[<div class="" hidden style=" " width="0" height="abc">content</div>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local div_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" then + div_tag = tag + break + end + end + + assert_not_nil(div_tag) + + -- Test empty attribute value + assert_true(div_tag:has_attribute("class")) + assert_equal("", div_tag:get_attribute("class")) + + -- Test boolean attribute (hidden) + assert_true(div_tag:has_attribute("hidden")) + + -- Test whitespace-only attribute + assert_true(div_tag:has_attribute("style")) + assert_equal(" ", div_tag:get_attribute("style")) + + -- Test numeric attributes with edge cases + assert_equal(0, div_tag:get_numeric_attribute("width")) + assert_nil(div_tag:get_numeric_attribute("height")) -- "abc" is not numeric + + -- Test non-existent attribute + assert_false(div_tag:has_attribute("nonexistent")) + assert_nil(div_tag:get_attribute("nonexistent")) + assert_nil(div_tag:get_numeric_attribute("nonexistent")) + + pool:destroy() + end) + + test("HTML tag complex nested structure", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = [[ + <table cellpadding="5" cellspacing="2" border="1"> + <tr> + <td align="center" valign="top" width="100"> + <img src="image1.jpg" width="80" height="60" alt="Image 1" /> + </td> + <td align="left" valign="middle"> + <p font-size="12">Text content</p> + </td> + </tr> + </table> + ]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + -- Find table tag + local table_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "table" then + table_tag = tag + break + end + end + + assert_not_nil(table_tag) + + -- Test table attributes + assert_equal(5, table_tag:get_numeric_attribute("cellpadding")) + assert_equal(2, table_tag:get_numeric_attribute("cellspacing")) + assert_equal("1", table_tag:get_attribute("border")) + + -- Test that table has children + local children = table_tag:get_children() + assert_not_nil(children) + assert_true(#children > 0) + + -- Find img tag + local img_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "img" then + img_tag = tag + break + end + end + + assert_not_nil(img_tag) + + -- Test img attributes + assert_equal("image1.jpg", img_tag:get_attribute("src")) + assert_equal(80, img_tag:get_numeric_attribute("width")) + assert_equal(60, img_tag:get_numeric_attribute("height")) + assert_equal("Image 1", img_tag:get_attribute("alt")) + + pool:destroy() + end) + + test("HTML tag with mixed known and unknown attributes", function() + local rspamd_mempool = require("rspamd_mempool") + local pool = rspamd_mempool.create() + + local html = + [[<div class="container" data-analytics="track" custom-attr="value" style="color: blue;" unknown123="test">content</div>]] + local parsed, tags = parse_html_and_extract_tags(html, pool) + + assert_not_nil(parsed) + + local div_tag = nil + for _, tag in ipairs(tags) do + if tag:get_type() == "div" then + div_tag = tag + break + end + end + + assert_not_nil(div_tag) + + local all_attrs = div_tag:get_all_attributes() + local unknown_attrs = div_tag:get_unknown_attributes() + + -- All attributes should include both known and unknown + assert_not_nil(all_attrs["class"]) -- known + assert_not_nil(all_attrs["style"]) -- known + assert_not_nil(all_attrs["custom-attr"]) -- unknown + assert_not_nil(all_attrs["unknown123"]) -- unknown + + -- Unknown attributes should only include unrecognized ones + assert_nil(unknown_attrs["class"]) -- known, shouldn't be here + assert_nil(unknown_attrs["style"]) -- known, shouldn't be here + assert_not_nil(unknown_attrs["custom-attr"]) -- unknown, should be here + assert_not_nil(unknown_attrs["unknown123"]) -- unknown, should be here + + assert_equal("value", unknown_attrs["custom-attr"]) + assert_equal("test", unknown_attrs["unknown123"]) + + pool:destroy() + end) end) diff --git a/test/lua/unit/logger.lua b/test/lua/unit/logger.lua index dc0120709..c28d8bb09 100644 --- a/test/lua/unit/logger.lua +++ b/test/lua/unit/logger.lua @@ -3,17 +3,17 @@ context("Logger unit tests", function() local log = require "rspamd_logger" local cases = { - {'string', 'string'}, - {'%1', 'string', 'string'}, - {'%1', '1.1', 1.1}, - {'%1', '1', 1}, - {'%1', 'true', true}, - {'%1', '{[1] = 1, [2] = test}', {1, 'test'}}, - {'%1', '{[1] = 1, [2] = 2.1, [k2] = test}', {1, 2.1, k2='test'}}, - {'%s', 'true', true}, + { 'string', 'string' }, + { '%1', 'string', 'string' }, + { '%1', '1.1', 1.1 }, + { '%1', '1', 1 }, + { '%1', 'true', true }, + { '%1', '{[1] = 1, [2] = test}', { 1, 'test' } }, + { '%1', '{[1] = 1, [2] = 2.1, [k2] = test}', { 1, 2.1, k2 = 'test' } }, + { '%s', 'true', true }, } - for _,c in ipairs(cases) do + for _, c in ipairs(cases) do local s if c[3] then s = log.slog(c[1], c[3]) @@ -21,7 +21,82 @@ context("Logger unit tests", function() s = log.slog(c[1]) end assert_equal(s, c[2], string.format("'%s' doesn't match with '%s'", - c[2], s)) + c[2], s)) + end + end) + + test("Logger graceful error handling", function() + local log = require "rspamd_logger" + + -- Test missing arguments + local missing_arg_cases = { + { '%1', '<MISSING ARGUMENT>' }, + { '%0', '<MISSING ARGUMENT>' }, -- %0 is invalid since Lua args are 1-indexed + { '%2', '<MISSING ARGUMENT>', 'arg1' }, + { '%1 %2', 'arg1 <MISSING ARGUMENT>', 'arg1' }, + { 'prefix %1 %3 suffix', 'prefix arg1 <MISSING ARGUMENT> suffix', 'arg1' }, + } + + for _, c in ipairs(missing_arg_cases) do + local s + if c[3] then + s = log.slog(c[1], c[3]) + else + s = log.slog(c[1]) + end + assert_equal(s, c[2], string.format("Missing arg test: '%s' doesn't match with '%s'", + c[2], s)) + end + + -- Test extra arguments + local extra_arg_cases = { + { '%1', 'arg1 <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' }, + { '%1', 'arg1 <EXTRA 2 ARGUMENTS>', 'arg1', 'extra1', 'extra2' }, + { '%s', 'arg1 <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' }, + { 'prefix %1 suffix', 'prefix arg1 suffix <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' }, + } + + for _, c in ipairs(extra_arg_cases) do + local s + if c[4] and c[5] then + s = log.slog(c[1], c[3], c[4], c[5]) + elseif c[4] then + s = log.slog(c[1], c[3], c[4]) + else + s = log.slog(c[1], c[3]) + end + assert_equal(s, c[2], string.format("Extra arg test: '%s' doesn't match with '%s'", + c[2], s)) + end + + -- Test literal percent sequences (should pass through as-is) + local literal_cases = { + { '%-1', '%-1' }, + { '%abc', '%abc' }, -- Should pass through as literal since it's not a valid number + { '%', '%' }, -- Single percent should pass through + } + + for _, c in ipairs(literal_cases) do + local s = log.slog(c[1]) + assert_equal(s, c[2], string.format("Literal test: '%s' doesn't match with '%s'", + c[2], s)) + end + + -- Test mixed scenarios + local mixed_cases = { + { '%1 %3', 'arg1 <MISSING ARGUMENT> <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' }, + { '%2 %4', 'extra1 <MISSING ARGUMENT> <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' }, + } + + for _, c in ipairs(mixed_cases) do + local s + if c[4] then + s = log.slog(c[1], c[3], c[4]) + else + s = log.slog(c[1], c[3]) + end + assert_equal(s, c[2], string.format("Mixed test: '%s' doesn't match with '%s'", + c[2], s)) end end) end)
\ No newline at end of file diff --git a/test/lua/unit/rspamd_resolver.lua b/test/lua/unit/rspamd_resolver.lua index e987ff00b..2fdec2c4b 100644 --- a/test/lua/unit/rspamd_resolver.lua +++ b/test/lua/unit/rspamd_resolver.lua @@ -6,24 +6,58 @@ context("Check punycoding UTF-8 URL", function() local resolver = rspamd_resolver.init(rspamd_util.create_event_base(), rspamd_config) - local cases = { - -- https://unicode.org/reports/tr46/#Deviations - ['faß.de'] = 'fass.de', -- IDNA2008 result: xn--fa-hia.de - ['βόλος.com'] = 'xn--nxasmq6b.com', -- IDNA2008 result: xn--nxasmm1c.com - ['نامهای.com'] = 'xn--mgba3gch31f.com', -- IDNA2008 result: xn--mgba3gch31f060k.com - ['ශ්රී.com'] = 'xn--10cl1a0b.com', -- IDNA2008 result: xn--10cl1a0b660p.com - - -- https://unicode.org/reports/tr46/#Table_Example_Processing - ['日本語。JP'] = 'xn--wgv71a119e.jp', -- Fullwidth characters are remapped, including 。 - --['u¨.com'] = 'xn--tda.com', -- Normalize changes u + umlaut to ü - ['☕.us'] = 'xn--53h.us', -- Post-Unicode 3.2 characters are allowed - - -- Other + -- Helper function to detect IDNA behavior by testing a known conversion + local function detect_idna_behavior() + -- Use faß.de as a test case - different results in IDNA2003 vs IDNA2008 + local test_result = resolver:idna_convert_utf8('faß.de') + if test_result == 'fass.de' then + return 'transitional' -- IDNA2003/transitional behavior + elseif test_result == 'xn--fa-hia.de' then + return 'nontransitional' -- IDNA2008/nontransitional behavior + else + return 'unknown' + end + end + + local idna_behavior = detect_idna_behavior() + + -- Define test cases with both expected results + local cases_transitional = { + -- IDNA2003/transitional results (ICU < 76 default) + ['faß.de'] = 'fass.de', + ['βόλος.com'] = 'xn--nxasmq6b.com', + ['نامهای.com'] = 'xn--mgba3gch31f.com', + ['ශ්රී.com'] = 'xn--10cl1a0b.com', + ['日本語。JP'] = 'xn--wgv71a119e.jp', + ['☕.us'] = 'xn--53h.us', + ['example.рф'] = 'example.xn--p1ai', + } + + local cases_nontransitional = { + -- IDNA2008/nontransitional results (ICU >= 76 default) + ['faß.de'] = 'xn--fa-hia.de', + ['βόλος.com'] = 'xn--nxasmm1c.com', + ['نامهای.com'] = 'xn--mgba3gch31f060k.com', + ['ශ්රී.com'] = 'xn--10cl1a0b660p.com', + ['日本語。JP'] = 'xn--wgv71a119e.jp', + ['☕.us'] = 'xn--53h.us', ['example.рф'] = 'example.xn--p1ai', } + -- Choose appropriate test cases based on detected behavior + local cases + if idna_behavior == 'transitional' then + cases = cases_transitional + print("Detected IDNA transitional behavior (ICU < 76 or configured for IDNA2003)") + elseif idna_behavior == 'nontransitional' then + cases = cases_nontransitional + print("Detected IDNA nontransitional behavior (ICU >= 76 default)") + else + error("Could not detect IDNA behavior - unexpected result for test case") + end + for k, v in pairs(cases) do - test(string.format("punycode %s -> %s", k, v), function() + test(string.format("punycode %s -> %s (%s)", k, v, idna_behavior), function() local res = resolver:idna_convert_utf8(k) assert_equal(res, v) end) diff --git a/test/rspamd_cryptobox_test.c b/test/rspamd_cryptobox_test.c index 03b833404..82225d071 100644 --- a/test/rspamd_cryptobox_test.c +++ b/test/rspamd_cryptobox_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 Vsevolod Stakhov + * Copyright 2025 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -153,7 +153,6 @@ create_constrained_split(struct rspamd_cryptobox_segment *seg, int mseg, void rspamd_cryptobox_test_func(void) { - void *map; unsigned char *begin, *end; rspamd_nm_t key; rspamd_nonce_t nonce; @@ -161,9 +160,8 @@ void rspamd_cryptobox_test_func(void) struct rspamd_cryptobox_segment *seg; double t1, t2; int i, cnt, ms; - gboolean checked_openssl = FALSE; - map = create_mapping(mapping_size, &begin, &end); + create_mapping(mapping_size, &begin, &end); ottery_rand_bytes(key, sizeof(key)); ottery_rand_bytes(nonce, sizeof(nonce)); diff --git a/test/rspamd_cxx_unit_cryptobox.hxx b/test/rspamd_cxx_unit_cryptobox.hxx index 7d9c76b4e..4624e2f93 100644 --- a/test/rspamd_cxx_unit_cryptobox.hxx +++ b/test/rspamd_cxx_unit_cryptobox.hxx @@ -1,5 +1,5 @@ /* - * Copyright 2024 Vsevolod Stakhov + * Copyright 2025 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -245,6 +245,225 @@ TEST_SUITE("rspamd_cryptobox") auto out_arr = std::vector(std::begin(out), std::end(out)); CHECK(out_arr == expected_arr); } + + // Test vectors for XChaCha20-Poly1305 compatibility with Go implementation + // These test cases use the same inputs as the Go version to verify compatibility + + TEST_CASE("rspamd xchacha20poly1305 compatibility all_zeros_64_bytes") + { + // Test case: all_zeros_64_bytes + // Key: 32 zero bytes + // Nonce: 24 zero bytes + // Plaintext: 64 zero bytes + + rspamd_nm_t key; + memset(key, 0, sizeof(key)); + + rspamd_nonce_t nonce; + memset(nonce, 0, sizeof(nonce)); + + unsigned char plaintext[64]; + memset(plaintext, 0, sizeof(plaintext)); + + // Expected values from C implementation + unsigned char expected_cipher[64] = { + 0x78, 0x9e, 0x96, 0x89, 0xe5, 0x20, 0x8d, 0x7f, 0xd9, 0xe1, 0xf3, 0xc5, 0xb5, 0x34, 0x1f, 0x48, + 0xef, 0x18, 0xa1, 0x3e, 0x41, 0x89, 0x98, 0xad, 0xda, 0xdd, 0x97, 0xa3, 0x69, 0x3a, 0x98, 0x7f, + 0x8e, 0x82, 0xec, 0xd5, 0xc1, 0x43, 0x3b, 0xfe, 0xd1, 0xaf, 0x49, 0x75, 0x0c, 0x0f, 0x1f, 0xf2, + 0x9c, 0x41, 0x74, 0xa0, 0x5b, 0x11, 0x9a, 0xa3, 0xa9, 0xe8, 0x33, 0x38, 0x12, 0xe0, 0xc0, 0xfe}; + + rspamd_mac_t expected_mac = { + 0x9c, 0x22, 0xbd, 0x8b, 0x7d, 0x68, 0x00, 0xca, 0x3f, 0x9d, 0xf1, 0xc0, 0x3e, 0x31, 0x3e, 0x68}; + + // Test encryption using Rspamd's nm (shared key) encryption + unsigned char ciphertext[64]; + memcpy(ciphertext, plaintext, sizeof(plaintext)); + + rspamd_mac_t mac; + + rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + + CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0); + CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0); + + // Test decryption + gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + CHECK(decrypt_ok == TRUE); + CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0); + } + + TEST_CASE("rspamd xchacha20poly1305 compatibility all_zeros_128_bytes") + { + // Test case: all_zeros_128_bytes + // Key: 32 zero bytes + // Nonce: 24 zero bytes + // Plaintext: 128 zero bytes + + rspamd_nm_t key; + memset(key, 0, sizeof(key)); + + rspamd_nonce_t nonce; + memset(nonce, 0, sizeof(nonce)); + + unsigned char plaintext[128]; + memset(plaintext, 0, sizeof(plaintext)); + + unsigned char expected_cipher[128] = { + 0x78, 0x9e, 0x96, 0x89, 0xe5, 0x20, 0x8d, 0x7f, 0xd9, 0xe1, 0xf3, 0xc5, 0xb5, 0x34, 0x1f, 0x48, + 0xef, 0x18, 0xa1, 0x3e, 0x41, 0x89, 0x98, 0xad, 0xda, 0xdd, 0x97, 0xa3, 0x69, 0x3a, 0x98, 0x7f, + 0x8e, 0x82, 0xec, 0xd5, 0xc1, 0x43, 0x3b, 0xfe, 0xd1, 0xaf, 0x49, 0x75, 0x0c, 0x0f, 0x1f, 0xf2, + 0x9c, 0x41, 0x74, 0xa0, 0x5b, 0x11, 0x9a, 0xa3, 0xa9, 0xe8, 0x33, 0x38, 0x12, 0xe0, 0xc0, 0xfe, + 0xa4, 0x9e, 0x1e, 0xe0, 0x13, 0x4a, 0x70, 0xa9, 0xd4, 0x9c, 0x24, 0xe0, 0xcb, 0xd8, 0xfc, 0x3b, + 0xa2, 0x7e, 0x97, 0xc3, 0x32, 0x2a, 0xd4, 0x87, 0xf7, 0x78, 0xf8, 0xdc, 0x6a, 0x12, 0x2f, 0xa5, + 0x9c, 0xbe, 0x33, 0xe7, 0x78, 0xea, 0x2e, 0x50, 0xbb, 0x59, 0x09, 0xc9, 0x97, 0x1c, 0x4f, 0xec, + 0x2f, 0x93, 0x52, 0x3f, 0x77, 0x89, 0x2d, 0x17, 0xca, 0xa5, 0x81, 0x67, 0xde, 0xc4, 0xd6, 0xc7}; + + rspamd_mac_t expected_mac = { + 0xcf, 0xe1, 0x4a, 0xc3, 0x39, 0x35, 0xd3, 0x63, 0x1a, 0x06, 0xbf, 0x55, 0x88, 0xf4, 0x12, 0xfa}; + + unsigned char ciphertext[128]; + memcpy(ciphertext, plaintext, sizeof(plaintext)); + + rspamd_mac_t mac; + rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + + CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0); + CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0); + + // Test decryption + gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + CHECK(decrypt_ok == TRUE); + CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0); + } + + TEST_CASE("rspamd xchacha20poly1305 compatibility test_pattern_64_bytes") + { + // Test case: test_pattern_64_bytes + // Key: 0x01 repeated 32 times + // Nonce: 0x01, 0x02, 0x03, ... 0x18 (24 bytes) + // Plaintext: 0x00, 0x01, 0x02, ... 0x41 (66 bytes) + + rspamd_nm_t key; + memset(key, 0x01, sizeof(key)); + + rspamd_nonce_t nonce = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, + 0x21, 0x22, 0x23, 0x24}; + + unsigned char plaintext[66]; + for (int i = 0; i < 66; i++) { + plaintext[i] = i; + } + + // Expected values from C implementation + unsigned char expected_cipher[66] = { + 0xe6, 0x0e, 0xf7, 0x6d, 0x7f, 0x04, 0x37, 0x81, 0x9f, 0x60, 0x03, 0x28, 0x60, 0xb1, 0x2b, 0xaa, + 0xae, 0x2b, 0x13, 0xef, 0x6d, 0xd3, 0x18, 0xf1, 0x3b, 0xc6, 0x06, 0xfb, 0x65, 0x9a, 0x53, 0x3b, + 0x23, 0xe6, 0x99, 0x0c, 0x65, 0x2f, 0xbf, 0x56, 0xcb, 0x7c, 0x18, 0x53, 0xa8, 0xbc, 0x11, 0xc4, + 0x0b, 0x35, 0xc9, 0x40, 0x9a, 0xc2, 0xe1, 0x7f, 0x1a, 0x72, 0xaa, 0xb3, 0x8b, 0x4e, 0x21, 0x32, + 0x87, 0xf7}; + + rspamd_mac_t expected_mac = { + 0xf2, 0xa7, 0xbd, 0xae, 0x53, 0x68, 0xfe, 0xd8, 0x4c, 0x92, 0xe8, 0x52, 0x35, 0x4d, 0x78, 0x7c}; + + unsigned char ciphertext[66]; + memcpy(ciphertext, plaintext, sizeof(plaintext)); + + rspamd_mac_t mac; + + rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + + CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0); + CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0); + + // Test decryption + gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac); + CHECK(decrypt_ok == TRUE); + CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0); + } + + TEST_CASE("rspamd mac key derivation compatibility all_zeros") + { + // Test MAC key derivation process + // Key: 32 zero bytes + // Nonce: 24 zero bytes + + rspamd_nm_t key; + memset(key, 0, sizeof(key)); + + rspamd_nonce_t nonce; + memset(nonce, 0, sizeof(nonce)); + + // Expected values from C implementation + unsigned char expected_subkey[64] = { + 0xbc, 0xd0, 0x2a, 0x18, 0xbf, 0x3f, 0x01, 0xd1, 0x92, 0x92, 0xde, 0x30, 0xa7, 0xa8, 0xfd, 0xac, + 0xa4, 0xb6, 0x5e, 0x50, 0xa6, 0x00, 0x2c, 0xc7, 0x2c, 0xd6, 0xd2, 0xf7, 0xc9, 0x1a, 0xc3, 0xd5, + 0x72, 0x8f, 0x83, 0xe0, 0xaa, 0xd2, 0xbf, 0xcf, 0x9a, 0xbd, 0x2d, 0x2d, 0xb5, 0x8f, 0xae, 0xdd, + 0x65, 0x01, 0x5d, 0xd8, 0x3f, 0xc0, 0x9b, 0x13, 0x1e, 0x27, 0x10, 0x43, 0x01, 0x9e, 0x8e, 0x0f}; + + unsigned char expected_mac_key[32] = { + 0xbc, 0xd0, 0x2a, 0x18, 0xbf, 0x3f, 0x01, 0xd1, 0x92, 0x92, 0xde, 0x30, 0xa7, 0xa8, 0xfd, 0xac, + 0xa4, 0xb6, 0x5e, 0x50, 0xa6, 0x00, 0x2c, 0xc7, 0x2c, 0xd6, 0xd2, 0xf7, 0xc9, 0x1a, 0xc3, 0xd5}; + + // Generate subkey using XChaCha20 (first 64 bytes of keystream) + // This simulates the MAC key derivation process used in secretbox + unsigned char subkey[64]; + memset(subkey, 0, sizeof(subkey)); + + // Use libsodium's ChaCha20 directly to generate the subkey + // This matches what happens inside the secretbox implementation + crypto_stream_xchacha20(subkey, sizeof(subkey), nonce, key); + + // MAC key is first 32 bytes of subkey + unsigned char mac_key[32]; + memcpy(mac_key, subkey, 32); + + CHECK(memcmp(subkey, expected_subkey, sizeof(expected_subkey)) == 0); + CHECK(memcmp(mac_key, expected_mac_key, sizeof(expected_mac_key)) == 0); + } + + TEST_CASE("rspamd mac key derivation compatibility test_pattern") + { + // Test MAC key derivation process + // Key: 0x01 repeated 32 times + // Nonce: 0x01, 0x02, 0x03, ... 0x18 (24 bytes) + + rspamd_nm_t key; + memset(key, 0x01, sizeof(key)); + + rspamd_nonce_t nonce = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, + 0x21, 0x22, 0x23, 0x24}; + + // Expected values from C implementation + unsigned char expected_subkey[64] = { + 0x47, 0xa6, 0xe3, 0xb5, 0x0f, 0xd4, 0x7f, 0x08, 0xb5, 0x35, 0x80, 0xfc, 0x93, 0x66, 0x1a, 0x7f, + 0x9c, 0xf5, 0x8c, 0x93, 0xae, 0x4e, 0x3f, 0xcf, 0x86, 0xb7, 0xdf, 0x34, 0x48, 0x73, 0x33, 0xdb, + 0x71, 0x31, 0x0f, 0xe1, 0xcc, 0xd9, 0x0c, 0x0a, 0x1a, 0x19, 0x54, 0x30, 0xdf, 0xe3, 0xda, 0xee, + 0x70, 0x29, 0xd9, 0xae, 0xf6, 0x4d, 0x78, 0xe3, 0xe8, 0x43, 0x98, 0xea, 0xaa, 0xd8, 0x85, 0x79}; + + unsigned char expected_mac_key[32] = { + 0x47, 0xa6, 0xe3, 0xb5, 0x0f, 0xd4, 0x7f, 0x08, 0xb5, 0x35, 0x80, 0xfc, 0x93, 0x66, 0x1a, 0x7f, + 0x9c, 0xf5, 0x8c, 0x93, 0xae, 0x4e, 0x3f, 0xcf, 0x86, 0xb7, 0xdf, 0x34, 0x48, 0x73, 0x33, 0xdb}; + + // Generate subkey using XChaCha20 (first 64 bytes of keystream) + // This simulates the MAC key derivation process used in secretbox + unsigned char subkey[64]; + memset(subkey, 0, sizeof(subkey)); + + // Use libsodium's ChaCha20 directly to generate the subkey + // This matches what happens inside the secretbox implementation + crypto_stream_xchacha20(subkey, sizeof(subkey), nonce, key); + + // MAC key is first 32 bytes of subkey + unsigned char mac_key[32]; + memcpy(mac_key, subkey, 32); + + CHECK(memcmp(subkey, expected_subkey, sizeof(expected_subkey)) == 0); + CHECK(memcmp(mac_key, expected_mac_key, sizeof(expected_mac_key)) == 0); + } } #endif diff --git a/test/rspamd_dns_test.c b/test/rspamd_dns_test.c index d041351df..ccdc47820 100644 --- a/test/rspamd_dns_test.c +++ b/test/rspamd_dns_test.c @@ -1,4 +1,20 @@ +/* + * Copyright 2025 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "config.h" #include "tests.h" #include "dns.h" @@ -51,6 +67,9 @@ test_dns_cb(struct rdns_reply *reply, gpointer arg) case RDNS_REQUEST_MX: msg_debug("got mx %s:%d", cur->content.mx.name, cur->content.mx.priority); break; + default: + msg_debug("got unknown type %d", cur->type); + break; } cur = cur->next; } diff --git a/test/rspamd_shingles_test.c b/test/rspamd_shingles_test.c index d1a10de84..5b88f4b2d 100644 --- a/test/rspamd_shingles_test.c +++ b/test/rspamd_shingles_test.c @@ -17,6 +17,7 @@ #include "rspamd.h" #include "shingles.h" #include "ottery.h" +#include "libserver/word.h" #include <math.h> static const char * @@ -52,63 +53,76 @@ generate_random_string(char *begin, size_t len) } } -static GArray * +static rspamd_words_t * generate_fuzzy_words(gsize cnt, gsize max_len) { - GArray *res; + rspamd_words_t *res; gsize i, wlen; - rspamd_ftok_t w; + rspamd_word_t word; char *t; - res = g_array_sized_new(FALSE, FALSE, sizeof(rspamd_ftok_t), cnt); + res = g_malloc(sizeof(*res)); + kv_init(*res); for (i = 0; i < cnt; i++) { wlen = ottery_rand_range(max_len) + 1; /* wlen = max_len; */ - w.len = wlen; t = g_malloc(wlen); generate_random_string(t, wlen); - w.begin = t; - g_array_append_val(res, w); + + memset(&word, 0, sizeof(word)); + word.stemmed.begin = t; + word.stemmed.len = wlen; + word.original.begin = t; + word.original.len = wlen; + word.flags = 0; /* No flags set, so it won't be skipped */ + + kv_push(rspamd_word_t, *res, word); } return res; } static void -permute_vector(GArray *in, double prob) +permute_vector(rspamd_words_t *in, double prob) { gsize i, total = 0; - rspamd_ftok_t *w; + rspamd_word_t *w; - for (i = 0; i < in->len; i++) { + for (i = 0; i < kv_size(*in); i++) { if (ottery_rand_unsigned() <= G_MAXUINT * prob) { - w = &g_array_index(in, rspamd_ftok_t, i); - generate_random_string((char *) w->begin, w->len); + w = &kv_A(*in, i); + generate_random_string((char *) w->stemmed.begin, w->stemmed.len); + /* Also update original since they point to same memory */ + w->original.begin = w->stemmed.begin; + w->original.len = w->stemmed.len; total++; } } - msg_debug("generated %z permutations of %ud words", total, in->len); + msg_debug("generated %z permutations of %ud words", total, (unsigned int) kv_size(*in)); } static void -free_fuzzy_words(GArray *ar) +free_fuzzy_words(rspamd_words_t *ar) { gsize i; - rspamd_ftok_t *w; + rspamd_word_t *w; - for (i = 0; i < ar->len; i++) { - w = &g_array_index(ar, rspamd_ftok_t, i); - g_free((gpointer) w->begin); + for (i = 0; i < kv_size(*ar); i++) { + w = &kv_A(*ar, i); + g_free((gpointer) w->stemmed.begin); } + + kv_destroy(*ar); + g_free(ar); } static void test_case(gsize cnt, gsize max_len, double perm_factor, enum rspamd_shingle_alg alg) { - GArray *input; + rspamd_words_t *input; struct rspamd_shingle *sgl, *sgl_permuted; double res; unsigned char key[16]; @@ -281,51 +295,59 @@ void rspamd_shingles_test_func(void) enum rspamd_shingle_alg alg = RSPAMD_SHINGLES_OLD; struct rspamd_shingle *sgl; unsigned char key[16]; - GArray *input; - rspamd_ftok_t tok; + rspamd_words_t input; + rspamd_word_t word; int i; memset(key, 0, sizeof(key)); - input = g_array_sized_new(FALSE, FALSE, sizeof(rspamd_ftok_t), 5); + kv_init(input); for (i = 0; i < 5; i++) { char *b = g_alloca(8); memset(b, 0, 8); memcpy(b + 1, "test", 4); b[0] = 'a' + i; - tok.begin = b; - tok.len = 5 + ((i + 1) % 4); - g_array_append_val(input, tok); + + memset(&word, 0, sizeof(word)); + word.stemmed.begin = b; + word.stemmed.len = 5 + ((i + 1) % 4); + word.original.begin = b; + word.original.len = word.stemmed.len; + word.flags = 0; /* No flags set, so it won't be skipped */ + + kv_push(rspamd_word_t, input, word); } - sgl = rspamd_shingles_from_text(input, key, NULL, + sgl = rspamd_shingles_from_text(&input, key, NULL, rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_OLD); for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) { g_assert(sgl->hashes[i] == expected_old[i]); } g_free(sgl); - sgl = rspamd_shingles_from_text(input, key, NULL, + sgl = rspamd_shingles_from_text(&input, key, NULL, rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_XXHASH); for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) { g_assert(sgl->hashes[i] == expected_xxhash[i]); } g_free(sgl); - sgl = rspamd_shingles_from_text(input, key, NULL, + sgl = rspamd_shingles_from_text(&input, key, NULL, rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_MUMHASH); for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) { g_assert(sgl->hashes[i] == expected_mumhash[i]); } g_free(sgl); - sgl = rspamd_shingles_from_text(input, key, NULL, + sgl = rspamd_shingles_from_text(&input, key, NULL, rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_FAST); for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) { g_assert(sgl->hashes[i] == expected_fasthash[i]); } g_free(sgl); + kv_destroy(input); + for (alg = RSPAMD_SHINGLES_OLD; alg <= RSPAMD_SHINGLES_FAST; alg++) { test_case(200, 10, 0.1, alg); test_case(500, 20, 0.01, alg); |