diff options
Diffstat (limited to 'test/functional')
25 files changed, 919 insertions, 28 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/110_statistics/300-multiclass-redis.robot b/test/functional/cases/110_statistics/300-multiclass-redis.robot new file mode 100644 index 000000000..278f7e0a0 --- /dev/null +++ b/test/functional/cases/110_statistics/300-multiclass-redis.robot @@ -0,0 +1,42 @@ +*** Settings *** +Documentation Multiclass Bayes Classification Tests with Redis Backend +Suite Setup Rspamd Redis Setup +Suite Teardown Rspamd Redis Teardown +Test Setup Set Test Hash Documentation +Resource multiclass_lib.robot + +*** Variables *** +${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT} +${RSPAMD_STATS_HASH} siphash +${CONFIG} ${RSPAMD_TESTDIR}/configs/multiclass_bayes.conf + +*** Test Cases *** +Multiclass Basic Learning and Classification + [Documentation] Test basic multiclass learning and classification + [Tags] multiclass basic learning + Multiclass Basic Learn Test + +Multiclass Legacy Compatibility + [Documentation] Test that old learn_spam/learn_ham commands still work + [Tags] multiclass compatibility legacy + Multiclass Legacy Compatibility Test + +Multiclass Relearn + [Documentation] Test reclassifying messages to different classes + [Tags] multiclass relearn + Multiclass Relearn Test + +Multiclass Cross-Class Learning + [Documentation] Test learning message as different class than expected + [Tags] multiclass cross-learn + Multiclass Cross-Learn Test + +Multiclass Unlearn + [Documentation] Test unlearning (learning message as different class) + [Tags] multiclass unlearn + Multiclass Unlearn Test + +Multiclass Statistics + [Documentation] Test that statistics show all class information + [Tags] multiclass statistics + Multiclass Stats Test
\ No newline at end of file diff --git a/test/functional/cases/110_statistics/320-multiclass-peruser.robot b/test/functional/cases/110_statistics/320-multiclass-peruser.robot new file mode 100644 index 000000000..e8ca34616 --- /dev/null +++ b/test/functional/cases/110_statistics/320-multiclass-peruser.robot @@ -0,0 +1,31 @@ +*** Settings *** +Suite Setup Rspamd Redis Setup +Suite Teardown Rspamd Redis Teardown +Test Setup Set Test Hash Documentation +Resource multiclass_lib.robot + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/multiclass_bayes.conf +${REDIS_SCOPE} Suite +${RSPAMD_REDIS_SERVER} ${RSPAMD_REDIS_ADDR}:${RSPAMD_REDIS_PORT} +${RSPAMD_SCOPE} Suite +${RSPAMD_STATS_BACKEND} redis +${RSPAMD_STATS_HASH} null +${RSPAMD_STATS_KEY} null +${RSPAMD_STATS_PER_USER} true + +*** Test Cases *** +Multiclass Per-User Basic Learn Test + Multiclass Basic Learn Test test@example.com + +Multiclass Per-User Legacy Compatibility Test + Multiclass Legacy Compatibility Test test@example.com + +Multiclass Per-User Relearn Test + Multiclass Relearn Test test@example.com + +Multiclass Per-User Cross-Learn Test + Multiclass Cross-Learn Test test@example.com + +Multiclass Per-User Unlearn Test + Multiclass Unlearn Test test@example.com
\ No newline at end of file diff --git a/test/functional/cases/110_statistics/multiclass_lib.robot b/test/functional/cases/110_statistics/multiclass_lib.robot new file mode 100644 index 000000000..9f70e05fb --- /dev/null +++ b/test/functional/cases/110_statistics/multiclass_lib.robot @@ -0,0 +1,169 @@ +*** Settings *** +Library OperatingSystem +Resource lib.robot + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/multiclass_bayes.conf +${MESSAGE_HAM} ${RSPAMD_TESTDIR}/messages/ham.eml +${MESSAGE_SPAM} ${RSPAMD_TESTDIR}/messages/spam_message.eml +${MESSAGE_NEWSLETTER} ${RSPAMD_TESTDIR}/messages/newsletter.eml +${REDIS_SCOPE} Suite +${RSPAMD_REDIS_SERVER} null +${RSPAMD_SCOPE} Suite +${RSPAMD_STATS_BACKEND} redis +${RSPAMD_STATS_HASH} null +${RSPAMD_STATS_KEY} null +${RSPAMD_STATS_PER_USER} ${EMPTY} + +*** Keywords *** +Learn Multiclass + [Arguments] ${user} ${class} ${message} + # Extract filename from message path for queue-id + ${path} ${filename} = Split Path ${message} + IF "${user}" + ${result} = Run Rspamc -d ${user} -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_class:${class} ${message} + ELSE + ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_class:${class} ${message} + END + Check Rspamc ${result} + +Learn Multiclass Legacy + [Arguments] ${user} ${class} ${message} + # Test backward compatibility with old learn_spam/learn_ham commands + # Extract filename from message path for queue-id + ${path} ${filename} = Split Path ${message} + IF "${user}" + ${result} = Run Rspamc -d ${user} -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_${class} ${message} + ELSE + ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} learn_${class} ${message} + END + Check Rspamc ${result} + +Multiclass Basic Learn Test + [Arguments] ${user}=${EMPTY} + Set Suite Variable ${RSPAMD_STATS_LEARNTEST} 0 + Set Test Variable ${kwargs} &{EMPTY} + IF "${user}" + Set To Dictionary ${kwargs} Deliver-To=${user} + END + + # Learn all classes + Learn Multiclass ${user} spam ${MESSAGE_SPAM} + Learn Multiclass ${user} ham ${MESSAGE_HAM} + Learn Multiclass ${user} newsletter ${MESSAGE_NEWSLETTER} + + # Test classification + Scan File ${MESSAGE_SPAM} &{kwargs} + Expect Symbol BAYES_SPAM + + Scan File ${MESSAGE_HAM} &{kwargs} + Expect Symbol BAYES_HAM + + Scan File ${MESSAGE_NEWSLETTER} &{kwargs} + Expect Symbol BAYES_NEWSLETTER + + Set Suite Variable ${RSPAMD_STATS_LEARNTEST} 1 + +Multiclass Legacy Compatibility Test + [Arguments] ${user}=${EMPTY} + Set Test Variable ${kwargs} &{EMPTY} + IF "${user}" + Set To Dictionary ${kwargs} Deliver-To=${user} + END + + # Test legacy learn_spam and learn_ham commands still work + Learn Multiclass Legacy ${user} spam ${MESSAGE_SPAM} + Learn Multiclass Legacy ${user} ham ${MESSAGE_HAM} + + # Should still classify correctly + Scan File ${MESSAGE_SPAM} &{kwargs} + Expect Symbol BAYES_SPAM + + Scan File ${MESSAGE_HAM} &{kwargs} + Expect Symbol BAYES_HAM + +Multiclass Relearn Test + [Arguments] ${user}=${EMPTY} + IF ${RSPAMD_STATS_LEARNTEST} == 0 + Fail "Learn test was not run" + END + + Set Test Variable ${kwargs} &{EMPTY} + IF "${user}" + Set To Dictionary ${kwargs} Deliver-To=${user} + END + + # Relearn spam message as ham + Learn Multiclass ${user} ham ${MESSAGE_SPAM} + + # Should now classify as ham or at least not spam + Scan File ${MESSAGE_SPAM} &{kwargs} + ${pass} = Run Keyword And Return Status Expect Symbol BAYES_HAM + IF ${pass} + Pass Execution Successfully reclassified spam as ham + END + Do Not Expect Symbol BAYES_SPAM + +Multiclass Cross-Learn Test + [Arguments] ${user}=${EMPTY} + Set Test Variable ${kwargs} &{EMPTY} + IF "${user}" + Set To Dictionary ${kwargs} Deliver-To=${user} + END + + # Learn newsletter message as ham to test cross-class learning + Learn Multiclass ${user} ham ${MESSAGE_NEWSLETTER} + + # Should classify as ham, not newsletter (since we trained it as ham) + Scan File ${MESSAGE_NEWSLETTER} &{kwargs} + Expect Symbol BAYES_HAM + Do Not Expect Symbol BAYES_NEWSLETTER + +Multiclass Unlearn Test + [Arguments] ${user}=${EMPTY} + Set Test Variable ${kwargs} &{EMPTY} + IF "${user}" + Set To Dictionary ${kwargs} Deliver-To=${user} + END + + # First learn spam + Learn Multiclass ${user} spam ${MESSAGE_SPAM} + Scan File ${MESSAGE_SPAM} &{kwargs} + Expect Symbol BAYES_SPAM + + # Then unlearn spam (learn as ham) + Learn Multiclass ${user} ham ${MESSAGE_SPAM} + + # Should no longer classify as spam + Scan File ${MESSAGE_SPAM} &{kwargs} + Do Not Expect Symbol BAYES_SPAM + +Check Multiclass Results + [Arguments] ${result} ${expected_class} + # Check that scan result contains expected class information + Should Contain ${result.stdout} BAYES_${expected_class.upper()} + # Check for multiclass result format [class_name] + Should Match Regexp ${result.stdout} BAYES_${expected_class.upper()}.*\\[${expected_class}\\] + +Multiclass Stats Test + # Check that rspamc stat shows learning counts for all classes + ${result} = Run Rspamc -h ${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_CONTROLLER} stat + # Don't use Check Rspamc for stat command as it expects JSON success format + Should Be Equal As Integers ${result.rc} 0 + + # Should show statistics for all classes + Should Contain ${result.stdout} BAYES_SPAM + Should Contain ${result.stdout} BAYES_HAM + Should Contain ${result.stdout} BAYES_NEWSLETTER + +Multiclass Configuration Migration Test + # Test that old binary config can be automatically migrated + Set Test Variable ${binary_config} ${RSPAMD_TESTDIR}/configs/stats.conf + + # Start with binary config + ${result} = Run Rspamc --config ${binary_config} stat + Check Rspamc ${result} + + # Should show deprecation warning but work + Should Contain ${result.stderr} deprecated ignore_case=True + diff --git a/test/functional/cases/120_fuzzy/lib.robot b/test/functional/cases/120_fuzzy/lib.robot index fda0af54a..a57ecc742 100644 --- a/test/functional/cases/120_fuzzy/lib.robot +++ b/test/functional/cases/120_fuzzy/lib.robot @@ -16,6 +16,7 @@ ${RSPAMD_FUZZY_ENCRYPTED_ONLY} false ${RSPAMD_FUZZY_ENCRYPTION_KEY} null ${RSPAMD_FUZZY_INCLUDE} ${RSPAMD_TESTDIR}/configs/empty.conf ${RSPAMD_FUZZY_KEY} null +${RSPAMD_FUZZY_SERVER_MODE} servers ${RSPAMD_FUZZY_SHINGLES_KEY} null ${RSPAMD_SCOPE} Suite ${SETTINGS_FUZZY_CHECK} ${EMPTY} @@ -109,6 +110,7 @@ Fuzzy Setup Encrypted Set Suite Variable ${RSPAMD_FUZZY_ENCRYPTION_KEY} ${RSPAMD_KEY_PUB1} Set Suite Variable ${RSPAMD_FUZZY_CLIENT_ENCRYPTION_KEY} ${RSPAMD_KEY_PUB1} Set Suite Variable ${RSPAMD_FUZZY_INCLUDE} ${RSPAMD_TESTDIR}/configs/fuzzy-encryption-key.conf + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} servers Rspamd Redis Setup Fuzzy Setup Encrypted Dyn1 @@ -142,6 +144,8 @@ Fuzzy Setup Encrypted Keyed Fuzzy Setup Plain [Arguments] ${algorithm} Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm} + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} servers + Set Suite Variable ${SETTINGS_FUZZY_CHECK} servers = "${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_FUZZY}"; Rspamd Redis Setup Fuzzy Setup Keyed @@ -149,6 +153,7 @@ Fuzzy Setup Keyed Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} ${algorithm} Set Suite Variable ${RSPAMD_FUZZY_KEY} mYN888sydwLTfE32g2hN Set Suite Variable ${RSPAMD_FUZZY_SHINGLES_KEY} hXUCgul9yYY3Zlk1QIT2 + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} servers Rspamd Redis Setup Fuzzy Setup Plain Fasthash @@ -218,3 +223,21 @@ Fuzzy Multimessage Overwrite Test FOR ${i} IN @{MESSAGES} Fuzzy Overwrite Test ${i} END + +Fuzzy Setup Split Servers + Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} siphash + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} split + Set Suite Variable ${SETTINGS_FUZZY_CHECK} read_servers = "${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_FUZZY}"; write_servers = "${RSPAMD_LOCAL_ADDR}:${RSPAMD_PORT_FUZZY}"; + Rspamd Redis Setup + +Fuzzy Setup Read Only + Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} siphash + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} read_only + Set Suite Variable ${SETTINGS_FUZZY_CHECK} read_only = true; + Rspamd Redis Setup + +Fuzzy Setup Write Only + Set Suite Variable ${RSPAMD_FUZZY_ALGORITHM} siphash + Set Suite Variable ${RSPAMD_FUZZY_SERVER_MODE} write_only + Set Suite Variable ${SETTINGS_FUZZY_CHECK} mode = "write_only"; + Rspamd Redis Setup diff --git a/test/functional/cases/120_fuzzy/read-only.robot b/test/functional/cases/120_fuzzy/read-only.robot new file mode 100644 index 000000000..0c3be7fec --- /dev/null +++ b/test/functional/cases/120_fuzzy/read-only.robot @@ -0,0 +1,14 @@ +*** Settings *** +Suite Setup Fuzzy Setup Read Only +Suite Teardown Rspamd Redis Teardown +Resource lib.robot + +*** Test Cases *** +Fuzzy Add + Fuzzy Multimessage Add Test + +Fuzzy Fuzzy + Fuzzy Multimessage Fuzzy Test + +Fuzzy Miss + Fuzzy Multimessage Miss Test diff --git a/test/functional/cases/120_fuzzy/split-servers.robot b/test/functional/cases/120_fuzzy/split-servers.robot new file mode 100644 index 000000000..41acb23ff --- /dev/null +++ b/test/functional/cases/120_fuzzy/split-servers.robot @@ -0,0 +1,18 @@ +*** Settings *** +Suite Setup Fuzzy Setup Split Servers +Suite Teardown Rspamd Redis Teardown +Resource lib.robot +Variables ${RSPAMD_TESTDIR}/lib/vars.py + +*** Variables *** +${CONFIG} ${RSPAMD_TESTDIR}/configs/fuzzy-split-servers.conf + +*** Test Cases *** +Fuzzy Add + Fuzzy Multimessage Add Test + +Fuzzy Fuzzy + Fuzzy Multimessage Fuzzy Test + +Fuzzy Miss + Fuzzy Multimessage Miss Test diff --git a/test/functional/cases/120_fuzzy/write-only.robot b/test/functional/cases/120_fuzzy/write-only.robot new file mode 100644 index 000000000..ee017a1d3 --- /dev/null +++ b/test/functional/cases/120_fuzzy/write-only.robot @@ -0,0 +1,14 @@ +*** Settings *** +Suite Setup Fuzzy Setup Write Only +Suite Teardown Rspamd Redis Teardown +Resource lib.robot + +*** Test Cases *** +Fuzzy Add + Fuzzy Multimessage Add Test + +Fuzzy Fuzzy + Fuzzy Multimessage Fuzzy Test + +Fuzzy Miss + Fuzzy Multimessage Miss Test 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/fuzzy-split-servers.conf b/test/functional/configs/fuzzy-split-servers.conf new file mode 100644 index 000000000..e7c3bb922 --- /dev/null +++ b/test/functional/configs/fuzzy-split-servers.conf @@ -0,0 +1,97 @@ +redis { + servers = "{= env.REDIS_ADDR =}:{= env.REDIS_PORT =}"; +} +lua = "{= env.TESTDIR =}/lua/test_coverage.lua"; +options = { + filters = "fuzzy_check"; + pidfile = "{= env.TMPDIR =}/rspamd.pid"; + control_socket = "{= env.TMPDIR =}/rspamd.sock mode=0600"; + url_tld = "{= env.TESTDIR =}/../lua/unit/test_tld.dat"; + dns { + retransmits = 10; + timeout = 2s; + } +} +logging = { + type = "file", + level = "debug" + filename = "{= env.TMPDIR =}/rspamd.log" +} +metric = { + name = "default", + actions = { + reject = 100500, + } + unknown_weight = 1 + symbol { + weight = 10.0; + name = "{= env.FLAG1_SYMBOL =}"; + } + symbol { + weight = -1.0; + name = "{= env.FLAG2_SYMBOL =}"; + } +} + +worker { + type = normal + bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_NORMAL =}"; + count = 1 + task_timeout = 60s; +} + +worker { + type = controller + bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_CONTROLLER =}"; + count = 1 + secure_ip = ["{= env.LOCAL_ADDR =}"]; + stats_path = "{= env.TMPDIR =}/stats.ucl"; +} + +worker { + count = 1; + backend = "{= env.FUZZY_BACKEND =}"; + bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_FUZZY =}"; + type = "fuzzy"; + hashfile = "{= env.TMPDIR =}/fuzzy.db"; + allow_update = ["{= env.LOCAL_ADDR =}"]; + encrypted_only = {= env.FUZZY_ENCRYPTED_ONLY =}; + keypair { + privkey = "{= env.KEY_PVT1 =}"; + pubkey = "{= env.KEY_PUB1 =}"; + } + dynamic_keys_map = "{= env.TESTDIR =}/configs/maps/fuzzy_keymap.map"; +} + +fuzzy_check { + min_bytes = 100; + timeout = 1s; + retransmits = 10; + + rule { + min_bytes = 0; + min_length = 0; + algorithm = "{= env.FUZZY_ALGORITHM =}"; + read_servers = "{= env.LOCAL_ADDR =}:{= env.PORT_FUZZY =}"; + write_servers = "{= env.LOCAL_ADDR =}:{= env.PORT_FUZZY =}"; + symbol = "R_TEST_FUZZY"; + max_score = 10.0; + mime_types = ["application/*"]; + read_only = false; + skip_unknown = true; + skip_hashes = "{= env.TMPDIR =}/skip_hash.map"; + fuzzy_key = {= env.FUZZY_KEY =}; + fuzzy_shingles_key = {= env.FUZZY_SHINGLES_KEY =}; +.include "{= env.FUZZY_INCLUDE =}"; + fuzzy_map = { + R_TEST_FUZZY_DENIED { + max_score = 10.0; + flag = {= env.FLAG1_NUMBER =}; + } + R_TEST_FUZZY_WHITE { + max_score = 1.0; + flag = {= env.FLAG2_NUMBER =}; + } + } + } +} 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/multiclass_bayes.conf b/test/functional/configs/multiclass_bayes.conf new file mode 100644 index 000000000..278aeeee9 --- /dev/null +++ b/test/functional/configs/multiclass_bayes.conf @@ -0,0 +1,129 @@ +options = { + filters = ["spf", "dkim", "regexp"] + url_tld = "{= env.TESTDIR =}/../lua/unit/test_tld.dat" + pidfile = "{= env.TMPDIR =}/rspamd.pid" + dns { + retransmits = 10; + timeout = 2s; + fake_records = [{ + name = "example.net"; + type = txt; + replies = ["v=spf1 -all"]; + }] + } +} + +logging = { + type = "file", + level = "debug" + filename = "{= env.TMPDIR =}/rspamd.log" +} + +metric = { + name = "default", + actions = { + reject = 100500, + } + unknown_weight = 1 +} + +worker { + type = normal + bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_NORMAL =}" + count = 1 + keypair { + pubkey = "{= env.KEY_PUB1 =}"; + privkey = "{= env.KEY_PVT1 =}"; + } + task_timeout = 60s; +} + +worker { + type = controller + bind_socket = "{= env.LOCAL_ADDR =}:{= env.PORT_CONTROLLER =}" + count = 1 + keypair { + pubkey = "{= env.KEY_PUB1 =}"; + privkey = "{= env.KEY_PVT1 =}"; + } + secure_ip = ["127.0.0.1", "::1"]; + stats_path = "{= env.TMPDIR =}/stats.ucl"; +} + +# Multi-class Bayes classifier configuration +classifier { + languages_enabled = true; + tokenizer { + name = "osb"; + hash = {= env.STATS_HASH =}; + key = {= env.STATS_KEY =}; + } + backend = "{= env.STATS_BACKEND =}"; + + # Multi-class statfiles + statfile { + class = "spam"; + symbol = BAYES_SPAM; + server = {= env.REDIS_SERVER =} + } + statfile { + class = "ham"; + symbol = BAYES_HAM; + server = {= env.REDIS_SERVER =} + } + statfile { + class = "newsletter"; + symbol = BAYES_NEWSLETTER; + server = {= env.REDIS_SERVER =} + } + + # Backend class labels for Redis + class_labels = { + "spam" = "S"; + "ham" = "H"; + "newsletter" = "N"; + } + + cache { + server = {= env.REDIS_SERVER =} + } + + # Multi-class autolearn configuration + autolearn = { + classes = { + spam = { + threshold = 15.0; + verdict_mapping = { spam = true }; + }; + ham = { + threshold = -5.0; + verdict_mapping = { ham = true }; + }; + newsletter = { + symbols = ["NEWSLETTER_HEADER", "BULK_MAIL", "UNSUBSCRIBE_LINK"]; + threshold = 8.0; + }; + }; + + check_balance = true; + max_class_ratio = 0.6; + skip_threshold = 0.95; + } + + min_learns = 1; + min_tokens = 1; + min_token_hits = 1; + min_prob_strength = 0.05; + + {% if env.STATS_PER_USER ~= '' %} + per_user = <<EOD +return function(task) + return task:get_principal_recipient() +end +EOD; + {% endif %} +} + +lua = "{= env.TESTDIR =}/lua/test_coverage.lua"; + +settings {} 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..f61998f46 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} @@ -410,10 +419,23 @@ Run Nginx ${nginx_log} = Get File ${RSPAMD_TMPDIR}/nginx.log Log ${nginx_log} +Set Test Hash Documentation + ${log_tag} = Evaluate __import__('hashlib').md5('${TEST NAME}'.encode()).hexdigest()[:8] + Log TEST CONTEXT: [${log_tag}] ${TEST NAME} console=True + Run Rspamc [Arguments] @{args} - ${result} = Run Process ${RSPAMC} -t 60 --header Queue-ID\=${TEST NAME} - ... @{args} env:LD_LIBRARY_PATH=${RSPAMD_TESTDIR}/../../contrib/aho-corasick + ${log_tag} = Evaluate __import__('hashlib').md5('${TEST NAME}'.encode()).hexdigest()[:8] + # Check if --queue-id is already provided in the arguments + ${args_str} = Evaluate ' '.join(@{args}) + ${has_queue_id} = Evaluate '--queue-id' in '${args_str}' + IF ${has_queue_id} + ${result} = Run Process ${RSPAMC} -t 60 --log-tag ${log_tag} + ... @{args} env:LD_LIBRARY_PATH=${RSPAMD_TESTDIR}/../../contrib/aho-corasick + ELSE + ${result} = Run Process ${RSPAMC} -t 60 --queue-id ${TEST NAME} --log-tag ${log_tag} + ... @{args} env:LD_LIBRARY_PATH=${RSPAMD_TESTDIR}/../../contrib/aho-corasick + END Log ${result.stdout} [Return] ${result} 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/functional/messages/newsletter.eml b/test/functional/messages/newsletter.eml new file mode 100644 index 000000000..93c996956 --- /dev/null +++ b/test/functional/messages/newsletter.eml @@ -0,0 +1,50 @@ +From: "Marketing Team" <newsletter@example.com> +To: user@example.org +Subject: 🎉 Monthly Newsletter - Exclusive Deals & Product Updates! +Date: Thu, 21 Jul 2023 10:00:00 +0000 +Message-ID: <newsletter-123@example.com> +MIME-Version: 1.0 +Content-Type: text/html; charset=utf-8 +List-Unsubscribe: <https://example.com/unsubscribe?id=123> +Precedence: bulk +X-Mailer: MailChimp/Pro 12.345 + +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Monthly Newsletter</title> +</head> +<body> + <h1>🎉 Exclusive Monthly Offers!</h1> + + <p>Dear Valued Subscriber,</p> + + <p>This month we're excited to bring you our <strong>BIGGEST SALE</strong> of the year!</p> + + <h2>🔥 Hot Deals This Month:</h2> + <ul> + <li>50% OFF all premium products</li> + <li>FREE shipping on orders over $50</li> + <li>Buy 2 Get 1 FREE on selected items</li> + </ul> + + <p><a href="https://example.com/shop?utm_source=newsletter&utm_campaign=monthly">SHOP NOW</a></p> + + <h2>📱 New Product Launch</h2> + <p>Check out our revolutionary new gadget that everyone is talking about!</p> + + <h2>🎁 Refer a Friend</h2> + <p>Share this newsletter and both you and your friend get $10 credit!</p> + + <hr> + + <p><small> + You're receiving this because you subscribed to our newsletter.<br> + <a href="https://example.com/unsubscribe?id=123">Unsubscribe here</a> | + <a href="https://example.com/preferences">Update preferences</a><br> + Marketing Team, Example Corp<br> + 123 Business St, City, State 12345 + </small></p> +</body> +</html>
\ No newline at end of file diff --git a/test/functional/messages/transactional.eml b/test/functional/messages/transactional.eml new file mode 100644 index 000000000..e227aaa77 --- /dev/null +++ b/test/functional/messages/transactional.eml @@ -0,0 +1,18 @@ +From: noreply@example.com +To: user@example.org +Subject: Password Reset Request +Date: Thu, 21 Jul 2023 11:00:00 +0000 +Message-ID: <pwd-reset-456@example.com> +MIME-Version: 1.0 +Content-Type: text/plain + +Hello, + +You have requested a password reset for your account. + +Click here to reset your password: https://example.com/reset?token=abc123 + +This link expires in 24 hours. + +Best regards, +Security Team
\ No newline at end of file |